Properly style CassandraAddCollectionPane (#862)

This commit is contained in:
victor-meng 2021-06-11 14:25:05 -07:00 committed by GitHub
parent 959d34d88d
commit baa3252ba8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 226 additions and 457 deletions

View File

@ -17,6 +17,7 @@ import { getUpsellMessage } from "../../../Utils/PricingUtils";
import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput"; import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { PanelInfoErrorComponent } from "../PanelInfoErrorComponent"; import { PanelInfoErrorComponent } from "../PanelInfoErrorComponent";
import { getTextFieldStyles } from "../PanelStyles";
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm"; import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
export interface AddDatabasePaneProps { export interface AddDatabasePaneProps {
@ -201,8 +202,7 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
value={databaseId} value={databaseId}
onChange={handleonChangeDBId} onChange={handleonChangeDBId}
autoFocus autoFocus
style={{ fontSize: 12 }} styles={getTextFieldStyles()}
styles={{ root: { width: 300 } }}
/> />
{!isServerlessAccount() && ( {!isServerlessAccount() && (

View File

@ -39,13 +39,16 @@ exports[`AddDatabasePane Pane should render Default properly 1`] = `
pattern="[^/?#\\\\\\\\]*[^/?# \\\\\\\\]" pattern="[^/?#\\\\\\\\]*[^/?# \\\\\\\\]"
placeholder="Type a new database id" placeholder="Type a new database id"
size={40} size={40}
style={
Object {
"fontSize": 12,
}
}
styles={ styles={
Object { Object {
"field": Object {
"fontSize": 12,
"selectors": Object {
"::placeholder": Object {
"fontSize": 12,
},
},
},
"root": Object { "root": Object {
"width": 300, "width": 300,
}, },

View File

@ -1,32 +1,30 @@
import { fireEvent, render, screen } from "@testing-library/react"; import { fireEvent, render, screen } from "@testing-library/react";
import { shallow } from "enzyme";
import React from "react"; import React from "react";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { CassandraAPIDataClient } from "../../Tables/TableDataClient"; import { CassandraAPIDataClient } from "../../Tables/TableDataClient";
import { CassandraAddCollectionPane } from "./CassandraAddCollectionPane"; 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(<CassandraAddCollectionPane {...props} />)); beforeEach(() => render(<CassandraAddCollectionPane {...props} />));
it("should render Default properly", () => { it("should render default properly", () => {
const wrapper = shallow(<CassandraAddCollectionPane {...props} />); expect(screen.getByRole("radio", { name: "Create new keyspace", checked: true })).toBeDefined();
expect(wrapper).toMatchSnapshot(); expect(screen.getByRole("checkbox", { name: "Provision shared throughput", checked: false })).toBeDefined();
});
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("Enter Keyspace name ", () => { it("click on use existing", () => {
fireEvent.change(screen.getByLabelText("Keyspace id"), { target: { value: "unittest1" } }); fireEvent.click(screen.getByRole("radio", { name: "Use existing keyspace" }));
expect(screen.getByLabelText("CREATE TABLE unittest1.")).toBeDefined(); 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();
}); });
}); });

View File

@ -1,22 +1,18 @@
import { Label, Stack, TextField } from "@fluentui/react"; import { Checkbox, Dropdown, IDropdownOption, Link, Stack, Text, TextField } from "@fluentui/react";
import React, { FunctionComponent, useEffect, useState } from "react"; import React, { FunctionComponent, useState } from "react";
import * as _ from "underscore";
import * as Constants from "../../../Common/Constants"; import * as Constants from "../../../Common/Constants";
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils"; import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip"; import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip";
import * as DataModels from "../../../Contracts/DataModels";
import * as ViewModels from "../../../Contracts/ViewModels";
import { useSidePanel } from "../../../hooks/useSidePanel"; import { useSidePanel } from "../../../hooks/useSidePanel";
import * as AddCollectionUtility from "../../../Shared/AddCollectionUtility";
import * as SharedConstants from "../../../Shared/Constants"; 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 * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../../UserContext"; import { userContext } from "../../../UserContext";
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
import { isServerlessAccount } from "../../../Utils/CapabilityUtils"; import { isServerlessAccount } from "../../../Utils/CapabilityUtils";
import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput"; import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { CassandraAPIDataClient } from "../../Tables/TableDataClient"; import { CassandraAPIDataClient } from "../../Tables/TableDataClient";
import { getTextFieldStyles } from "../PanelStyles";
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm"; import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
export interface CassandraAddCollectionPaneProps { export interface CassandraAddCollectionPaneProps {
@ -28,183 +24,73 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
explorer: container, explorer: container,
cassandraApiClient, cassandraApiClient,
}: CassandraAddCollectionPaneProps) => { }: CassandraAddCollectionPaneProps) => {
let newKeySpaceThroughput: number;
let isNewKeySpaceAutoscale: boolean;
let tableThroughput: number;
let isTableAutoscale: boolean;
let isCostAcknowledged: boolean;
const closeSidePanel = useSidePanel((state) => state.closeSidePanel); const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
const throughputDefaults = userContext.collectionCreationDefaults.throughput; const [newKeyspaceId, setNewKeyspaceId] = useState<string>("");
const [createTableQuery, setCreateTableQuery] = useState<string>("CREATE TABLE "); const [existingKeyspaceId, setExistingKeyspaceId] = useState<string>("");
const [keyspaceId, setKeyspaceId] = useState<string>("");
const [tableId, setTableId] = useState<string>(""); const [tableId, setTableId] = useState<string>("");
const [throughput, setThroughput] = useState<number>(
AddCollectionUtility.getMaxThroughput(userContext.collectionCreationDefaults, container)
);
const [isAutoPilotSelected, setIsAutoPilotSelected] = useState<boolean>(userContext.features.autoscaleDefault);
const [isSharedAutoPilotSelected, setIsSharedAutoPilotSelected] = useState<boolean>(
userContext.features.autoscaleDefault
);
const [userTableQuery, setUserTableQuery] = useState<string>( const [userTableQuery, setUserTableQuery] = useState<string>(
"(userid int, name text, email text, PRIMARY KEY (userid))" "(userid int, name text, email text, PRIMARY KEY (userid))"
); );
const [isKeyspaceShared, setIsKeyspaceShared] = useState<boolean>(false);
const [keyspaceHasSharedOffer, setKeyspaceHasSharedOffer] = useState<boolean>(false);
const [keyspaceIds, setKeyspaceIds] = useState<string[]>([]);
const [keyspaceThroughput, setKeyspaceThroughput] = useState<number>(throughputDefaults.shared);
const [keyspaceCreateNew, setKeyspaceCreateNew] = useState<boolean>(true); const [keyspaceCreateNew, setKeyspaceCreateNew] = useState<boolean>(true);
const [dedicateTableThroughput, setDedicateTableThroughput] = useState<boolean>(false); const [dedicateTableThroughput, setDedicateTableThroughput] = useState<boolean>(false);
const [throughputSpendAck, setThroughputSpendAck] = useState<boolean>(false);
const [sharedThroughputSpendAck, setSharedThroughputSpendAck] = useState<boolean>(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<boolean>(); const [isExecuting, setIsExecuting] = useState<boolean>();
const [formErrors, setFormErrors] = useState<string>(""); const [formError, setFormError] = useState<string>("");
const isFreeTierAccount: boolean = userContext.databaseAccount?.properties?.enableFreeTier;
useEffect(() => {
if (keyspaceIds.indexOf(keyspaceId) >= 0) {
setKeyspaceHasSharedOffer(keyspaceOffers.has(keyspaceId));
}
setCreateTableQuery(`CREATE TABLE ${keyspaceId}.`);
}, [keyspaceId]);
const addCollectionPaneOpenMessage = { const addCollectionPaneOpenMessage = {
collection: { collection: {
id: tableId, id: tableId,
storage: Constants.BackendDefaults.multiPartitionStorageInGb, storage: Constants.BackendDefaults.multiPartitionStorageInGb,
offerThroughput: throughput, offerThroughput: newKeySpaceThroughput || tableThroughput,
partitionKey: "", partitionKey: "",
databaseId: keyspaceId, databaseId: keyspaceCreateNew ? newKeyspaceId : existingKeyspaceId,
}, },
subscriptionType: userContext.subscriptionType, subscriptionType: userContext.subscriptionType,
subscriptionQuotaId: userContext.quotaId, subscriptionQuotaId: userContext.quotaId,
defaultsCheck: { defaultsCheck: {
storage: "u", storage: "u",
throughput, throughput: newKeySpaceThroughput || tableThroughput,
flight: userContext.addCollectionFlight, flight: userContext.addCollectionFlight,
}, },
dataExplorerArea: Constants.Areas.ContextualPane, 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 () => { 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; return;
} }
setIsExecuting(true); setIsExecuting(true);
const autoPilotCommand = `cosmosdb_autoscale_max_throughput`; 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 createKeyspaceQueryPrefix = `CREATE KEYSPACE ${keyspaceId.trim()} WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 3 }`;
const createKeyspaceQuery: string = keyspaceHasSharedOffer const createKeyspaceQuery: string = isKeyspaceShared
? useAutoPilotForKeyspace ? isNewKeySpaceAutoscale
? `${createKeyspaceQueryPrefix} AND ${autoPilotCommand}=${keyspaceThroughput};` ? `${createKeyspaceQueryPrefix} AND ${autoPilotCommand}=${newKeySpaceThroughput};`
: `${createKeyspaceQueryPrefix} AND cosmosdb_provisioned_throughput=${keyspaceThroughput};` : `${createKeyspaceQueryPrefix} AND cosmosdb_provisioned_throughput=${newKeySpaceThroughput};`
: `${createKeyspaceQueryPrefix};`; : `${createKeyspaceQueryPrefix};`;
let tableQuery: string; let tableQuery: string;
const createTableQueryPrefix = `${createTableQuery}${tableId.trim()} ${userTableQuery}`; const createTableQueryPrefix = `CREATE TABLE ${keyspaceId}.${tableId.trim()} ${userTableQuery}`;
if (canConfigureThroughput && (dedicateTableThroughput || !keyspaceHasSharedOffer)) { if (tableThroughput) {
if (isAutoPilotSelected && selectedAutoPilotThroughput) { if (isTableAutoscale) {
tableQuery = `${createTableQueryPrefix} WITH ${autoPilotCommand}=${throughput};`; tableQuery = `${createTableQueryPrefix} WITH ${autoPilotCommand}=${tableThroughput};`;
} else { } else {
tableQuery = `${createTableQueryPrefix} WITH cosmosdb_provisioned_throughput=${throughput};`; tableQuery = `${createTableQueryPrefix} WITH cosmosdb_provisioned_throughput=${tableThroughput};`;
} }
} else { } else {
tableQuery = `${createTableQueryPrefix};`; tableQuery = `${createTableQueryPrefix};`;
@ -216,15 +102,15 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
...addCollectionPaneOpenMessage.collection, ...addCollectionPaneOpenMessage.collection,
hasDedicatedThroughput: dedicateTableThroughput, hasDedicatedThroughput: dedicateTableThroughput,
}, },
keyspaceHasSharedOffer, isKeyspaceShared,
toCreateKeyspace, keyspaceCreateNew,
createKeyspaceQuery, createKeyspaceQuery,
createTableQuery: tableQuery, createTableQuery: tableQuery,
}; };
const startKey: number = TelemetryProcessor.traceStart(Action.CreateCollection, addCollectionPaneStartMessage); const startKey: number = TelemetryProcessor.traceStart(Action.CreateCollection, addCollectionPaneStartMessage);
try { try {
if (toCreateKeyspace) { if (keyspaceCreateNew) {
await cassandraApiClient.createTableAndKeyspace( await cassandraApiClient.createTableAndKeyspace(
userContext?.databaseAccount?.properties?.cassandraEndpoint, userContext?.databaseAccount?.properties?.cassandraEndpoint,
userContext?.databaseAccount?.id, userContext?.databaseAccount?.id,
@ -247,7 +133,7 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
TelemetryProcessor.traceSuccess(Action.CreateCollection, addCollectionPaneStartMessage, startKey); TelemetryProcessor.traceSuccess(Action.CreateCollection, addCollectionPaneStartMessage, startKey);
} catch (error) { } catch (error) {
const errorMessage = getErrorMessage(error); const errorMessage = getErrorMessage(error);
setFormErrors(errorMessage); setFormError(errorMessage);
setIsExecuting(false); setIsExecuting(false);
const addCollectionPaneFailedMessage = { const addCollectionPaneFailedMessage = {
...addCollectionPaneStartMessage, ...addCollectionPaneStartMessage,
@ -257,129 +143,158 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
TelemetryProcessor.traceFailure(Action.CreateCollection, addCollectionPaneFailedMessage, startKey); TelemetryProcessor.traceFailure(Action.CreateCollection, addCollectionPaneFailedMessage, startKey);
} }
}; };
const handleOnChangeKeyspaceType = (ev: React.FormEvent<HTMLInputElement>, mode: string): void => {
setKeyspaceCreateNew(mode === "Create new");
};
const props: RightPaneFormProps = { const props: RightPaneFormProps = {
formError: formErrors, formError,
isExecuting, isExecuting,
submitButtonText: "Apply", submitButtonText: "OK",
onSubmit, onSubmit,
}; };
return ( return (
<RightPaneForm {...props}> <RightPaneForm {...props}>
<div className="paneMainContent"> <div className="panelMainContent">
<div className="seconddivpadding"> <Stack>
<p> <Stack horizontal>
<Label required> <span className="mandatoryStar">*&nbsp;</span>
<Text className="panelTextBold" variant="small">
Keyspace name <InfoTooltip>Select an existing keyspace or enter a new keyspace id.</InfoTooltip> Keyspace name <InfoTooltip>Select an existing keyspace or enter a new keyspace id.</InfoTooltip>
</Label> </Text>
</p> </Stack>
<Stack horizontal verticalAlign="center"> <Stack horizontal verticalAlign="center">
<input <input
className="throughputInputRadioBtn" className="panelRadioBtn"
aria-label="Create new keyspace" aria-label="Create new keyspace"
checked={keyspaceCreateNew} checked={keyspaceCreateNew}
type="radio" type="radio"
role="radio" role="radio"
tabIndex={0} tabIndex={0}
onChange={(e) => handleOnChangeKeyspaceType(e, "Create new")} onChange={() => {
setKeyspaceCreateNew(true);
setIsKeyspaceShared(false);
setExistingKeyspaceId("");
}}
/> />
<span className="throughputInputRadioBtnLabel">Create new</span> <span className="panelRadioBtnLabel">Create new</span>
<input <input
className="throughputInputRadioBtn" className="panelRadioBtn"
aria-label="Use existing keyspace" aria-label="Use existing keyspace"
checked={!keyspaceCreateNew} checked={!keyspaceCreateNew}
type="radio" type="radio"
role="radio" role="radio"
tabIndex={0} tabIndex={0}
onChange={(e) => handleOnChangeKeyspaceType(e, "Use existing")} onChange={() => {
setKeyspaceCreateNew(false);
setIsKeyspaceShared(false);
}}
/>
<span className="panelRadioBtnLabel">Use existing</span>
</Stack>
{keyspaceCreateNew && (
<Stack className="panelGroupSpacing">
<TextField
aria-required="true"
autoComplete="off"
styles={getTextFieldStyles()}
pattern="[^/?#\\]*[^/?# \\]"
title="May not end with space nor contain characters '\' '/' '#' '?'"
placeholder="Type a new keyspace id"
size={40}
value={newKeyspaceId}
onChange={(e, newValue) => setNewKeyspaceId(newValue)}
ariaLabel="Keyspace id"
autoFocus
/>
{!isServerlessAccount() && (
<Stack horizontal>
<Checkbox
label="Provision shared throughput"
checked={isKeyspaceShared}
styles={{
text: { fontSize: 12 },
checkbox: { width: 12, height: 12 },
label: { padding: 0, alignItems: "center" },
}}
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) => setIsKeyspaceShared(isChecked)}
/>
<InfoTooltip>
Provisioned throughput at the keyspace level will be shared across unlimited number of tables within
the keyspace
</InfoTooltip>
</Stack>
)}
</Stack>
)}
{!keyspaceCreateNew && (
<Dropdown
ariaLabel="Choose existing keyspace id"
styles={{ root: { width: 300 }, title: { fontSize: 12 }, dropdownItem: { fontSize: 12 } }}
placeholder="Choose existing keyspace id"
defaultSelectedKey={existingKeyspaceId}
options={container?.databases()?.map((keyspace) => ({
key: keyspace.id(),
text: keyspace.id(),
data: {
isShared: !!keyspace.offer(),
},
}))}
onChange={(event: React.FormEvent<HTMLDivElement>, option: IDropdownOption) => {
setExistingKeyspaceId(option.key as string);
setIsKeyspaceShared(option.data.isShared);
}}
responsiveMode={999}
/>
)}
{!isServerlessAccount() && keyspaceCreateNew && isKeyspaceShared && (
<ThroughputInput
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !container.isFirstResourceCreated()}
isDatabase
isSharded
setThroughputValue={(throughput: number) => (newKeySpaceThroughput = throughput)}
setIsAutoscale={(isAutoscale: boolean) => (isNewKeySpaceAutoscale = isAutoscale)}
onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)}
/>
)}
</Stack>
<Stack>
<Stack horizontal>
<span className="mandatoryStar">*&nbsp;</span>
<Text className="panelTextBold" variant="small">
Enter CQL command to create the table.{" "}
<Link href="https://aka.ms/cassandra-create-table" target="_blank">
Learn More
</Link>
</Text>
</Stack>
<Stack horizontal verticalAlign="center">
<Text variant="small" style={{ marginRight: 4 }}>
{`CREATE TABLE ${keyspaceCreateNew ? newKeyspaceId : existingKeyspaceId}.`}
</Text>
<TextField
underlined
styles={getTextFieldStyles({ fontSize: 12, width: 150 })}
aria-required="true"
ariaLabel="addCollection-tableId"
autoComplete="off"
pattern="[^/?#\\]*[^/?# \\]"
title="May not end with space nor contain characters '\' '/' '#' '?'"
placeholder="Enter table Id"
size={20}
value={tableId}
onChange={(e, newValue) => setTableId(newValue)}
/> />
<span className="throughputInputRadioBtnLabel">Use existing</span>
</Stack> </Stack>
<TextField <TextField
aria-required="true" styles={getTextFieldStyles()}
autoComplete="off"
pattern="[^/?#\\]*[^/?# \\]"
title="May not end with space nor contain characters '\' '/' '#' '?'"
list={keyspaceCreateNew ? "" : "keyspacesList"}
placeholder={keyspaceCreateNew ? "Type a new keyspace id" : "Choose existing keyspace id"}
size={40}
data-test="addCollection-keyspaceId"
value={keyspaceId}
onChange={(e, newValue) => setKeyspaceId(newValue)}
ariaLabel="Keyspace id"
autoFocus
/>
<datalist id="keyspacesList">
{keyspaceIds?.map((id: string, index: number) => (
<option key={index}>{id}</option>
))}
</datalist>
{canConfigureThroughput && keyspaceCreateNew && (
<div className="databaseProvision">
<input
tabIndex={0}
type="checkbox"
id="keyspaceSharedThroughput"
title="Provision shared throughput"
checked={keyspaceHasSharedOffer}
onChange={(e) => setKeyspaceHasSharedOffer(e.target.checked)}
/>
<span className="databaseProvisionText" aria-label="Provision keyspace throughput">
Provision keyspace throughput
</span>
<InfoTooltip>
Provisioned throughput at the keyspace level will be shared across unlimited number of tables within the
keyspace
</InfoTooltip>
</div>
)}
{canConfigureThroughput && keyspaceCreateNew && keyspaceHasSharedOffer && (
<div>
<ThroughputInput
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !container.isFirstResourceCreated()}
isDatabase
isSharded
setThroughputValue={(throughput: number) => setKeyspaceThroughput(throughput)}
setIsAutoscale={(isAutoscale: boolean) => setIsSharedAutoPilotSelected(isAutoscale)}
onCostAcknowledgeChange={(isAcknowledge: boolean) => {
setSharedThroughputSpendAck(isAcknowledge);
}}
/>
</div>
)}
</div>
<div className="seconddivpadding">
<p>
<Label required>
Enter CQL command to create the table.
<a href="https://aka.ms/cassandra-create-table" target="_blank" rel="noreferrer">
Learn More
</a>
</Label>
</p>
<div aria-label={createTableQuery} style={{ float: "left", paddingTop: "3px", paddingRight: "3px" }}>
{createTableQuery}
</div>
<TextField
aria-required="true"
ariaLabel="addCollection-tableId"
autoComplete="off"
pattern="[^/?#\\]*[^/?# \\]"
title="May not end with space nor contain characters '\' '/' '#' '?'"
placeholder="Enter tableId"
size={20}
className="textfontclr"
value={tableId}
onChange={(e, newValue) => setTableId(newValue)}
style={{ marginBottom: "5px" }}
/>
<TextField
multiline multiline
id="editor-area" id="editor-area"
rows={5} rows={5}
@ -387,10 +302,10 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
value={userTableQuery} value={userTableQuery}
onChange={(e, newValue) => setUserTableQuery(newValue)} onChange={(e, newValue) => setUserTableQuery(newValue)}
/> />
</div> </Stack>
{canConfigureThroughput && keyspaceHasSharedOffer && !keyspaceCreateNew && ( {!isServerlessAccount() && isKeyspaceShared && !keyspaceCreateNew && (
<div className="seconddivpadding"> <Stack>
<input <input
type="checkbox" type="checkbox"
id="tableSharedThroughput" id="tableSharedThroughput"
@ -405,21 +320,17 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
does not count towards the throughput you provisioned for the keyspace. This throughput amount will be 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. billed in addition to the throughput amount you provisioned at the keyspace level.
</InfoTooltip> </InfoTooltip>
</div> </Stack>
)} )}
{canConfigureThroughput && (!keyspaceHasSharedOffer || dedicateTableThroughput) && ( {!isServerlessAccount() && (!isKeyspaceShared || dedicateTableThroughput) && (
<div> <ThroughputInput
<ThroughputInput showFreeTierExceedThroughputTooltip={isFreeTierAccount && !container.isFirstResourceCreated()}
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !container.isFirstResourceCreated()} isDatabase={false}
isDatabase={false} isSharded={false}
isSharded={false} setThroughputValue={(throughput: number) => (tableThroughput = throughput)}
setThroughputValue={(throughput: number) => setThroughput(throughput)} setIsAutoscale={(isAutoscale: boolean) => (isTableAutoscale = isAutoscale)}
setIsAutoscale={(isAutoscale: boolean) => setIsAutoPilotSelected(isAutoscale)} onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)}
onCostAcknowledgeChange={(isAcknowledge: boolean) => { />
setThroughputSpendAck(isAcknowledge);
}}
/>
</div>
)} )}
</div> </div>
</RightPaneForm> </RightPaneForm>

View File

@ -1,163 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CassandraAddCollectionPane Pane should render Default properly 1`] = `
<RightPaneForm
formError=""
onSubmit={[Function]}
submitButtonText="Apply"
>
<div
className="paneMainContent"
>
<div
className="seconddivpadding"
>
<p>
<StyledLabelBase
required={true}
>
Keyspace name
<InfoTooltip>
Select an existing keyspace or enter a new keyspace id.
</InfoTooltip>
</StyledLabelBase>
</p>
<Stack
horizontal={true}
verticalAlign="center"
>
<input
aria-label="Create new keyspace"
checked={true}
className="throughputInputRadioBtn"
onChange={[Function]}
role="radio"
tabIndex={0}
type="radio"
/>
<span
className="throughputInputRadioBtnLabel"
>
Create new
</span>
<input
aria-label="Use existing keyspace"
checked={false}
className="throughputInputRadioBtn"
onChange={[Function]}
role="radio"
tabIndex={0}
type="radio"
/>
<span
className="throughputInputRadioBtnLabel"
>
Use existing
</span>
</Stack>
<StyledTextFieldBase
aria-required="true"
ariaLabel="Keyspace id"
autoComplete="off"
autoFocus={true}
data-test="addCollection-keyspaceId"
list=""
onChange={[Function]}
pattern="[^/?#\\\\\\\\]*[^/?# \\\\\\\\]"
placeholder="Type a new keyspace id"
size={40}
title="May not end with space nor contain characters '\\\\' '/' '#' '?'"
value=""
/>
<datalist
id="keyspacesList"
/>
<div
className="databaseProvision"
>
<input
checked={false}
id="keyspaceSharedThroughput"
onChange={[Function]}
tabIndex={0}
title="Provision shared throughput"
type="checkbox"
/>
<span
aria-label="Provision keyspace throughput"
className="databaseProvisionText"
>
Provision keyspace throughput
</span>
<InfoTooltip>
Provisioned throughput at the keyspace level will be shared across unlimited number of tables within the keyspace
</InfoTooltip>
</div>
</div>
<div
className="seconddivpadding"
>
<p>
<StyledLabelBase
required={true}
>
Enter CQL command to create the table.
<a
href="https://aka.ms/cassandra-create-table"
rel="noreferrer"
target="_blank"
>
Learn More
</a>
</StyledLabelBase>
</p>
<div
aria-label="CREATE TABLE "
style={
Object {
"float": "left",
"paddingRight": "3px",
"paddingTop": "3px",
}
}
>
CREATE TABLE
</div>
<StyledTextFieldBase
aria-required="true"
ariaLabel="addCollection-tableId"
autoComplete="off"
className="textfontclr"
onChange={[Function]}
pattern="[^/?#\\\\\\\\]*[^/?# \\\\\\\\]"
placeholder="Enter tableId"
size={20}
style={
Object {
"marginBottom": "5px",
}
}
title="May not end with space nor contain characters '\\\\' '/' '#' '?'"
value=""
/>
<StyledTextFieldBase
aria-label="Table Schema"
id="editor-area"
multiline={true}
onChange={[Function]}
rows={5}
value="(userid int, name text, email text, PRIMARY KEY (userid))"
/>
</div>
<div>
<ThroughputInput
isDatabase={false}
isSharded={false}
onCostAcknowledgeChange={[Function]}
setIsAutoscale={[Function]}
setThroughputValue={[Function]}
/>
</div>
</div>
</RightPaneForm>
`;

View File

@ -0,0 +1,20 @@
import { ITextFieldStyles } from "@fluentui/react";
interface TextFieldStylesProps {
fontSize: number | string;
width: number | string;
}
export const getTextFieldStyles = (params?: TextFieldStylesProps): Partial<ITextFieldStyles> => ({
field: {
fontSize: params?.fontSize || 12,
selectors: {
"::placeholder": {
fontSize: params?.fontSize || 12,
},
},
},
root: {
width: params?.width || 300,
},
});