From 8028734cb0c97ad7b3c1bfd0cecf871e1fdef259 Mon Sep 17 00:00:00 2001 From: Srinath Narayanan Date: Thu, 8 Oct 2020 14:32:54 -0700 Subject: [PATCH] Fixed settingsV2 bugs and added experimentation (#264) * inital commit for flight tests - FIxed bugs with settingstab v2 * minor edits * removed console log * fixed bug with autoscale throughput step increase * resolved PR comments * fixed compile error * Added comment --- src/Common/Constants.ts | 6 +- src/Contracts/ViewModels.ts | 1 + .../FeaturePanel/FeaturePanelComponent.tsx | 1 - .../FeaturePanelComponent.test.tsx.snap | 6 -- .../Settings/SettingsComponent.test.tsx | 9 +- .../Controls/Settings/SettingsComponent.tsx | 87 ++++-------------- .../ConflictResolutionComponent.tsx | 39 ++++---- .../IndexingPolicyComponent.test.tsx | 3 - .../IndexingPolicyComponent.tsx | 3 - .../SubSettingsComponent.test.tsx | 9 +- .../SubSettingsComponent.tsx | 90 +++++++++++++------ .../ThroughputInputAutoPilotV3Component.tsx | 23 +++-- ...putInputAutoPilotV3Component.test.tsx.snap | 9 +- .../Controls/Settings/SettingsUtils.test.tsx | 8 ++ .../Controls/Settings/SettingsUtils.tsx | 11 +++ .../SettingsComponent.test.tsx.snap | 1 - src/Explorer/Explorer.ts | 16 +++- 17 files changed, 166 insertions(+), 156 deletions(-) diff --git a/src/Common/Constants.ts b/src/Common/Constants.ts index a3d410cc9..2ac739e8a 100644 --- a/src/Common/Constants.ts +++ b/src/Common/Constants.ts @@ -117,7 +117,6 @@ export class Features { public static readonly enableGalleryPublish = "enablegallerypublish"; public static readonly enableCodeOfConduct = "enablecodeofconduct"; public static readonly enableLinkInjection = "enablelinkinjection"; - public static readonly enableSettingsV2 = "enablesettingsv2"; public static readonly enableSpark = "enablespark"; public static readonly livyEndpoint = "livyendpoint"; public static readonly notebookServerUrl = "notebookserverurl"; @@ -131,6 +130,11 @@ export class Features { public static readonly enableSDKoperations = "enablesdkoperations"; } +// flight names returned from the portal are always lowercase +export class Flights { + public static readonly SettingsV2 = "settingsv2"; +} + export class AfecFeatures { public static readonly Spark = "spark-public-preview"; public static readonly Notebooks = "sparknotebooks-public-preview"; diff --git a/src/Contracts/ViewModels.ts b/src/Contracts/ViewModels.ts index 7a1630609..ab3b95628 100644 --- a/src/Contracts/ViewModels.ts +++ b/src/Contracts/ViewModels.ts @@ -388,6 +388,7 @@ export interface DataExplorerInputsFrame { dataExplorerVersion?: string; isAuthWithresourceToken?: boolean; defaultCollectionThroughput?: CollectionCreationDefaults; + flights?: readonly string[]; } export interface CollectionCreationDefaults { diff --git a/src/Explorer/Controls/FeaturePanel/FeaturePanelComponent.tsx b/src/Explorer/Controls/FeaturePanel/FeaturePanelComponent.tsx index 5234e281d..b0f1733f7 100644 --- a/src/Explorer/Controls/FeaturePanel/FeaturePanelComponent.tsx +++ b/src/Explorer/Controls/FeaturePanel/FeaturePanelComponent.tsx @@ -55,7 +55,6 @@ export const FeaturePanelComponent: React.FunctionComponent = () => { label: "Enable Injecting Notebook Viewer Link into the first cell", value: "true" }, - { key: "feature.enablesettingsv2", label: "Enable SettingsV2 Tab", value: "true" }, { key: "feature.canexceedmaximumvalue", label: "Can exceed max value", value: "true" }, { key: "feature.enablefixedcollectionwithsharedthroughput", diff --git a/src/Explorer/Controls/FeaturePanel/__snapshots__/FeaturePanelComponent.test.tsx.snap b/src/Explorer/Controls/FeaturePanel/__snapshots__/FeaturePanelComponent.test.tsx.snap index f0628b6f0..7f4a39014 100644 --- a/src/Explorer/Controls/FeaturePanel/__snapshots__/FeaturePanelComponent.test.tsx.snap +++ b/src/Explorer/Controls/FeaturePanel/__snapshots__/FeaturePanelComponent.test.tsx.snap @@ -178,12 +178,6 @@ exports[`Feature panel renders all flags 1`] = ` className="checkboxRow" horizontalAlign="space-between" > - ({ @@ -220,13 +220,6 @@ describe("SettingsComponent", () => { expect(isDirty(state.throughput, state.throughputBaseline)).toEqual(false); }); - it("getTtlValue", async () => { - const settingsComponentInstance = new SettingsComponent(baseProps); - expect(settingsComponentInstance.getTtlValue(TtlType.OnNoDefault)).toEqual(TtlOnNoDefault); - expect(settingsComponentInstance.getTtlValue(TtlType.On)).toEqual(TtlOn); - expect(settingsComponentInstance.getTtlValue(TtlType.Off)).toEqual(TtlOff); - }); - it("getAnalyticalStorageTtl", () => { const newCollection = { ...collection }; newCollection.analyticalStorageTtl = ko.observable(10); diff --git a/src/Explorer/Controls/Settings/SettingsComponent.tsx b/src/Explorer/Controls/Settings/SettingsComponent.tsx index de78632ee..7d2e4a336 100644 --- a/src/Explorer/Controls/Settings/SettingsComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsComponent.tsx @@ -27,9 +27,6 @@ import { SettingsV2TabTypes, getTabTitle, isDirty, - TtlOff, - TtlOn, - TtlOnNoDefault, parseConflictResolutionMode, parseConflictResolutionProcedure } from "./SettingsUtils"; @@ -38,7 +35,7 @@ import { ConflictResolutionComponentProps } from "./SettingsSubComponents/ConflictResolutionComponent"; import { SubSettingsComponent, SubSettingsComponentProps } from "./SettingsSubComponents/SubSettingsComponent"; -import { Pivot, PivotItem, IPivotProps, IPivotItemProps, IChoiceGroupOption } from "office-ui-fabric-react"; +import { Pivot, PivotItem, IPivotProps, IPivotItemProps } from "office-ui-fabric-react"; import "./SettingsComponent.less"; import { IndexingPolicyComponent, IndexingPolicyComponentProps } from "./SettingsSubComponents/IndexingPolicyComponent"; @@ -85,7 +82,6 @@ export interface SettingsComponentState { indexingPolicyContent: DataModels.IndexingPolicy; indexingPolicyContentBaseline: DataModels.IndexingPolicy; shouldDiscardIndexingPolicy: boolean; - indexingPolicyElementFocussed: boolean; isIndexingPolicyDirty: boolean; conflictResolutionPolicyMode: DataModels.ConflictResolutionMode; @@ -102,7 +98,6 @@ export interface SettingsComponentState { export class SettingsComponent extends React.Component { private static readonly sixMonthsInSeconds = 15768000; - private static readonly zeroSeconds = 0; public saveSettingsButton: ButtonV2; public discardSettingsChangesButton: ButtonV2; @@ -160,7 +155,6 @@ export class SettingsComponent extends React.Component this.setState({ isScaleDiscardable: isScaleDiscardable }); - private onIndexingPolicyElementFocusChange = (indexingPolicyElementFocussed: boolean): void => - this.setState({ indexingPolicyElementFocussed: indexingPolicyElementFocussed }); - private onIndexingPolicyContentChange = (newIndexingPolicy: DataModels.IndexingPolicy): void => this.setState({ indexingPolicyContent: newIndexingPolicy }); @@ -544,79 +535,34 @@ export class SettingsComponent extends React.Component, - option?: IChoiceGroupOption - ): void => - this.setState({ - conflictResolutionPolicyMode: - DataModels.ConflictResolutionMode[option.key as keyof typeof DataModels.ConflictResolutionMode] - }); + private onConflictResolutionPolicyModeChange = (newMode: DataModels.ConflictResolutionMode): void => + this.setState({ conflictResolutionPolicyMode: newMode }); - private onConflictResolutionPolicyPathChange = ( - event: React.FormEvent, - newValue?: string - ): void => this.setState({ conflictResolutionPolicyPath: newValue }); + private onConflictResolutionPolicyPathChange = (newPath: string): void => + this.setState({ conflictResolutionPolicyPath: newPath }); - private onConflictResolutionPolicyProcedureChange = ( - event: React.FormEvent, - newValue?: string - ): void => this.setState({ conflictResolutionPolicyProcedure: newValue }); + private onConflictResolutionPolicyProcedureChange = (newProcedure: string): void => + this.setState({ conflictResolutionPolicyProcedure: newProcedure }); private onConflictResolutionDirtyChange = (isConflictResolutionDirty: boolean): void => this.setState({ isConflictResolutionDirty: isConflictResolutionDirty }); - public getTtlValue = (value: string): TtlType => { - switch (value) { - case TtlOn: - return TtlType.On; - case TtlOff: - return TtlType.Off; - case TtlOnNoDefault: - return TtlType.OnNoDefault; - } - return undefined; - }; + private onTtlChange = (newTtl: TtlType): void => this.setState({ timeToLive: newTtl }); - private onTtlChange = (ev?: React.FormEvent, option?: IChoiceGroupOption): void => - this.setState({ timeToLive: this.getTtlValue(option.key) }); - - private onTimeToLiveSecondsChange = ( - event: React.FormEvent, - newValue?: string - ): void => { - let newTimeToLiveSeconds = parseInt(newValue); - newTimeToLiveSeconds = isNaN(newTimeToLiveSeconds) ? SettingsComponent.zeroSeconds : newTimeToLiveSeconds; + private onTimeToLiveSecondsChange = (newTimeToLiveSeconds: number): void => this.setState({ timeToLiveSeconds: newTimeToLiveSeconds }); - }; - private onGeoSpatialConfigTypeChange = ( - ev?: React.FormEvent, - option?: IChoiceGroupOption - ): void => - this.setState({ geospatialConfigType: GeospatialConfigType[option.key as keyof typeof GeospatialConfigType] }); + private onGeoSpatialConfigTypeChange = (newGeoSpatialConfigType: GeospatialConfigType): void => + this.setState({ geospatialConfigType: newGeoSpatialConfigType }); - private onAnalyticalStorageTtlSelectionChange = ( - ev?: React.FormEvent, - option?: IChoiceGroupOption - ): void => this.setState({ analyticalStorageTtlSelection: this.getTtlValue(option.key) }); + private onAnalyticalStorageTtlSelectionChange = (newAnalyticalStorageTtlSelection: TtlType): void => + this.setState({ analyticalStorageTtlSelection: newAnalyticalStorageTtlSelection }); - private onAnalyticalStorageTtlSecondsChange = ( - event: React.FormEvent, - newValue?: string - ): void => { - let newAnalyticalStorageTtlSeconds = parseInt(newValue); - newAnalyticalStorageTtlSeconds = isNaN(newAnalyticalStorageTtlSeconds) - ? SettingsComponent.zeroSeconds - : newAnalyticalStorageTtlSeconds; + private onAnalyticalStorageTtlSecondsChange = (newAnalyticalStorageTtlSeconds: number): void => this.setState({ analyticalStorageTtlSeconds: newAnalyticalStorageTtlSeconds }); - }; - private onChangeFeedPolicyChange = ( - ev?: React.FormEvent, - option?: IChoiceGroupOption - ): void => - this.setState({ changeFeedPolicy: ChangeFeedPolicyState[option.key as keyof typeof ChangeFeedPolicyState] }); + private onChangeFeedPolicyChange = (newChangeFeedPolicy: ChangeFeedPolicyState): void => + this.setState({ changeFeedPolicy: newChangeFeedPolicy }); private onSubSettingsSaveableChange = (isSubSettingsSaveable: boolean): void => this.setState({ isSubSettingsSaveable: isSubSettingsSaveable }); @@ -844,7 +790,6 @@ export class SettingsComponent extends React.Component, - option?: IChoiceGroupOption - ) => void; + onConflictResolutionPolicyModeChange: (newMode: DataModels.ConflictResolutionMode) => void; conflictResolutionPolicyPath: string; conflictResolutionPolicyPathBaseline: string; - onConflictResolutionPolicyPathChange: ( - event: React.FormEvent, - newValue?: string - ) => void; + onConflictResolutionPolicyPathChange: (newPath: string) => void; conflictResolutionPolicyProcedure: string; conflictResolutionPolicyProcedureBaseline: string; - onConflictResolutionPolicyProcedureChange: ( - event: React.FormEvent, - newValue?: string - ) => void; + onConflictResolutionPolicyProcedureChange: (newProcedure: string) => void; onConflictResolutionDirtyChange: (isConflictResolutionDirty: boolean) => void; } @@ -77,12 +68,30 @@ export class ConflictResolutionComponent extends React.Component, + option?: IChoiceGroupOption + ): void => + this.props.onConflictResolutionPolicyModeChange( + DataModels.ConflictResolutionMode[option.key as keyof typeof DataModels.ConflictResolutionMode] + ); + + private onConflictResolutionPolicyPathChange = ( + event: React.FormEvent, + newValue?: string + ): void => this.props.onConflictResolutionPolicyPathChange(newValue); + + private onConflictResolutionPolicyProcedureChange = ( + event: React.FormEvent, + newValue?: string + ): void => this.props.onConflictResolutionPolicyProcedureChange(newValue); + private getConflictResolutionModeComponent = (): JSX.Element => ( ); @@ -122,7 +131,7 @@ export class ConflictResolutionComponent extends React.Component ); diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyComponent.test.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyComponent.test.tsx index 0aff95860..38d192f6c 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyComponent.test.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyComponent.test.tsx @@ -17,9 +17,6 @@ describe("IndexingPolicyComponent", () => { }, indexingPolicyContent: initialIndexingPolicyContent, indexingPolicyContentBaseline: initialIndexingPolicyContent, - onIndexingPolicyElementFocusChange: () => { - return; - }, onIndexingPolicyContentChange: () => { return; }, diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyComponent.tsx index 335f552eb..f1dede13a 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyComponent.tsx @@ -10,7 +10,6 @@ export interface IndexingPolicyComponentProps { resetShouldDiscardIndexingPolicy: () => void; indexingPolicyContent: DataModels.IndexingPolicy; indexingPolicyContentBaseline: DataModels.IndexingPolicy; - onIndexingPolicyElementFocusChange: (indexingPolicyContentFocussed: boolean) => void; onIndexingPolicyContentChange: (newIndexingPolicy: DataModels.IndexingPolicy) => void; logIndexingPolicySuccessMessage: () => void; onIndexingPolicyDirtyChange: (isIndexingPolicyDirty: boolean) => void; @@ -89,8 +88,6 @@ export class IndexingPolicyComponent extends React.Component< ariaLabel: "Indexing Policy" }); if (this.indexingPolicyEditor) { - this.indexingPolicyEditor.onDidFocusEditorText(() => this.props.onIndexingPolicyElementFocusChange(true)); - this.indexingPolicyEditor.onDidBlurEditorText(() => this.props.onIndexingPolicyElementFocusChange(false)); const indexingPolicyEditorModel = this.indexingPolicyEditor.getModel(); indexingPolicyEditorModel.onDidChangeContent(this.onEditorContentChange.bind(this)); this.props.logIndexingPolicySuccessMessage(); diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/SubSettingsComponent.test.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/SubSettingsComponent.test.tsx index 2f9c16297..4793edbdb 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/SubSettingsComponent.test.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/SubSettingsComponent.test.tsx @@ -2,7 +2,7 @@ import { shallow } from "enzyme"; import React from "react"; import { SubSettingsComponent, SubSettingsComponentProps } from "./SubSettingsComponent"; import { container, collection } from "../TestUtils"; -import { TtlType, GeospatialConfigType, ChangeFeedPolicyState } from "../SettingsUtils"; +import { TtlType, GeospatialConfigType, ChangeFeedPolicyState, TtlOnNoDefault, TtlOn, TtlOff } from "../SettingsUtils"; import ko from "knockout"; import Explorer from "../../../Explorer"; @@ -133,4 +133,11 @@ describe("SubSettingsComponent", () => { expect(isComponentDirtyResult.isSaveable).toEqual(true); expect(isComponentDirtyResult.isDiscardable).toEqual(true); }); + + it("getTtlValue", async () => { + const subSettingsComponentInstance = new SubSettingsComponent(baseProps); + expect(subSettingsComponentInstance.getTtlValue(TtlType.OnNoDefault)).toEqual(TtlOnNoDefault); + expect(subSettingsComponentInstance.getTtlValue(TtlType.On)).toEqual(TtlOn); + expect(subSettingsComponentInstance.getTtlValue(TtlType.Off)).toEqual(TtlOff); + }); }); diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/SubSettingsComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/SubSettingsComponent.tsx index 5e9eaefb3..4322bd6e8 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/SubSettingsComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/SubSettingsComponent.tsx @@ -5,7 +5,11 @@ import { TtlType, ChangeFeedPolicyState, isDirty, - IsComponentDirtyResult + IsComponentDirtyResult, + TtlOn, + TtlOff, + TtlOnNoDefault, + getSanitizedInputValue } from "../SettingsUtils"; import Explorer from "../../../Explorer"; import { Int32 } from "../../../Panes/Tables/Validators/EntityPropertyValidationCommon"; @@ -37,40 +41,28 @@ export interface SubSettingsComponentProps { timeToLive: TtlType; timeToLiveBaseline: TtlType; - onTtlChange: (ev?: React.FormEvent, option?: IChoiceGroupOption) => void; + onTtlChange: (newTtl: TtlType) => void; timeToLiveSeconds: number; timeToLiveSecondsBaseline: number; - onTimeToLiveSecondsChange: ( - event: React.FormEvent, - newValue?: string - ) => void; + onTimeToLiveSecondsChange: (newTimeToLiveSeconds: number) => void; geospatialConfigType: GeospatialConfigType; geospatialConfigTypeBaseline: GeospatialConfigType; - onGeoSpatialConfigTypeChange: ( - ev?: React.FormEvent, - option?: IChoiceGroupOption - ) => void; + onGeoSpatialConfigTypeChange: (newGeoSpatialConfigType: GeospatialConfigType) => void; isAnalyticalStorageEnabled: boolean; analyticalStorageTtlSelection: TtlType; analyticalStorageTtlSelectionBaseline: TtlType; - onAnalyticalStorageTtlSelectionChange: ( - ev?: React.FormEvent, - option?: IChoiceGroupOption - ) => void; + onAnalyticalStorageTtlSelectionChange: (newAnalyticalStorageTtlSelection: TtlType) => void; analyticalStorageTtlSeconds: number; analyticalStorageTtlSecondsBaseline: number; - onAnalyticalStorageTtlSecondsChange: ( - event: React.FormEvent, - newValue?: string - ) => void; + onAnalyticalStorageTtlSecondsChange: (newAnalyticalStorageTtlSeconds: number) => void; changeFeedPolicyVisible: boolean; changeFeedPolicy: ChangeFeedPolicyState; changeFeedPolicyBaseline: ChangeFeedPolicyState; - onChangeFeedPolicyChange: (ev?: React.FormEvent, option?: IChoiceGroupOption) => void; + onChangeFeedPolicyChange: (newChangeFeedPolicyState: ChangeFeedPolicyState) => void; onSubSettingsSaveableChange: (isSubSettingsSaveable: boolean) => void; onSubSettingsDiscardableChange: (isSubSettingsDiscardable: boolean) => void; } @@ -139,6 +131,54 @@ export class SubSettingsComponent extends React.Component { + switch (value) { + case TtlOn: + return TtlType.On; + case TtlOff: + return TtlType.Off; + case TtlOnNoDefault: + return TtlType.OnNoDefault; + } + return undefined; + }; + + private onTtlChange = (ev?: React.FormEvent, option?: IChoiceGroupOption): void => + this.props.onTtlChange(this.getTtlValue(option.key)); + + private onTimeToLiveSecondsChange = ( + event: React.FormEvent, + newValue?: string + ): void => { + const newTimeToLiveSeconds = getSanitizedInputValue(newValue, Int32.Max); + this.props.onTimeToLiveSecondsChange(newTimeToLiveSeconds); + }; + + private onGeoSpatialConfigTypeChange = ( + ev?: React.FormEvent, + option?: IChoiceGroupOption + ): void => + this.props.onGeoSpatialConfigTypeChange(GeospatialConfigType[option.key as keyof typeof GeospatialConfigType]); + + private onAnalyticalStorageTtlSelectionChange = ( + ev?: React.FormEvent, + option?: IChoiceGroupOption + ): void => this.props.onAnalyticalStorageTtlSelectionChange(this.getTtlValue(option.key)); + + private onAnalyticalStorageTtlSecondsChange = ( + event: React.FormEvent, + newValue?: string + ): void => { + const newAnalyticalStorageTtlSeconds = getSanitizedInputValue(newValue, Int32.Max); + this.props.onAnalyticalStorageTtlSecondsChange(newAnalyticalStorageTtlSeconds); + }; + + private onChangeFeedPolicyChange = ( + ev?: React.FormEvent, + option?: IChoiceGroupOption + ): void => + this.props.onChangeFeedPolicyChange(ChangeFeedPolicyState[option.key as keyof typeof ChangeFeedPolicyState]); + private getTtlComponent = (): JSX.Element => ( {isDirty(this.props.timeToLive, this.props.timeToLiveBaseline) && this.props.timeToLive === TtlType.On && ( @@ -163,7 +203,7 @@ export class SubSettingsComponent extends React.Component )} @@ -183,7 +223,7 @@ export class SubSettingsComponent extends React.Component )} @@ -219,7 +259,7 @@ export class SubSettingsComponent extends React.Component ); @@ -241,7 +281,7 @@ export class SubSettingsComponent extends React.Component diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputInputAutoPilotV3Component.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputInputAutoPilotV3Component.tsx index 372865593..279c8da5e 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputInputAutoPilotV3Component.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputInputAutoPilotV3Component.tsx @@ -26,9 +26,10 @@ import { MessageBarType } from "office-ui-fabric-react"; import { ToolTipLabelComponent } from "../ToolTipLabelComponent"; -import { IsComponentDirtyResult, isDirty } from "../../SettingsUtils"; +import { getSanitizedInputValue, IsComponentDirtyResult, isDirty } from "../../SettingsUtils"; import * as SharedConstants from "../../../../../Shared/Constants"; import * as DataModels from "../../../../../Contracts/DataModels"; +import { Int32 } from "../../../../Panes/Tables/Validators/EntityPropertyValidationCommon"; export interface ThroughputInputAutoPilotV3Props { databaseAccount: DataModels.DatabaseAccount; @@ -71,9 +72,9 @@ export class ThroughputInputAutoPilotV3Component extends React.Component< > { private shouldCheckComponentIsDirty = true; private static readonly defaultStep = 100; - private static readonly zeroThroughput = 0; private step: number; - private choiceGroupFixedStyle = getChoiceGroupStyles(undefined, undefined); + private throughputInputMaxValue: number; + private autoPilotInputMaxValue: number; private options: IChoiceGroupOption[] = [ { key: "true", text: "Autoscale" }, { key: "false", text: "Manual" } @@ -140,6 +141,8 @@ export class ThroughputInputAutoPilotV3Component extends React.Component< }; this.step = this.props.step ?? ThroughputInputAutoPilotV3Component.defaultStep; + this.throughputInputMaxValue = this.props.canExceedMaximumValue ? Int32.Max : this.props.maximum; + this.autoPilotInputMaxValue = Int32.Max; } public hasProvisioningTypeChanged = (): boolean => @@ -200,8 +203,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component< event: React.FormEvent, newValue?: string ): void => { - let newThroughput = parseInt(newValue); - newThroughput = isNaN(newThroughput) ? ThroughputInputAutoPilotV3Component.zeroThroughput : newThroughput; + const newThroughput = getSanitizedInputValue(newValue, this.autoPilotInputMaxValue); this.props.onMaxAutoPilotThroughputChange(newThroughput); }; @@ -209,9 +211,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component< event: React.FormEvent, newValue?: string ): void => { - let newThroughput = parseInt(newValue); - newThroughput = isNaN(newThroughput) ? ThroughputInputAutoPilotV3Component.zeroThroughput : newThroughput; - + const newThroughput = getSanitizedInputValue(newValue, this.throughputInputMaxValue); if (this.overrideWithAutoPilotSettings()) { this.props.onMaxAutoPilotThroughputChange(newThroughput); } else { @@ -245,7 +245,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component< onChange={this.onChoiceGroupChange} required={this.props.showAsMandatory} ariaLabelledBy={labelId} - styles={this.choiceGroupFixedStyle} + styles={getChoiceGroupStyles(this.props.wasAutopilotOriginallySet, this.props.isAutoPilotSelected)} /> ); @@ -270,8 +270,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component< key="auto pilot throughput input" styles={getTextFieldStyles(this.props.maxAutoPilotThroughput, this.props.maxAutoPilotThroughputBaseline)} disabled={this.overrideWithProvisionedThroughputSettings()} - step={this.step} - min={AutoPilotUtils.minAutoPilotThroughput} + step={AutoPilotUtils.autoPilotIncrementStep} value={this.overrideWithProvisionedThroughputSettings() ? "" : this.props.maxAutoPilotThroughput?.toString()} onChange={this.onAutoPilotThroughputChange} /> @@ -298,8 +297,6 @@ export class ThroughputInputAutoPilotV3Component extends React.Component< styles={getTextFieldStyles(this.props.throughput, this.props.throughputBaseline)} disabled={this.overrideWithAutoPilotSettings()} step={this.step} - min={this.props.minimum} - max={this.props.canExceedMaximumValue ? undefined : this.props.maximum} value={ this.overrideWithAutoPilotSettings() ? this.props.maxAutoPilotThroughputBaseline?.toString() diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/__snapshots__/ThroughputInputAutoPilotV3Component.test.tsx.snap b/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/__snapshots__/ThroughputInputAutoPilotV3Component.test.tsx.snap index e035baa96..9fe18a4cc 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/__snapshots__/ThroughputInputAutoPilotV3Component.test.tsx.snap +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/__snapshots__/ThroughputInputAutoPilotV3Component.test.tsx.snap @@ -81,10 +81,10 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = ` Object { "selectors": Object { ".ms-ChoiceField-field.is-checked::after": Object { - "borderColor": "", + "borderColor": undefined, }, ".ms-ChoiceField-field.is-checked::before": Object { - "borderColor": "", + "borderColor": undefined, }, ".ms-ChoiceField-wrapper label": Object { "fontFamily": undefined, @@ -113,10 +113,9 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = ` id="autopilotInput" key="auto pilot throughput input" label="Max RU/s" - min={4000} onChange={[Function]} required={true} - step={100} + step={1000} styles={ Object { "fieldGroup": Object { @@ -219,7 +218,6 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = ` disabled={false} id="throughputInput" key="provisioned throughput input" - min={10000} onChange={[Function]} required={true} step={100} @@ -375,7 +373,6 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = ` disabled={false} id="throughputInput" key="provisioned throughput input" - min={10000} onChange={[Function]} required={true} step={100} diff --git a/src/Explorer/Controls/Settings/SettingsUtils.test.tsx b/src/Explorer/Controls/Settings/SettingsUtils.test.tsx index b69e72ac7..b072f63d9 100644 --- a/src/Explorer/Controls/Settings/SettingsUtils.test.tsx +++ b/src/Explorer/Controls/Settings/SettingsUtils.test.tsx @@ -2,6 +2,7 @@ import { collection, container } from "./TestUtils"; import { getMaxRUs, getMinRUs, + getSanitizedInputValue, hasDatabaseSharedThroughput, isDirty, isDirtyTypes, @@ -86,4 +87,11 @@ describe("SettingsUtils", () => { expect(isDirty(baseline, current)).toEqual(true); }); }); + + it("getSanitizedInputValue", () => { + const max = 100; + expect(getSanitizedInputValue("", max)).toEqual(0); + expect(getSanitizedInputValue("999", max)).toEqual(99); + expect(getSanitizedInputValue("10", max)).toEqual(10); + }); }); diff --git a/src/Explorer/Controls/Settings/SettingsUtils.tsx b/src/Explorer/Controls/Settings/SettingsUtils.tsx index 026868c2b..790c08f2f 100644 --- a/src/Explorer/Controls/Settings/SettingsUtils.tsx +++ b/src/Explorer/Controls/Settings/SettingsUtils.tsx @@ -6,6 +6,7 @@ import * as PricingUtils from "../../../Utils/PricingUtils"; import Explorer from "../../Explorer"; +const zeroValue = 0; export type isDirtyTypes = boolean | string | number | DataModels.IndexingPolicy; export const TtlOff = "off"; export const TtlOn = "on"; @@ -129,6 +130,16 @@ export const parseConflictResolutionProcedure = (procedureFromBackEnd: string): return procedureFromBackEnd; }; +export const getSanitizedInputValue = (newValueString: string, max: number): number => { + let newValue = parseInt(newValueString); + if (isNaN(newValue)) { + newValue = zeroValue; + } else if (newValue > max) { + newValue = Math.floor(newValue / 10); + } + return newValue; +}; + export const isDirty = (current: isDirtyTypes, baseline: isDirtyTypes): boolean => { const currentType = typeof current; const baselineType = typeof baseline; diff --git a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap index b03865c5f..37746400b 100644 --- a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap +++ b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap @@ -5325,7 +5325,6 @@ exports[`SettingsComponent renders 1`] = ` logIndexingPolicySuccessMessage={[Function]} onIndexingPolicyContentChange={[Function]} onIndexingPolicyDirtyChange={[Function]} - onIndexingPolicyElementFocusChange={[Function]} resetShouldDiscardIndexingPolicy={[Function]} shouldDiscardIndexingPolicy={false} /> diff --git a/src/Explorer/Explorer.ts b/src/Explorer/Explorer.ts index 3a38cb183..e98526700 100644 --- a/src/Explorer/Explorer.ts +++ b/src/Explorer/Explorer.ts @@ -212,7 +212,7 @@ export default class Explorer { public isGalleryPublishEnabled: ko.Computed; public isCodeOfConductEnabled: ko.Computed; public isLinkInjectionEnabled: ko.Computed; - public isSettingsV2Enabled: ko.Computed; + public isSettingsV2Enabled: ko.Observable; public isGitHubPaneEnabled: ko.Observable; public isPublishNotebookPaneEnabled: ko.Observable; public isCopyNotebookPaneEnabled: ko.Observable; @@ -421,7 +421,8 @@ export default class Explorer { this.isLinkInjectionEnabled = ko.computed(() => this.isFeatureEnabled(Constants.Features.enableLinkInjection) ); - this.isSettingsV2Enabled = ko.computed(() => this.isFeatureEnabled(Constants.Features.enableSettingsV2)); + //this.isSettingsV2Enabled = ko.computed(() => this.isFeatureEnabled(Constants.Features.enableSettingsV2)); + this.isSettingsV2Enabled = ko.observable(false); this.isGitHubPaneEnabled = ko.observable(false); this.isPublishNotebookPaneEnabled = ko.observable(false); this.isCopyNotebookPaneEnabled = ko.observable(false); @@ -1919,6 +1920,7 @@ export default class Explorer { this.flight(inputs.addCollectionDefaultFlight); this.isTryCosmosDBSubscription(inputs.isTryCosmosDBSubscription); this.isAuthWithResourceToken(inputs.isAuthWithresourceToken); + this.setFeatureFlagsFromFlights(inputs.flights); if (!!inputs.dataExplorerVersion) { this.parentFrameDataExplorerVersion(inputs.dataExplorerVersion); @@ -1953,6 +1955,16 @@ export default class Explorer { return Q(); } + public setFeatureFlagsFromFlights(flights: readonly string[]): void { + if (!flights) { + return; + } + + if (flights.indexOf(Constants.Flights.SettingsV2) !== -1) { + this.isSettingsV2Enabled(true); + } + } + public findSelectedCollection(): ViewModels.Collection { if (this.selectedNode().nodeKind === "Collection") { return this.findSelectedCollectionForSelectedNode();