mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2024-11-25 23:16:56 +00:00
Fix throughput input component and add database panel (#773)
This commit is contained in:
parent
0a6c7c0ff9
commit
2f6dbd83f3
@ -19,18 +19,4 @@ describe("ThroughputInput Pane", () => {
|
|||||||
it("should render Default properly", () => {
|
it("should render Default properly", () => {
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("test Autoscale Mode select", () => {
|
|
||||||
wrapper.setProps({ isAutoscaleSelected: true });
|
|
||||||
expect(wrapper.find('[aria-label="ruDescription"]').at(0).text()).toBe(
|
|
||||||
"Estimate your required RU/s with capacity calculator."
|
|
||||||
);
|
|
||||||
expect(wrapper.find('[aria-label="maxRUDescription"]').at(0).text()).toContain("Max RU/s");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("test Manual Mode select", () => {
|
|
||||||
wrapper.setProps({ isAutoscaleSelected: false });
|
|
||||||
expect(wrapper.find('[aria-label="ruDescription"]').at(0).text()).toContain("Estimate your required RU/s with");
|
|
||||||
expect(wrapper.find('[aria-label="capacityLink"]').at(0).text()).toContain("capacity calculator");
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -17,8 +17,6 @@ export interface ThroughputInputProps {
|
|||||||
setThroughputValue: (throughput: number) => void;
|
setThroughputValue: (throughput: number) => void;
|
||||||
setIsAutoscale: (isAutoscale: boolean) => void;
|
setIsAutoscale: (isAutoscale: boolean) => void;
|
||||||
onCostAcknowledgeChange: (isAcknowledged: boolean) => void;
|
onCostAcknowledgeChange: (isAcknowledged: boolean) => void;
|
||||||
isAutoscaleSelected?: boolean;
|
|
||||||
throughput?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
||||||
@ -27,12 +25,16 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
|||||||
setThroughputValue,
|
setThroughputValue,
|
||||||
setIsAutoscale,
|
setIsAutoscale,
|
||||||
isSharded,
|
isSharded,
|
||||||
isAutoscaleSelected = true,
|
|
||||||
throughput = AutoPilotUtils.minAutoPilotThroughput,
|
|
||||||
onCostAcknowledgeChange,
|
onCostAcknowledgeChange,
|
||||||
}: ThroughputInputProps) => {
|
}: ThroughputInputProps) => {
|
||||||
|
const [isAutoscaleSelected, setIsAutoScaleSelected] = useState<boolean>(true);
|
||||||
|
const [throughput, setThroughput] = useState<number>(AutoPilotUtils.minAutoPilotThroughput);
|
||||||
const [isCostAcknowledged, setIsCostAcknowledged] = useState<boolean>(false);
|
const [isCostAcknowledged, setIsCostAcknowledged] = useState<boolean>(false);
|
||||||
const [throughputError, setThroughputError] = useState<string>("");
|
const [throughputError, setThroughputError] = useState<string>("");
|
||||||
|
|
||||||
|
setIsAutoscale(isAutoscaleSelected);
|
||||||
|
setThroughputValue(throughput);
|
||||||
|
|
||||||
const getThroughputLabelText = (): string => {
|
const getThroughputLabelText = (): string => {
|
||||||
let throughputHeaderText: string;
|
let throughputHeaderText: string;
|
||||||
if (isAutoscaleSelected) {
|
if (isAutoscaleSelected) {
|
||||||
@ -49,6 +51,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
|||||||
|
|
||||||
const onThroughputValueChange = (newInput: string): void => {
|
const onThroughputValueChange = (newInput: string): void => {
|
||||||
const newThroughput = parseInt(newInput);
|
const newThroughput = parseInt(newInput);
|
||||||
|
setThroughput(newThroughput);
|
||||||
setThroughputValue(newThroughput);
|
setThroughputValue(newThroughput);
|
||||||
if (!isSharded && newThroughput > 10000) {
|
if (!isSharded && newThroughput > 10000) {
|
||||||
setThroughputError("Unsharded collections support up to 10,000 RUs");
|
setThroughputError("Unsharded collections support up to 10,000 RUs");
|
||||||
@ -82,9 +85,13 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
|||||||
|
|
||||||
const handleOnChangeMode = (event: React.ChangeEvent<HTMLInputElement>, mode: string): void => {
|
const handleOnChangeMode = (event: React.ChangeEvent<HTMLInputElement>, mode: string): void => {
|
||||||
if (mode === "Autoscale") {
|
if (mode === "Autoscale") {
|
||||||
|
setThroughput(AutoPilotUtils.minAutoPilotThroughput);
|
||||||
|
setIsAutoScaleSelected(true);
|
||||||
setThroughputValue(AutoPilotUtils.minAutoPilotThroughput);
|
setThroughputValue(AutoPilotUtils.minAutoPilotThroughput);
|
||||||
setIsAutoscale(true);
|
setIsAutoscale(true);
|
||||||
} else {
|
} else {
|
||||||
|
setThroughput(SharedConstants.CollectionCreation.DefaultCollectionRUs400);
|
||||||
|
setIsAutoScaleSelected(false);
|
||||||
setThroughputValue(SharedConstants.CollectionCreation.DefaultCollectionRUs400);
|
setThroughputValue(SharedConstants.CollectionCreation.DefaultCollectionRUs400);
|
||||||
setIsAutoscale(false);
|
setIsAutoscale(false);
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ 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 { getCollectionName } from "../../Utils/APITypeUtils";
|
import { getCollectionName } from "../../Utils/APITypeUtils";
|
||||||
import { isCapabilityEnabled } from "../../Utils/CapabilityUtils";
|
import { isCapabilityEnabled, isServerlessAccount } from "../../Utils/CapabilityUtils";
|
||||||
import { getUpsellMessage } from "../../Utils/PricingUtils";
|
import { getUpsellMessage } from "../../Utils/PricingUtils";
|
||||||
import { CollapsibleSectionComponent } from "../Controls/CollapsiblePanel/CollapsibleSectionComponent";
|
import { CollapsibleSectionComponent } from "../Controls/CollapsiblePanel/CollapsibleSectionComponent";
|
||||||
import { ThroughputInput } from "../Controls/ThroughputInput/ThroughputInput";
|
import { ThroughputInput } from "../Controls/ThroughputInput/ThroughputInput";
|
||||||
@ -182,7 +182,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!this.isServerlessAccount() && (
|
{!isServerlessAccount() && (
|
||||||
<Stack horizontal>
|
<Stack horizontal>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label={`Share throughput across ${getCollectionName(true).toLocaleLowerCase()}`}
|
label={`Share throughput across ${getCollectionName(true).toLocaleLowerCase()}`}
|
||||||
@ -207,14 +207,12 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!this.isServerlessAccount() && this.state.isSharedThroughputChecked && (
|
{!isServerlessAccount() && this.state.isSharedThroughputChecked && (
|
||||||
<ThroughputInput
|
<ThroughputInput
|
||||||
showFreeTierExceedThroughputTooltip={
|
showFreeTierExceedThroughputTooltip={
|
||||||
this.isFreeTierAccount() && !this.props.explorer.isFirstResourceCreated()
|
this.isFreeTierAccount() && !this.props.explorer.isFirstResourceCreated()
|
||||||
}
|
}
|
||||||
isDatabase={true}
|
isDatabase={true}
|
||||||
isAutoscaleSelected={this.isNewDatabaseAutoscale}
|
|
||||||
throughput={this.newDatabaseThroughput}
|
|
||||||
isSharded={this.state.isSharded}
|
isSharded={this.state.isSharded}
|
||||||
setThroughputValue={(throughput: number) => (this.newDatabaseThroughput = throughput)}
|
setThroughputValue={(throughput: number) => (this.newDatabaseThroughput = throughput)}
|
||||||
setIsAutoscale={(isAutoscale: boolean) => (this.isNewDatabaseAutoscale = isAutoscale)}
|
setIsAutoscale={(isAutoscale: boolean) => (this.isNewDatabaseAutoscale = isAutoscale)}
|
||||||
@ -398,7 +396,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
if (
|
if (
|
||||||
userContext.apiType !== "Mongo" &&
|
userContext.apiType !== "Mongo" &&
|
||||||
this.state.partitionKey === "" &&
|
!this.state.partitionKey &&
|
||||||
!event.target.value.startsWith("/")
|
!event.target.value.startsWith("/")
|
||||||
) {
|
) {
|
||||||
this.setState({ partitionKey: "/" + event.target.value });
|
this.setState({ partitionKey: "/" + event.target.value });
|
||||||
@ -410,7 +408,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!this.isServerlessAccount() && !this.state.createNewDatabase && this.isSelectedDatabaseSharedThroughput() && (
|
{!isServerlessAccount() && !this.state.createNewDatabase && this.isSelectedDatabaseSharedThroughput() && (
|
||||||
<Stack horizontal verticalAlign="center">
|
<Stack horizontal verticalAlign="center">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label={`Provision dedicated throughput for this ${getCollectionName().toLocaleLowerCase()}`}
|
label={`Provision dedicated throughput for this ${getCollectionName().toLocaleLowerCase()}`}
|
||||||
@ -444,8 +442,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
this.isFreeTierAccount() && !this.props.explorer.isFirstResourceCreated()
|
this.isFreeTierAccount() && !this.props.explorer.isFirstResourceCreated()
|
||||||
}
|
}
|
||||||
isDatabase={false}
|
isDatabase={false}
|
||||||
isAutoscaleSelected={this.isCollectionAutoscale}
|
|
||||||
throughput={this.collectionThroughput}
|
|
||||||
isSharded={this.state.isSharded}
|
isSharded={this.state.isSharded}
|
||||||
setThroughputValue={(throughput: number) => (this.collectionThroughput = throughput)}
|
setThroughputValue={(throughput: number) => (this.collectionThroughput = throughput)}
|
||||||
setIsAutoscale={(isAutoscale: boolean) => (this.isCollectionAutoscale = isAutoscale)}
|
setIsAutoscale={(isAutoscale: boolean) => (this.isCollectionAutoscale = isAutoscale)}
|
||||||
@ -755,14 +751,8 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
return userContext.databaseAccount?.properties?.enableFreeTier;
|
return userContext.databaseAccount?.properties?.enableFreeTier;
|
||||||
}
|
}
|
||||||
|
|
||||||
private isServerlessAccount(): boolean {
|
|
||||||
return userContext.databaseAccount.properties?.capabilities?.some(
|
|
||||||
(capability) => capability.name === Constants.CapabilityNames.EnableServerless
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private getSharedThroughputDefault(): boolean {
|
private getSharedThroughputDefault(): boolean {
|
||||||
return userContext.subscriptionType !== SubscriptionType.EA && !this.isServerlessAccount();
|
return userContext.subscriptionType !== SubscriptionType.EA && !isServerlessAccount();
|
||||||
}
|
}
|
||||||
|
|
||||||
private getFreeTierIndexingText(): string {
|
private getFreeTierIndexingText(): string {
|
||||||
@ -800,7 +790,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private shouldShowCollectionThroughputInput(): boolean {
|
private shouldShowCollectionThroughputInput(): boolean {
|
||||||
if (this.isServerlessAccount()) {
|
if (isServerlessAccount()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -830,7 +820,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isServerlessAccount()) {
|
if (isServerlessAccount()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import { Checkbox, Text, TextField } from "@fluentui/react";
|
import { Checkbox, Stack, Text, TextField } from "@fluentui/react";
|
||||||
import React, { FunctionComponent, useEffect, useState } from "react";
|
import React, { FunctionComponent, useEffect, useState } from "react";
|
||||||
import * as Constants from "../../../Common/Constants";
|
import * as Constants from "../../../Common/Constants";
|
||||||
import { createDatabase } from "../../../Common/dataAccess/createDatabase";
|
import { createDatabase } from "../../../Common/dataAccess/createDatabase";
|
||||||
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 { configContext, Platform } from "../../../ConfigContext";
|
|
||||||
import * as DataModels from "../../../Contracts/DataModels";
|
import * as DataModels from "../../../Contracts/DataModels";
|
||||||
import { SubscriptionType } from "../../../Contracts/SubscriptionType";
|
import { SubscriptionType } from "../../../Contracts/SubscriptionType";
|
||||||
import * as SharedConstants from "../../../Shared/Constants";
|
import * as SharedConstants from "../../../Shared/Constants";
|
||||||
@ -12,7 +11,8 @@ import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryCons
|
|||||||
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 * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
|
||||||
import * as PricingUtils from "../../../Utils/PricingUtils";
|
import { isServerlessAccount } from "../../../Utils/CapabilityUtils";
|
||||||
|
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";
|
||||||
@ -29,17 +29,10 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
|||||||
closePanel,
|
closePanel,
|
||||||
openNotificationConsole,
|
openNotificationConsole,
|
||||||
}: AddDatabasePaneProps) => {
|
}: AddDatabasePaneProps) => {
|
||||||
|
let throughput: number;
|
||||||
|
let isAutoscaleSelected: boolean;
|
||||||
|
let isCostAcknowledged: boolean;
|
||||||
const { subscriptionType } = userContext;
|
const { subscriptionType } = userContext;
|
||||||
const getSharedThroughputDefault = !(subscriptionType === SubscriptionType.EA || container.isServerlessEnabled());
|
|
||||||
const _isAutoPilotSelectedAndWhatTier = (): DataModels.AutoPilotCreationSettings => {
|
|
||||||
if (isAutoPilotSelected && maxAutoPilotThroughputSet) {
|
|
||||||
return {
|
|
||||||
maxThroughput: maxAutoPilotThroughputSet * 1,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
const isCassandraAccount: boolean = userContext.apiType === "Cassandra";
|
const isCassandraAccount: boolean = userContext.apiType === "Cassandra";
|
||||||
const databaseLabel: string = isCassandraAccount ? "keyspace" : "database";
|
const databaseLabel: string = isCassandraAccount ? "keyspace" : "database";
|
||||||
const collectionsLabel: string = isCassandraAccount ? "tables" : "collections";
|
const collectionsLabel: string = isCassandraAccount ? "tables" : "collections";
|
||||||
@ -52,61 +45,14 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
|||||||
} is a logical container of one or more ${isCassandraAccount ? "tables" : "collections"}`;
|
} is a logical container of one or more ${isCassandraAccount ? "tables" : "collections"}`;
|
||||||
|
|
||||||
const databaseLevelThroughputTooltipText = `Provisioned throughput at the ${databaseLabel} level will be shared across all ${collectionsLabel} within the ${databaseLabel}.`;
|
const databaseLevelThroughputTooltipText = `Provisioned throughput at the ${databaseLabel} level will be shared across all ${collectionsLabel} within the ${databaseLabel}.`;
|
||||||
const [databaseCreateNewShared, setDatabaseCreateNewShared] = useState<boolean>(getSharedThroughputDefault);
|
const [databaseCreateNewShared, setDatabaseCreateNewShared] = useState<boolean>(
|
||||||
|
subscriptionType !== SubscriptionType.EA && !isServerlessAccount()
|
||||||
|
);
|
||||||
const [formErrorsDetails, setFormErrorsDetails] = useState<string>();
|
const [formErrorsDetails, setFormErrorsDetails] = useState<string>();
|
||||||
const [formErrors, setFormErrors] = useState<string>("");
|
const [formErrors, setFormErrors] = useState<string>("");
|
||||||
|
|
||||||
const [isAutoPilotSelected, setIsAutoPilotSelected] = useState<boolean>(container.isAutoscaleDefaultEnabled());
|
|
||||||
|
|
||||||
const throughputDefaults = container.collectionCreationDefaults.throughput;
|
|
||||||
const [throughput, setThroughput] = useState<number>(
|
|
||||||
isAutoPilotSelected ? AutoPilotUtils.minAutoPilotThroughput : throughputDefaults.shared
|
|
||||||
);
|
|
||||||
|
|
||||||
const [throughputSpendAck, setThroughputSpendAck] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const canRequestSupport = () => {
|
|
||||||
if (
|
|
||||||
configContext.platform !== Platform.Emulator &&
|
|
||||||
!userContext.isTryCosmosDBSubscription &&
|
|
||||||
configContext.platform !== Platform.Portal
|
|
||||||
) {
|
|
||||||
const offerThroughput: number = throughput;
|
|
||||||
return offerThroughput <= 100000;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
const isFreeTierAccount: boolean = userContext.databaseAccount?.properties?.enableFreeTier;
|
|
||||||
const upsellMessage: string = PricingUtils.getUpsellMessage(
|
|
||||||
userContext.portalEnv,
|
|
||||||
isFreeTierAccount,
|
|
||||||
container.isFirstResourceCreated(),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
const upsellAnchorUrl: string = isFreeTierAccount ? Constants.Urls.freeTierInformation : Constants.Urls.cosmosPricing;
|
|
||||||
|
|
||||||
const upsellAnchorText: string = isFreeTierAccount ? "Learn more" : "More details";
|
|
||||||
const maxAutoPilotThroughputSet = AutoPilotUtils.minAutoPilotThroughput;
|
|
||||||
|
|
||||||
const canConfigureThroughput = !container.isServerlessEnabled();
|
|
||||||
const showUpsellMessage = () => {
|
|
||||||
if (container.isServerlessEnabled()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isFreeTierAccount) {
|
|
||||||
return databaseCreateNewShared;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
const [isExecuting, setIsExecuting] = useState<boolean>(false);
|
const [isExecuting, setIsExecuting] = useState<boolean>(false);
|
||||||
|
|
||||||
useEffect(() => {
|
const isFreeTierAccount: boolean = userContext.databaseAccount?.properties?.enableFreeTier;
|
||||||
setDatabaseCreateNewShared(getSharedThroughputDefault);
|
|
||||||
}, [subscriptionType]);
|
|
||||||
|
|
||||||
const addDatabasePaneMessage = {
|
const addDatabasePaneMessage = {
|
||||||
database: {
|
database: {
|
||||||
@ -126,7 +72,7 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
|||||||
subscriptionType: SubscriptionType[subscriptionType],
|
subscriptionType: SubscriptionType[subscriptionType],
|
||||||
subscriptionQuotaId: userContext.quotaId,
|
subscriptionQuotaId: userContext.quotaId,
|
||||||
defaultsCheck: {
|
defaultsCheck: {
|
||||||
throughput: throughput,
|
throughput,
|
||||||
flight: userContext.addCollectionFlight,
|
flight: userContext.addCollectionFlight,
|
||||||
},
|
},
|
||||||
dataExplorerArea: Constants.Areas.ContextualPane,
|
dataExplorerArea: Constants.Areas.ContextualPane,
|
||||||
@ -139,11 +85,9 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const offerThroughput: number = _computeOfferThroughput();
|
|
||||||
|
|
||||||
const addDatabasePaneStartMessage = {
|
const addDatabasePaneStartMessage = {
|
||||||
...addDatabasePaneMessage,
|
...addDatabasePaneMessage,
|
||||||
offerThroughput,
|
throughput,
|
||||||
};
|
};
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.CreateDatabase, addDatabasePaneStartMessage);
|
const startKey: number = TelemetryProcessor.traceStart(Action.CreateDatabase, addDatabasePaneStartMessage);
|
||||||
setFormErrors("");
|
setFormErrors("");
|
||||||
@ -153,18 +97,18 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
|||||||
databaseId: addDatabasePaneStartMessage.database.id,
|
databaseId: addDatabasePaneStartMessage.database.id,
|
||||||
databaseLevelThroughput: addDatabasePaneStartMessage.database.shared,
|
databaseLevelThroughput: addDatabasePaneStartMessage.database.shared,
|
||||||
};
|
};
|
||||||
if (isAutoPilotSelected) {
|
if (isAutoscaleSelected) {
|
||||||
createDatabaseParams.autoPilotMaxThroughput = addDatabasePaneStartMessage.offerThroughput;
|
createDatabaseParams.autoPilotMaxThroughput = addDatabasePaneStartMessage.throughput;
|
||||||
} else {
|
} else {
|
||||||
createDatabaseParams.offerThroughput = addDatabasePaneStartMessage.offerThroughput;
|
createDatabaseParams.offerThroughput = addDatabasePaneStartMessage.throughput;
|
||||||
}
|
}
|
||||||
|
|
||||||
createDatabase(createDatabaseParams).then(
|
createDatabase(createDatabaseParams).then(
|
||||||
() => {
|
() => {
|
||||||
_onCreateDatabaseSuccess(offerThroughput, startKey);
|
_onCreateDatabaseSuccess(throughput, startKey);
|
||||||
},
|
},
|
||||||
(error: string) => {
|
(error: string) => {
|
||||||
_onCreateDatabaseFailure(error, offerThroughput, startKey);
|
_onCreateDatabaseFailure(error, throughput, startKey);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -194,48 +138,19 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
|||||||
TelemetryProcessor.traceFailure(Action.CreateDatabase, addDatabasePaneFailedMessage, startKey);
|
TelemetryProcessor.traceFailure(Action.CreateDatabase, addDatabasePaneFailedMessage, startKey);
|
||||||
};
|
};
|
||||||
|
|
||||||
const _getThroughput = (): number => {
|
|
||||||
return isNaN(throughput) ? 0 : Number(throughput);
|
|
||||||
};
|
|
||||||
|
|
||||||
const _computeOfferThroughput = (): number => {
|
|
||||||
if (!canConfigureThroughput) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _getThroughput();
|
|
||||||
};
|
|
||||||
|
|
||||||
const _isValid = (): boolean => {
|
const _isValid = (): boolean => {
|
||||||
// TODO add feature flag that disables validation for customers with custom accounts
|
// TODO add feature flag that disables validation for customers with custom accounts
|
||||||
if (isAutoPilotSelected) {
|
if (isAutoscaleSelected) {
|
||||||
const autoPilot = _isAutoPilotSelectedAndWhatTier();
|
if (!AutoPilotUtils.isValidAutoPilotThroughput(throughput)) {
|
||||||
if (
|
|
||||||
!autoPilot ||
|
|
||||||
!autoPilot.maxThroughput ||
|
|
||||||
!AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput)
|
|
||||||
) {
|
|
||||||
setFormErrors(
|
setFormErrors(
|
||||||
`Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput`
|
`Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput`
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const throughput = _getThroughput();
|
|
||||||
|
|
||||||
if (throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && !throughputSpendAck) {
|
if (throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && !isCostAcknowledged) {
|
||||||
setFormErrors(`Please acknowledge the estimated daily spend.`);
|
setFormErrors(`Please acknowledge the estimated ${isAutoscaleSelected ? "monthly" : "daily"} spend.`);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const autoscaleThroughput = maxAutoPilotThroughputSet * 1;
|
|
||||||
|
|
||||||
if (
|
|
||||||
isAutoPilotSelected &&
|
|
||||||
autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K &&
|
|
||||||
!throughputSpendAck
|
|
||||||
) {
|
|
||||||
setFormErrors(`Please acknowledge the estimated monthly spend.`);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,7 +165,7 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const props: RightPaneFormProps = {
|
const props: RightPaneFormProps = {
|
||||||
expandConsole: container.expandConsole,
|
expandConsole: openNotificationConsole,
|
||||||
formError: formErrors,
|
formError: formErrors,
|
||||||
formErrorDetail: formErrorsDetails,
|
formErrorDetail: formErrorsDetails,
|
||||||
isExecuting,
|
isExecuting,
|
||||||
@ -260,24 +175,23 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<RightPaneForm {...props}>
|
<RightPaneForm {...props}>
|
||||||
<div className="paneContentContainer" role="dialog" aria-labelledby="databaseTitle">
|
{!formErrors && isFreeTierAccount && (
|
||||||
{showUpsellMessage && formErrors === "" && (
|
|
||||||
<PanelInfoErrorComponent
|
<PanelInfoErrorComponent
|
||||||
message={upsellMessage}
|
message={getUpsellMessage(userContext.portalEnv, true, container.isFirstResourceCreated(), true)}
|
||||||
messageType="info"
|
messageType="info"
|
||||||
showErrorDetails={false}
|
showErrorDetails={false}
|
||||||
openNotificationConsole={openNotificationConsole}
|
openNotificationConsole={openNotificationConsole}
|
||||||
link={upsellAnchorUrl}
|
link={Constants.Urls.freeTierInformation}
|
||||||
linkText={upsellAnchorText}
|
linkText="Learn more"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className="paneMainContent">
|
<div className="panelMainContent">
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<Stack horizontal>
|
||||||
<span className="mandatoryStar">*</span>
|
<span className="mandatoryStar">*</span>
|
||||||
<Text variant="small">{databaseIdLabel}</Text>
|
<Text variant="small">{databaseIdLabel}</Text>
|
||||||
<InfoTooltip>{databaseIdTooltipText}</InfoTooltip>
|
<InfoTooltip>{databaseIdTooltipText}</InfoTooltip>
|
||||||
</p>
|
</Stack>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
id="database-id"
|
id="database-id"
|
||||||
@ -292,50 +206,36 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
|
|||||||
value={databaseId}
|
value={databaseId}
|
||||||
onChange={handleonChangeDBId}
|
onChange={handleonChangeDBId}
|
||||||
autoFocus
|
autoFocus
|
||||||
|
style={{ fontSize: 12 }}
|
||||||
|
styles={{ root: { width: 300 } }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<Stack horizontal>
|
||||||
className="databaseProvision"
|
|
||||||
aria-label="New database provision support"
|
|
||||||
style={{ display: "block ruby" }}
|
|
||||||
>
|
|
||||||
<Checkbox
|
<Checkbox
|
||||||
title="Provision shared throughput"
|
title="Provision shared throughput"
|
||||||
styles={{
|
styles={{
|
||||||
|
text: { fontSize: 12 },
|
||||||
checkbox: { width: 12, height: 12 },
|
checkbox: { width: 12, height: 12 },
|
||||||
label: { padding: 0, alignItems: "center" },
|
label: { padding: 0, alignItems: "center" },
|
||||||
}}
|
}}
|
||||||
label="Provision throughput"
|
label="Provision throughput"
|
||||||
checked={databaseCreateNewShared}
|
checked={databaseCreateNewShared}
|
||||||
onChange={() => setDatabaseCreateNewShared(!databaseCreateNewShared)}
|
onChange={() => setDatabaseCreateNewShared(!databaseCreateNewShared)}
|
||||||
/>{" "}
|
/>
|
||||||
<InfoTooltip>{databaseLevelThroughputTooltipText}</InfoTooltip>
|
<InfoTooltip>{databaseLevelThroughputTooltipText}</InfoTooltip>
|
||||||
</div>
|
</Stack>
|
||||||
{databaseCreateNewShared && (
|
|
||||||
<div>
|
{!isServerlessAccount() && databaseCreateNewShared && (
|
||||||
<ThroughputInput
|
<ThroughputInput
|
||||||
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !container?.isFirstResourceCreated()}
|
showFreeTierExceedThroughputTooltip={isFreeTierAccount && !container?.isFirstResourceCreated()}
|
||||||
isDatabase={true}
|
isDatabase={true}
|
||||||
isSharded={databaseCreateNewShared}
|
isSharded={databaseCreateNewShared}
|
||||||
isAutoscaleSelected={isAutoPilotSelected}
|
setThroughputValue={(newThroughput: number) => (throughput = newThroughput)}
|
||||||
throughput={throughput}
|
setIsAutoscale={(isAutoscale: boolean) => (isAutoscaleSelected = isAutoscale)}
|
||||||
setThroughputValue={(throughput: number) => setThroughput(throughput)}
|
onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)}
|
||||||
setIsAutoscale={(isAutoscale: boolean) => setIsAutoPilotSelected(isAutoscale)}
|
|
||||||
onCostAcknowledgeChange={(isAcknowledged: boolean) => setThroughputSpendAck(isAcknowledged)}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{canRequestSupport() && (
|
|
||||||
<p>
|
|
||||||
<a href="https://aka.ms/cosmosdbfeedback?subject=Cosmos%20DB%20More%20Throughput%20Request">
|
|
||||||
Contact support{" "}
|
|
||||||
</a>
|
|
||||||
for more than <span>{throughputDefaults.unlimitedmax?.toLocaleString()} </span> RU/s.
|
|
||||||
</p>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</RightPaneForm>
|
</RightPaneForm>
|
||||||
);
|
);
|
||||||
|
@ -9,23 +9,12 @@ exports[`AddDatabasePane Pane should render Default properly 1`] = `
|
|||||||
submitButtonText="OK"
|
submitButtonText="OK"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-labelledby="databaseTitle"
|
className="panelMainContent"
|
||||||
className="paneContentContainer"
|
|
||||||
role="dialog"
|
|
||||||
>
|
|
||||||
<PanelInfoErrorComponent
|
|
||||||
link="https://aka.ms/azure-cosmos-db-pricing"
|
|
||||||
linkText="More details"
|
|
||||||
message="Start at $24/mo per database, multiple containers included"
|
|
||||||
messageType="info"
|
|
||||||
openNotificationConsole={[Function]}
|
|
||||||
showErrorDetails={false}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className="paneMainContent"
|
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<Stack
|
||||||
|
horizontal={true}
|
||||||
|
>
|
||||||
<span
|
<span
|
||||||
className="mandatoryStar"
|
className="mandatoryStar"
|
||||||
>
|
>
|
||||||
@ -39,7 +28,7 @@ exports[`AddDatabasePane Pane should render Default properly 1`] = `
|
|||||||
<InfoTooltip>
|
<InfoTooltip>
|
||||||
A database is a logical container of one or more collections
|
A database is a logical container of one or more collections
|
||||||
</InfoTooltip>
|
</InfoTooltip>
|
||||||
</p>
|
</Stack>
|
||||||
<StyledTextFieldBase
|
<StyledTextFieldBase
|
||||||
aria-label="Database id"
|
aria-label="Database id"
|
||||||
aria-required="true"
|
aria-required="true"
|
||||||
@ -50,18 +39,24 @@ 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={
|
||||||
|
Object {
|
||||||
|
"root": Object {
|
||||||
|
"width": 300,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
title="May not end with space nor contain characters '\\\\' '/' '#' '?'"
|
title="May not end with space nor contain characters '\\\\' '/' '#' '?'"
|
||||||
type="text"
|
type="text"
|
||||||
value=""
|
value=""
|
||||||
/>
|
/>
|
||||||
<div
|
<Stack
|
||||||
aria-label="New database provision support"
|
horizontal={true}
|
||||||
className="databaseProvision"
|
|
||||||
style={
|
|
||||||
Object {
|
|
||||||
"display": "block ruby",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<StyledCheckboxBase
|
<StyledCheckboxBase
|
||||||
checked={true}
|
checked={true}
|
||||||
@ -77,28 +72,25 @@ exports[`AddDatabasePane Pane should render Default properly 1`] = `
|
|||||||
"alignItems": "center",
|
"alignItems": "center",
|
||||||
"padding": 0,
|
"padding": 0,
|
||||||
},
|
},
|
||||||
|
"text": Object {
|
||||||
|
"fontSize": 12,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
title="Provision shared throughput"
|
title="Provision shared throughput"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<InfoTooltip>
|
<InfoTooltip>
|
||||||
Provisioned throughput at the database level will be shared across all collections within the database.
|
Provisioned throughput at the database level will be shared across all collections within the database.
|
||||||
</InfoTooltip>
|
</InfoTooltip>
|
||||||
</div>
|
</Stack>
|
||||||
<div>
|
|
||||||
<ThroughputInput
|
<ThroughputInput
|
||||||
isAutoscaleSelected={false}
|
|
||||||
isDatabase={true}
|
isDatabase={true}
|
||||||
isSharded={true}
|
isSharded={true}
|
||||||
onCostAcknowledgeChange={[Function]}
|
onCostAcknowledgeChange={[Function]}
|
||||||
setIsAutoscale={[Function]}
|
setIsAutoscale={[Function]}
|
||||||
setThroughputValue={[Function]}
|
setThroughputValue={[Function]}
|
||||||
throughput={400}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</RightPaneForm>
|
</RightPaneForm>
|
||||||
`;
|
`;
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
|
import * as Constants from "../Common/Constants";
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
|
|
||||||
export const isCapabilityEnabled = (capabilityName: string): boolean =>
|
export const isCapabilityEnabled = (capabilityName: string): boolean =>
|
||||||
userContext.databaseAccount?.properties?.capabilities?.some((capability) => capability.name === capabilityName);
|
userContext.databaseAccount?.properties?.capabilities?.some((capability) => capability.name === capabilityName);
|
||||||
|
|
||||||
|
export const isServerlessAccount = (): boolean => isCapabilityEnabled(Constants.CapabilityNames.EnableServerless);
|
||||||
|
@ -18,7 +18,7 @@ export const useSidePanel = (): SidePanelHooks => {
|
|||||||
setHeaderText(headerText);
|
setHeaderText(headerText);
|
||||||
setPanelContent(panelContent);
|
setPanelContent(panelContent);
|
||||||
setIsPanelOpen(true);
|
setIsPanelOpen(true);
|
||||||
setOnCloseCallback({ callback: onClose });
|
!!onClose && setOnCloseCallback({ callback: onClose });
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeSidePanel = (): void => {
|
const closeSidePanel = (): void => {
|
||||||
|
Loading…
Reference in New Issue
Block a user