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 { useSidePanel } from "hooks/useSidePanel"; import { Keys, t } from "Localization"; import React, { FunctionComponent, useState } from "react"; 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 { ValidCosmosDbIdDescription, ValidCosmosDbIdInputPattern } from "Utils/ValidationUtils"; 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, }, 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 ? t(Keys.panes.addCollection.acknowledgeSpendErrorMonthly) : t(Keys.panes.addCollection.acknowledgeSpendErrorDaily); 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: t(Keys.common.ok), isSubmitButtonDisabled: isThroughputCapExceeded, onSubmit, }; return (
{t(Keys.panes.cassandraAddCollection.keyspaceLabel)}{" "} {t(Keys.panes.cassandraAddCollection.keyspaceTooltip)} { setKeyspaceCreateNew(true); setIsKeyspaceShared(false); setExistingKeyspaceId(""); }} /> {t(Keys.panes.addCollection.createNew)} { setKeyspaceCreateNew(false); setIsKeyspaceShared(false); }} /> {t(Keys.panes.addCollection.useExisting)} {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)} /> )} {t(Keys.panes.cassandraAddCollection.tableIdLabel)}{" "} {t(Keys.common.learnMore)} {`CREATE TABLE ${keyspaceCreateNew ? newKeyspaceId : existingKeyspaceId}.`} setTableId(newValue)} /> setUserTableQuery(newValue)} /> {!isServerlessAccount() && isKeyspaceShared && !keyspaceCreateNew && ( setDedicateTableThroughput(e.target.checked)} /> {t(Keys.panes.cassandraAddCollection.provisionDedicatedThroughput)} {t(Keys.panes.cassandraAddCollection.provisionDedicatedThroughputTooltip)} )} {!isServerlessAccount() && (!isKeyspaceShared || dedicateTableThroughput) && ( (tableThroughput = throughput)} setIsAutoscale={(isAutoscale: boolean) => (isTableAutoscale = isAutoscale)} setIsThroughputCapExceeded={(isCapExceeded: boolean) => setIsThroughputCapExceeded(isCapExceeded)} onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)} /> )}
); };