mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2024-11-25 23:16:56 +00:00
Properly style CassandraAddCollectionPane (#862)
This commit is contained in:
parent
959d34d88d
commit
baa3252ba8
@ -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() && (
|
||||||
|
@ -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,
|
||||||
},
|
},
|
||||||
|
@ -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";
|
||||||
|
|
||||||
|
describe("Cassandra add collection pane test", () => {
|
||||||
const props = {
|
const props = {
|
||||||
explorer: new Explorer(),
|
explorer: new Explorer(),
|
||||||
closePanel: (): void => undefined,
|
closePanel: (): void => undefined,
|
||||||
cassandraApiClient: new CassandraAPIDataClient(),
|
cassandraApiClient: new CassandraAPIDataClient(),
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("CassandraAddCollectionPane Pane", () => {
|
|
||||||
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();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -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,116 +143,144 @@ 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">* </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="throughputInputRadioBtnLabel">Use existing</span>
|
<span className="panelRadioBtnLabel">Use existing</span>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
|
{keyspaceCreateNew && (
|
||||||
|
<Stack className="panelGroupSpacing">
|
||||||
<TextField
|
<TextField
|
||||||
aria-required="true"
|
aria-required="true"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
|
styles={getTextFieldStyles()}
|
||||||
pattern="[^/?#\\]*[^/?# \\]"
|
pattern="[^/?#\\]*[^/?# \\]"
|
||||||
title="May not end with space nor contain characters '\' '/' '#' '?'"
|
title="May not end with space nor contain characters '\' '/' '#' '?'"
|
||||||
list={keyspaceCreateNew ? "" : "keyspacesList"}
|
placeholder="Type a new keyspace id"
|
||||||
placeholder={keyspaceCreateNew ? "Type a new keyspace id" : "Choose existing keyspace id"}
|
|
||||||
size={40}
|
size={40}
|
||||||
data-test="addCollection-keyspaceId"
|
value={newKeyspaceId}
|
||||||
value={keyspaceId}
|
onChange={(e, newValue) => setNewKeyspaceId(newValue)}
|
||||||
onChange={(e, newValue) => setKeyspaceId(newValue)}
|
|
||||||
ariaLabel="Keyspace id"
|
ariaLabel="Keyspace id"
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
<datalist id="keyspacesList">
|
|
||||||
{keyspaceIds?.map((id: string, index: number) => (
|
{!isServerlessAccount() && (
|
||||||
<option key={index}>{id}</option>
|
<Stack horizontal>
|
||||||
))}
|
<Checkbox
|
||||||
</datalist>
|
label="Provision shared throughput"
|
||||||
{canConfigureThroughput && keyspaceCreateNew && (
|
checked={isKeyspaceShared}
|
||||||
<div className="databaseProvision">
|
styles={{
|
||||||
<input
|
text: { fontSize: 12 },
|
||||||
tabIndex={0}
|
checkbox: { width: 12, height: 12 },
|
||||||
type="checkbox"
|
label: { padding: 0, alignItems: "center" },
|
||||||
id="keyspaceSharedThroughput"
|
}}
|
||||||
title="Provision shared throughput"
|
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) => setIsKeyspaceShared(isChecked)}
|
||||||
checked={keyspaceHasSharedOffer}
|
|
||||||
onChange={(e) => setKeyspaceHasSharedOffer(e.target.checked)}
|
|
||||||
/>
|
/>
|
||||||
<span className="databaseProvisionText" aria-label="Provision keyspace throughput">
|
|
||||||
Provision keyspace throughput
|
|
||||||
</span>
|
|
||||||
<InfoTooltip>
|
<InfoTooltip>
|
||||||
Provisioned throughput at the keyspace level will be shared across unlimited number of tables within the
|
Provisioned throughput at the keyspace level will be shared across unlimited number of tables within
|
||||||
keyspace
|
the keyspace
|
||||||
</InfoTooltip>
|
</InfoTooltip>
|
||||||
</div>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
{canConfigureThroughput && keyspaceCreateNew && keyspaceHasSharedOffer && (
|
</Stack>
|
||||||
<div>
|
)}
|
||||||
|
|
||||||
|
{!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
|
<ThroughputInput
|
||||||
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !container.isFirstResourceCreated()}
|
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !container.isFirstResourceCreated()}
|
||||||
isDatabase
|
isDatabase
|
||||||
isSharded
|
isSharded
|
||||||
setThroughputValue={(throughput: number) => setKeyspaceThroughput(throughput)}
|
setThroughputValue={(throughput: number) => (newKeySpaceThroughput = throughput)}
|
||||||
setIsAutoscale={(isAutoscale: boolean) => setIsSharedAutoPilotSelected(isAutoscale)}
|
setIsAutoscale={(isAutoscale: boolean) => (isNewKeySpaceAutoscale = isAutoscale)}
|
||||||
onCostAcknowledgeChange={(isAcknowledge: boolean) => {
|
onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)}
|
||||||
setSharedThroughputSpendAck(isAcknowledge);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</Stack>
|
||||||
<div className="seconddivpadding">
|
|
||||||
<p>
|
<Stack>
|
||||||
<Label required>
|
<Stack horizontal>
|
||||||
Enter CQL command to create the table.
|
<span className="mandatoryStar">* </span>
|
||||||
<a href="https://aka.ms/cassandra-create-table" target="_blank" rel="noreferrer">
|
<Text className="panelTextBold" variant="small">
|
||||||
|
Enter CQL command to create the table.{" "}
|
||||||
|
<Link href="https://aka.ms/cassandra-create-table" target="_blank">
|
||||||
Learn More
|
Learn More
|
||||||
</a>
|
</Link>
|
||||||
</Label>
|
</Text>
|
||||||
</p>
|
</Stack>
|
||||||
<div aria-label={createTableQuery} style={{ float: "left", paddingTop: "3px", paddingRight: "3px" }}>
|
|
||||||
{createTableQuery}
|
<Stack horizontal verticalAlign="center">
|
||||||
</div>
|
<Text variant="small" style={{ marginRight: 4 }}>
|
||||||
|
{`CREATE TABLE ${keyspaceCreateNew ? newKeyspaceId : existingKeyspaceId}.`}
|
||||||
|
</Text>
|
||||||
<TextField
|
<TextField
|
||||||
|
underlined
|
||||||
|
styles={getTextFieldStyles({ fontSize: 12, width: 150 })}
|
||||||
aria-required="true"
|
aria-required="true"
|
||||||
ariaLabel="addCollection-tableId"
|
ariaLabel="addCollection-tableId"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
@ -374,12 +288,13 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
|
|||||||
title="May not end with space nor contain characters '\' '/' '#' '?'"
|
title="May not end with space nor contain characters '\' '/' '#' '?'"
|
||||||
placeholder="Enter table Id"
|
placeholder="Enter table Id"
|
||||||
size={20}
|
size={20}
|
||||||
className="textfontclr"
|
|
||||||
value={tableId}
|
value={tableId}
|
||||||
onChange={(e, newValue) => setTableId(newValue)}
|
onChange={(e, newValue) => setTableId(newValue)}
|
||||||
style={{ marginBottom: "5px" }}
|
|
||||||
/>
|
/>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
|
styles={getTextFieldStyles()}
|
||||||
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) => setThroughput(throughput)}
|
setThroughputValue={(throughput: number) => (tableThroughput = throughput)}
|
||||||
setIsAutoscale={(isAutoscale: boolean) => setIsAutoPilotSelected(isAutoscale)}
|
setIsAutoscale={(isAutoscale: boolean) => (isTableAutoscale = isAutoscale)}
|
||||||
onCostAcknowledgeChange={(isAcknowledge: boolean) => {
|
onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)}
|
||||||
setThroughputSpendAck(isAcknowledge);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</RightPaneForm>
|
</RightPaneForm>
|
||||||
|
@ -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>
|
|
||||||
`;
|
|
20
src/Explorer/Panes/PanelStyles.ts
Normal file
20
src/Explorer/Panes/PanelStyles.ts
Normal 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,
|
||||||
|
},
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user