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
This commit is contained in:
Srinath Narayanan 2020-10-08 14:32:54 -07:00 committed by GitHub
parent 444f663733
commit 8028734cb0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 166 additions and 156 deletions

View File

@ -117,7 +117,6 @@ export class Features {
public static readonly enableGalleryPublish = "enablegallerypublish"; public static readonly enableGalleryPublish = "enablegallerypublish";
public static readonly enableCodeOfConduct = "enablecodeofconduct"; public static readonly enableCodeOfConduct = "enablecodeofconduct";
public static readonly enableLinkInjection = "enablelinkinjection"; public static readonly enableLinkInjection = "enablelinkinjection";
public static readonly enableSettingsV2 = "enablesettingsv2";
public static readonly enableSpark = "enablespark"; public static readonly enableSpark = "enablespark";
public static readonly livyEndpoint = "livyendpoint"; public static readonly livyEndpoint = "livyendpoint";
public static readonly notebookServerUrl = "notebookserverurl"; public static readonly notebookServerUrl = "notebookserverurl";
@ -131,6 +130,11 @@ export class Features {
public static readonly enableSDKoperations = "enablesdkoperations"; 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 { export class AfecFeatures {
public static readonly Spark = "spark-public-preview"; public static readonly Spark = "spark-public-preview";
public static readonly Notebooks = "sparknotebooks-public-preview"; public static readonly Notebooks = "sparknotebooks-public-preview";

View File

@ -388,6 +388,7 @@ export interface DataExplorerInputsFrame {
dataExplorerVersion?: string; dataExplorerVersion?: string;
isAuthWithresourceToken?: boolean; isAuthWithresourceToken?: boolean;
defaultCollectionThroughput?: CollectionCreationDefaults; defaultCollectionThroughput?: CollectionCreationDefaults;
flights?: readonly string[];
} }
export interface CollectionCreationDefaults { export interface CollectionCreationDefaults {

View File

@ -55,7 +55,6 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
label: "Enable Injecting Notebook Viewer Link into the first cell", label: "Enable Injecting Notebook Viewer Link into the first cell",
value: "true" value: "true"
}, },
{ key: "feature.enablesettingsv2", label: "Enable SettingsV2 Tab", value: "true" },
{ key: "feature.canexceedmaximumvalue", label: "Can exceed max value", value: "true" }, { key: "feature.canexceedmaximumvalue", label: "Can exceed max value", value: "true" },
{ {
key: "feature.enablefixedcollectionwithsharedthroughput", key: "feature.enablefixedcollectionwithsharedthroughput",

View File

@ -178,12 +178,6 @@ exports[`Feature panel renders all flags 1`] = `
className="checkboxRow" className="checkboxRow"
horizontalAlign="space-between" horizontalAlign="space-between"
> >
<StyledCheckboxBase
checked={false}
key="feature.enablesettingsv2"
label="Enable SettingsV2 Tab"
onChange={[Function]}
/>
<StyledCheckboxBase <StyledCheckboxBase
checked={false} checked={false}
key="feature.canexceedmaximumvalue" key="feature.canexceedmaximumvalue"

View File

@ -6,7 +6,7 @@ import SettingsTabV2 from "../../Tabs/SettingsTabV2";
import { collection } from "./TestUtils"; import { collection } from "./TestUtils";
import * as DataModels from "../../../Contracts/DataModels"; import * as DataModels from "../../../Contracts/DataModels";
import ko from "knockout"; import ko from "knockout";
import { TtlType, isDirty, TtlOnNoDefault, TtlOn, TtlOff } from "./SettingsUtils"; import { TtlType, isDirty } from "./SettingsUtils";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
import { updateCollection } from "../../../Common/dataAccess/updateCollection"; import { updateCollection } from "../../../Common/dataAccess/updateCollection";
jest.mock("../../../Common/dataAccess/updateCollection", () => ({ jest.mock("../../../Common/dataAccess/updateCollection", () => ({
@ -220,13 +220,6 @@ describe("SettingsComponent", () => {
expect(isDirty(state.throughput, state.throughputBaseline)).toEqual(false); 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", () => { it("getAnalyticalStorageTtl", () => {
const newCollection = { ...collection }; const newCollection = { ...collection };
newCollection.analyticalStorageTtl = ko.observable(10); newCollection.analyticalStorageTtl = ko.observable(10);

View File

@ -27,9 +27,6 @@ import {
SettingsV2TabTypes, SettingsV2TabTypes,
getTabTitle, getTabTitle,
isDirty, isDirty,
TtlOff,
TtlOn,
TtlOnNoDefault,
parseConflictResolutionMode, parseConflictResolutionMode,
parseConflictResolutionProcedure parseConflictResolutionProcedure
} from "./SettingsUtils"; } from "./SettingsUtils";
@ -38,7 +35,7 @@ import {
ConflictResolutionComponentProps ConflictResolutionComponentProps
} from "./SettingsSubComponents/ConflictResolutionComponent"; } from "./SettingsSubComponents/ConflictResolutionComponent";
import { SubSettingsComponent, SubSettingsComponentProps } from "./SettingsSubComponents/SubSettingsComponent"; 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 "./SettingsComponent.less";
import { IndexingPolicyComponent, IndexingPolicyComponentProps } from "./SettingsSubComponents/IndexingPolicyComponent"; import { IndexingPolicyComponent, IndexingPolicyComponentProps } from "./SettingsSubComponents/IndexingPolicyComponent";
@ -85,7 +82,6 @@ export interface SettingsComponentState {
indexingPolicyContent: DataModels.IndexingPolicy; indexingPolicyContent: DataModels.IndexingPolicy;
indexingPolicyContentBaseline: DataModels.IndexingPolicy; indexingPolicyContentBaseline: DataModels.IndexingPolicy;
shouldDiscardIndexingPolicy: boolean; shouldDiscardIndexingPolicy: boolean;
indexingPolicyElementFocussed: boolean;
isIndexingPolicyDirty: boolean; isIndexingPolicyDirty: boolean;
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode; conflictResolutionPolicyMode: DataModels.ConflictResolutionMode;
@ -102,7 +98,6 @@ export interface SettingsComponentState {
export class SettingsComponent extends React.Component<SettingsComponentProps, SettingsComponentState> { export class SettingsComponent extends React.Component<SettingsComponentProps, SettingsComponentState> {
private static readonly sixMonthsInSeconds = 15768000; private static readonly sixMonthsInSeconds = 15768000;
private static readonly zeroSeconds = 0;
public saveSettingsButton: ButtonV2; public saveSettingsButton: ButtonV2;
public discardSettingsChangesButton: ButtonV2; public discardSettingsChangesButton: ButtonV2;
@ -160,7 +155,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
indexingPolicyContent: undefined, indexingPolicyContent: undefined,
indexingPolicyContentBaseline: undefined, indexingPolicyContentBaseline: undefined,
indexingPolicyElementFocussed: false,
shouldDiscardIndexingPolicy: false, shouldDiscardIndexingPolicy: false,
isIndexingPolicyDirty: false, isIndexingPolicyDirty: false,
@ -518,9 +512,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
private onScaleDiscardableChange = (isScaleDiscardable: boolean): void => private onScaleDiscardableChange = (isScaleDiscardable: boolean): void =>
this.setState({ isScaleDiscardable: isScaleDiscardable }); this.setState({ isScaleDiscardable: isScaleDiscardable });
private onIndexingPolicyElementFocusChange = (indexingPolicyElementFocussed: boolean): void =>
this.setState({ indexingPolicyElementFocussed: indexingPolicyElementFocussed });
private onIndexingPolicyContentChange = (newIndexingPolicy: DataModels.IndexingPolicy): void => private onIndexingPolicyContentChange = (newIndexingPolicy: DataModels.IndexingPolicy): void =>
this.setState({ indexingPolicyContent: newIndexingPolicy }); this.setState({ indexingPolicyContent: newIndexingPolicy });
@ -544,79 +535,34 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
} }
}; };
private onConflictResolutionPolicyModeChange = ( private onConflictResolutionPolicyModeChange = (newMode: DataModels.ConflictResolutionMode): void =>
event?: React.FormEvent<HTMLElement | HTMLInputElement>, this.setState({ conflictResolutionPolicyMode: newMode });
option?: IChoiceGroupOption
): void =>
this.setState({
conflictResolutionPolicyMode:
DataModels.ConflictResolutionMode[option.key as keyof typeof DataModels.ConflictResolutionMode]
});
private onConflictResolutionPolicyPathChange = ( private onConflictResolutionPolicyPathChange = (newPath: string): void =>
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, this.setState({ conflictResolutionPolicyPath: newPath });
newValue?: string
): void => this.setState({ conflictResolutionPolicyPath: newValue });
private onConflictResolutionPolicyProcedureChange = ( private onConflictResolutionPolicyProcedureChange = (newProcedure: string): void =>
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, this.setState({ conflictResolutionPolicyProcedure: newProcedure });
newValue?: string
): void => this.setState({ conflictResolutionPolicyProcedure: newValue });
private onConflictResolutionDirtyChange = (isConflictResolutionDirty: boolean): void => private onConflictResolutionDirtyChange = (isConflictResolutionDirty: boolean): void =>
this.setState({ isConflictResolutionDirty: isConflictResolutionDirty }); this.setState({ isConflictResolutionDirty: isConflictResolutionDirty });
public getTtlValue = (value: string): TtlType => { private onTtlChange = (newTtl: TtlType): void => this.setState({ timeToLive: newTtl });
switch (value) {
case TtlOn:
return TtlType.On;
case TtlOff:
return TtlType.Off;
case TtlOnNoDefault:
return TtlType.OnNoDefault;
}
return undefined;
};
private onTtlChange = (ev?: React.FormEvent<HTMLElement | HTMLInputElement>, option?: IChoiceGroupOption): void => private onTimeToLiveSecondsChange = (newTimeToLiveSeconds: number): void =>
this.setState({ timeToLive: this.getTtlValue(option.key) });
private onTimeToLiveSecondsChange = (
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
newValue?: string
): void => {
let newTimeToLiveSeconds = parseInt(newValue);
newTimeToLiveSeconds = isNaN(newTimeToLiveSeconds) ? SettingsComponent.zeroSeconds : newTimeToLiveSeconds;
this.setState({ timeToLiveSeconds: newTimeToLiveSeconds }); this.setState({ timeToLiveSeconds: newTimeToLiveSeconds });
};
private onGeoSpatialConfigTypeChange = ( private onGeoSpatialConfigTypeChange = (newGeoSpatialConfigType: GeospatialConfigType): void =>
ev?: React.FormEvent<HTMLElement | HTMLInputElement>, this.setState({ geospatialConfigType: newGeoSpatialConfigType });
option?: IChoiceGroupOption
): void =>
this.setState({ geospatialConfigType: GeospatialConfigType[option.key as keyof typeof GeospatialConfigType] });
private onAnalyticalStorageTtlSelectionChange = ( private onAnalyticalStorageTtlSelectionChange = (newAnalyticalStorageTtlSelection: TtlType): void =>
ev?: React.FormEvent<HTMLElement | HTMLInputElement>, this.setState({ analyticalStorageTtlSelection: newAnalyticalStorageTtlSelection });
option?: IChoiceGroupOption
): void => this.setState({ analyticalStorageTtlSelection: this.getTtlValue(option.key) });
private onAnalyticalStorageTtlSecondsChange = ( private onAnalyticalStorageTtlSecondsChange = (newAnalyticalStorageTtlSeconds: number): void =>
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
newValue?: string
): void => {
let newAnalyticalStorageTtlSeconds = parseInt(newValue);
newAnalyticalStorageTtlSeconds = isNaN(newAnalyticalStorageTtlSeconds)
? SettingsComponent.zeroSeconds
: newAnalyticalStorageTtlSeconds;
this.setState({ analyticalStorageTtlSeconds: newAnalyticalStorageTtlSeconds }); this.setState({ analyticalStorageTtlSeconds: newAnalyticalStorageTtlSeconds });
};
private onChangeFeedPolicyChange = ( private onChangeFeedPolicyChange = (newChangeFeedPolicy: ChangeFeedPolicyState): void =>
ev?: React.FormEvent<HTMLElement | HTMLInputElement>, this.setState({ changeFeedPolicy: newChangeFeedPolicy });
option?: IChoiceGroupOption
): void =>
this.setState({ changeFeedPolicy: ChangeFeedPolicyState[option.key as keyof typeof ChangeFeedPolicyState] });
private onSubSettingsSaveableChange = (isSubSettingsSaveable: boolean): void => private onSubSettingsSaveableChange = (isSubSettingsSaveable: boolean): void =>
this.setState({ isSubSettingsSaveable: isSubSettingsSaveable }); this.setState({ isSubSettingsSaveable: isSubSettingsSaveable });
@ -844,7 +790,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
resetShouldDiscardIndexingPolicy: this.resetShouldDiscardIndexingPolicy, resetShouldDiscardIndexingPolicy: this.resetShouldDiscardIndexingPolicy,
indexingPolicyContent: this.state.indexingPolicyContent, indexingPolicyContent: this.state.indexingPolicyContent,
indexingPolicyContentBaseline: this.state.indexingPolicyContentBaseline, indexingPolicyContentBaseline: this.state.indexingPolicyContentBaseline,
onIndexingPolicyElementFocusChange: this.onIndexingPolicyElementFocusChange,
onIndexingPolicyContentChange: this.onIndexingPolicyContentChange, onIndexingPolicyContentChange: this.onIndexingPolicyContentChange,
logIndexingPolicySuccessMessage: this.logIndexingPolicySuccessMessage, logIndexingPolicySuccessMessage: this.logIndexingPolicySuccessMessage,
onIndexingPolicyDirtyChange: this.onIndexingPolicyDirtyChange onIndexingPolicyDirtyChange: this.onIndexingPolicyDirtyChange

View File

@ -18,24 +18,15 @@ export interface ConflictResolutionComponentProps {
container: Explorer; container: Explorer;
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode; conflictResolutionPolicyMode: DataModels.ConflictResolutionMode;
conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode; conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode;
onConflictResolutionPolicyModeChange: ( onConflictResolutionPolicyModeChange: (newMode: DataModels.ConflictResolutionMode) => void;
event?: React.FormEvent<HTMLElement | HTMLInputElement>,
option?: IChoiceGroupOption
) => void;
conflictResolutionPolicyPath: string; conflictResolutionPolicyPath: string;
conflictResolutionPolicyPathBaseline: string; conflictResolutionPolicyPathBaseline: string;
onConflictResolutionPolicyPathChange: ( onConflictResolutionPolicyPathChange: (newPath: string) => void;
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
newValue?: string
) => void;
conflictResolutionPolicyProcedure: string; conflictResolutionPolicyProcedure: string;
conflictResolutionPolicyProcedureBaseline: string; conflictResolutionPolicyProcedureBaseline: string;
onConflictResolutionPolicyProcedureChange: ( onConflictResolutionPolicyProcedureChange: (newProcedure: string) => void;
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
newValue?: string
) => void;
onConflictResolutionDirtyChange: (isConflictResolutionDirty: boolean) => void; onConflictResolutionDirtyChange: (isConflictResolutionDirty: boolean) => void;
} }
@ -77,12 +68,30 @@ export class ConflictResolutionComponent extends React.Component<ConflictResolut
return false; return false;
}; };
private onConflictResolutionPolicyModeChange = (
event?: React.FormEvent<HTMLElement | HTMLInputElement>,
option?: IChoiceGroupOption
): void =>
this.props.onConflictResolutionPolicyModeChange(
DataModels.ConflictResolutionMode[option.key as keyof typeof DataModels.ConflictResolutionMode]
);
private onConflictResolutionPolicyPathChange = (
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
newValue?: string
): void => this.props.onConflictResolutionPolicyPathChange(newValue);
private onConflictResolutionPolicyProcedureChange = (
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
newValue?: string
): void => this.props.onConflictResolutionPolicyProcedureChange(newValue);
private getConflictResolutionModeComponent = (): JSX.Element => ( private getConflictResolutionModeComponent = (): JSX.Element => (
<ChoiceGroup <ChoiceGroup
label="Mode" label="Mode"
selectedKey={this.props.conflictResolutionPolicyMode} selectedKey={this.props.conflictResolutionPolicyMode}
options={this.conflictResolutionChoiceGroupOptions} options={this.conflictResolutionChoiceGroupOptions}
onChange={this.props.onConflictResolutionPolicyModeChange} onChange={this.onConflictResolutionPolicyModeChange}
styles={getChoiceGroupStyles( styles={getChoiceGroupStyles(
this.props.conflictResolutionPolicyMode, this.props.conflictResolutionPolicyMode,
this.props.conflictResolutionPolicyModeBaseline this.props.conflictResolutionPolicyModeBaseline
@ -104,7 +113,7 @@ export class ConflictResolutionComponent extends React.Component<ConflictResolut
this.props.conflictResolutionPolicyPathBaseline this.props.conflictResolutionPolicyPathBaseline
)} )}
value={this.props.conflictResolutionPolicyPath} value={this.props.conflictResolutionPolicyPath}
onChange={this.props.onConflictResolutionPolicyPathChange} onChange={this.onConflictResolutionPolicyPathChange}
/> />
); );
@ -122,7 +131,7 @@ export class ConflictResolutionComponent extends React.Component<ConflictResolut
this.props.conflictResolutionPolicyProcedureBaseline this.props.conflictResolutionPolicyProcedureBaseline
)} )}
value={this.props.conflictResolutionPolicyProcedure} value={this.props.conflictResolutionPolicyProcedure}
onChange={this.props.onConflictResolutionPolicyProcedureChange} onChange={this.onConflictResolutionPolicyProcedureChange}
/> />
); );

View File

@ -17,9 +17,6 @@ describe("IndexingPolicyComponent", () => {
}, },
indexingPolicyContent: initialIndexingPolicyContent, indexingPolicyContent: initialIndexingPolicyContent,
indexingPolicyContentBaseline: initialIndexingPolicyContent, indexingPolicyContentBaseline: initialIndexingPolicyContent,
onIndexingPolicyElementFocusChange: () => {
return;
},
onIndexingPolicyContentChange: () => { onIndexingPolicyContentChange: () => {
return; return;
}, },

View File

@ -10,7 +10,6 @@ export interface IndexingPolicyComponentProps {
resetShouldDiscardIndexingPolicy: () => void; resetShouldDiscardIndexingPolicy: () => void;
indexingPolicyContent: DataModels.IndexingPolicy; indexingPolicyContent: DataModels.IndexingPolicy;
indexingPolicyContentBaseline: DataModels.IndexingPolicy; indexingPolicyContentBaseline: DataModels.IndexingPolicy;
onIndexingPolicyElementFocusChange: (indexingPolicyContentFocussed: boolean) => void;
onIndexingPolicyContentChange: (newIndexingPolicy: DataModels.IndexingPolicy) => void; onIndexingPolicyContentChange: (newIndexingPolicy: DataModels.IndexingPolicy) => void;
logIndexingPolicySuccessMessage: () => void; logIndexingPolicySuccessMessage: () => void;
onIndexingPolicyDirtyChange: (isIndexingPolicyDirty: boolean) => void; onIndexingPolicyDirtyChange: (isIndexingPolicyDirty: boolean) => void;
@ -89,8 +88,6 @@ export class IndexingPolicyComponent extends React.Component<
ariaLabel: "Indexing Policy" ariaLabel: "Indexing Policy"
}); });
if (this.indexingPolicyEditor) { if (this.indexingPolicyEditor) {
this.indexingPolicyEditor.onDidFocusEditorText(() => this.props.onIndexingPolicyElementFocusChange(true));
this.indexingPolicyEditor.onDidBlurEditorText(() => this.props.onIndexingPolicyElementFocusChange(false));
const indexingPolicyEditorModel = this.indexingPolicyEditor.getModel(); const indexingPolicyEditorModel = this.indexingPolicyEditor.getModel();
indexingPolicyEditorModel.onDidChangeContent(this.onEditorContentChange.bind(this)); indexingPolicyEditorModel.onDidChangeContent(this.onEditorContentChange.bind(this));
this.props.logIndexingPolicySuccessMessage(); this.props.logIndexingPolicySuccessMessage();

View File

@ -2,7 +2,7 @@ import { shallow } from "enzyme";
import React from "react"; import React from "react";
import { SubSettingsComponent, SubSettingsComponentProps } from "./SubSettingsComponent"; import { SubSettingsComponent, SubSettingsComponentProps } from "./SubSettingsComponent";
import { container, collection } from "../TestUtils"; 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 ko from "knockout";
import Explorer from "../../../Explorer"; import Explorer from "../../../Explorer";
@ -133,4 +133,11 @@ describe("SubSettingsComponent", () => {
expect(isComponentDirtyResult.isSaveable).toEqual(true); expect(isComponentDirtyResult.isSaveable).toEqual(true);
expect(isComponentDirtyResult.isDiscardable).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);
});
}); });

View File

@ -5,7 +5,11 @@ import {
TtlType, TtlType,
ChangeFeedPolicyState, ChangeFeedPolicyState,
isDirty, isDirty,
IsComponentDirtyResult IsComponentDirtyResult,
TtlOn,
TtlOff,
TtlOnNoDefault,
getSanitizedInputValue
} from "../SettingsUtils"; } from "../SettingsUtils";
import Explorer from "../../../Explorer"; import Explorer from "../../../Explorer";
import { Int32 } from "../../../Panes/Tables/Validators/EntityPropertyValidationCommon"; import { Int32 } from "../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
@ -37,40 +41,28 @@ export interface SubSettingsComponentProps {
timeToLive: TtlType; timeToLive: TtlType;
timeToLiveBaseline: TtlType; timeToLiveBaseline: TtlType;
onTtlChange: (ev?: React.FormEvent<HTMLElement | HTMLInputElement>, option?: IChoiceGroupOption) => void; onTtlChange: (newTtl: TtlType) => void;
timeToLiveSeconds: number; timeToLiveSeconds: number;
timeToLiveSecondsBaseline: number; timeToLiveSecondsBaseline: number;
onTimeToLiveSecondsChange: ( onTimeToLiveSecondsChange: (newTimeToLiveSeconds: number) => void;
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
newValue?: string
) => void;
geospatialConfigType: GeospatialConfigType; geospatialConfigType: GeospatialConfigType;
geospatialConfigTypeBaseline: GeospatialConfigType; geospatialConfigTypeBaseline: GeospatialConfigType;
onGeoSpatialConfigTypeChange: ( onGeoSpatialConfigTypeChange: (newGeoSpatialConfigType: GeospatialConfigType) => void;
ev?: React.FormEvent<HTMLElement | HTMLInputElement>,
option?: IChoiceGroupOption
) => void;
isAnalyticalStorageEnabled: boolean; isAnalyticalStorageEnabled: boolean;
analyticalStorageTtlSelection: TtlType; analyticalStorageTtlSelection: TtlType;
analyticalStorageTtlSelectionBaseline: TtlType; analyticalStorageTtlSelectionBaseline: TtlType;
onAnalyticalStorageTtlSelectionChange: ( onAnalyticalStorageTtlSelectionChange: (newAnalyticalStorageTtlSelection: TtlType) => void;
ev?: React.FormEvent<HTMLElement | HTMLInputElement>,
option?: IChoiceGroupOption
) => void;
analyticalStorageTtlSeconds: number; analyticalStorageTtlSeconds: number;
analyticalStorageTtlSecondsBaseline: number; analyticalStorageTtlSecondsBaseline: number;
onAnalyticalStorageTtlSecondsChange: ( onAnalyticalStorageTtlSecondsChange: (newAnalyticalStorageTtlSeconds: number) => void;
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
newValue?: string
) => void;
changeFeedPolicyVisible: boolean; changeFeedPolicyVisible: boolean;
changeFeedPolicy: ChangeFeedPolicyState; changeFeedPolicy: ChangeFeedPolicyState;
changeFeedPolicyBaseline: ChangeFeedPolicyState; changeFeedPolicyBaseline: ChangeFeedPolicyState;
onChangeFeedPolicyChange: (ev?: React.FormEvent<HTMLElement | HTMLInputElement>, option?: IChoiceGroupOption) => void; onChangeFeedPolicyChange: (newChangeFeedPolicyState: ChangeFeedPolicyState) => void;
onSubSettingsSaveableChange: (isSubSettingsSaveable: boolean) => void; onSubSettingsSaveableChange: (isSubSettingsSaveable: boolean) => void;
onSubSettingsDiscardableChange: (isSubSettingsDiscardable: boolean) => void; onSubSettingsDiscardableChange: (isSubSettingsDiscardable: boolean) => void;
} }
@ -139,6 +131,54 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
{ key: TtlType.On, text: "On" } { key: TtlType.On, text: "On" }
]; ];
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 = (ev?: React.FormEvent<HTMLElement | HTMLInputElement>, option?: IChoiceGroupOption): void =>
this.props.onTtlChange(this.getTtlValue(option.key));
private onTimeToLiveSecondsChange = (
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
newValue?: string
): void => {
const newTimeToLiveSeconds = getSanitizedInputValue(newValue, Int32.Max);
this.props.onTimeToLiveSecondsChange(newTimeToLiveSeconds);
};
private onGeoSpatialConfigTypeChange = (
ev?: React.FormEvent<HTMLElement | HTMLInputElement>,
option?: IChoiceGroupOption
): void =>
this.props.onGeoSpatialConfigTypeChange(GeospatialConfigType[option.key as keyof typeof GeospatialConfigType]);
private onAnalyticalStorageTtlSelectionChange = (
ev?: React.FormEvent<HTMLElement | HTMLInputElement>,
option?: IChoiceGroupOption
): void => this.props.onAnalyticalStorageTtlSelectionChange(this.getTtlValue(option.key));
private onAnalyticalStorageTtlSecondsChange = (
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
newValue?: string
): void => {
const newAnalyticalStorageTtlSeconds = getSanitizedInputValue(newValue, Int32.Max);
this.props.onAnalyticalStorageTtlSecondsChange(newAnalyticalStorageTtlSeconds);
};
private onChangeFeedPolicyChange = (
ev?: React.FormEvent<HTMLElement | HTMLInputElement>,
option?: IChoiceGroupOption
): void =>
this.props.onChangeFeedPolicyChange(ChangeFeedPolicyState[option.key as keyof typeof ChangeFeedPolicyState]);
private getTtlComponent = (): JSX.Element => ( private getTtlComponent = (): JSX.Element => (
<Stack {...titleAndInputStackProps}> <Stack {...titleAndInputStackProps}>
<ChoiceGroup <ChoiceGroup
@ -146,7 +186,7 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
label="Time to Live" label="Time to Live"
selectedKey={this.props.timeToLive} selectedKey={this.props.timeToLive}
options={this.ttlChoiceGroupOptions} options={this.ttlChoiceGroupOptions}
onChange={this.props.onTtlChange} onChange={this.onTtlChange}
styles={getChoiceGroupStyles(this.props.timeToLive, this.props.timeToLiveBaseline)} styles={getChoiceGroupStyles(this.props.timeToLive, this.props.timeToLiveBaseline)}
/> />
{isDirty(this.props.timeToLive, this.props.timeToLiveBaseline) && this.props.timeToLive === TtlType.On && ( {isDirty(this.props.timeToLive, this.props.timeToLiveBaseline) && this.props.timeToLive === TtlType.On && (
@ -163,7 +203,7 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
min={1} min={1}
max={Int32.Max} max={Int32.Max}
value={this.props.timeToLiveSeconds?.toString()} value={this.props.timeToLiveSeconds?.toString()}
onChange={this.props.onTimeToLiveSecondsChange} onChange={this.onTimeToLiveSecondsChange}
suffix="second(s)" suffix="second(s)"
/> />
)} )}
@ -183,7 +223,7 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
label="Analytical Storage Time to Live" label="Analytical Storage Time to Live"
selectedKey={this.props.analyticalStorageTtlSelection} selectedKey={this.props.analyticalStorageTtlSelection}
options={this.analyticalTtlChoiceGroupOptions} options={this.analyticalTtlChoiceGroupOptions}
onChange={this.props.onAnalyticalStorageTtlSelectionChange} onChange={this.onAnalyticalStorageTtlSelectionChange}
styles={getChoiceGroupStyles( styles={getChoiceGroupStyles(
this.props.analyticalStorageTtlSelection, this.props.analyticalStorageTtlSelection,
this.props.analyticalStorageTtlSelectionBaseline this.props.analyticalStorageTtlSelectionBaseline
@ -202,7 +242,7 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
max={Int32.Max} max={Int32.Max}
value={this.props.analyticalStorageTtlSeconds?.toString()} value={this.props.analyticalStorageTtlSeconds?.toString()}
suffix="second(s)" suffix="second(s)"
onChange={this.props.onAnalyticalStorageTtlSecondsChange} onChange={this.onAnalyticalStorageTtlSecondsChange}
/> />
)} )}
</Stack> </Stack>
@ -219,7 +259,7 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
label="Geospatial Configuration" label="Geospatial Configuration"
selectedKey={this.props.geospatialConfigType} selectedKey={this.props.geospatialConfigType}
options={this.geoSpatialConfigTypeChoiceGroupOptions} options={this.geoSpatialConfigTypeChoiceGroupOptions}
onChange={this.props.onGeoSpatialConfigTypeChange} onChange={this.onGeoSpatialConfigTypeChange}
styles={getChoiceGroupStyles(this.props.geospatialConfigType, this.props.geospatialConfigTypeBaseline)} styles={getChoiceGroupStyles(this.props.geospatialConfigType, this.props.geospatialConfigTypeBaseline)}
/> />
); );
@ -241,7 +281,7 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
id="changeFeedPolicy" id="changeFeedPolicy"
selectedKey={this.props.changeFeedPolicy} selectedKey={this.props.changeFeedPolicy}
options={this.changeFeedChoiceGroupOptions} options={this.changeFeedChoiceGroupOptions}
onChange={this.props.onChangeFeedPolicyChange} onChange={this.onChangeFeedPolicyChange}
styles={getChoiceGroupStyles(this.props.changeFeedPolicy, this.props.changeFeedPolicyBaseline)} styles={getChoiceGroupStyles(this.props.changeFeedPolicy, this.props.changeFeedPolicyBaseline)}
aria-labelledby={labelId} aria-labelledby={labelId}
/> />

View File

@ -26,9 +26,10 @@ import {
MessageBarType MessageBarType
} from "office-ui-fabric-react"; } from "office-ui-fabric-react";
import { ToolTipLabelComponent } from "../ToolTipLabelComponent"; import { ToolTipLabelComponent } from "../ToolTipLabelComponent";
import { IsComponentDirtyResult, isDirty } from "../../SettingsUtils"; import { getSanitizedInputValue, IsComponentDirtyResult, isDirty } from "../../SettingsUtils";
import * as SharedConstants from "../../../../../Shared/Constants"; import * as SharedConstants from "../../../../../Shared/Constants";
import * as DataModels from "../../../../../Contracts/DataModels"; import * as DataModels from "../../../../../Contracts/DataModels";
import { Int32 } from "../../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
export interface ThroughputInputAutoPilotV3Props { export interface ThroughputInputAutoPilotV3Props {
databaseAccount: DataModels.DatabaseAccount; databaseAccount: DataModels.DatabaseAccount;
@ -71,9 +72,9 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
> { > {
private shouldCheckComponentIsDirty = true; private shouldCheckComponentIsDirty = true;
private static readonly defaultStep = 100; private static readonly defaultStep = 100;
private static readonly zeroThroughput = 0;
private step: number; private step: number;
private choiceGroupFixedStyle = getChoiceGroupStyles(undefined, undefined); private throughputInputMaxValue: number;
private autoPilotInputMaxValue: number;
private options: IChoiceGroupOption[] = [ private options: IChoiceGroupOption[] = [
{ key: "true", text: "Autoscale" }, { key: "true", text: "Autoscale" },
{ key: "false", text: "Manual" } { key: "false", text: "Manual" }
@ -140,6 +141,8 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
}; };
this.step = this.props.step ?? ThroughputInputAutoPilotV3Component.defaultStep; this.step = this.props.step ?? ThroughputInputAutoPilotV3Component.defaultStep;
this.throughputInputMaxValue = this.props.canExceedMaximumValue ? Int32.Max : this.props.maximum;
this.autoPilotInputMaxValue = Int32.Max;
} }
public hasProvisioningTypeChanged = (): boolean => public hasProvisioningTypeChanged = (): boolean =>
@ -200,8 +203,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
newValue?: string newValue?: string
): void => { ): void => {
let newThroughput = parseInt(newValue); const newThroughput = getSanitizedInputValue(newValue, this.autoPilotInputMaxValue);
newThroughput = isNaN(newThroughput) ? ThroughputInputAutoPilotV3Component.zeroThroughput : newThroughput;
this.props.onMaxAutoPilotThroughputChange(newThroughput); this.props.onMaxAutoPilotThroughputChange(newThroughput);
}; };
@ -209,9 +211,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
newValue?: string newValue?: string
): void => { ): void => {
let newThroughput = parseInt(newValue); const newThroughput = getSanitizedInputValue(newValue, this.throughputInputMaxValue);
newThroughput = isNaN(newThroughput) ? ThroughputInputAutoPilotV3Component.zeroThroughput : newThroughput;
if (this.overrideWithAutoPilotSettings()) { if (this.overrideWithAutoPilotSettings()) {
this.props.onMaxAutoPilotThroughputChange(newThroughput); this.props.onMaxAutoPilotThroughputChange(newThroughput);
} else { } else {
@ -245,7 +245,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
onChange={this.onChoiceGroupChange} onChange={this.onChoiceGroupChange}
required={this.props.showAsMandatory} required={this.props.showAsMandatory}
ariaLabelledBy={labelId} ariaLabelledBy={labelId}
styles={this.choiceGroupFixedStyle} styles={getChoiceGroupStyles(this.props.wasAutopilotOriginallySet, this.props.isAutoPilotSelected)}
/> />
</Stack> </Stack>
); );
@ -270,8 +270,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
key="auto pilot throughput input" key="auto pilot throughput input"
styles={getTextFieldStyles(this.props.maxAutoPilotThroughput, this.props.maxAutoPilotThroughputBaseline)} styles={getTextFieldStyles(this.props.maxAutoPilotThroughput, this.props.maxAutoPilotThroughputBaseline)}
disabled={this.overrideWithProvisionedThroughputSettings()} disabled={this.overrideWithProvisionedThroughputSettings()}
step={this.step} step={AutoPilotUtils.autoPilotIncrementStep}
min={AutoPilotUtils.minAutoPilotThroughput}
value={this.overrideWithProvisionedThroughputSettings() ? "" : this.props.maxAutoPilotThroughput?.toString()} value={this.overrideWithProvisionedThroughputSettings() ? "" : this.props.maxAutoPilotThroughput?.toString()}
onChange={this.onAutoPilotThroughputChange} onChange={this.onAutoPilotThroughputChange}
/> />
@ -298,8 +297,6 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
styles={getTextFieldStyles(this.props.throughput, this.props.throughputBaseline)} styles={getTextFieldStyles(this.props.throughput, this.props.throughputBaseline)}
disabled={this.overrideWithAutoPilotSettings()} disabled={this.overrideWithAutoPilotSettings()}
step={this.step} step={this.step}
min={this.props.minimum}
max={this.props.canExceedMaximumValue ? undefined : this.props.maximum}
value={ value={
this.overrideWithAutoPilotSettings() this.overrideWithAutoPilotSettings()
? this.props.maxAutoPilotThroughputBaseline?.toString() ? this.props.maxAutoPilotThroughputBaseline?.toString()

View File

@ -81,10 +81,10 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
Object { Object {
"selectors": Object { "selectors": Object {
".ms-ChoiceField-field.is-checked::after": Object { ".ms-ChoiceField-field.is-checked::after": Object {
"borderColor": "", "borderColor": undefined,
}, },
".ms-ChoiceField-field.is-checked::before": Object { ".ms-ChoiceField-field.is-checked::before": Object {
"borderColor": "", "borderColor": undefined,
}, },
".ms-ChoiceField-wrapper label": Object { ".ms-ChoiceField-wrapper label": Object {
"fontFamily": undefined, "fontFamily": undefined,
@ -113,10 +113,9 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
id="autopilotInput" id="autopilotInput"
key="auto pilot throughput input" key="auto pilot throughput input"
label="Max RU/s" label="Max RU/s"
min={4000}
onChange={[Function]} onChange={[Function]}
required={true} required={true}
step={100} step={1000}
styles={ styles={
Object { Object {
"fieldGroup": Object { "fieldGroup": Object {
@ -219,7 +218,6 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
disabled={false} disabled={false}
id="throughputInput" id="throughputInput"
key="provisioned throughput input" key="provisioned throughput input"
min={10000}
onChange={[Function]} onChange={[Function]}
required={true} required={true}
step={100} step={100}
@ -375,7 +373,6 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
disabled={false} disabled={false}
id="throughputInput" id="throughputInput"
key="provisioned throughput input" key="provisioned throughput input"
min={10000}
onChange={[Function]} onChange={[Function]}
required={true} required={true}
step={100} step={100}

View File

@ -2,6 +2,7 @@ import { collection, container } from "./TestUtils";
import { import {
getMaxRUs, getMaxRUs,
getMinRUs, getMinRUs,
getSanitizedInputValue,
hasDatabaseSharedThroughput, hasDatabaseSharedThroughput,
isDirty, isDirty,
isDirtyTypes, isDirtyTypes,
@ -86,4 +87,11 @@ describe("SettingsUtils", () => {
expect(isDirty(baseline, current)).toEqual(true); 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);
});
}); });

View File

@ -6,6 +6,7 @@ import * as PricingUtils from "../../../Utils/PricingUtils";
import Explorer from "../../Explorer"; import Explorer from "../../Explorer";
const zeroValue = 0;
export type isDirtyTypes = boolean | string | number | DataModels.IndexingPolicy; export type isDirtyTypes = boolean | string | number | DataModels.IndexingPolicy;
export const TtlOff = "off"; export const TtlOff = "off";
export const TtlOn = "on"; export const TtlOn = "on";
@ -129,6 +130,16 @@ export const parseConflictResolutionProcedure = (procedureFromBackEnd: string):
return procedureFromBackEnd; 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 => { export const isDirty = (current: isDirtyTypes, baseline: isDirtyTypes): boolean => {
const currentType = typeof current; const currentType = typeof current;
const baselineType = typeof baseline; const baselineType = typeof baseline;

View File

@ -5325,7 +5325,6 @@ exports[`SettingsComponent renders 1`] = `
logIndexingPolicySuccessMessage={[Function]} logIndexingPolicySuccessMessage={[Function]}
onIndexingPolicyContentChange={[Function]} onIndexingPolicyContentChange={[Function]}
onIndexingPolicyDirtyChange={[Function]} onIndexingPolicyDirtyChange={[Function]}
onIndexingPolicyElementFocusChange={[Function]}
resetShouldDiscardIndexingPolicy={[Function]} resetShouldDiscardIndexingPolicy={[Function]}
shouldDiscardIndexingPolicy={false} shouldDiscardIndexingPolicy={false}
/> />

View File

@ -212,7 +212,7 @@ export default class Explorer {
public isGalleryPublishEnabled: ko.Computed<boolean>; public isGalleryPublishEnabled: ko.Computed<boolean>;
public isCodeOfConductEnabled: ko.Computed<boolean>; public isCodeOfConductEnabled: ko.Computed<boolean>;
public isLinkInjectionEnabled: ko.Computed<boolean>; public isLinkInjectionEnabled: ko.Computed<boolean>;
public isSettingsV2Enabled: ko.Computed<boolean>; public isSettingsV2Enabled: ko.Observable<boolean>;
public isGitHubPaneEnabled: ko.Observable<boolean>; public isGitHubPaneEnabled: ko.Observable<boolean>;
public isPublishNotebookPaneEnabled: ko.Observable<boolean>; public isPublishNotebookPaneEnabled: ko.Observable<boolean>;
public isCopyNotebookPaneEnabled: ko.Observable<boolean>; public isCopyNotebookPaneEnabled: ko.Observable<boolean>;
@ -421,7 +421,8 @@ export default class Explorer {
this.isLinkInjectionEnabled = ko.computed<boolean>(() => this.isLinkInjectionEnabled = ko.computed<boolean>(() =>
this.isFeatureEnabled(Constants.Features.enableLinkInjection) this.isFeatureEnabled(Constants.Features.enableLinkInjection)
); );
this.isSettingsV2Enabled = ko.computed<boolean>(() => this.isFeatureEnabled(Constants.Features.enableSettingsV2)); //this.isSettingsV2Enabled = ko.computed<boolean>(() => this.isFeatureEnabled(Constants.Features.enableSettingsV2));
this.isSettingsV2Enabled = ko.observable(false);
this.isGitHubPaneEnabled = ko.observable<boolean>(false); this.isGitHubPaneEnabled = ko.observable<boolean>(false);
this.isPublishNotebookPaneEnabled = ko.observable<boolean>(false); this.isPublishNotebookPaneEnabled = ko.observable<boolean>(false);
this.isCopyNotebookPaneEnabled = ko.observable<boolean>(false); this.isCopyNotebookPaneEnabled = ko.observable<boolean>(false);
@ -1919,6 +1920,7 @@ export default class Explorer {
this.flight(inputs.addCollectionDefaultFlight); this.flight(inputs.addCollectionDefaultFlight);
this.isTryCosmosDBSubscription(inputs.isTryCosmosDBSubscription); this.isTryCosmosDBSubscription(inputs.isTryCosmosDBSubscription);
this.isAuthWithResourceToken(inputs.isAuthWithresourceToken); this.isAuthWithResourceToken(inputs.isAuthWithresourceToken);
this.setFeatureFlagsFromFlights(inputs.flights);
if (!!inputs.dataExplorerVersion) { if (!!inputs.dataExplorerVersion) {
this.parentFrameDataExplorerVersion(inputs.dataExplorerVersion); this.parentFrameDataExplorerVersion(inputs.dataExplorerVersion);
@ -1953,6 +1955,16 @@ export default class Explorer {
return Q(); 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 { public findSelectedCollection(): ViewModels.Collection {
if (this.selectedNode().nodeKind === "Collection") { if (this.selectedNode().nodeKind === "Collection") {
return this.findSelectedCollectionForSelectedNode(); return this.findSelectedCollectionForSelectedNode();