diff --git a/src/Explorer/Controls/ThroughputInput/ThroughputInput.test.tsx b/src/Explorer/Controls/ThroughputInput/ThroughputInput.test.tsx index 7b59ae003..be6a8636f 100644 --- a/src/Explorer/Controls/ThroughputInput/ThroughputInput.test.tsx +++ b/src/Explorer/Controls/ThroughputInput/ThroughputInput.test.tsx @@ -19,18 +19,4 @@ describe("ThroughputInput Pane", () => { it("should render Default properly", () => { 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"); - }); }); diff --git a/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx b/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx index 3991c0cbd..b0c029d73 100644 --- a/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx +++ b/src/Explorer/Controls/ThroughputInput/ThroughputInput.tsx @@ -17,8 +17,6 @@ export interface ThroughputInputProps { setThroughputValue: (throughput: number) => void; setIsAutoscale: (isAutoscale: boolean) => void; onCostAcknowledgeChange: (isAcknowledged: boolean) => void; - isAutoscaleSelected?: boolean; - throughput?: number; } export const ThroughputInput: FunctionComponent = ({ @@ -27,12 +25,16 @@ export const ThroughputInput: FunctionComponent = ({ setThroughputValue, setIsAutoscale, isSharded, - isAutoscaleSelected = true, - throughput = AutoPilotUtils.minAutoPilotThroughput, onCostAcknowledgeChange, }: ThroughputInputProps) => { + const [isAutoscaleSelected, setIsAutoScaleSelected] = useState(true); + const [throughput, setThroughput] = useState(AutoPilotUtils.minAutoPilotThroughput); const [isCostAcknowledged, setIsCostAcknowledged] = useState(false); const [throughputError, setThroughputError] = useState(""); + + setIsAutoscale(isAutoscaleSelected); + setThroughputValue(throughput); + const getThroughputLabelText = (): string => { let throughputHeaderText: string; if (isAutoscaleSelected) { @@ -49,6 +51,7 @@ export const ThroughputInput: FunctionComponent = ({ const onThroughputValueChange = (newInput: string): void => { const newThroughput = parseInt(newInput); + setThroughput(newThroughput); setThroughputValue(newThroughput); if (!isSharded && newThroughput > 10000) { setThroughputError("Unsharded collections support up to 10,000 RUs"); @@ -82,9 +85,13 @@ export const ThroughputInput: FunctionComponent = ({ const handleOnChangeMode = (event: React.ChangeEvent, mode: string): void => { if (mode === "Autoscale") { + setThroughput(AutoPilotUtils.minAutoPilotThroughput); + setIsAutoScaleSelected(true); setThroughputValue(AutoPilotUtils.minAutoPilotThroughput); setIsAutoscale(true); } else { + setThroughput(SharedConstants.CollectionCreation.DefaultCollectionRUs400); + setIsAutoScaleSelected(false); setThroughputValue(SharedConstants.CollectionCreation.DefaultCollectionRUs400); setIsAutoscale(false); } diff --git a/src/Explorer/Panes/AddCollectionPanel.tsx b/src/Explorer/Panes/AddCollectionPanel.tsx index 6220e0099..0af7c3225 100644 --- a/src/Explorer/Panes/AddCollectionPanel.tsx +++ b/src/Explorer/Panes/AddCollectionPanel.tsx @@ -25,7 +25,7 @@ import { Action } from "../../Shared/Telemetry/TelemetryConstants"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import { userContext } from "../../UserContext"; import { getCollectionName } from "../../Utils/APITypeUtils"; -import { isCapabilityEnabled } from "../../Utils/CapabilityUtils"; +import { isCapabilityEnabled, isServerlessAccount } from "../../Utils/CapabilityUtils"; import { getUpsellMessage } from "../../Utils/PricingUtils"; import { CollapsibleSectionComponent } from "../Controls/CollapsiblePanel/CollapsibleSectionComponent"; import { ThroughputInput } from "../Controls/ThroughputInput/ThroughputInput"; @@ -182,7 +182,7 @@ export class AddCollectionPanel extends React.Component - {!this.isServerlessAccount() && ( + {!isServerlessAccount() && ( )} - {!this.isServerlessAccount() && this.state.isSharedThroughputChecked && ( + {!isServerlessAccount() && this.state.isSharedThroughputChecked && ( (this.newDatabaseThroughput = throughput)} setIsAutoscale={(isAutoscale: boolean) => (this.isNewDatabaseAutoscale = isAutoscale)} @@ -398,7 +396,7 @@ export class AddCollectionPanel extends React.Component) => { if ( userContext.apiType !== "Mongo" && - this.state.partitionKey === "" && + !this.state.partitionKey && !event.target.value.startsWith("/") ) { this.setState({ partitionKey: "/" + event.target.value }); @@ -410,7 +408,7 @@ export class AddCollectionPanel extends React.Component )} - {!this.isServerlessAccount() && !this.state.createNewDatabase && this.isSelectedDatabaseSharedThroughput() && ( + {!isServerlessAccount() && !this.state.createNewDatabase && this.isSelectedDatabaseSharedThroughput() && ( (this.collectionThroughput = throughput)} setIsAutoscale={(isAutoscale: boolean) => (this.isCollectionAutoscale = isAutoscale)} @@ -755,14 +751,8 @@ export class AddCollectionPanel extends React.Component capability.name === Constants.CapabilityNames.EnableServerless - ); - } - private getSharedThroughputDefault(): boolean { - return userContext.subscriptionType !== SubscriptionType.EA && !this.isServerlessAccount(); + return userContext.subscriptionType !== SubscriptionType.EA && !isServerlessAccount(); } private getFreeTierIndexingText(): string { @@ -800,7 +790,7 @@ export class AddCollectionPanel extends React.Component = ({ closePanel, openNotificationConsole, }: AddDatabasePaneProps) => { + let throughput: number; + let isAutoscaleSelected: boolean; + let isCostAcknowledged: boolean; 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 databaseLabel: string = isCassandraAccount ? "keyspace" : "database"; const collectionsLabel: string = isCassandraAccount ? "tables" : "collections"; @@ -52,61 +45,14 @@ export const AddDatabasePanel: FunctionComponent = ({ } 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 [databaseCreateNewShared, setDatabaseCreateNewShared] = useState(getSharedThroughputDefault); + const [databaseCreateNewShared, setDatabaseCreateNewShared] = useState( + subscriptionType !== SubscriptionType.EA && !isServerlessAccount() + ); const [formErrorsDetails, setFormErrorsDetails] = useState(); const [formErrors, setFormErrors] = useState(""); - - const [isAutoPilotSelected, setIsAutoPilotSelected] = useState(container.isAutoscaleDefaultEnabled()); - - const throughputDefaults = container.collectionCreationDefaults.throughput; - const [throughput, setThroughput] = useState( - isAutoPilotSelected ? AutoPilotUtils.minAutoPilotThroughput : throughputDefaults.shared - ); - - const [throughputSpendAck, setThroughputSpendAck] = useState(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(false); - useEffect(() => { - setDatabaseCreateNewShared(getSharedThroughputDefault); - }, [subscriptionType]); + const isFreeTierAccount: boolean = userContext.databaseAccount?.properties?.enableFreeTier; const addDatabasePaneMessage = { database: { @@ -126,7 +72,7 @@ export const AddDatabasePanel: FunctionComponent = ({ subscriptionType: SubscriptionType[subscriptionType], subscriptionQuotaId: userContext.quotaId, defaultsCheck: { - throughput: throughput, + throughput, flight: userContext.addCollectionFlight, }, dataExplorerArea: Constants.Areas.ContextualPane, @@ -139,11 +85,9 @@ export const AddDatabasePanel: FunctionComponent = ({ return; } - const offerThroughput: number = _computeOfferThroughput(); - const addDatabasePaneStartMessage = { ...addDatabasePaneMessage, - offerThroughput, + throughput, }; const startKey: number = TelemetryProcessor.traceStart(Action.CreateDatabase, addDatabasePaneStartMessage); setFormErrors(""); @@ -153,18 +97,18 @@ export const AddDatabasePanel: FunctionComponent = ({ databaseId: addDatabasePaneStartMessage.database.id, databaseLevelThroughput: addDatabasePaneStartMessage.database.shared, }; - if (isAutoPilotSelected) { - createDatabaseParams.autoPilotMaxThroughput = addDatabasePaneStartMessage.offerThroughput; + if (isAutoscaleSelected) { + createDatabaseParams.autoPilotMaxThroughput = addDatabasePaneStartMessage.throughput; } else { - createDatabaseParams.offerThroughput = addDatabasePaneStartMessage.offerThroughput; + createDatabaseParams.offerThroughput = addDatabasePaneStartMessage.throughput; } createDatabase(createDatabaseParams).then( () => { - _onCreateDatabaseSuccess(offerThroughput, startKey); + _onCreateDatabaseSuccess(throughput, startKey); }, (error: string) => { - _onCreateDatabaseFailure(error, offerThroughput, startKey); + _onCreateDatabaseFailure(error, throughput, startKey); } ); }; @@ -194,48 +138,19 @@ export const AddDatabasePanel: FunctionComponent = ({ 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 => { // TODO add feature flag that disables validation for customers with custom accounts - if (isAutoPilotSelected) { - const autoPilot = _isAutoPilotSelectedAndWhatTier(); - if ( - !autoPilot || - !autoPilot.maxThroughput || - !AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput) - ) { + if (isAutoscaleSelected) { + if (!AutoPilotUtils.isValidAutoPilotThroughput(throughput)) { setFormErrors( `Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput` ); return false; } } - const throughput = _getThroughput(); - if (throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && !throughputSpendAck) { - setFormErrors(`Please acknowledge the estimated daily spend.`); - return false; - } - - const autoscaleThroughput = maxAutoPilotThroughputSet * 1; - - if ( - isAutoPilotSelected && - autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && - !throughputSpendAck - ) { - setFormErrors(`Please acknowledge the estimated monthly spend.`); + if (throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && !isCostAcknowledged) { + setFormErrors(`Please acknowledge the estimated ${isAutoscaleSelected ? "monthly" : "daily"} spend.`); return false; } @@ -250,7 +165,7 @@ export const AddDatabasePanel: FunctionComponent = ({ ); const props: RightPaneFormProps = { - expandConsole: container.expandConsole, + expandConsole: openNotificationConsole, formError: formErrors, formErrorDetail: formErrorsDetails, isExecuting, @@ -260,81 +175,66 @@ export const AddDatabasePanel: FunctionComponent = ({ return ( -
- {showUpsellMessage && formErrors === "" && ( - + )} +
+
+ + * + {databaseIdLabel} + {databaseIdTooltipText} + + + - )} -
-
-

- * - {databaseIdLabel} - {databaseIdTooltipText} -

- + setDatabaseCreateNewShared(!databaseCreateNewShared)} /> + {databaseLevelThroughputTooltipText} + -
- setDatabaseCreateNewShared(!databaseCreateNewShared)} - />{" "} - {databaseLevelThroughputTooltipText} -
- {databaseCreateNewShared && ( -
- setThroughput(throughput)} - setIsAutoscale={(isAutoscale: boolean) => setIsAutoPilotSelected(isAutoscale)} - onCostAcknowledgeChange={(isAcknowledged: boolean) => setThroughputSpendAck(isAcknowledged)} - /> - - {canRequestSupport() && ( -

- - Contact support{" "} - - for more than {throughputDefaults.unlimitedmax?.toLocaleString()} RU/s. -

- )} -
- )} -
+ {!isServerlessAccount() && databaseCreateNewShared && ( + (throughput = newThroughput)} + setIsAutoscale={(isAutoscale: boolean) => (isAutoscaleSelected = isAutoscale)} + onCostAcknowledgeChange={(isAcknowledged: boolean) => (isCostAcknowledged = isAcknowledged)} + /> + )}
diff --git a/src/Explorer/Panes/AddDatabasePanel/__snapshots__/AddDatabasePanel.test.tsx.snap b/src/Explorer/Panes/AddDatabasePanel/__snapshots__/AddDatabasePanel.test.tsx.snap index bdc4f1526..2c5343436 100644 --- a/src/Explorer/Panes/AddDatabasePanel/__snapshots__/AddDatabasePanel.test.tsx.snap +++ b/src/Explorer/Panes/AddDatabasePanel/__snapshots__/AddDatabasePanel.test.tsx.snap @@ -9,95 +9,87 @@ exports[`AddDatabasePane Pane should render Default properly 1`] = ` submitButtonText="OK" >
- -
-
-

- - * - - - Database id - - - A database is a logical container of one or more collections - -

- + + + * + + + Database id + + + A database is a logical container of one or more collections + + + + + -
- - - - Provisioned throughput at the database level will be shared across all collections within the database. - -
-
- -
-
+ title="Provision shared throughput" + /> + + Provisioned throughput at the database level will be shared across all collections within the database. + + +
diff --git a/src/Utils/CapabilityUtils.ts b/src/Utils/CapabilityUtils.ts index 8e04e044c..040766219 100644 --- a/src/Utils/CapabilityUtils.ts +++ b/src/Utils/CapabilityUtils.ts @@ -1,4 +1,7 @@ +import * as Constants from "../Common/Constants"; import { userContext } from "../UserContext"; export const isCapabilityEnabled = (capabilityName: string): boolean => userContext.databaseAccount?.properties?.capabilities?.some((capability) => capability.name === capabilityName); + +export const isServerlessAccount = (): boolean => isCapabilityEnabled(Constants.CapabilityNames.EnableServerless); diff --git a/src/hooks/useSidePanel.ts b/src/hooks/useSidePanel.ts index 67cb1b200..e5d93c84b 100644 --- a/src/hooks/useSidePanel.ts +++ b/src/hooks/useSidePanel.ts @@ -18,7 +18,7 @@ export const useSidePanel = (): SidePanelHooks => { setHeaderText(headerText); setPanelContent(panelContent); setIsPanelOpen(true); - setOnCloseCallback({ callback: onClose }); + !!onClose && setOnCloseCallback({ callback: onClose }); }; const closeSidePanel = (): void => {