import { Checkbox, Dropdown, IDropdownOption, Link, Stack, Text, TextField } from "@fluentui/react"; import * as Constants from "Common/Constants"; import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils"; import { InfoTooltip } from "Common/Tooltip/InfoTooltip"; import * as SharedConstants from "Shared/Constants"; import { Action } from "Shared/Telemetry/TelemetryConstants"; import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor"; import { userContext } from "UserContext"; import { isServerlessAccount } from "Utils/CapabilityUtils"; import { useSidePanel } from "hooks/useSidePanel"; import React, { FunctionComponent, useState } from "react"; import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput"; import Explorer from "../../Explorer"; import { CassandraAPIDataClient } from "../../Tables/TableDataClient"; import { useDatabases } from "../../useDatabases"; import { getTextFieldStyles } from "../PanelStyles"; import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm"; export interface CassandraAddCollectionPaneProps { explorer: Explorer; cassandraApiClient: CassandraAPIDataClient; } export const CassandraAddCollectionPane: FunctionComponent = ({ explorer: container, cassandraApiClient, }: CassandraAddCollectionPaneProps) => { let newKeySpaceThroughput: number; let isNewKeySpaceAutoscale: boolean; let tableThroughput: number; let isTableAutoscale: boolean; let isCostAcknowledged: boolean; const closeSidePanel = useSidePanel((state) => state.closeSidePanel); const [newKeyspaceId, setNewKeyspaceId] = useState(""); const [existingKeyspaceId, setExistingKeyspaceId] = useState(""); const [tableId, setTableId] = useState(""); const [userTableQuery, setUserTableQuery] = useState( "(userid int, name text, email text, PRIMARY KEY (userid))", ); const [isKeyspaceShared, setIsKeyspaceShared] = useState(false); const [keyspaceCreateNew, setKeyspaceCreateNew] = useState(true); const [dedicateTableThroughput, setDedicateTableThroughput] = useState(false); const [isExecuting, setIsExecuting] = useState(); const [formError, setFormError] = useState(""); const [isThroughputCapExceeded, setIsThroughputCapExceeded] = useState(false); const isFreeTierAccount: boolean = userContext.databaseAccount?.properties?.enableFreeTier; const addCollectionPaneOpenMessage = { collection: { id: tableId, storage: Constants.BackendDefaults.multiPartitionStorageInGb, offerThroughput: newKeySpaceThroughput || tableThroughput, partitionKey: "", databaseId: keyspaceCreateNew ? newKeyspaceId : existingKeyspaceId, }, subscriptionType: userContext.subscriptionType, subscriptionQuotaId: userContext.quotaId, defaultsCheck: { storage: "u", throughput: newKeySpaceThroughput || tableThroughput, flight: userContext.addCollectionFlight, }, dataExplorerArea: Constants.Areas.ContextualPane, }; const onSubmit = async () => { const throughput = keyspaceCreateNew ? newKeySpaceThroughput : tableThroughput; const keyspaceId = keyspaceCreateNew ? newKeyspaceId : existingKeyspaceId; if (throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && !isCostAcknowledged) { const errorMessage = isNewKeySpaceAutoscale || isTableAutoscale ? "Please acknowledge the estimated monthly spend." : "Please acknowledge the estimated daily spend."; setFormError(errorMessage); return; } setIsExecuting(true); const autoPilotCommand = `cosmosdb_autoscale_max_throughput`; const createKeyspaceQueryPrefix = `CREATE KEYSPACE ${keyspaceId.trim()} WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 3 }`; const createKeyspaceQuery: string = isKeyspaceShared ? isNewKeySpaceAutoscale ? `${createKeyspaceQueryPrefix} AND ${autoPilotCommand}=${newKeySpaceThroughput};` : `${createKeyspaceQueryPrefix} AND cosmosdb_provisioned_throughput=${newKeySpaceThroughput};` : `${createKeyspaceQueryPrefix};`; let tableQuery: string; const createTableQueryPrefix = `CREATE TABLE ${keyspaceId}.${tableId.trim()} ${userTableQuery}`; if (tableThroughput) { if (isTableAutoscale) { tableQuery = `${createTableQueryPrefix} WITH ${autoPilotCommand}=${tableThroughput};`; } else { tableQuery = `${createTableQueryPrefix} WITH cosmosdb_provisioned_throughput=${tableThroughput};`; } } else { tableQuery = `${createTableQueryPrefix};`; } const addCollectionPaneStartMessage = { ...addCollectionPaneOpenMessage, collection: { ...addCollectionPaneOpenMessage.collection, hasDedicatedThroughput: dedicateTableThroughput, }, isKeyspaceShared, keyspaceCreateNew, createKeyspaceQuery, createTableQuery: tableQuery, }; const startKey: number = TelemetryProcessor.traceStart(Action.CreateCollection, addCollectionPaneStartMessage); try { if (keyspaceCreateNew) { await cassandraApiClient.createTableAndKeyspace( userContext?.databaseAccount?.properties?.cassandraEndpoint, userContext?.databaseAccount?.id, container, tableQuery, createKeyspaceQuery, ); } else { await cassandraApiClient.createTableAndKeyspace( userContext?.databaseAccount?.properties?.cassandraEndpoint, userContext?.databaseAccount?.id, container, tableQuery, ); } container.refreshAllDatabases(); setIsExecuting(false); closeSidePanel(); TelemetryProcessor.traceSuccess(Action.CreateCollection, addCollectionPaneStartMessage, startKey); } catch (error) { const errorMessage = getErrorMessage(error); setFormError(errorMessage); setIsExecuting(false); const addCollectionPaneFailedMessage = { ...addCollectionPaneStartMessage, error: errorMessage, errorStack: getErrorStack(error), }; TelemetryProcessor.traceFailure(Action.CreateCollection, addCollectionPaneFailedMessage, startKey); } }; const props: RightPaneFormProps = { formError, isExecuting, submitButtonText: "OK", isSubmitButtonDisabled: isThroughputCapExceeded, onSubmit, }; return (
Keyspace name Select an existing keyspace or enter a new keyspace id. { setKeyspaceCreateNew(true); setIsKeyspaceShared(false); setExistingKeyspaceId(""); }} /> Create new { setKeyspaceCreateNew(false); setIsKeyspaceShared(false); }} /> Use existing {keyspaceCreateNew && ( setNewKeyspaceId(newValue)} ariaLabel="Keyspace id" autoFocus /> {!isServerlessAccount() && ( , isChecked: boolean) => setIsKeyspaceShared(isChecked)} /> Provisioned throughput at the keyspace level will be shared across unlimited number of tables within the keyspace )} )} {!keyspaceCreateNew && ( ({ key: keyspace.id(), text: keyspace.id(), data: { isShared: !!keyspace.offer(), }, }))} onChange={(event: React.FormEvent, option: IDropdownOption) => { setExistingKeyspaceId(option.key as string); setIsKeyspaceShared(option.data.isShared); }} responsiveMode={999} /> )} {!isServerlessAccount() && keyspaceCreateNew && isKeyspaceShared && ( (newKeySpaceThroughput = throughput)} setIsAutoscale={(isAutoscale: boolean) => (isNewKeySpaceAutoscale = isAutoscale)} setIsThroughputCapExceeded={(isCapExceeded: boolean) => setIsThroughputCapExceeded(isCapExceeded)} onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)} /> )} Enter CQL command to create the table.{" "} Learn More {`CREATE TABLE ${keyspaceCreateNew ? newKeyspaceId : existingKeyspaceId}.`} setTableId(newValue)} /> setUserTableQuery(newValue)} /> {!isServerlessAccount() && isKeyspaceShared && !keyspaceCreateNew && ( setDedicateTableThroughput(e.target.checked)} /> Provision dedicated throughput for this table You can optionally provision dedicated throughput for a table within a keyspace that has throughput provisioned. This dedicated throughput amount will not be shared with other tables in the keyspace and does not count towards the throughput you provisioned for the keyspace. This throughput amount will be billed in addition to the throughput amount you provisioned at the keyspace level. )} {!isServerlessAccount() && (!isKeyspaceShared || dedicateTableThroughput) && ( (tableThroughput = throughput)} setIsAutoscale={(isAutoscale: boolean) => (isTableAutoscale = isAutoscale)} setIsThroughputCapExceeded={(isCapExceeded: boolean) => setIsThroughputCapExceeded(isCapExceeded)} onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)} /> )}
); };