diff --git a/src/Explorer/Panes/AddDatabasePanel/AddDatabasePanel.tsx b/src/Explorer/Panes/AddDatabasePanel/AddDatabasePanel.tsx index 4194e820b..9bd37368b 100644 --- a/src/Explorer/Panes/AddDatabasePanel/AddDatabasePanel.tsx +++ b/src/Explorer/Panes/AddDatabasePanel/AddDatabasePanel.tsx @@ -17,6 +17,7 @@ import { getUpsellMessage } from "../../../Utils/PricingUtils"; import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput"; import Explorer from "../../Explorer"; import { PanelInfoErrorComponent } from "../PanelInfoErrorComponent"; +import { getTextFieldStyles } from "../PanelStyles"; import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm"; export interface AddDatabasePaneProps { @@ -201,8 +202,7 @@ export const AddDatabasePanel: FunctionComponent = ({ value={databaseId} onChange={handleonChangeDBId} autoFocus - style={{ fontSize: 12 }} - styles={{ root: { width: 300 } }} + styles={getTextFieldStyles()} /> {!isServerlessAccount() && ( diff --git a/src/Explorer/Panes/AddDatabasePanel/__snapshots__/AddDatabasePanel.test.tsx.snap b/src/Explorer/Panes/AddDatabasePanel/__snapshots__/AddDatabasePanel.test.tsx.snap index e89600bec..9c4bbaa32 100644 --- a/src/Explorer/Panes/AddDatabasePanel/__snapshots__/AddDatabasePanel.test.tsx.snap +++ b/src/Explorer/Panes/AddDatabasePanel/__snapshots__/AddDatabasePanel.test.tsx.snap @@ -39,13 +39,16 @@ exports[`AddDatabasePane Pane should render Default properly 1`] = ` pattern="[^/?#\\\\\\\\]*[^/?# \\\\\\\\]" placeholder="Type a new database id" size={40} - style={ - Object { - "fontSize": 12, - } - } styles={ Object { + "field": Object { + "fontSize": 12, + "selectors": Object { + "::placeholder": Object { + "fontSize": 12, + }, + }, + }, "root": Object { "width": 300, }, diff --git a/src/Explorer/Panes/CassandraAddCollectionPane/CassandraAddCollectionPane.test.tsx b/src/Explorer/Panes/CassandraAddCollectionPane/CassandraAddCollectionPane.test.tsx index 20433bac5..f9624ac80 100644 --- a/src/Explorer/Panes/CassandraAddCollectionPane/CassandraAddCollectionPane.test.tsx +++ b/src/Explorer/Panes/CassandraAddCollectionPane/CassandraAddCollectionPane.test.tsx @@ -1,32 +1,30 @@ import { fireEvent, render, screen } from "@testing-library/react"; -import { shallow } from "enzyme"; import React from "react"; import Explorer from "../../Explorer"; import { CassandraAPIDataClient } from "../../Tables/TableDataClient"; import { CassandraAddCollectionPane } from "./CassandraAddCollectionPane"; -const props = { - explorer: new Explorer(), - closePanel: (): void => undefined, - cassandraApiClient: new CassandraAPIDataClient(), -}; -describe("CassandraAddCollectionPane Pane", () => { +describe("Cassandra add collection pane test", () => { + const props = { + explorer: new Explorer(), + closePanel: (): void => undefined, + cassandraApiClient: new CassandraAPIDataClient(), + }; + beforeEach(() => render()); - it("should render Default properly", () => { - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); - }); - it("click on is Create new keyspace", () => { - fireEvent.click(screen.getByLabelText("Create new keyspace")); - expect(screen.getByLabelText("Provision keyspace throughput")).toBeDefined(); - }); - it("click on Use existing", () => { - fireEvent.click(screen.getByLabelText("Use existing keyspace")); + it("should render default properly", () => { + expect(screen.getByRole("radio", { name: "Create new keyspace", checked: true })).toBeDefined(); + expect(screen.getByRole("checkbox", { name: "Provision shared throughput", checked: false })).toBeDefined(); }); - it("Enter Keyspace name ", () => { - fireEvent.change(screen.getByLabelText("Keyspace id"), { target: { value: "unittest1" } }); - expect(screen.getByLabelText("CREATE TABLE unittest1.")).toBeDefined(); + it("click on use existing", () => { + fireEvent.click(screen.getByRole("radio", { name: "Use existing keyspace" })); + expect(screen.getByRole("combobox", { name: "Choose existing keyspace id" })).toBeDefined(); + }); + + it("enter Keyspace name ", () => { + fireEvent.change(screen.getByRole("textbox", { name: "Keyspace id" }), { target: { value: "table1" } }); + expect(screen.getByText("CREATE TABLE table1.")).toBeDefined(); }); }); diff --git a/src/Explorer/Panes/CassandraAddCollectionPane/CassandraAddCollectionPane.tsx b/src/Explorer/Panes/CassandraAddCollectionPane/CassandraAddCollectionPane.tsx index 48d846632..3f829c191 100644 --- a/src/Explorer/Panes/CassandraAddCollectionPane/CassandraAddCollectionPane.tsx +++ b/src/Explorer/Panes/CassandraAddCollectionPane/CassandraAddCollectionPane.tsx @@ -1,22 +1,18 @@ -import { Label, Stack, TextField } from "@fluentui/react"; -import React, { FunctionComponent, useEffect, useState } from "react"; -import * as _ from "underscore"; +import { Checkbox, Dropdown, IDropdownOption, Link, Stack, Text, TextField } from "@fluentui/react"; +import React, { FunctionComponent, useState } from "react"; import * as Constants from "../../../Common/Constants"; import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils"; import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip"; -import * as DataModels from "../../../Contracts/DataModels"; -import * as ViewModels from "../../../Contracts/ViewModels"; import { useSidePanel } from "../../../hooks/useSidePanel"; -import * as AddCollectionUtility from "../../../Shared/AddCollectionUtility"; import * as SharedConstants from "../../../Shared/Constants"; -import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants"; +import { Action } from "../../../Shared/Telemetry/TelemetryConstants"; import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; import { userContext } from "../../../UserContext"; -import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils"; import { isServerlessAccount } from "../../../Utils/CapabilityUtils"; import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput"; import Explorer from "../../Explorer"; import { CassandraAPIDataClient } from "../../Tables/TableDataClient"; +import { getTextFieldStyles } from "../PanelStyles"; import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm"; export interface CassandraAddCollectionPaneProps { @@ -28,183 +24,73 @@ export const CassandraAddCollectionPane: FunctionComponent { + let newKeySpaceThroughput: number; + let isNewKeySpaceAutoscale: boolean; + let tableThroughput: number; + let isTableAutoscale: boolean; + let isCostAcknowledged: boolean; + const closeSidePanel = useSidePanel((state) => state.closeSidePanel); - const throughputDefaults = userContext.collectionCreationDefaults.throughput; - const [createTableQuery, setCreateTableQuery] = useState("CREATE TABLE "); - const [keyspaceId, setKeyspaceId] = useState(""); + const [newKeyspaceId, setNewKeyspaceId] = useState(""); + const [existingKeyspaceId, setExistingKeyspaceId] = useState(""); const [tableId, setTableId] = useState(""); - const [throughput, setThroughput] = useState( - AddCollectionUtility.getMaxThroughput(userContext.collectionCreationDefaults, container) - ); - - const [isAutoPilotSelected, setIsAutoPilotSelected] = useState(userContext.features.autoscaleDefault); - - const [isSharedAutoPilotSelected, setIsSharedAutoPilotSelected] = useState( - userContext.features.autoscaleDefault - ); - const [userTableQuery, setUserTableQuery] = useState( "(userid int, name text, email text, PRIMARY KEY (userid))" ); - - const [keyspaceHasSharedOffer, setKeyspaceHasSharedOffer] = useState(false); - const [keyspaceIds, setKeyspaceIds] = useState([]); - const [keyspaceThroughput, setKeyspaceThroughput] = useState(throughputDefaults.shared); + const [isKeyspaceShared, setIsKeyspaceShared] = useState(false); const [keyspaceCreateNew, setKeyspaceCreateNew] = useState(true); const [dedicateTableThroughput, setDedicateTableThroughput] = useState(false); - const [throughputSpendAck, setThroughputSpendAck] = useState(false); - const [sharedThroughputSpendAck, setSharedThroughputSpendAck] = useState(false); - - const { minAutoPilotThroughput: selectedAutoPilotThroughput } = AutoPilotUtils; - const { minAutoPilotThroughput: sharedAutoPilotThroughput } = AutoPilotUtils; - - const _getAutoPilot = (): DataModels.AutoPilotCreationSettings => { - if (keyspaceCreateNew && keyspaceHasSharedOffer && isSharedAutoPilotSelected && sharedAutoPilotThroughput) { - return { - maxThroughput: sharedAutoPilotThroughput * 1, - }; - } - - if (selectedAutoPilotThroughput) { - return { - maxThroughput: selectedAutoPilotThroughput * 1, - }; - } - - return undefined; - }; - - const isFreeTierAccount: boolean = userContext.databaseAccount?.properties?.enableFreeTier; - - const canConfigureThroughput = !isServerlessAccount(); - - const keyspaceOffers = new Map(); const [isExecuting, setIsExecuting] = useState(); - const [formErrors, setFormErrors] = useState(""); - - useEffect(() => { - if (keyspaceIds.indexOf(keyspaceId) >= 0) { - setKeyspaceHasSharedOffer(keyspaceOffers.has(keyspaceId)); - } - setCreateTableQuery(`CREATE TABLE ${keyspaceId}.`); - }, [keyspaceId]); + const [formError, setFormError] = useState(""); + const isFreeTierAccount: boolean = userContext.databaseAccount?.properties?.enableFreeTier; const addCollectionPaneOpenMessage = { collection: { id: tableId, storage: Constants.BackendDefaults.multiPartitionStorageInGb, - offerThroughput: throughput, + offerThroughput: newKeySpaceThroughput || tableThroughput, partitionKey: "", - databaseId: keyspaceId, + databaseId: keyspaceCreateNew ? newKeyspaceId : existingKeyspaceId, }, subscriptionType: userContext.subscriptionType, subscriptionQuotaId: userContext.quotaId, defaultsCheck: { storage: "u", - throughput, + throughput: newKeySpaceThroughput || tableThroughput, flight: userContext.addCollectionFlight, }, dataExplorerArea: Constants.Areas.ContextualPane, }; - useEffect(() => { - if (!isServerlessAccount()) { - setIsAutoPilotSelected(userContext.features.autoscaleDefault); - } - - TelemetryProcessor.trace(Action.CreateCollection, ActionModifiers.Open, addCollectionPaneOpenMessage); - }, []); - - useEffect(() => { - if (container) { - const newKeyspaceIds: ViewModels.Database[] = container.databases(); - const cachedKeyspaceIdsList = _.map(newKeyspaceIds, (keyspace: ViewModels.Database) => { - if (keyspace && keyspace.offer && !!keyspace.offer()) { - keyspaceOffers.set(keyspace.id(), keyspace.offer()); - } - return keyspace.id(); - }); - setKeyspaceIds(cachedKeyspaceIdsList); - } - }, []); - - const _isValid = () => { - const sharedAutoscaleThroughput = sharedAutoPilotThroughput * 1; - if ( - isSharedAutoPilotSelected && - sharedAutoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && - !sharedThroughputSpendAck - ) { - setFormErrors(`Please acknowledge the estimated monthly spend.`); - return false; - } - - const dedicatedAutoscaleThroughput = selectedAutoPilotThroughput * 1; - if ( - isAutoPilotSelected && - dedicatedAutoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && - !throughputSpendAck - ) { - setFormErrors(`Please acknowledge the estimated monthly spend.`); - return false; - } - - if ((keyspaceCreateNew && keyspaceHasSharedOffer && isSharedAutoPilotSelected) || isAutoPilotSelected) { - const autoPilot = _getAutoPilot(); - if ( - !autoPilot || - !autoPilot.maxThroughput || - !AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput) - ) { - setFormErrors( - `Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput` - ); - return false; - } - return true; - } - - if (throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && !throughputSpendAck) { - setFormErrors(`Please acknowledge the estimated daily spend.`); - return false; - } - - if ( - keyspaceHasSharedOffer && - keyspaceCreateNew && - keyspaceThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && - !sharedThroughputSpendAck - ) { - setFormErrors("Please acknowledge the estimated daily spend"); - return false; - } - - return true; - }; - const onSubmit = async () => { - if (!_isValid()) { + 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 toCreateKeyspace: boolean = keyspaceCreateNew; - const useAutoPilotForKeyspace: boolean = isSharedAutoPilotSelected && !!sharedAutoPilotThroughput; const createKeyspaceQueryPrefix = `CREATE KEYSPACE ${keyspaceId.trim()} WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 3 }`; - const createKeyspaceQuery: string = keyspaceHasSharedOffer - ? useAutoPilotForKeyspace - ? `${createKeyspaceQueryPrefix} AND ${autoPilotCommand}=${keyspaceThroughput};` - : `${createKeyspaceQueryPrefix} AND cosmosdb_provisioned_throughput=${keyspaceThroughput};` + const createKeyspaceQuery: string = isKeyspaceShared + ? isNewKeySpaceAutoscale + ? `${createKeyspaceQueryPrefix} AND ${autoPilotCommand}=${newKeySpaceThroughput};` + : `${createKeyspaceQueryPrefix} AND cosmosdb_provisioned_throughput=${newKeySpaceThroughput};` : `${createKeyspaceQueryPrefix};`; let tableQuery: string; - const createTableQueryPrefix = `${createTableQuery}${tableId.trim()} ${userTableQuery}`; + const createTableQueryPrefix = `CREATE TABLE ${keyspaceId}.${tableId.trim()} ${userTableQuery}`; - if (canConfigureThroughput && (dedicateTableThroughput || !keyspaceHasSharedOffer)) { - if (isAutoPilotSelected && selectedAutoPilotThroughput) { - tableQuery = `${createTableQueryPrefix} WITH ${autoPilotCommand}=${throughput};`; + if (tableThroughput) { + if (isTableAutoscale) { + tableQuery = `${createTableQueryPrefix} WITH ${autoPilotCommand}=${tableThroughput};`; } else { - tableQuery = `${createTableQueryPrefix} WITH cosmosdb_provisioned_throughput=${throughput};`; + tableQuery = `${createTableQueryPrefix} WITH cosmosdb_provisioned_throughput=${tableThroughput};`; } } else { tableQuery = `${createTableQueryPrefix};`; @@ -216,15 +102,15 @@ export const CassandraAddCollectionPane: FunctionComponent, mode: string): void => { - setKeyspaceCreateNew(mode === "Create new"); - }; const props: RightPaneFormProps = { - formError: formErrors, + formError, isExecuting, - submitButtonText: "Apply", + submitButtonText: "OK", onSubmit, }; + return ( -
-
-

-

+ + + + Keyspace name Select an existing keyspace or enter a new keyspace id. - -

+
+
handleOnChangeKeyspaceType(e, "Create new")} + onChange={() => { + setKeyspaceCreateNew(true); + setIsKeyspaceShared(false); + setExistingKeyspaceId(""); + }} /> - Create new + Create new handleOnChangeKeyspaceType(e, "Use existing")} + onChange={() => { + 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)} + onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)} + /> + )} +
+ + + + + + Enter CQL command to create the table.{" "} + + Learn More + + + + + + + {`CREATE TABLE ${keyspaceCreateNew ? newKeyspaceId : existingKeyspaceId}.`} + + setTableId(newValue)} /> - Use existing setKeyspaceId(newValue)} - ariaLabel="Keyspace id" - autoFocus - /> - - {keyspaceIds?.map((id: string, index: number) => ( - - ))} - - {canConfigureThroughput && keyspaceCreateNew && ( -
- setKeyspaceHasSharedOffer(e.target.checked)} - /> - - Provision keyspace throughput - - - Provisioned throughput at the keyspace level will be shared across unlimited number of tables within the - keyspace - -
- )} - {canConfigureThroughput && keyspaceCreateNew && keyspaceHasSharedOffer && ( -
- setKeyspaceThroughput(throughput)} - setIsAutoscale={(isAutoscale: boolean) => setIsSharedAutoPilotSelected(isAutoscale)} - onCostAcknowledgeChange={(isAcknowledge: boolean) => { - setSharedThroughputSpendAck(isAcknowledge); - }} - /> -
- )} -
-
-

- -

-
- {createTableQuery} -
- setTableId(newValue)} - style={{ marginBottom: "5px" }} - /> - setUserTableQuery(newValue)} /> -
+ - {canConfigureThroughput && keyspaceHasSharedOffer && !keyspaceCreateNew && ( -
+ {!isServerlessAccount() && isKeyspaceShared && !keyspaceCreateNew && ( + -
+ )} - {canConfigureThroughput && (!keyspaceHasSharedOffer || dedicateTableThroughput) && ( -
- setThroughput(throughput)} - setIsAutoscale={(isAutoscale: boolean) => setIsAutoPilotSelected(isAutoscale)} - onCostAcknowledgeChange={(isAcknowledge: boolean) => { - setThroughputSpendAck(isAcknowledge); - }} - /> -
+ {!isServerlessAccount() && (!isKeyspaceShared || dedicateTableThroughput) && ( + (tableThroughput = throughput)} + setIsAutoscale={(isAutoscale: boolean) => (isTableAutoscale = isAutoscale)} + onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)} + /> )}
diff --git a/src/Explorer/Panes/CassandraAddCollectionPane/__snapshots__/CassandraAddCollectionPane.test.tsx.snap b/src/Explorer/Panes/CassandraAddCollectionPane/__snapshots__/CassandraAddCollectionPane.test.tsx.snap deleted file mode 100644 index d3f41614e..000000000 --- a/src/Explorer/Panes/CassandraAddCollectionPane/__snapshots__/CassandraAddCollectionPane.test.tsx.snap +++ /dev/null @@ -1,163 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CassandraAddCollectionPane Pane should render Default properly 1`] = ` - -
-
-

- - Keyspace name - - Select an existing keyspace or enter a new keyspace id. - - -

- - - - Create new - - - - Use existing - - - - -
- - - Provision keyspace throughput - - - Provisioned throughput at the keyspace level will be shared across unlimited number of tables within the keyspace - -
-
-
-

- - Enter CQL command to create the table. - - Learn More - - -

-
- CREATE TABLE -
- - -
-
- -
-
-
-`; diff --git a/src/Explorer/Panes/PanelStyles.ts b/src/Explorer/Panes/PanelStyles.ts new file mode 100644 index 000000000..517fd3ed0 --- /dev/null +++ b/src/Explorer/Panes/PanelStyles.ts @@ -0,0 +1,20 @@ +import { ITextFieldStyles } from "@fluentui/react"; + +interface TextFieldStylesProps { + fontSize: number | string; + width: number | string; +} + +export const getTextFieldStyles = (params?: TextFieldStylesProps): Partial => ({ + field: { + fontSize: params?.fontSize || 12, + selectors: { + "::placeholder": { + fontSize: params?.fontSize || 12, + }, + }, + }, + root: { + width: params?.width || 300, + }, +});