Merge branch 'master' into users/chskelt/pkupdate

This commit is contained in:
vchske
2026-05-07 17:13:59 -07:00
committed by GitHub
54 changed files with 4772 additions and 793 deletions
@@ -17,7 +17,6 @@ import {
} from "@fluentui/react";
import * as Constants from "Common/Constants";
import { createCollection } from "Common/dataAccess/createCollection";
import { getNewDatabaseSharedThroughputDefault } from "Common/DatabaseUtility";
import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
import { configContext, Platform } from "ConfigContext";
import * as DataModels from "Contracts/DataModels";
@@ -77,7 +76,6 @@ export const DefaultVectorEmbeddingPolicy: DataModels.VectorEmbeddingPolicy = {
export interface AddCollectionPanelState {
createNewDatabase: boolean;
newDatabaseId: string;
isSharedThroughputChecked: boolean;
selectedDatabaseId: string;
collectionId: string;
enableIndexing: boolean;
@@ -103,8 +101,6 @@ export interface AddCollectionPanelState {
}
export class AddCollectionPanel extends React.Component<AddCollectionPanelProps, AddCollectionPanelState> {
private newDatabaseThroughput: number;
private isNewDatabaseAutoscale: boolean;
private collectionThroughput: number;
private isCollectionAutoscale: boolean;
private isCostAcknowledged: boolean;
@@ -117,7 +113,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
createNewDatabase:
userContext.apiType !== "Tables" && configContext.platform !== Platform.Fabric && !this.props.databaseId,
newDatabaseId: props.isQuickstart ? this.getSampleDBName() : "",
isSharedThroughputChecked: getNewDatabaseSharedThroughputDefault(),
selectedDatabaseId:
userContext.apiType === "Tables" ? CollectionCreation.TablesAPIDefaultDatabase : this.props.databaseId,
collectionId: props.isQuickstart ? `Sample${getCollectionName()}` : "",
@@ -351,61 +346,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
this.setState({ newDatabaseId: event.target.value })
}
/>
{!isServerlessAccount() && (
<Stack horizontal>
<Checkbox
label={t(Keys.panes.addCollection.shareThroughput, {
collectionName: getCollectionName(true).toLocaleLowerCase(),
})}
checked={this.state.isSharedThroughputChecked}
styles={{
text: { fontSize: 12, color: "var(--colorNeutralForeground1)" },
checkbox: { width: 12, height: 12 },
label: { padding: 0, alignItems: "center" },
root: {
selectors: {
":hover .ms-Checkbox-text": { color: "var(--colorNeutralForeground1)" },
},
},
}}
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) =>
this.setState({ isSharedThroughputChecked: isChecked })
}
/>
<TooltipHost
directionalHint={DirectionalHint.bottomLeftEdge}
content={t(Keys.panes.addCollection.shareThroughputTooltip, {
collectionName: getCollectionName(true).toLocaleLowerCase(),
})}
>
<Icon
iconName="Info"
className="panelInfoIcon"
tabIndex={0}
ariaLabel={t(Keys.panes.addCollection.shareThroughputTooltip, {
collectionName: getCollectionName(true).toLocaleLowerCase(),
})}
/>
</TooltipHost>
</Stack>
)}
{!isServerlessAccount() && this.state.isSharedThroughputChecked && (
<ThroughputInput
showFreeTierExceedThroughputTooltip={isFreeTierAccount() && !isFirstResourceCreated}
isDatabase={true}
isSharded={this.state.isSharded}
isFreeTier={isFreeTierAccount()}
isQuickstart={this.props.isQuickstart}
setThroughputValue={(throughput: number) => (this.newDatabaseThroughput = throughput)}
setIsAutoscale={(isAutoscale: boolean) => (this.isNewDatabaseAutoscale = isAutoscale)}
setIsThroughputCapExceeded={(isThroughputCapExceeded: boolean) =>
this.setState({ isThroughputCapExceeded })
}
onCostAcknowledgeChange={(isAcknowledge: boolean) => (this.isCostAcknowledged = isAcknowledge)}
/>
)}
</Stack>
)}
{!this.state.createNewDatabase && (
@@ -515,59 +455,57 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
</Stack>
)}
{userContext.apiType === "Mongo" &&
(!this.state.isSharedThroughputChecked ||
this.props.explorer.isFixedCollectionWithSharedThroughputSupported()) && (
<Stack>
<Stack horizontal style={{ marginTop: -5, marginBottom: -4 }}>
<span className="mandatoryStar">*&nbsp;</span>
<Text className="panelTextBold" variant="small">
{t(Keys.panes.addCollection.sharding)}
</Text>
<TooltipHost
directionalHint={DirectionalHint.bottomLeftEdge}
content={t(Keys.panes.addCollection.shardingTooltip)}
>
<Icon
iconName="Info"
className="panelInfoIcon"
tabIndex={0}
ariaLabel={t(Keys.panes.addCollection.shardingTooltip)}
/>
</TooltipHost>
</Stack>
<Stack horizontal verticalAlign="center">
<input
className="panelRadioBtn"
checked={!this.state.isSharded}
aria-label={t(Keys.panes.addCollection.unsharded)}
aria-checked={!this.state.isSharded}
name="unsharded"
type="radio"
role="radio"
id="unshardedOption"
{userContext.apiType === "Mongo" && this.props.explorer.isFixedCollectionWithSharedThroughputSupported() && (
<Stack>
<Stack horizontal style={{ marginTop: -5, marginBottom: -4 }}>
<span className="mandatoryStar">*&nbsp;</span>
<Text className="panelTextBold" variant="small">
{t(Keys.panes.addCollection.sharding)}
</Text>
<TooltipHost
directionalHint={DirectionalHint.bottomLeftEdge}
content={t(Keys.panes.addCollection.shardingTooltip)}
>
<Icon
iconName="Info"
className="panelInfoIcon"
tabIndex={0}
onChange={this.onUnshardedRadioBtnChange.bind(this)}
ariaLabel={t(Keys.panes.addCollection.shardingTooltip)}
/>
<span className="panelRadioBtnLabel">{t(Keys.panes.addCollection.unshardedLabel)}</span>
<input
className="panelRadioBtn"
checked={this.state.isSharded}
aria-label={t(Keys.panes.addCollection.sharded)}
aria-checked={this.state.isSharded}
name="sharded"
type="radio"
role="radio"
id="shardedOption"
tabIndex={0}
onChange={this.onShardedRadioBtnChange.bind(this)}
/>
<span className="panelRadioBtnLabel">{t(Keys.panes.addCollection.sharded)}</span>
</Stack>
</TooltipHost>
</Stack>
)}
<Stack horizontal verticalAlign="center">
<input
className="panelRadioBtn"
checked={!this.state.isSharded}
aria-label={t(Keys.panes.addCollection.unsharded)}
aria-checked={!this.state.isSharded}
name="unsharded"
type="radio"
role="radio"
id="unshardedOption"
tabIndex={0}
onChange={this.onUnshardedRadioBtnChange.bind(this)}
/>
<span className="panelRadioBtnLabel">{t(Keys.panes.addCollection.unshardedLabel)}</span>
<input
className="panelRadioBtn"
checked={this.state.isSharded}
aria-label={t(Keys.panes.addCollection.sharded)}
aria-checked={this.state.isSharded}
name="sharded"
type="radio"
role="radio"
id="shardedOption"
tabIndex={0}
onChange={this.onShardedRadioBtnChange.bind(this)}
/>
<span className="panelRadioBtnLabel">{t(Keys.panes.addCollection.sharded)}</span>
</Stack>
</Stack>
)}
{this.state.isSharded && (
<Stack>
@@ -1191,7 +1129,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
}
if (this.state.createNewDatabase) {
return !this.state.isSharedThroughputChecked;
return true;
}
if (this.state.enableDedicatedThroughput) {
@@ -1206,9 +1144,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
return false;
}
return this.state.createNewDatabase
? this.state.isSharedThroughputChecked
: this.isSelectedDatabaseSharedThroughput();
return this.state.createNewDatabase ? false : this.isSelectedDatabaseSharedThroughput();
}
private shouldShowVectorSearchParameters() {
@@ -1253,9 +1189,9 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
return false;
}
const throughput = this.state.createNewDatabase ? this.newDatabaseThroughput : this.collectionThroughput;
const throughput = this.collectionThroughput;
if (throughput > CollectionCreation.DefaultCollectionRUs100K && !this.isCostAcknowledged) {
const errorMessage = this.isNewDatabaseAutoscale
const errorMessage = this.isCollectionAutoscale
? t(Keys.panes.addCollection.acknowledgeSpendErrorMonthly)
: t(Keys.panes.addCollection.acknowledgeSpendErrorDaily);
this.setState({ errorMessage });
@@ -1379,9 +1315,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
database: {
id: databaseId,
new: this.state.createNewDatabase,
shared: this.state.createNewDatabase
? this.state.isSharedThroughputChecked
: this.isSelectedDatabaseSharedThroughput(),
shared: this.state.createNewDatabase ? false : this.isSelectedDatabaseSharedThroughput(),
},
collection: {
id: this.state.collectionId,
@@ -1399,7 +1333,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
const startKey: number = TelemetryProcessor.traceStart(Action.CreateCollection, telemetryData);
const databaseLevelThroughput: boolean = this.state.createNewDatabase
? this.state.isSharedThroughputChecked
? false
: this.isSelectedDatabaseSharedThroughput() && !this.state.enableDedicatedThroughput;
let offerThroughput: number;
@@ -1410,13 +1344,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
autoPilotMaxThroughput = DEFAULT_FABRIC_NATIVE_CONTAINER_THROUGHPUT;
offerThroughput = undefined;
} else if (databaseLevelThroughput) {
if (this.state.createNewDatabase) {
if (this.isNewDatabaseAutoscale) {
autoPilotMaxThroughput = this.newDatabaseThroughput;
} else {
offerThroughput = this.newDatabaseThroughput;
}
}
// Existing shared database: no collection-level throughput needed
} else {
if (this.isCollectionAutoscale) {
autoPilotMaxThroughput = this.collectionThroughput;
@@ -105,49 +105,6 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
type="text"
value=""
/>
<Stack
horizontal={true}
>
<StyledCheckboxBase
checked={false}
label="Share throughput across containers"
onChange={[Function]}
styles={
{
"checkbox": {
"height": 12,
"width": 12,
},
"label": {
"alignItems": "center",
"padding": 0,
},
"root": {
"selectors": {
":hover .ms-Checkbox-text": {
"color": "var(--colorNeutralForeground1)",
},
},
},
"text": {
"color": "var(--colorNeutralForeground1)",
"fontSize": 12,
},
}
}
/>
<StyledTooltipHostBase
content="Throughput configured at the database level will be shared across all containers within the database."
directionalHint={4}
>
<Icon
ariaLabel="Throughput configured at the database level will be shared across all containers within the database."
className="panelInfoIcon"
iconName="Info"
tabIndex={0}
/>
</StyledTooltipHostBase>
</Stack>
</Stack>
<Separator
className="panelSeparator"
@@ -1,5 +1,4 @@
import { Checkbox, Stack, Text, TextField } from "@fluentui/react";
import { getNewDatabaseSharedThroughputDefault } from "Common/DatabaseUtility";
import { Stack, Text, TextField } from "@fluentui/react";
import { Keys, t } from "Localization";
import { ValidCosmosDbIdDescription, ValidCosmosDbIdInputPattern } from "Utils/ValidationUtils";
import React, { FunctionComponent, useEffect, useState } from "react";
@@ -9,15 +8,11 @@ import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip";
import { createDatabase } from "../../../Common/dataAccess/createDatabase";
import * as DataModels from "../../../Contracts/DataModels";
import { SubscriptionType } from "../../../Contracts/SubscriptionType";
import * as SharedConstants from "../../../Shared/Constants";
import { Action, ActionModifiers } 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 { getUpsellMessage } from "../../../Utils/PricingUtils";
import { useSidePanel } from "../../../hooks/useSidePanel";
import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput";
import Explorer from "../../Explorer";
import { useDatabases } from "../../useDatabases";
import { PanelInfoErrorComponent } from "../PanelInfoErrorComponent";
@@ -34,9 +29,6 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
buttonElement,
}: AddDatabasePaneProps) => {
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
let throughput: number;
let isAutoscaleSelected: boolean;
let isCostAcknowledged: boolean;
const { subscriptionType } = userContext;
const isCassandraAccount: boolean = userContext.apiType === "Cassandra";
const databaseLabel: string = isCassandraAccount ? "keyspace" : "database";
@@ -49,23 +41,15 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
const [databaseId, setDatabaseId] = useState<string>("");
const databaseIdTooltipText = t(Keys.panes.addDatabase.databaseTooltip, { databaseLabel, collectionsLabel });
const databaseLevelThroughputTooltipText = t(Keys.panes.addDatabase.shareThroughputTooltip, {
databaseLabel,
collectionsLabel,
});
const [databaseCreateNewShared, setDatabaseCreateNewShared] = useState<boolean>(
getNewDatabaseSharedThroughputDefault(),
);
const [formErrors, setFormErrors] = useState<string>("");
const [isExecuting, setIsExecuting] = useState<boolean>(false);
const [isThroughputCapExceeded, setIsThroughputCapExceeded] = useState<boolean>(false);
const isFreeTierAccount: boolean = userContext.databaseAccount?.properties?.enableFreeTier;
const addDatabasePaneMessage = {
database: {
id: databaseId,
shared: databaseCreateNewShared,
shared: false,
},
subscriptionType: SubscriptionType[subscriptionType],
subscriptionQuotaId: userContext.quotaId,
@@ -76,9 +60,7 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
const addDatabasePaneOpenMessage = {
subscriptionType: SubscriptionType[subscriptionType],
subscriptionQuotaId: userContext.quotaId,
defaultsCheck: {
throughput,
},
defaultsCheck: {},
dataExplorerArea: Constants.Areas.ContextualPane,
};
TelemetryProcessor.trace(Action.CreateDatabase, ActionModifiers.Open, addDatabasePaneOpenMessage);
@@ -88,13 +70,8 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
}, []);
const onSubmit = () => {
if (!_isValid()) {
return;
}
const addDatabasePaneStartMessage = {
...addDatabasePaneMessage,
throughput,
};
const startKey: number = TelemetryProcessor.traceStart(Action.CreateDatabase, addDatabasePaneStartMessage);
setFormErrors("");
@@ -102,69 +79,41 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
const createDatabaseParams: DataModels.CreateDatabaseParams = {
databaseId: addDatabasePaneStartMessage.database.id,
databaseLevelThroughput: addDatabasePaneStartMessage.database.shared,
databaseLevelThroughput: false,
};
if (isAutoscaleSelected) {
createDatabaseParams.autoPilotMaxThroughput = addDatabasePaneStartMessage.throughput;
} else {
createDatabaseParams.offerThroughput = addDatabasePaneStartMessage.throughput;
}
createDatabase(createDatabaseParams).then(
() => {
_onCreateDatabaseSuccess(throughput, startKey);
_onCreateDatabaseSuccess(startKey);
},
(error: string) => {
_onCreateDatabaseFailure(error, throughput, startKey);
_onCreateDatabaseFailure(error, startKey);
},
);
};
const _onCreateDatabaseSuccess = (offerThroughput: number, startKey: number): void => {
const _onCreateDatabaseSuccess = (startKey: number): void => {
setIsExecuting(false);
closeSidePanel();
container.refreshAllDatabases();
const addDatabasePaneSuccessMessage = {
...addDatabasePaneMessage,
offerThroughput,
};
TelemetryProcessor.traceSuccess(Action.CreateDatabase, addDatabasePaneSuccessMessage, startKey);
};
const _onCreateDatabaseFailure = (error: string, offerThroughput: number, startKey: number): void => {
const _onCreateDatabaseFailure = (error: string, startKey: number): void => {
setIsExecuting(false);
const errorMessage = getErrorMessage(error);
setFormErrors(errorMessage);
const addDatabasePaneFailedMessage = {
...addDatabasePaneMessage,
offerThroughput,
error: errorMessage,
errorStack: getErrorStack(error),
};
TelemetryProcessor.traceFailure(Action.CreateDatabase, addDatabasePaneFailedMessage, startKey);
};
const _isValid = (): boolean => {
// TODO add feature flag that disables validation for customers with custom accounts
if (isAutoscaleSelected) {
if (!AutoPilotUtils.isValidAutoPilotThroughput(throughput)) {
setFormErrors(t(Keys.panes.addDatabase.greaterThanError, { minValue: AutoPilotUtils.autoPilotThroughput1K }));
return false;
}
}
if (throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && !isCostAcknowledged) {
setFormErrors(
isAutoscaleSelected
? t(Keys.panes.addDatabase.acknowledgeSpendErrorMonthly)
: t(Keys.panes.addDatabase.acknowledgeSpendErrorDaily),
);
return false;
}
return true;
};
const handleonChangeDBId = React.useCallback(
(event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) => {
setDatabaseId(newValue || "");
@@ -176,7 +125,6 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
formError: formErrors,
isExecuting,
submitButtonText: t(Keys.common.ok),
isSubmitButtonDisabled: isThroughputCapExceeded,
onSubmit,
};
@@ -224,42 +172,7 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
data-lpignore={true}
data-1p-ignore={true}
/>
{!isServerlessAccount() && (
<Stack horizontal>
<Checkbox
title={t(Keys.panes.addDatabase.provisionSharedThroughputTitle)}
styles={{
text: { fontSize: 12, color: "var(--colorNeutralForeground1)" },
checkbox: { width: 12, height: 12 },
label: { padding: 0, alignItems: "center" },
root: {
selectors: {
":hover .ms-Checkbox-text": { color: "var(--colorNeutralForeground1)" },
},
},
}}
label={t(Keys.panes.addDatabase.provisionThroughputLabel)}
checked={databaseCreateNewShared}
onChange={() => setDatabaseCreateNewShared(!databaseCreateNewShared)}
/>
<InfoTooltip>{databaseLevelThroughputTooltipText}</InfoTooltip>
</Stack>
)}
</Stack>
{!isServerlessAccount() && databaseCreateNewShared && (
<ThroughputInput
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !useDatabases.getState().isFirstResourceCreated()}
isDatabase={true}
isSharded={databaseCreateNewShared}
isFreeTier={isFreeTierAccount}
setThroughputValue={(newThroughput: number) => (throughput = newThroughput)}
setIsAutoscale={(isAutoscale: boolean) => (isAutoscaleSelected = isAutoscale)}
setIsThroughputCapExceeded={(isCapExceeded: boolean) => setIsThroughputCapExceeded(isCapExceeded)}
onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)}
/>
)}
</div>
</RightPaneForm>
);
@@ -4,7 +4,6 @@ exports[`AddDatabasePane Pane should render Default properly 1`] = `
<RightPaneForm
formError=""
isExecuting={false}
isSubmitButtonDisabled={false}
onSubmit={[Function]}
submitButtonText="OK"
>
@@ -61,42 +60,6 @@ exports[`AddDatabasePane Pane should render Default properly 1`] = `
type="text"
value=""
/>
<Stack
horizontal={true}
>
<StyledCheckboxBase
checked={false}
label="Provision throughput"
onChange={[Function]}
styles={
{
"checkbox": {
"height": 12,
"width": 12,
},
"label": {
"alignItems": "center",
"padding": 0,
},
"root": {
"selectors": {
":hover .ms-Checkbox-text": {
"color": "var(--colorNeutralForeground1)",
},
},
},
"text": {
"color": "var(--colorNeutralForeground1)",
"fontSize": 12,
},
}
}
title="Provision shared throughput"
/>
<InfoTooltip>
Provisioned throughput at the database level will be shared across all collections within the database.
</InfoTooltip>
</Stack>
</Stack>
</div>
</RightPaneForm>
@@ -15,7 +15,7 @@ describe("Cassandra add collection pane test", () => {
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();
expect(screen.queryByRole("checkbox", { name: "Provision shared throughput" })).toBeNull();
});
it("click on use existing", () => {
@@ -1,4 +1,4 @@
import { Checkbox, Dropdown, IDropdownOption, Link, Stack, Text, TextField } from "@fluentui/react";
import { 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";
@@ -27,8 +27,6 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
explorer: container,
cassandraApiClient,
}: CassandraAddCollectionPaneProps) => {
let newKeySpaceThroughput: number;
let isNewKeySpaceAutoscale: boolean;
let tableThroughput: number;
let isTableAutoscale: boolean;
let isCostAcknowledged: boolean;
@@ -52,7 +50,7 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
collection: {
id: tableId,
storage: Constants.BackendDefaults.multiPartitionStorageInGb,
offerThroughput: newKeySpaceThroughput || tableThroughput,
offerThroughput: tableThroughput,
partitionKey: "",
databaseId: keyspaceCreateNew ? newKeyspaceId : existingKeyspaceId,
},
@@ -60,33 +58,28 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
subscriptionQuotaId: userContext.quotaId,
defaultsCheck: {
storage: "u",
throughput: newKeySpaceThroughput || tableThroughput,
throughput: tableThroughput,
},
dataExplorerArea: Constants.Areas.ContextualPane,
};
const onSubmit = async () => {
const throughput = keyspaceCreateNew ? newKeySpaceThroughput : tableThroughput;
const throughput = 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);
const errorMessage = 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};`;
const createKeyspaceQuery = `${createKeyspaceQueryPrefix};`;
let tableQuery: string;
const autoPilotCommand = `cosmosdb_autoscale_max_throughput`;
const createTableQueryPrefix = `CREATE TABLE ${keyspaceId}.${tableId.trim()} ${userTableQuery}`;
if (tableThroughput) {
@@ -177,7 +170,6 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
tabIndex={0}
onChange={() => {
setKeyspaceCreateNew(true);
setIsKeyspaceShared(false);
setExistingKeyspaceId("");
}}
/>
@@ -192,7 +184,6 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
tabIndex={0}
onChange={() => {
setKeyspaceCreateNew(false);
setIsKeyspaceShared(false);
}}
/>
<span className="panelRadioBtnLabel">{t(Keys.panes.addCollection.useExisting)}</span>
@@ -214,25 +205,6 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
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>
)}
@@ -256,21 +228,6 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
responsiveMode={999}
/>
)}
{!isServerlessAccount() && keyspaceCreateNew && isKeyspaceShared && (
<ThroughputInput
showFreeTierExceedThroughputTooltip={
isFreeTierAccount && !useDatabases.getState().isFirstResourceCreated()
}
isDatabase
isSharded
isFreeTier={isFreeTierAccount}
setThroughputValue={(throughput: number) => (newKeySpaceThroughput = throughput)}
setIsAutoscale={(isAutoscale: boolean) => (isNewKeySpaceAutoscale = isAutoscale)}
setIsThroughputCapExceeded={(isCapExceeded: boolean) => setIsThroughputCapExceeded(isCapExceeded)}
onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)}
/>
)}
</Stack>
<Stack>
@@ -328,7 +285,7 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
<InfoTooltip>{t(Keys.panes.cassandraAddCollection.provisionDedicatedThroughputTooltip)}</InfoTooltip>
</Stack>
)}
{!isServerlessAccount() && (!isKeyspaceShared || dedicateTableThroughput) && (
{!isServerlessAccount() && (keyspaceCreateNew || !isKeyspaceShared || dedicateTableThroughput) && (
<ThroughputInput
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !useDatabases.getState().isFirstResourceCreated()}
isDatabase={false}
@@ -112,6 +112,9 @@ describe("Delete Collection Confirmation Pane", () => {
const wrapper = mount(<DeleteCollectionConfirmationPane refreshDatabases={() => undefined} />);
expect(wrapper).toMatchSnapshot();
expect(wrapper.exists("#copyableCollectionId")).toBe(true);
expect(wrapper.find("#copyableCollectionId").hostNodes().prop("value")).toBe(selectedCollectionId);
expect(wrapper.exists("#confirmCollectionId")).toBe(true);
wrapper
.find("#confirmCollectionId")
@@ -1,4 +1,4 @@
import { Text, TextField } from "@fluentui/react";
import { IconButton, Text, TextField } from "@fluentui/react";
import { Areas } from "Common/Constants";
import DeleteFeedback from "Common/DeleteFeedback";
import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
@@ -17,6 +17,7 @@ import React, { FunctionComponent, useState } from "react";
import { useDatabases } from "../../useDatabases";
import { useSelectedNode } from "../../useSelectedNode";
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
import { PanelInfoErrorComponent, PanelInfoErrorProps } from "../PanelInfoErrorComponent";
const themedTextFieldStyles = {
fieldGroup: {
@@ -54,6 +55,10 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
const collectionName = getCollectionName().toLocaleLowerCase();
const paneTitle = t(Keys.panes.deleteCollection.panelTitle, { collectionName });
const selectedCollection = useSelectedNode.getState().selectedNode
? useSelectedNode.getState().findSelectedCollection()
: undefined;
const selectedCollectionId = selectedCollection?.id() ?? "";
const onSubmit = async (): Promise<void> => {
const collection = useSelectedNode.getState().findSelectedCollection();
@@ -131,6 +136,14 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
submitButtonText: t(Keys.common.ok),
onSubmit,
};
const errorProps: PanelInfoErrorProps = {
messageType: "warning",
showErrorDetails: false,
message: t(Keys.panes.deleteCollection.warningMessage),
};
const copyableIdLabel = t(Keys.panes.deleteCollection.copyableId, {
collectionName: getCollectionName(),
});
const confirmContainer = t(Keys.panes.deleteCollection.confirmPrompt, {
collectionName: collectionName.toLowerCase(),
});
@@ -140,9 +153,29 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
t(Keys.panes.deleteCollection.feedbackReason, { collectionName });
return (
<RightPaneForm {...props}>
{!formError && <PanelInfoErrorComponent {...errorProps} />}
<div className="panelFormWrapper">
<div className="panelMainContent">
<div className="confirmDeleteInput">
<Text variant="small" style={{ color: "var(--colorNeutralForeground1)" }}>
{copyableIdLabel}
</Text>
<TextField
id="copyableCollectionId"
readOnly
value={selectedCollectionId}
styles={themedTextFieldStyles}
onRenderSuffix={() => (
<IconButton
iconProps={{ iconName: "Copy" }}
title={t(Keys.common.copy)}
ariaLabel={t(Keys.common.copy)}
onClick={() => navigator.clipboard.writeText(selectedCollectionId)}
styles={{ root: { height: "100%" } }}
/>
)}
ariaLabel={copyableIdLabel}
/>
<span className="mandatoryStar">* </span>
<Text variant="small" style={{ color: "var(--colorNeutralForeground1)" }}>
{confirmContainer}
@@ -62,13 +62,15 @@ describe("Delete Database Confirmation Pane", () => {
const wrapper = mount(<DeleteDatabaseConfirmationPanel refreshDatabases={() => undefined} />);
expect(wrapper).toMatchSnapshot();
expect(wrapper.exists("#confirmDatabaseId")).toBe(true);
expect(wrapper.exists("#copyableDatabaseId")).toBe(true);
expect(wrapper.find("#copyableDatabaseId").hostNodes().prop("value")).toBe(selectedDatabaseId);
wrapper
.find("#confirmDatabaseId")
.hostNodes()
.simulate("change", { target: { value: selectedDatabaseId } });
expect(wrapper.exists("button")).toBe(true);
wrapper.find("button").hostNodes().simulate("submit");
wrapper.find("#sidePanelOkButton").hostNodes().simulate("submit");
expect(deleteDatabase).toHaveBeenCalledWith(selectedDatabaseId);
wrapper.unmount();
});
@@ -1,4 +1,4 @@
import { Text, TextField } from "@fluentui/react";
import { IconButton, Text, TextField } from "@fluentui/react";
import { useBoolean } from "@fluentui/react-hooks";
import { Areas } from "Common/Constants";
import DeleteFeedback from "Common/DeleteFeedback";
@@ -150,6 +150,7 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
showErrorDetails: false,
message: t(Keys.panes.deleteDatabase.warningMessage),
};
const copyableIdLabel = t(Keys.panes.deleteDatabase.copyableId, { databaseName: getDatabaseName() });
const confirmDatabase = t(Keys.panes.deleteDatabase.confirmPrompt, { databaseName: getDatabaseName() });
const reasonInfo =
t(Keys.panes.deleteDatabase.feedbackTitle) +
@@ -160,6 +161,25 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
{!formError && <PanelInfoErrorComponent {...errorProps} />}
<div className="panelMainContent">
<div className="confirmDeleteInput">
<Text variant="small" style={{ color: "var(--colorNeutralForeground1)" }}>
{copyableIdLabel}
</Text>
<TextField
id="copyableDatabaseId"
readOnly
value={selectedDatabase?.id() ?? ""}
styles={themedTextFieldStyles}
onRenderSuffix={() => (
<IconButton
iconProps={{ iconName: "Copy" }}
title={t(Keys.common.copy)}
ariaLabel={t(Keys.common.copy)}
onClick={() => navigator.clipboard.writeText(selectedDatabase?.id() ?? "")}
styles={{ root: { height: "100%" } }}
/>
)}
ariaLabel={copyableIdLabel}
/>
<span className="mandatoryStar">* </span>
<Text variant="small" style={{ color: "var(--colorNeutralForeground1)" }}>
{confirmDatabase}
File diff suppressed because it is too large Load Diff