Compare commits

..

2 Commits

Author SHA1 Message Date
Steve Faulkner
a636c70ce8 Remove old panel component 2021-02-24 13:07:15 -06:00
Steve Faulkner
92891a4878 Remove unused SparkMasterTab 2020-10-26 22:15:19 -05:00
52 changed files with 2209 additions and 392 deletions

View File

@@ -206,7 +206,6 @@ src/Explorer/Tabs/QueryTablesTab.ts
src/Explorer/Tabs/ScriptTabBase.ts src/Explorer/Tabs/ScriptTabBase.ts
src/Explorer/Tabs/SettingsTab.test.ts src/Explorer/Tabs/SettingsTab.test.ts
src/Explorer/Tabs/SettingsTab.ts src/Explorer/Tabs/SettingsTab.ts
src/Explorer/Tabs/SparkMasterTab.ts
src/Explorer/Tabs/StoredProcedureTab.ts src/Explorer/Tabs/StoredProcedureTab.ts
src/Explorer/Tabs/TabComponents.ts src/Explorer/Tabs/TabComponents.ts
src/Explorer/Tabs/TabsBase.ts src/Explorer/Tabs/TabsBase.ts

View File

@@ -3,6 +3,7 @@
"offerThroughput": 400, "offerThroughput": 400,
"databaseLevelThroughput": false, "databaseLevelThroughput": false,
"collectionId": "Persons", "collectionId": "Persons",
"rupmEnabled": false,
"partitionKey": { "kind": "Hash", "paths": ["/name"] }, "partitionKey": { "kind": "Hash", "paths": ["/name"] },
"data": [ "data": [
"g.addV('person').property(id, '1').property('name', 'Eva').property('age', 44)", "g.addV('person').property(id, '1').property('name', 'Eva').property('age', 44)",

View File

@@ -1,3 +1,4 @@
import { AutopilotTier } from "../Contracts/DataModels";
import { HashMap } from "./HashMap"; import { HashMap } from "./HashMap";
export class AuthorizationEndpoints { export class AuthorizationEndpoints {
@@ -108,6 +109,7 @@ export class CapabilityNames {
export class Features { export class Features {
public static readonly cosmosdb = "cosmosdb"; public static readonly cosmosdb = "cosmosdb";
public static readonly enableChangeFeedPolicy = "enablechangefeedpolicy"; public static readonly enableChangeFeedPolicy = "enablechangefeedpolicy";
public static readonly enableRupm = "enablerupm";
public static readonly executeSproc = "dataexplorerexecutesproc"; public static readonly executeSproc = "dataexplorerexecutesproc";
public static readonly hostedDataExplorer = "hosteddataexplorerenabled"; public static readonly hostedDataExplorer = "hosteddataexplorerenabled";
public static readonly enableTtl = "enablettl"; public static readonly enableTtl = "enablettl";
@@ -122,6 +124,7 @@ export class Features {
public static readonly notebookBasePath = "notebookbasepath"; public static readonly notebookBasePath = "notebookbasepath";
public static readonly canExceedMaximumValue = "canexceedmaximumvalue"; public static readonly canExceedMaximumValue = "canexceedmaximumvalue";
public static readonly enableFixedCollectionWithSharedThroughput = "enablefixedcollectionwithsharedthroughput"; public static readonly enableFixedCollectionWithSharedThroughput = "enablefixedcollectionwithsharedthroughput";
public static readonly enableAutoPilotV2 = "enableautopilotv2";
public static readonly ttl90Days = "ttl90days"; public static readonly ttl90Days = "ttl90days";
public static readonly enableRightPanelV2 = "enablerightpanelv2"; public static readonly enableRightPanelV2 = "enablerightpanelv2";
public static readonly enableSDKoperations = "enablesdkoperations"; public static readonly enableSDKoperations = "enablesdkoperations";
@@ -177,6 +180,12 @@ export class CassandraBackend {
public static readonly schemaApi: string = "api/cassandra/schema"; public static readonly schemaApi: string = "api/cassandra/schema";
public static readonly guestSchemaApi: string = "api/guest/cassandra/schema"; public static readonly guestSchemaApi: string = "api/guest/cassandra/schema";
} }
export class RUPMStates {
public static on: string = "on";
public static off: string = "off";
}
export class Queries { export class Queries {
public static CustomPageOption: string = "custom"; public static CustomPageOption: string = "custom";
public static UnlimitedPageOption: string = "unlimited"; public static UnlimitedPageOption: string = "unlimited";
@@ -253,6 +262,7 @@ export class HttpHeaders {
public static usePolygonsSmallerThanAHemisphere = "x-ms-documentdb-usepolygonssmallerthanahemisphere"; public static usePolygonsSmallerThanAHemisphere = "x-ms-documentdb-usepolygonssmallerthanahemisphere";
public static autoPilotThroughput = "autoscaleSettings"; public static autoPilotThroughput = "autoscaleSettings";
public static autoPilotThroughputSDK = "x-ms-cosmos-offer-autopilot-settings"; public static autoPilotThroughputSDK = "x-ms-cosmos-offer-autopilot-settings";
public static autoPilotTier = "x-ms-cosmos-offer-autopilot-tier";
public static partitionKey: string = "x-ms-documentdb-partitionkey"; public static partitionKey: string = "x-ms-documentdb-partitionkey";
public static migrateOfferToManualThroughput: string = "x-ms-cosmos-migrate-offer-to-manual-throughput"; public static migrateOfferToManualThroughput: string = "x-ms-cosmos-migrate-offer-to-manual-throughput";
public static migrateOfferToAutopilot: string = "x-ms-cosmos-migrate-offer-to-autopilot"; public static migrateOfferToAutopilot: string = "x-ms-cosmos-migrate-offer-to-autopilot";
@@ -397,6 +407,54 @@ export enum ConflictOperationType {
Delete = "delete" Delete = "delete"
} }
export class AutoPilot {
public static tier1Text: string = "4,000 RU/s";
public static tier2Text: string = "20,000 RU/s";
public static tier3Text: string = "100,000 RU/s";
public static tier4Text: string = "500,000 RU/s";
public static tierText = {
[AutopilotTier.Tier1]: "Tier 1",
[AutopilotTier.Tier2]: "Tier 2",
[AutopilotTier.Tier3]: "Tier 3",
[AutopilotTier.Tier4]: "Tier 4"
};
public static tierMaxRus = {
[AutopilotTier.Tier1]: 2000,
[AutopilotTier.Tier2]: 20000,
[AutopilotTier.Tier3]: 100000,
[AutopilotTier.Tier4]: 500000
};
public static tierMinRus = {
[AutopilotTier.Tier1]: 0,
[AutopilotTier.Tier2]: 0,
[AutopilotTier.Tier3]: 0,
[AutopilotTier.Tier4]: 0
};
public static tierStorageInGB = {
[AutopilotTier.Tier1]: 50,
[AutopilotTier.Tier2]: 200,
[AutopilotTier.Tier3]: 1000,
[AutopilotTier.Tier4]: 5000
};
}
export class DataExplorerVersions {
public static readonly v_1_0_0: string = "1.0.0";
public static readonly v_1_0_1: string = "1.0.1";
}
export class DataExplorerFeatures {
public static offerCache: string = "OfferCache";
}
export const DataExplorerFeaturesVersions: any = {
OfferCache: DataExplorerVersions.v_1_0_1
};
export const EmulatorMasterKey = export const EmulatorMasterKey =
//[SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Well known public masterKey for emulator")] //[SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Well known public masterKey for emulator")]
"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="; "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";

View File

@@ -376,7 +376,8 @@ const updateOfferWithSDK = async (params: UpdateOfferParams): Promise<Offer> =>
const currentOffer = params.currentOffer; const currentOffer = params.currentOffer;
const newOffer: Offer = { const newOffer: Offer = {
content: { content: {
offerThroughput: undefined offerThroughput: undefined,
offerIsRUPerMinuteThroughputEnabled: false
}, },
_etag: undefined, _etag: undefined,
_ts: undefined, _ts: undefined,

View File

@@ -17,7 +17,8 @@ describe("updateOfferThroughputBeyondLimit", () => {
resourceGroup: "foo", resourceGroup: "foo",
databaseAccountName: "foo", databaseAccountName: "foo",
databaseName: "foo", databaseName: "foo",
throughput: 1000000000 throughput: 1000000000,
offerIsRUPerMinuteThroughputEnabled: false
}); });
expect(window.fetch).toHaveBeenCalled(); expect(window.fetch).toHaveBeenCalled();
}); });

View File

@@ -11,6 +11,7 @@ interface UpdateOfferThroughputRequest {
databaseName: string; databaseName: string;
collectionName?: string; collectionName?: string;
throughput: number; throughput: number;
offerIsRUPerMinuteThroughputEnabled: boolean;
offerAutopilotSettings?: AutoPilotOfferSettings; offerAutopilotSettings?: AutoPilotOfferSettings;
} }

View File

@@ -179,6 +179,7 @@ export interface Offer extends Resource {
offerType?: string; offerType?: string;
content?: { content?: {
offerThroughput: number; offerThroughput: number;
offerIsRUPerMinuteThroughputEnabled: boolean;
collectionThroughputInfo?: OfferThroughputInfo; collectionThroughputInfo?: OfferThroughputInfo;
offerAutopilotSettings?: AutoPilotOfferSettings; offerAutopilotSettings?: AutoPilotOfferSettings;
}; };
@@ -232,17 +233,27 @@ export interface CreateDatabaseAndCollectionRequest {
collectionId: string; collectionId: string;
offerThroughput: number; offerThroughput: number;
databaseLevelThroughput: boolean; databaseLevelThroughput: boolean;
rupmEnabled?: boolean;
partitionKey?: PartitionKey; partitionKey?: PartitionKey;
indexingPolicy?: IndexingPolicy; indexingPolicy?: IndexingPolicy;
uniqueKeyPolicy?: UniqueKeyPolicy; uniqueKeyPolicy?: UniqueKeyPolicy;
autoPilot?: AutoPilotCreationSettings; autoPilot?: AutoPilotCreationSettings;
analyticalStorageTtl?: number; analyticalStorageTtl?: number;
hasAutoPilotV2FeatureFlag?: boolean;
} }
export interface AutoPilotCreationSettings { export interface AutoPilotCreationSettings {
autopilotTier?: AutopilotTier;
maxThroughput?: number; maxThroughput?: number;
} }
export enum AutopilotTier {
Tier1 = 1,
Tier2 = 2,
Tier3 = 3,
Tier4 = 4
}
export interface Query { export interface Query {
id: string; id: string;
resourceId: string; resourceId: string;
@@ -251,7 +262,9 @@ export interface Query {
} }
export interface AutoPilotOfferSettings { export interface AutoPilotOfferSettings {
tier?: AutopilotTier;
maximumTierThroughput?: number; maximumTierThroughput?: number;
targetTier?: AutopilotTier;
maxThroughput?: number; maxThroughput?: number;
targetMaxThroughput?: number; targetMaxThroughput?: number;
} }
@@ -478,6 +491,7 @@ export interface MongoParameters extends RpParameters {
rid?: string; rid?: string;
rtype?: string; rtype?: string;
isAutoPilot?: Boolean; isAutoPilot?: Boolean;
autoPilotTier?: string;
autoPilotThroughput?: string; autoPilotThroughput?: string;
analyticalStorageTtl?: number; analyticalStorageTtl?: number;
} }

View File

@@ -355,7 +355,6 @@ export enum CollectionTabKind {
Notebook = 13 /* Deprecated */, Notebook = 13 /* Deprecated */,
Terminal = 14, Terminal = 14,
NotebookV2 = 15, NotebookV2 = 15,
SparkMasterTab = 16,
Gallery = 17, Gallery = 17,
NotebookViewer = 18, NotebookViewer = 18,
SettingsV2 = 19 SettingsV2 = 19

View File

@@ -20,10 +20,6 @@ describe("Component Registerer", () => {
expect(ko.components.isRegistered("graph-style")).toBe(true); expect(ko.components.isRegistered("graph-style")).toBe(true);
}); });
it("should register collapsible-panel component", () => {
expect(ko.components.isRegistered("collapsible-panel")).toBe(true);
});
it("should register json-editor component", () => { it("should register json-editor component", () => {
expect(ko.components.isRegistered("json-editor")).toBe(true); expect(ko.components.isRegistered("json-editor")).toBe(true);
}); });
@@ -123,4 +119,8 @@ describe("Component Registerer", () => {
it("should register dynamic-list component", () => { it("should register dynamic-list component", () => {
expect(ko.components.isRegistered("dynamic-list")).toBe(true); expect(ko.components.isRegistered("dynamic-list")).toBe(true);
}); });
it("should register throughput-input component", () => {
expect(ko.components.isRegistered("throughput-input")).toBe(true);
});
}); });

View File

@@ -1,7 +1,6 @@
import * as ko from "knockout"; import * as ko from "knockout";
import * as PaneComponents from "./Panes/PaneComponents"; import * as PaneComponents from "./Panes/PaneComponents";
import * as TabComponents from "./Tabs/TabComponents"; import * as TabComponents from "./Tabs/TabComponents";
import { CollapsiblePanelComponent } from "./Controls/CollapsiblePanel/CollapsiblePanelComponent";
import { DiffEditorComponent } from "./Controls/DiffEditor/DiffEditorComponent"; import { DiffEditorComponent } from "./Controls/DiffEditor/DiffEditorComponent";
import { DynamicListComponent } from "./Controls/DynamicList/DynamicListComponent"; import { DynamicListComponent } from "./Controls/DynamicList/DynamicListComponent";
import { EditorComponent } from "./Controls/Editor/EditorComponent"; import { EditorComponent } from "./Controls/Editor/EditorComponent";
@@ -11,17 +10,18 @@ import { InputTypeaheadComponent } from "./Controls/InputTypeahead/InputTypeahea
import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent"; import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent";
import { NewVertexComponent } from "./Graph/NewVertexComponent/NewVertexComponent"; import { NewVertexComponent } from "./Graph/NewVertexComponent/NewVertexComponent";
import { TabsManagerKOComponent } from "./Tabs/TabsManager"; import { TabsManagerKOComponent } from "./Tabs/TabsManager";
import { ThroughputInputComponent } from "./Controls/ThroughputInput/ThroughputInputComponent";
import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3"; import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3";
ko.components.register("input-typeahead", new InputTypeaheadComponent()); ko.components.register("input-typeahead", new InputTypeaheadComponent());
ko.components.register("new-vertex-form", NewVertexComponent); ko.components.register("new-vertex-form", NewVertexComponent);
ko.components.register("error-display", new ErrorDisplayComponent()); ko.components.register("error-display", new ErrorDisplayComponent());
ko.components.register("graph-style", GraphStyleComponent); ko.components.register("graph-style", GraphStyleComponent);
ko.components.register("collapsible-panel", new CollapsiblePanelComponent());
ko.components.register("editor", new EditorComponent()); ko.components.register("editor", new EditorComponent());
ko.components.register("json-editor", new JsonEditorComponent()); ko.components.register("json-editor", new JsonEditorComponent());
ko.components.register("diff-editor", new DiffEditorComponent()); ko.components.register("diff-editor", new DiffEditorComponent());
ko.components.register("dynamic-list", DynamicListComponent); ko.components.register("dynamic-list", DynamicListComponent);
ko.components.register("throughput-input", ThroughputInputComponent);
ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3); ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3);
ko.components.register("tabs-manager", TabsManagerKOComponent()); ko.components.register("tabs-manager", TabsManagerKOComponent());
@@ -40,7 +40,6 @@ ko.components.register("mongo-shell-tab", new TabComponents.MongoShellTab());
ko.components.register("conflicts-tab", new TabComponents.ConflictsTab()); ko.components.register("conflicts-tab", new TabComponents.ConflictsTab());
ko.components.register("notebookv2-tab", new TabComponents.NotebookV2Tab()); ko.components.register("notebookv2-tab", new TabComponents.NotebookV2Tab());
ko.components.register("terminal-tab", new TabComponents.TerminalTab()); ko.components.register("terminal-tab", new TabComponents.TerminalTab());
ko.components.register("spark-master-tab", new TabComponents.SparkMasterTab());
ko.components.register("gallery-tab", new TabComponents.GalleryTab()); ko.components.register("gallery-tab", new TabComponents.GalleryTab());
ko.components.register("notebook-viewer-tab", new TabComponents.NotebookViewerTab()); ko.components.register("notebook-viewer-tab", new TabComponents.NotebookViewerTab());

View File

@@ -1,56 +0,0 @@
import * as ko from "knockout";
import template from "./collapsible-panel-component.html";
/**
* Helper class for ko component registration
*/
export class CollapsiblePanelComponent {
constructor() {
return {
viewModel: CollapsiblePanelViewModel,
template
};
}
}
/**
* Parameters for this component
*/
interface CollapsiblePanelParams {
collapsedTitle: ko.Observable<string>;
expandedTitle: ko.Observable<string>;
isCollapsed?: ko.Observable<boolean>;
collapseToLeft?: boolean;
}
/**
* Collapsible panel:
* Contains a header with [>] button to collapse and an title ("expandedTitle").
* Collapsing the panel:
* - shrinks width to narrow amount
* - hides children
* - shows [<]
* - shows vertical title ("collapsedTitle")
* - the default behavior is to collapse to the right (ie, place this component on the right or use "collapseToLeft" parameter)
*
* How to use in your markup:
* <collapsible-panel params="{ collapsedTitle:'Properties', expandedTitle:'Expanded properties' }">
* <!-- add your markup here: the ko context is the same as outside of collapsible-panel (ie $data) -->
* </collapsible-panel>
*
* Use the optional "isCollapsed" parameter to programmatically collapse/expand the pane from outside the component.
* Use the optional "collapseToLeft" parameter to collapse to the left.
*/
class CollapsiblePanelViewModel {
private params: CollapsiblePanelParams;
private isCollapsed: ko.Observable<boolean>;
public constructor(params: CollapsiblePanelParams) {
this.params = params;
this.isCollapsed = params.isCollapsed || ko.observable(false);
}
private toggleCollapse(): void {
this.isCollapsed(!this.isCollapsed());
}
}

View File

@@ -1,44 +0,0 @@
<div class="collapsiblePanel" data-bind="css: { paneCollapsed:isCollapsed() }">
<div class="panelHeader" data-bind="visible: !isCollapsed()">
<span
class="collapsedIconContainer collapseExpandButton"
data-bind="click:toggleCollapse, css: { 'pull-right':params.collapseToLeft }"
>
<img
class="collapsedIcon imgVerticalAlignment"
src="/imgarrowlefticon.svg"
alt="Collapse"
data-bind="css: { expanded:!isCollapsed(), iconMirror:params.collapseToLeft }"
/>
</span>
<span
class="expandedTitle"
data-bind="text: params.expandedTitle, css:{ iconSpacer:!params.collapseToLeft }"
></span>
</div>
<div class="collapsibleNav nav" data-bind="visible:isCollapsed">
<ul class="nav">
<li class="collapsedBtn collapseExpandButton">
<span class="collapsedIconContainer" data-bind="click: toggleCollapse">
<img
class="collapsedIcon"
src="/imgarrowlefticon.svg"
data-bind="css: { expanded:!isCollapsed(), iconMirror:params.collapseToLeft }"
alt="Expand"
/>
</span>
<span class="rotatedInner" data-bind="click: toggleCollapse">
<span data-bind="text: params.collapsedTitle"></span>
</span>
</li>
</ul>
</div>
<div class="panelContent" data-bind="visible:!isCollapsed()">
<!-- ko with:$parent -->
<!-- ko template: { nodes: $componentTemplateNodes } -->
<!-- /ko -->
<!-- /ko -->
</div>
</div>

View File

@@ -44,6 +44,7 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
onChange?: (_?: React.FormEvent<HTMLElement | HTMLInputElement>, checked?: boolean) => void; onChange?: (_?: React.FormEvent<HTMLElement | HTMLInputElement>, checked?: boolean) => void;
}[] = [ }[] = [
{ key: "feature.enablechangefeedpolicy", label: "Enable change feed policy", value: "true" }, { key: "feature.enablechangefeedpolicy", label: "Enable change feed policy", value: "true" },
{ key: "feature.enablerupm", label: "Enable RUPM", value: "true" },
{ key: "feature.dataexplorerexecutesproc", label: "Execute stored procedure", value: "true" }, { key: "feature.dataexplorerexecutesproc", label: "Execute stored procedure", value: "true" },
{ key: "feature.hosteddataexplorerenabled", label: "Hosted Data Explorer (deprecated?)", value: "true" }, { key: "feature.hosteddataexplorerenabled", label: "Hosted Data Explorer (deprecated?)", value: "true" },
{ key: "feature.enablettl", label: "Enable TTL", value: "true" }, { key: "feature.enablettl", label: "Enable TTL", value: "true" },

View File

@@ -131,6 +131,12 @@ exports[`Feature panel renders all flags 1`] = `
label="Enable change feed policy" label="Enable change feed policy"
onChange={[Function]} onChange={[Function]}
/> />
<StyledCheckboxBase
checked={false}
key="feature.enablerupm"
label="Enable RUPM"
onChange={[Function]}
/>
<StyledCheckboxBase <StyledCheckboxBase
checked={false} checked={false}
key="feature.dataexplorerexecutesproc" key="feature.dataexplorerexecutesproc"

View File

@@ -125,6 +125,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
private container: Explorer; private container: Explorer;
private changeFeedPolicyVisible: boolean; private changeFeedPolicyVisible: boolean;
private isFixedContainer: boolean; private isFixedContainer: boolean;
private autoPilotTiersList: ViewModels.DropdownOption<DataModels.AutopilotTier>[];
private shouldShowIndexingPolicyEditor: boolean; private shouldShowIndexingPolicyEditor: boolean;
public mongoDBCollectionResource: MongoDBCollectionResource; public mongoDBCollectionResource: MongoDBCollectionResource;
@@ -229,7 +230,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
if ( if (
this.container.isMongoIndexEditorEnabled() && this.container.isMongoIndexEditorEnabled() &&
this.container.isPreferredApiMongoDB() && this.container.isPreferredApiMongoDB() &&
this.container.isEnableMongoCapabilityPresent() &&
this.container.databaseAccount() this.container.databaseAccount()
) { ) {
await this.refreshIndexTransformationProgress(); await this.refreshIndexTransformationProgress();
@@ -395,13 +395,11 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
} }
if (this.state.isMongoIndexingPolicySaveable && this.mongoDBCollectionResource) { if (this.state.isMongoIndexingPolicySaveable && this.mongoDBCollectionResource) {
try {
const newMongoIndexes = this.getMongoIndexesToSave(); const newMongoIndexes = this.getMongoIndexesToSave();
const newMongoCollection: MongoDBCollectionResource = { const newMongoCollection: MongoDBCollectionResource = {
...this.mongoDBCollectionResource, ...this.mongoDBCollectionResource,
indexes: newMongoIndexes indexes: newMongoIndexes
}; };
this.mongoDBCollectionResource = await updateMongoDBCollectionThroughRP( this.mongoDBCollectionResource = await updateMongoDBCollectionThroughRP(
this.collection.databaseId, this.collection.databaseId,
this.collection.id(), this.collection.id(),
@@ -415,34 +413,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
indexesToAdd: [], indexesToAdd: [],
currentMongoIndexes: [...this.mongoDBCollectionResource.indexes] currentMongoIndexes: [...this.mongoDBCollectionResource.indexes]
}); });
traceSuccess(
Action.MongoIndexUpdated,
{
databaseAccountName: this.container.databaseAccount()?.name,
databaseName: this.collection?.databaseId,
collectionName: this.collection?.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.props.settingsTab.tabTitle()
},
startKey
);
} catch (error) {
traceFailure(
Action.MongoIndexUpdated,
{
databaseAccountName: this.container.databaseAccount()?.name,
databaseName: this.collection?.databaseId,
collectionName: this.collection?.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.props.settingsTab.tabTitle(),
error: error.message
},
startKey
);
throw error;
}
} }
if (this.state.isScaleSaveable) { if (this.state.isScaleSaveable) {
@@ -454,7 +424,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
newOffer.content.offerThroughput = newThroughput; newOffer.content.offerThroughput = newThroughput;
} else { } else {
newOffer.content = { newOffer.content = {
offerThroughput: newThroughput offerThroughput: newThroughput,
offerIsRUPerMinuteThroughputEnabled: false
}; };
} }
@@ -497,7 +468,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
resourceGroup: userContext.resourceGroup, resourceGroup: userContext.resourceGroup,
databaseName: this.collection.databaseId, databaseName: this.collection.databaseId,
collectionName: this.collection.id(), collectionName: this.collection.id(),
throughput: newThroughput throughput: newThroughput,
offerIsRUPerMinuteThroughputEnabled: false
}; };
await updateOfferThroughputBeyondLimit(requestPayload); await updateOfferThroughputBeyondLimit(requestPayload);
@@ -570,7 +542,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
defaultExperience: this.container.defaultExperience(), defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab, dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.props.settingsTab.tabTitle(), tabTitle: this.props.settingsTab.tabTitle(),
error: reason.message error: reason
}, },
startKey startKey
); );
@@ -895,6 +867,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
collection: this.collection, collection: this.collection,
container: this.container, container: this.container,
isFixedContainer: this.isFixedContainer, isFixedContainer: this.isFixedContainer,
autoPilotTiersList: this.autoPilotTiersList,
onThroughputChange: this.onThroughputChange, onThroughputChange: this.onThroughputChange,
throughput: this.state.throughput, throughput: this.state.throughput,
throughputBaseline: this.state.throughputBaseline, throughputBaseline: this.state.throughputBaseline,

View File

@@ -31,7 +31,7 @@ class SettingsRenderUtilsTestComponent extends React.Component {
{getAutoPilotV3SpendElement(1000, true)} {getAutoPilotV3SpendElement(1000, true)}
{getAutoPilotV3SpendElement(undefined, true)} {getAutoPilotV3SpendElement(undefined, true)}
{getEstimatedSpendElement(1000, "mooncake", 2, false)} {getEstimatedSpendElement(1000, "mooncake", 2, false, true)}
{getEstimatedAutoscaleSpendElement(1000, "mooncake", 2, false)} {getEstimatedAutoscaleSpendElement(1000, "mooncake", 2, false)}

View File

@@ -199,9 +199,10 @@ export const getEstimatedSpendElement = (
throughput: number, throughput: number,
serverId: string, serverId: string,
regions: number, regions: number,
multimaster: boolean multimaster: boolean,
rupmEnabled: boolean
): JSX.Element => { ): JSX.Element => {
const hourlyPrice: number = computeRUUsagePriceHourly(serverId, throughput, regions, multimaster); const hourlyPrice: number = computeRUUsagePriceHourly(serverId, rupmEnabled, throughput, regions, multimaster);
const dailyPrice: number = hourlyPrice * 24; const dailyPrice: number = hourlyPrice * 24;
const monthlyPrice: number = hourlyPrice * hoursInAMonth; const monthlyPrice: number = hourlyPrice * hoursInAMonth;
const currency: string = getPriceCurrency(serverId); const currency: string = getPriceCurrency(serverId);

View File

@@ -20,6 +20,7 @@ describe("ScaleComponent", () => {
collection: collection, collection: collection,
container: container, container: container,
isFixedContainer: false, isFixedContainer: false,
autoPilotTiersList: [],
onThroughputChange: () => { onThroughputChange: () => {
return; return;
}, },

View File

@@ -24,6 +24,7 @@ export interface ScaleComponentProps {
collection: ViewModels.Collection; collection: ViewModels.Collection;
container: Explorer; container: Explorer;
isFixedContainer: boolean; isFixedContainer: boolean;
autoPilotTiersList: ViewModels.DropdownOption<DataModels.AutopilotTier>[];
onThroughputChange: (newThroughput: number) => void; onThroughputChange: (newThroughput: number) => void;
throughput: number; throughput: number;
throughputBaseline: number; throughputBaseline: number;
@@ -85,7 +86,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
public getThroughputTitle = (): string => { public getThroughputTitle = (): string => {
if (this.props.isAutoPilotSelected) { if (this.props.isAutoPilotSelected) {
return AutoPilotUtils.getAutoPilotHeaderText(); return AutoPilotUtils.getAutoPilotHeaderText(false);
} }
const minThroughput: string = getMinRUs(this.props.collection, this.props.container).toLocaleString(); const minThroughput: string = getMinRUs(this.props.collection, this.props.container).toLocaleString();

View File

@@ -174,7 +174,8 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
this.overrideWithAutoPilotSettings() ? this.props.maxAutoPilotThroughput : offerThroughput, this.overrideWithAutoPilotSettings() ? this.props.maxAutoPilotThroughput : offerThroughput,
serverId, serverId,
regions, regions,
multimaster multimaster,
false
); );
} else { } else {
estimatedSpend = getEstimatedAutoscaleSpendElement( estimatedSpend = getEstimatedAutoscaleSpendElement(

View File

@@ -22,6 +22,7 @@ export const collection = ({
offer: ko.observable<DataModels.Offer>({ offer: ko.observable<DataModels.Offer>({
content: { content: {
offerThroughput: 10000, offerThroughput: 10000,
offerIsRUPerMinuteThroughputEnabled: false,
collectionThroughputInfo: { collectionThroughputInfo: {
minimumRUForCollection: 6000, minimumRUForCollection: 6000,
numPhysicalPartitions: 4 numPhysicalPartitions: 4

View File

@@ -40,6 +40,7 @@ exports[`SettingsComponent renders 1`] = `
"_openShareDialog": [Function], "_openShareDialog": [Function],
"_panes": Array [ "_panes": Array [
AddDatabasePane { AddDatabasePane {
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -55,6 +56,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function], "firstFieldHasFocus": [Function],
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "adddatabasepane", "id": "adddatabasepane",
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
"isExecuting": [Function], "isExecuting": [Function],
@@ -67,6 +69,7 @@ exports[`SettingsComponent renders 1`] = `
"onMoreDetailsKeyPress": [Function], "onMoreDetailsKeyPress": [Function],
"requestUnitsUsageCost": [Function], "requestUnitsUsageCost": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"selectedAutoPilotTier": [Function],
"showUpsellMessage": [Function], "showUpsellMessage": [Function],
"throughput": [Function], "throughput": [Function],
"throughputRangeText": [Function], "throughputRangeText": [Function],
@@ -83,6 +86,7 @@ exports[`SettingsComponent renders 1`] = `
AddCollectionPane { AddCollectionPane {
"_isSynapseLinkEnabled": [Function], "_isSynapseLinkEnabled": [Function],
"autoPilotThroughput": [Function], "autoPilotThroughput": [Function],
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -104,6 +108,7 @@ exports[`SettingsComponent renders 1`] = `
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"formWarnings": [Function], "formWarnings": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "addcollectionpane", "id": "addcollectionpane",
"isAnalyticalStorageOn": [Function], "isAnalyticalStorageOn": [Function],
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
@@ -133,7 +138,12 @@ exports[`SettingsComponent renders 1`] = `
"partitionKeyVisible": [Function], "partitionKeyVisible": [Function],
"requestUnitsUsageCost": [Function], "requestUnitsUsageCost": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"rupm": [Function],
"rupmVisible": [Function],
"selectedAutoPilotTier": [Function],
"selectedSharedAutoPilotTier": [Function],
"sharedAutoPilotThroughput": [Function], "sharedAutoPilotThroughput": [Function],
"sharedAutoPilotTiersList": [Function],
"sharedThroughputRangeText": [Function], "sharedThroughputRangeText": [Function],
"shouldCreateMongoWildcardIndex": [Function], "shouldCreateMongoWildcardIndex": [Function],
"shouldUseDatabaseThroughput": [Function], "shouldUseDatabaseThroughput": [Function],
@@ -344,6 +354,7 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function], "visible": [Function],
}, },
CassandraAddCollectionPane { CassandraAddCollectionPane {
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -355,6 +366,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function], "firstFieldHasFocus": [Function],
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "cassandraaddcollectionpane", "id": "cassandraaddcollectionpane",
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
"isExecuting": [Function], "isExecuting": [Function],
@@ -375,7 +387,10 @@ exports[`SettingsComponent renders 1`] = `
"requestUnitsUsageCostShared": [Function], "requestUnitsUsageCostShared": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"selectedAutoPilotThroughput": [Function], "selectedAutoPilotThroughput": [Function],
"selectedAutoPilotTier": [Function],
"selectedSharedAutoPilotTier": [Function],
"sharedAutoPilotThroughput": [Function], "sharedAutoPilotThroughput": [Function],
"sharedAutoPilotTiersList": [Function],
"sharedThroughputRangeText": [Function], "sharedThroughputRangeText": [Function],
"sharedThroughputSpendAck": [Function], "sharedThroughputSpendAck": [Function],
"sharedThroughputSpendAckText": [Function], "sharedThroughputSpendAckText": [Function],
@@ -570,6 +585,7 @@ exports[`SettingsComponent renders 1`] = `
"addCollectionPane": AddCollectionPane { "addCollectionPane": AddCollectionPane {
"_isSynapseLinkEnabled": [Function], "_isSynapseLinkEnabled": [Function],
"autoPilotThroughput": [Function], "autoPilotThroughput": [Function],
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -591,6 +607,7 @@ exports[`SettingsComponent renders 1`] = `
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"formWarnings": [Function], "formWarnings": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "addcollectionpane", "id": "addcollectionpane",
"isAnalyticalStorageOn": [Function], "isAnalyticalStorageOn": [Function],
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
@@ -620,7 +637,12 @@ exports[`SettingsComponent renders 1`] = `
"partitionKeyVisible": [Function], "partitionKeyVisible": [Function],
"requestUnitsUsageCost": [Function], "requestUnitsUsageCost": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"rupm": [Function],
"rupmVisible": [Function],
"selectedAutoPilotTier": [Function],
"selectedSharedAutoPilotTier": [Function],
"sharedAutoPilotThroughput": [Function], "sharedAutoPilotThroughput": [Function],
"sharedAutoPilotTiersList": [Function],
"sharedThroughputRangeText": [Function], "sharedThroughputRangeText": [Function],
"shouldCreateMongoWildcardIndex": [Function], "shouldCreateMongoWildcardIndex": [Function],
"shouldUseDatabaseThroughput": [Function], "shouldUseDatabaseThroughput": [Function],
@@ -650,6 +672,7 @@ exports[`SettingsComponent renders 1`] = `
}, },
"addCollectionText": [Function], "addCollectionText": [Function],
"addDatabasePane": AddDatabasePane { "addDatabasePane": AddDatabasePane {
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -665,6 +688,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function], "firstFieldHasFocus": [Function],
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "adddatabasepane", "id": "adddatabasepane",
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
"isExecuting": [Function], "isExecuting": [Function],
@@ -677,6 +701,7 @@ exports[`SettingsComponent renders 1`] = `
"onMoreDetailsKeyPress": [Function], "onMoreDetailsKeyPress": [Function],
"requestUnitsUsageCost": [Function], "requestUnitsUsageCost": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"selectedAutoPilotTier": [Function],
"showUpsellMessage": [Function], "showUpsellMessage": [Function],
"throughput": [Function], "throughput": [Function],
"throughputRangeText": [Function], "throughputRangeText": [Function],
@@ -753,6 +778,7 @@ exports[`SettingsComponent renders 1`] = `
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
"canSaveQueries": [Function], "canSaveQueries": [Function],
"cassandraAddCollectionPane": CassandraAddCollectionPane { "cassandraAddCollectionPane": CassandraAddCollectionPane {
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -764,6 +790,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function], "firstFieldHasFocus": [Function],
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "cassandraaddcollectionpane", "id": "cassandraaddcollectionpane",
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
"isExecuting": [Function], "isExecuting": [Function],
@@ -784,7 +811,10 @@ exports[`SettingsComponent renders 1`] = `
"requestUnitsUsageCostShared": [Function], "requestUnitsUsageCostShared": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"selectedAutoPilotThroughput": [Function], "selectedAutoPilotThroughput": [Function],
"selectedAutoPilotTier": [Function],
"selectedSharedAutoPilotTier": [Function],
"sharedAutoPilotThroughput": [Function], "sharedAutoPilotThroughput": [Function],
"sharedAutoPilotTiersList": [Function],
"sharedThroughputRangeText": [Function], "sharedThroughputRangeText": [Function],
"sharedThroughputSpendAck": [Function], "sharedThroughputSpendAck": [Function],
"sharedThroughputSpendAckText": [Function], "sharedThroughputSpendAckText": [Function],
@@ -939,6 +969,7 @@ exports[`SettingsComponent renders 1`] = `
"title": [Function], "title": [Function],
"visible": [Function], "visible": [Function],
}, },
"hasAutoPilotV2FeatureFlag": [Function],
"hasStorageAnalyticsAfecFeature": [Function], "hasStorageAnalyticsAfecFeature": [Function],
"hasWriteAccess": [Function], "hasWriteAccess": [Function],
"isAccountReady": [Function], "isAccountReady": [Function],
@@ -1316,6 +1347,7 @@ exports[`SettingsComponent renders 1`] = `
"_openShareDialog": [Function], "_openShareDialog": [Function],
"_panes": Array [ "_panes": Array [
AddDatabasePane { AddDatabasePane {
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -1331,6 +1363,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function], "firstFieldHasFocus": [Function],
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "adddatabasepane", "id": "adddatabasepane",
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
"isExecuting": [Function], "isExecuting": [Function],
@@ -1343,6 +1376,7 @@ exports[`SettingsComponent renders 1`] = `
"onMoreDetailsKeyPress": [Function], "onMoreDetailsKeyPress": [Function],
"requestUnitsUsageCost": [Function], "requestUnitsUsageCost": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"selectedAutoPilotTier": [Function],
"showUpsellMessage": [Function], "showUpsellMessage": [Function],
"throughput": [Function], "throughput": [Function],
"throughputRangeText": [Function], "throughputRangeText": [Function],
@@ -1359,6 +1393,7 @@ exports[`SettingsComponent renders 1`] = `
AddCollectionPane { AddCollectionPane {
"_isSynapseLinkEnabled": [Function], "_isSynapseLinkEnabled": [Function],
"autoPilotThroughput": [Function], "autoPilotThroughput": [Function],
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -1380,6 +1415,7 @@ exports[`SettingsComponent renders 1`] = `
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"formWarnings": [Function], "formWarnings": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "addcollectionpane", "id": "addcollectionpane",
"isAnalyticalStorageOn": [Function], "isAnalyticalStorageOn": [Function],
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
@@ -1409,7 +1445,12 @@ exports[`SettingsComponent renders 1`] = `
"partitionKeyVisible": [Function], "partitionKeyVisible": [Function],
"requestUnitsUsageCost": [Function], "requestUnitsUsageCost": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"rupm": [Function],
"rupmVisible": [Function],
"selectedAutoPilotTier": [Function],
"selectedSharedAutoPilotTier": [Function],
"sharedAutoPilotThroughput": [Function], "sharedAutoPilotThroughput": [Function],
"sharedAutoPilotTiersList": [Function],
"sharedThroughputRangeText": [Function], "sharedThroughputRangeText": [Function],
"shouldCreateMongoWildcardIndex": [Function], "shouldCreateMongoWildcardIndex": [Function],
"shouldUseDatabaseThroughput": [Function], "shouldUseDatabaseThroughput": [Function],
@@ -1620,6 +1661,7 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function], "visible": [Function],
}, },
CassandraAddCollectionPane { CassandraAddCollectionPane {
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -1631,6 +1673,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function], "firstFieldHasFocus": [Function],
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "cassandraaddcollectionpane", "id": "cassandraaddcollectionpane",
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
"isExecuting": [Function], "isExecuting": [Function],
@@ -1651,7 +1694,10 @@ exports[`SettingsComponent renders 1`] = `
"requestUnitsUsageCostShared": [Function], "requestUnitsUsageCostShared": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"selectedAutoPilotThroughput": [Function], "selectedAutoPilotThroughput": [Function],
"selectedAutoPilotTier": [Function],
"selectedSharedAutoPilotTier": [Function],
"sharedAutoPilotThroughput": [Function], "sharedAutoPilotThroughput": [Function],
"sharedAutoPilotTiersList": [Function],
"sharedThroughputRangeText": [Function], "sharedThroughputRangeText": [Function],
"sharedThroughputSpendAck": [Function], "sharedThroughputSpendAck": [Function],
"sharedThroughputSpendAckText": [Function], "sharedThroughputSpendAckText": [Function],
@@ -1846,6 +1892,7 @@ exports[`SettingsComponent renders 1`] = `
"addCollectionPane": AddCollectionPane { "addCollectionPane": AddCollectionPane {
"_isSynapseLinkEnabled": [Function], "_isSynapseLinkEnabled": [Function],
"autoPilotThroughput": [Function], "autoPilotThroughput": [Function],
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -1867,6 +1914,7 @@ exports[`SettingsComponent renders 1`] = `
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"formWarnings": [Function], "formWarnings": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "addcollectionpane", "id": "addcollectionpane",
"isAnalyticalStorageOn": [Function], "isAnalyticalStorageOn": [Function],
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
@@ -1896,7 +1944,12 @@ exports[`SettingsComponent renders 1`] = `
"partitionKeyVisible": [Function], "partitionKeyVisible": [Function],
"requestUnitsUsageCost": [Function], "requestUnitsUsageCost": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"rupm": [Function],
"rupmVisible": [Function],
"selectedAutoPilotTier": [Function],
"selectedSharedAutoPilotTier": [Function],
"sharedAutoPilotThroughput": [Function], "sharedAutoPilotThroughput": [Function],
"sharedAutoPilotTiersList": [Function],
"sharedThroughputRangeText": [Function], "sharedThroughputRangeText": [Function],
"shouldCreateMongoWildcardIndex": [Function], "shouldCreateMongoWildcardIndex": [Function],
"shouldUseDatabaseThroughput": [Function], "shouldUseDatabaseThroughput": [Function],
@@ -1926,6 +1979,7 @@ exports[`SettingsComponent renders 1`] = `
}, },
"addCollectionText": [Function], "addCollectionText": [Function],
"addDatabasePane": AddDatabasePane { "addDatabasePane": AddDatabasePane {
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -1941,6 +1995,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function], "firstFieldHasFocus": [Function],
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "adddatabasepane", "id": "adddatabasepane",
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
"isExecuting": [Function], "isExecuting": [Function],
@@ -1953,6 +2008,7 @@ exports[`SettingsComponent renders 1`] = `
"onMoreDetailsKeyPress": [Function], "onMoreDetailsKeyPress": [Function],
"requestUnitsUsageCost": [Function], "requestUnitsUsageCost": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"selectedAutoPilotTier": [Function],
"showUpsellMessage": [Function], "showUpsellMessage": [Function],
"throughput": [Function], "throughput": [Function],
"throughputRangeText": [Function], "throughputRangeText": [Function],
@@ -2029,6 +2085,7 @@ exports[`SettingsComponent renders 1`] = `
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
"canSaveQueries": [Function], "canSaveQueries": [Function],
"cassandraAddCollectionPane": CassandraAddCollectionPane { "cassandraAddCollectionPane": CassandraAddCollectionPane {
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -2040,6 +2097,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function], "firstFieldHasFocus": [Function],
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "cassandraaddcollectionpane", "id": "cassandraaddcollectionpane",
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
"isExecuting": [Function], "isExecuting": [Function],
@@ -2060,7 +2118,10 @@ exports[`SettingsComponent renders 1`] = `
"requestUnitsUsageCostShared": [Function], "requestUnitsUsageCostShared": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"selectedAutoPilotThroughput": [Function], "selectedAutoPilotThroughput": [Function],
"selectedAutoPilotTier": [Function],
"selectedSharedAutoPilotTier": [Function],
"sharedAutoPilotThroughput": [Function], "sharedAutoPilotThroughput": [Function],
"sharedAutoPilotTiersList": [Function],
"sharedThroughputRangeText": [Function], "sharedThroughputRangeText": [Function],
"sharedThroughputSpendAck": [Function], "sharedThroughputSpendAck": [Function],
"sharedThroughputSpendAckText": [Function], "sharedThroughputSpendAckText": [Function],
@@ -2215,6 +2276,7 @@ exports[`SettingsComponent renders 1`] = `
"title": [Function], "title": [Function],
"visible": [Function], "visible": [Function],
}, },
"hasAutoPilotV2FeatureFlag": [Function],
"hasStorageAnalyticsAfecFeature": [Function], "hasStorageAnalyticsAfecFeature": [Function],
"hasWriteAccess": [Function], "hasWriteAccess": [Function],
"isAccountReady": [Function], "isAccountReady": [Function],
@@ -2605,6 +2667,7 @@ exports[`SettingsComponent renders 1`] = `
"_openShareDialog": [Function], "_openShareDialog": [Function],
"_panes": Array [ "_panes": Array [
AddDatabasePane { AddDatabasePane {
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -2620,6 +2683,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function], "firstFieldHasFocus": [Function],
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "adddatabasepane", "id": "adddatabasepane",
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
"isExecuting": [Function], "isExecuting": [Function],
@@ -2632,6 +2696,7 @@ exports[`SettingsComponent renders 1`] = `
"onMoreDetailsKeyPress": [Function], "onMoreDetailsKeyPress": [Function],
"requestUnitsUsageCost": [Function], "requestUnitsUsageCost": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"selectedAutoPilotTier": [Function],
"showUpsellMessage": [Function], "showUpsellMessage": [Function],
"throughput": [Function], "throughput": [Function],
"throughputRangeText": [Function], "throughputRangeText": [Function],
@@ -2648,6 +2713,7 @@ exports[`SettingsComponent renders 1`] = `
AddCollectionPane { AddCollectionPane {
"_isSynapseLinkEnabled": [Function], "_isSynapseLinkEnabled": [Function],
"autoPilotThroughput": [Function], "autoPilotThroughput": [Function],
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -2669,6 +2735,7 @@ exports[`SettingsComponent renders 1`] = `
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"formWarnings": [Function], "formWarnings": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "addcollectionpane", "id": "addcollectionpane",
"isAnalyticalStorageOn": [Function], "isAnalyticalStorageOn": [Function],
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
@@ -2698,7 +2765,12 @@ exports[`SettingsComponent renders 1`] = `
"partitionKeyVisible": [Function], "partitionKeyVisible": [Function],
"requestUnitsUsageCost": [Function], "requestUnitsUsageCost": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"rupm": [Function],
"rupmVisible": [Function],
"selectedAutoPilotTier": [Function],
"selectedSharedAutoPilotTier": [Function],
"sharedAutoPilotThroughput": [Function], "sharedAutoPilotThroughput": [Function],
"sharedAutoPilotTiersList": [Function],
"sharedThroughputRangeText": [Function], "sharedThroughputRangeText": [Function],
"shouldCreateMongoWildcardIndex": [Function], "shouldCreateMongoWildcardIndex": [Function],
"shouldUseDatabaseThroughput": [Function], "shouldUseDatabaseThroughput": [Function],
@@ -2909,6 +2981,7 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function], "visible": [Function],
}, },
CassandraAddCollectionPane { CassandraAddCollectionPane {
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -2920,6 +2993,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function], "firstFieldHasFocus": [Function],
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "cassandraaddcollectionpane", "id": "cassandraaddcollectionpane",
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
"isExecuting": [Function], "isExecuting": [Function],
@@ -2940,7 +3014,10 @@ exports[`SettingsComponent renders 1`] = `
"requestUnitsUsageCostShared": [Function], "requestUnitsUsageCostShared": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"selectedAutoPilotThroughput": [Function], "selectedAutoPilotThroughput": [Function],
"selectedAutoPilotTier": [Function],
"selectedSharedAutoPilotTier": [Function],
"sharedAutoPilotThroughput": [Function], "sharedAutoPilotThroughput": [Function],
"sharedAutoPilotTiersList": [Function],
"sharedThroughputRangeText": [Function], "sharedThroughputRangeText": [Function],
"sharedThroughputSpendAck": [Function], "sharedThroughputSpendAck": [Function],
"sharedThroughputSpendAckText": [Function], "sharedThroughputSpendAckText": [Function],
@@ -3135,6 +3212,7 @@ exports[`SettingsComponent renders 1`] = `
"addCollectionPane": AddCollectionPane { "addCollectionPane": AddCollectionPane {
"_isSynapseLinkEnabled": [Function], "_isSynapseLinkEnabled": [Function],
"autoPilotThroughput": [Function], "autoPilotThroughput": [Function],
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -3156,6 +3234,7 @@ exports[`SettingsComponent renders 1`] = `
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"formWarnings": [Function], "formWarnings": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "addcollectionpane", "id": "addcollectionpane",
"isAnalyticalStorageOn": [Function], "isAnalyticalStorageOn": [Function],
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
@@ -3185,7 +3264,12 @@ exports[`SettingsComponent renders 1`] = `
"partitionKeyVisible": [Function], "partitionKeyVisible": [Function],
"requestUnitsUsageCost": [Function], "requestUnitsUsageCost": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"rupm": [Function],
"rupmVisible": [Function],
"selectedAutoPilotTier": [Function],
"selectedSharedAutoPilotTier": [Function],
"sharedAutoPilotThroughput": [Function], "sharedAutoPilotThroughput": [Function],
"sharedAutoPilotTiersList": [Function],
"sharedThroughputRangeText": [Function], "sharedThroughputRangeText": [Function],
"shouldCreateMongoWildcardIndex": [Function], "shouldCreateMongoWildcardIndex": [Function],
"shouldUseDatabaseThroughput": [Function], "shouldUseDatabaseThroughput": [Function],
@@ -3215,6 +3299,7 @@ exports[`SettingsComponent renders 1`] = `
}, },
"addCollectionText": [Function], "addCollectionText": [Function],
"addDatabasePane": AddDatabasePane { "addDatabasePane": AddDatabasePane {
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -3230,6 +3315,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function], "firstFieldHasFocus": [Function],
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "adddatabasepane", "id": "adddatabasepane",
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
"isExecuting": [Function], "isExecuting": [Function],
@@ -3242,6 +3328,7 @@ exports[`SettingsComponent renders 1`] = `
"onMoreDetailsKeyPress": [Function], "onMoreDetailsKeyPress": [Function],
"requestUnitsUsageCost": [Function], "requestUnitsUsageCost": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"selectedAutoPilotTier": [Function],
"showUpsellMessage": [Function], "showUpsellMessage": [Function],
"throughput": [Function], "throughput": [Function],
"throughputRangeText": [Function], "throughputRangeText": [Function],
@@ -3318,6 +3405,7 @@ exports[`SettingsComponent renders 1`] = `
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
"canSaveQueries": [Function], "canSaveQueries": [Function],
"cassandraAddCollectionPane": CassandraAddCollectionPane { "cassandraAddCollectionPane": CassandraAddCollectionPane {
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -3329,6 +3417,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function], "firstFieldHasFocus": [Function],
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "cassandraaddcollectionpane", "id": "cassandraaddcollectionpane",
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
"isExecuting": [Function], "isExecuting": [Function],
@@ -3349,7 +3438,10 @@ exports[`SettingsComponent renders 1`] = `
"requestUnitsUsageCostShared": [Function], "requestUnitsUsageCostShared": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"selectedAutoPilotThroughput": [Function], "selectedAutoPilotThroughput": [Function],
"selectedAutoPilotTier": [Function],
"selectedSharedAutoPilotTier": [Function],
"sharedAutoPilotThroughput": [Function], "sharedAutoPilotThroughput": [Function],
"sharedAutoPilotTiersList": [Function],
"sharedThroughputRangeText": [Function], "sharedThroughputRangeText": [Function],
"sharedThroughputSpendAck": [Function], "sharedThroughputSpendAck": [Function],
"sharedThroughputSpendAckText": [Function], "sharedThroughputSpendAckText": [Function],
@@ -3504,6 +3596,7 @@ exports[`SettingsComponent renders 1`] = `
"title": [Function], "title": [Function],
"visible": [Function], "visible": [Function],
}, },
"hasAutoPilotV2FeatureFlag": [Function],
"hasStorageAnalyticsAfecFeature": [Function], "hasStorageAnalyticsAfecFeature": [Function],
"hasWriteAccess": [Function], "hasWriteAccess": [Function],
"isAccountReady": [Function], "isAccountReady": [Function],
@@ -3881,6 +3974,7 @@ exports[`SettingsComponent renders 1`] = `
"_openShareDialog": [Function], "_openShareDialog": [Function],
"_panes": Array [ "_panes": Array [
AddDatabasePane { AddDatabasePane {
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -3896,6 +3990,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function], "firstFieldHasFocus": [Function],
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "adddatabasepane", "id": "adddatabasepane",
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
"isExecuting": [Function], "isExecuting": [Function],
@@ -3908,6 +4003,7 @@ exports[`SettingsComponent renders 1`] = `
"onMoreDetailsKeyPress": [Function], "onMoreDetailsKeyPress": [Function],
"requestUnitsUsageCost": [Function], "requestUnitsUsageCost": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"selectedAutoPilotTier": [Function],
"showUpsellMessage": [Function], "showUpsellMessage": [Function],
"throughput": [Function], "throughput": [Function],
"throughputRangeText": [Function], "throughputRangeText": [Function],
@@ -3924,6 +4020,7 @@ exports[`SettingsComponent renders 1`] = `
AddCollectionPane { AddCollectionPane {
"_isSynapseLinkEnabled": [Function], "_isSynapseLinkEnabled": [Function],
"autoPilotThroughput": [Function], "autoPilotThroughput": [Function],
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -3945,6 +4042,7 @@ exports[`SettingsComponent renders 1`] = `
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"formWarnings": [Function], "formWarnings": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "addcollectionpane", "id": "addcollectionpane",
"isAnalyticalStorageOn": [Function], "isAnalyticalStorageOn": [Function],
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
@@ -3974,7 +4072,12 @@ exports[`SettingsComponent renders 1`] = `
"partitionKeyVisible": [Function], "partitionKeyVisible": [Function],
"requestUnitsUsageCost": [Function], "requestUnitsUsageCost": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"rupm": [Function],
"rupmVisible": [Function],
"selectedAutoPilotTier": [Function],
"selectedSharedAutoPilotTier": [Function],
"sharedAutoPilotThroughput": [Function], "sharedAutoPilotThroughput": [Function],
"sharedAutoPilotTiersList": [Function],
"sharedThroughputRangeText": [Function], "sharedThroughputRangeText": [Function],
"shouldCreateMongoWildcardIndex": [Function], "shouldCreateMongoWildcardIndex": [Function],
"shouldUseDatabaseThroughput": [Function], "shouldUseDatabaseThroughput": [Function],
@@ -4185,6 +4288,7 @@ exports[`SettingsComponent renders 1`] = `
"visible": [Function], "visible": [Function],
}, },
CassandraAddCollectionPane { CassandraAddCollectionPane {
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -4196,6 +4300,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function], "firstFieldHasFocus": [Function],
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "cassandraaddcollectionpane", "id": "cassandraaddcollectionpane",
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
"isExecuting": [Function], "isExecuting": [Function],
@@ -4216,7 +4321,10 @@ exports[`SettingsComponent renders 1`] = `
"requestUnitsUsageCostShared": [Function], "requestUnitsUsageCostShared": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"selectedAutoPilotThroughput": [Function], "selectedAutoPilotThroughput": [Function],
"selectedAutoPilotTier": [Function],
"selectedSharedAutoPilotTier": [Function],
"sharedAutoPilotThroughput": [Function], "sharedAutoPilotThroughput": [Function],
"sharedAutoPilotTiersList": [Function],
"sharedThroughputRangeText": [Function], "sharedThroughputRangeText": [Function],
"sharedThroughputSpendAck": [Function], "sharedThroughputSpendAck": [Function],
"sharedThroughputSpendAckText": [Function], "sharedThroughputSpendAckText": [Function],
@@ -4411,6 +4519,7 @@ exports[`SettingsComponent renders 1`] = `
"addCollectionPane": AddCollectionPane { "addCollectionPane": AddCollectionPane {
"_isSynapseLinkEnabled": [Function], "_isSynapseLinkEnabled": [Function],
"autoPilotThroughput": [Function], "autoPilotThroughput": [Function],
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -4432,6 +4541,7 @@ exports[`SettingsComponent renders 1`] = `
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"formWarnings": [Function], "formWarnings": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "addcollectionpane", "id": "addcollectionpane",
"isAnalyticalStorageOn": [Function], "isAnalyticalStorageOn": [Function],
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
@@ -4461,7 +4571,12 @@ exports[`SettingsComponent renders 1`] = `
"partitionKeyVisible": [Function], "partitionKeyVisible": [Function],
"requestUnitsUsageCost": [Function], "requestUnitsUsageCost": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"rupm": [Function],
"rupmVisible": [Function],
"selectedAutoPilotTier": [Function],
"selectedSharedAutoPilotTier": [Function],
"sharedAutoPilotThroughput": [Function], "sharedAutoPilotThroughput": [Function],
"sharedAutoPilotTiersList": [Function],
"sharedThroughputRangeText": [Function], "sharedThroughputRangeText": [Function],
"shouldCreateMongoWildcardIndex": [Function], "shouldCreateMongoWildcardIndex": [Function],
"shouldUseDatabaseThroughput": [Function], "shouldUseDatabaseThroughput": [Function],
@@ -4491,6 +4606,7 @@ exports[`SettingsComponent renders 1`] = `
}, },
"addCollectionText": [Function], "addCollectionText": [Function],
"addDatabasePane": AddDatabasePane { "addDatabasePane": AddDatabasePane {
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -4506,6 +4622,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function], "firstFieldHasFocus": [Function],
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "adddatabasepane", "id": "adddatabasepane",
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
"isExecuting": [Function], "isExecuting": [Function],
@@ -4518,6 +4635,7 @@ exports[`SettingsComponent renders 1`] = `
"onMoreDetailsKeyPress": [Function], "onMoreDetailsKeyPress": [Function],
"requestUnitsUsageCost": [Function], "requestUnitsUsageCost": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"selectedAutoPilotTier": [Function],
"showUpsellMessage": [Function], "showUpsellMessage": [Function],
"throughput": [Function], "throughput": [Function],
"throughputRangeText": [Function], "throughputRangeText": [Function],
@@ -4594,6 +4712,7 @@ exports[`SettingsComponent renders 1`] = `
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
"canSaveQueries": [Function], "canSaveQueries": [Function],
"cassandraAddCollectionPane": CassandraAddCollectionPane { "cassandraAddCollectionPane": CassandraAddCollectionPane {
"autoPilotTiersList": [Function],
"autoPilotUsageCost": [Function], "autoPilotUsageCost": [Function],
"canConfigureThroughput": [Function], "canConfigureThroughput": [Function],
"canExceedMaximumValue": [Function], "canExceedMaximumValue": [Function],
@@ -4605,6 +4724,7 @@ exports[`SettingsComponent renders 1`] = `
"firstFieldHasFocus": [Function], "firstFieldHasFocus": [Function],
"formErrors": [Function], "formErrors": [Function],
"formErrorsDetails": [Function], "formErrorsDetails": [Function],
"hasAutoPilotV2FeatureFlag": [Function],
"id": "cassandraaddcollectionpane", "id": "cassandraaddcollectionpane",
"isAutoPilotSelected": [Function], "isAutoPilotSelected": [Function],
"isExecuting": [Function], "isExecuting": [Function],
@@ -4625,7 +4745,10 @@ exports[`SettingsComponent renders 1`] = `
"requestUnitsUsageCostShared": [Function], "requestUnitsUsageCostShared": [Function],
"ruToolTipText": [Function], "ruToolTipText": [Function],
"selectedAutoPilotThroughput": [Function], "selectedAutoPilotThroughput": [Function],
"selectedAutoPilotTier": [Function],
"selectedSharedAutoPilotTier": [Function],
"sharedAutoPilotThroughput": [Function], "sharedAutoPilotThroughput": [Function],
"sharedAutoPilotTiersList": [Function],
"sharedThroughputRangeText": [Function], "sharedThroughputRangeText": [Function],
"sharedThroughputSpendAck": [Function], "sharedThroughputSpendAck": [Function],
"sharedThroughputSpendAckText": [Function], "sharedThroughputSpendAckText": [Function],
@@ -4780,6 +4903,7 @@ exports[`SettingsComponent renders 1`] = `
"title": [Function], "title": [Function],
"visible": [Function], "visible": [Function],
}, },
"hasAutoPilotV2FeatureFlag": [Function],
"hasStorageAnalyticsAfecFeature": [Function], "hasStorageAnalyticsAfecFeature": [Function],
"hasWriteAccess": [Function], "hasWriteAccess": [Function],
"isAccountReady": [Function], "isAccountReady": [Function],

View File

@@ -69,15 +69,15 @@ exports[`SettingsUtils functions render 1`] = `
<b> <b>
¥ ¥
1.02 1.29
hourly hourly
/ /
¥ ¥
24.48 31.06
daily daily
/ /
¥ ¥
744.60 944.60
monthly monthly
</b> </b>

View File

@@ -0,0 +1,222 @@
import * as ko from "knockout";
import * as ViewModels from "../../../Contracts/ViewModels";
import editable from "../../../Common/EditableUtility";
import { ThroughputInputComponent, ThroughputInputParams, ThroughputInputViewModel } from "./ThroughputInputComponent";
const $ = (selector: string) => document.querySelector(selector) as HTMLElement;
describe.skip("Throughput Input Component", () => {
let component: any;
let vm: ThroughputInputViewModel;
const testId: string = "ThroughputValue";
const value: ViewModels.Editable<number> = editable.observable(500);
const minimum: ko.Observable<number> = ko.observable(400);
const maximum: ko.Observable<number> = ko.observable(2000);
function buildListOptions(
value: ViewModels.Editable<number>,
minimum: ko.Observable<number>,
maxium: ko.Observable<number>,
canExceedMaximumValue?: boolean
): ThroughputInputParams {
return {
testId,
value,
minimum,
maximum,
canExceedMaximumValue: ko.computed<boolean>(() => Boolean(canExceedMaximumValue)),
costsVisible: ko.observable(false),
isFixed: false,
label: ko.observable("Label"),
requestUnitsUsageCost: ko.observable("requestUnitsUsageCost"),
showAsMandatory: false,
autoPilotTiersList: null,
autoPilotUsageCost: null,
isAutoPilotSelected: null,
selectedAutoPilotTier: null,
throughputAutoPilotRadioId: null,
throughputProvisionedRadioId: null,
throughputModeRadioName: null
};
}
function simulateKeyPressSpace(target: HTMLElement): Promise<boolean> {
const event = new KeyboardEvent("keydown", {
key: "space"
});
const result = target.dispatchEvent(event);
return new Promise(resolve => {
setTimeout(() => {
resolve(result);
}, 1000);
});
}
beforeEach(() => {
component = ThroughputInputComponent;
document.body.innerHTML = component.template as any;
});
afterEach(async () => {
await ko.cleanNode(document);
});
describe("Rendering", () => {
it("should display value text", async () => {
vm = new component.viewModel(buildListOptions(value, minimum, maximum));
await ko.applyBindings(vm);
expect(($("input") as HTMLInputElement).value).toContain(value().toString());
});
});
describe("Behavior", () => {
it("should decrease value", async () => {
vm = new component.viewModel(buildListOptions(value, minimum, maximum));
await ko.applyBindings(vm);
value(450);
$(".testhook-decreaseThroughput").click();
expect(value()).toBe(400);
$(".testhook-decreaseThroughput").click();
expect(value()).toBe(400);
});
it("should increase value", async () => {
vm = new component.viewModel(buildListOptions(value, minimum, maximum));
await ko.applyBindings(vm);
value(1950);
$(".test-increaseThroughput").click();
expect(value()).toBe(2000);
$(".test-increaseThroughput").click();
expect(value()).toBe(2000);
});
it("should respect lower bound limits", async () => {
vm = new component.viewModel(buildListOptions(value, minimum, maximum));
await ko.applyBindings(vm);
value(minimum());
$(".testhook-decreaseThroughput").click();
expect(value()).toBe(minimum());
});
it("should respect upper bound limits", async () => {
vm = new component.viewModel(buildListOptions(value, minimum, maximum));
await ko.applyBindings(vm);
value(maximum());
$(".test-increaseThroughput").click();
expect(value()).toBe(maximum());
});
it("should allow throughput to exceed upper bound limit when canExceedMaximumValue is set", async () => {
vm = new component.viewModel(buildListOptions(value, minimum, maximum, true));
await ko.applyBindings(vm);
value(maximum());
$(".test-increaseThroughput").click();
expect(value()).toBe(maximum() + 100);
});
});
describe("Accessibility", () => {
it.skip("should decrease value with keypress", async () => {
vm = new component.viewModel(buildListOptions(value, minimum, maximum));
await ko.applyBindings(vm);
const target = $(".testhook-decreaseThroughput");
value(500);
expect(value()).toBe(500);
const result = await simulateKeyPressSpace(target);
expect(value()).toBe(400);
});
it.skip("should increase value with keypress", async () => {
vm = new component.viewModel(buildListOptions(value, minimum, maximum));
await ko.applyBindings(vm);
const target = $(".test-increaseThroughput");
value(400);
expect(value()).toBe(400);
const result = await simulateKeyPressSpace(target);
// expect(value()).toBe(500);
});
it("should set the decreaseButtonAriaLabel using the default step value", async () => {
vm = new component.viewModel(buildListOptions(value, minimum, maximum, true));
await ko.applyBindings(vm);
expect(vm.decreaseButtonAriaLabel).toBe("Decrease throughput by 100");
});
it("should set the increaseButtonAriaLabel using the default step value", async () => {
vm = new component.viewModel(buildListOptions(value, minimum, maximum, true));
await ko.applyBindings(vm);
expect(vm.increaseButtonAriaLabel).toBe("Increase throughput by 100");
});
it("should set the increaseButtonAriaLabel using the params step value", async () => {
const options = buildListOptions(value, minimum, maximum, true);
options.step = 10;
vm = new component.viewModel(options);
await ko.applyBindings(vm);
expect(vm.increaseButtonAriaLabel).toBe("Increase throughput by 10");
});
it("should set the decreaseButtonAriaLabel using the params step value", async () => {
const options = buildListOptions(value, minimum, maximum, true);
options.step = 10;
vm = new component.viewModel(options);
await ko.applyBindings(vm);
expect(vm.decreaseButtonAriaLabel).toBe("Decrease throughput by 10");
});
it("should set the decreaseButtonAriaLabel using the params step value", async () => {
const options = buildListOptions(value, minimum, maximum, true);
options.step = 10;
vm = new component.viewModel(options);
await ko.applyBindings(vm);
});
it("should have aria-label attribute on increase button", async () => {
vm = new component.viewModel(buildListOptions(value, minimum, maximum, true));
await ko.applyBindings(vm);
const ariaLabel = $(".test-increaseThroughput").attributes.getNamedItem("aria-label").value;
expect(ariaLabel).toBe("Increase throughput by 100");
});
it("should have aria-label attribute on increase button", async () => {
vm = new component.viewModel(buildListOptions(value, minimum, maximum, true));
await ko.applyBindings(vm);
const ariaLabel = $(".testhook-decreaseThroughput").attributes.getNamedItem("aria-label").value;
expect(ariaLabel).toBe("Decrease throughput by 100");
});
it("should have role on increase button", async () => {
vm = new component.viewModel(buildListOptions(value, minimum, maximum, true));
await ko.applyBindings(vm);
const role = $(".test-increaseThroughput").attributes.getNamedItem("role").value;
expect(role).toBe("button");
});
it("should have role on decrease button", async () => {
vm = new component.viewModel(buildListOptions(value, minimum, maximum, true));
await ko.applyBindings(vm);
const role = $(".testhook-decreaseThroughput").attributes.getNamedItem("role").value;
expect(role).toBe("button");
});
it("should have tabindex 0 on increase button", async () => {
vm = new component.viewModel(buildListOptions(value, minimum, maximum, true));
await ko.applyBindings(vm);
const role = $(".testhook-decreaseThroughput").attributes.getNamedItem("tabindex").value;
expect(role).toBe("0");
});
it("should have tabindex 0 on decrease button", async () => {
vm = new component.viewModel(buildListOptions(value, minimum, maximum, true));
await ko.applyBindings(vm);
const role = $(".testhook-decreaseThroughput").attributes.getNamedItem("tabindex").value;
expect(role).toBe("0");
});
});
});

View File

@@ -0,0 +1,145 @@
<div>
<div>
<p class="pkPadding">
<!-- ko if: showAsMandatory -->
<span class="mandatoryStar">*</span>
<!-- /ko -->
<span class="addCollectionLabel" data-bind="text: label"></span>
<!-- ko if: infoBubbleText -->
<span class="infoTooltip" role="tooltip" tabindex="0">
<img class="infoImg" src="../../../../images/info-bubble.svg" alt="More information" />
<span data-bind="text: infoBubbleText" class="tooltiptext throughputRuInfo"></span>
</span>
<!-- /ko -->
</p>
</div>
<!-- ko if: !isFixed -->
<div data-bind="visible: showAutoPilot" class="throughputModeContainer">
<input
class="throughputModeRadio"
aria-label="Autopilot mode"
data-test="throughput-autoPilot"
type="radio"
role="radio"
tabindex="0"
data-bind="
checked: isAutoPilotSelected,
checkedValue: true,
attr: {
id: throughputAutoPilotRadioId,
name: throughputModeRadioName,
'aria-checked': isAutoPilotSelected() ? 'true' : 'false'
}"
/>
<span
class="throughputModeSpace"
data-bind="
attr: {
for: throughputAutoPilotRadioId
}"
>Autopilot (preview)
</span>
<input
class="throughputModeRadio nonFirstRadio"
aria-label="Manual mode"
type="radio"
role="radio"
tabindex="0"
data-bind="
checked: isAutoPilotSelected,
checkedValue: false,
attr: {
id: throughputProvisionedRadioId,
name: throughputModeRadioName,
'aria-checked': !isAutoPilotSelected() ? 'true' : 'false'
}"
/>
<span
class="throughputModeSpace"
data-bind="
attr: {
for: throughputProvisionedRadioId
}"
>Manual
</span>
</div>
<!-- /ko -->
<div data-bind="visible: isAutoPilotSelected">
<select
name="autoPilotTiers"
class="collid select-font-size"
aria-label="Autopilot Max RU/s"
data-bind="
options: autoPilotTiersList,
optionsText: 'text',
optionsValue: 'value',
value: selectedAutoPilotTier,
optionsCaption: 'Choose Max RU/s'"
>
</select>
<p>
<span
data-bind="
html: autoPilotUsageCost,
visible: selectedAutoPilotTier"
>
</span>
</p>
</div>
<div data-bind="visible: !isAutoPilotSelected()">
<div data-bind="setTemplateReady: true">
<p class="addContainerThroughputInput">
<input
type="number"
required
data-bind="
textInput: value,
css: {
dirty: value.editableIsDirty
},
enable: isEnabled,
attr:{
'data-test': testId,
'class': cssClass,
step: step,
min: minimum,
max: canExceedMaximumValue() ? null : maximum,
'aria-label': ariaLabel
}"
/>
</p>
</div>
<p data-bind="visible: costsVisible">
<span data-bind="html: requestUnitsUsageCost"></span>
</p>
<!-- ko if: spendAckVisible -->
<p class="pkPadding">
<input
type="checkbox"
aria-label="acknowledge spend throughput"
data-bind="
attr: {
title: spendAckText,
id: spendAckId
},
checked: spendAckChecked"
/>
<span data-bind="text: spendAckText, attr: { for: spendAckId }"></span>
</p>
<!-- /ko -->
<!-- ko if: isFixed -->
<p>
Choose unlimited storage capacity for more than 10,000 RU/s.
</p>
<!-- /ko -->
</div>
</div>

View File

@@ -0,0 +1,261 @@
import * as DataModels from "../../../Contracts/DataModels";
import * as ko from "knockout";
import * as ViewModels from "../../../Contracts/ViewModels";
import { KeyCodes } from "../../../Common/Constants";
import { WaitsForTemplateViewModel } from "../../WaitsForTemplateViewModel";
import ThroughputInputComponentTemplate from "./ThroughputInputComponent.html";
/**
* Throughput Input:
*
* Creates a set of controls to input, sanitize and increase/decrease throughput
*
* How to use in your markup:
* <throughput-input params="{ value: anObservableToHoldTheValue, minimum: anObservableWithMinimum, maximum: anObservableWithMaximum }">
* </throughput-input>
*
*/
/**
* Parameters for this component
*/
export interface ThroughputInputParams {
/**
* Callback triggered when the template is bound to the component (for testing purposes)
*/
onTemplateReady?: () => void;
/**
* Observable to bind the Throughput value to
*/
value: ViewModels.Editable<number>;
/**
* Text to use as id for testing
*/
testId: string;
/**
* Text to use as aria-label
*/
ariaLabel?: ko.Observable<string>;
/**
* Minimum value in the range
*/
minimum: ko.Observable<number>;
/**
* Maximum value in the range
*/
maximum: ko.Observable<number>;
/**
* Step value for increase/decrease
*/
step?: number;
/**
* Observable to bind the Throughput enabled status
*/
isEnabled?: ko.Observable<boolean>;
/**
* Should show pricing controls
*/
costsVisible: ko.Observable<boolean>;
/**
* RU price
*/
requestUnitsUsageCost: ko.Subscribable<string>; // Our code assigns to ko.Computed, but unit test assigns to ko.Observable
/**
* State of the spending acknowledge checkbox
*/
spendAckChecked?: ko.Observable<boolean>;
/**
* id of the spending acknowledge checkbox
*/
spendAckId?: ko.Observable<string>;
/**
* spending acknowledge text
*/
spendAckText?: ko.Observable<string>;
/**
* Show spending acknowledge controls
*/
spendAckVisible?: ko.Observable<boolean>;
/**
* Display * to the left of the label
*/
showAsMandatory: boolean;
/**
* If true, it will display a text to prompt users to use unlimited collections to go beyond max for fixed
*/
isFixed: boolean;
/**
* Label of the provisioned throughut control
*/
label: ko.Observable<string>;
/**
* Text of the info bubble for provisioned throughut control
*/
infoBubbleText?: ko.Observable<string>;
/**
* Computed value that decides if value can exceed maximum allowable value
*/
canExceedMaximumValue?: ko.Computed<boolean>;
/**
* CSS classes to apply on input element
*/
cssClass?: string;
isAutoPilotSelected: ko.Observable<boolean>;
throughputAutoPilotRadioId: string;
throughputProvisionedRadioId: string;
throughputModeRadioName: string;
autoPilotTiersList: ko.ObservableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>;
selectedAutoPilotTier: ko.Observable<DataModels.AutopilotTier>;
autoPilotUsageCost: ko.Computed<string>;
showAutoPilot?: ko.Observable<boolean>;
}
export class ThroughputInputViewModel extends WaitsForTemplateViewModel {
public ariaLabel: ko.Observable<string>;
public canExceedMaximumValue: ko.Computed<boolean>;
public step: number;
public testId: string;
public value: ViewModels.Editable<number>;
public minimum: ko.Observable<number>;
public maximum: ko.Observable<number>;
public isEnabled: ko.Observable<boolean>;
public cssClass: string;
public decreaseButtonAriaLabel: string;
public increaseButtonAriaLabel: string;
public costsVisible: ko.Observable<boolean>;
public requestUnitsUsageCost: ko.Subscribable<string>;
public spendAckChecked: ko.Observable<boolean>;
public spendAckId: ko.Observable<string>;
public spendAckText: ko.Observable<string>;
public spendAckVisible: ko.Observable<boolean>;
public showAsMandatory: boolean;
public infoBubbleText: string | ko.Observable<string>;
public label: ko.Observable<string>;
public isFixed: boolean;
public showAutoPilot: ko.Observable<boolean>;
public isAutoPilotSelected: ko.Observable<boolean>;
public throughputAutoPilotRadioId: string;
public throughputProvisionedRadioId: string;
public throughputModeRadioName: string;
public autoPilotTiersList: ko.ObservableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>;
public selectedAutoPilotTier: ko.Observable<DataModels.AutopilotTier>;
public autoPilotUsageCost: ko.Computed<string>;
public constructor(options: ThroughputInputParams) {
super();
super.onTemplateReady((isTemplateReady: boolean) => {
if (isTemplateReady && options.onTemplateReady) {
options.onTemplateReady();
}
});
const params: ThroughputInputParams = options;
this.testId = params.testId || "ThroughputValue";
this.ariaLabel = ko.observable((params.ariaLabel && params.ariaLabel()) || "");
this.canExceedMaximumValue = params.canExceedMaximumValue || ko.computed(() => false);
this.step = params.step || ThroughputInputViewModel._defaultStep;
this.isEnabled = params.isEnabled || ko.observable(true);
this.cssClass = params.cssClass || "textfontclr collid";
this.minimum = params.minimum;
this.maximum = params.maximum;
this.value = params.value;
this.decreaseButtonAriaLabel = "Decrease throughput by " + this.step.toString();
this.increaseButtonAriaLabel = "Increase throughput by " + this.step.toString();
this.costsVisible = options.costsVisible;
this.requestUnitsUsageCost = options.requestUnitsUsageCost;
this.spendAckChecked = options.spendAckChecked || ko.observable<boolean>(false);
this.spendAckId = options.spendAckId || ko.observable<string>();
this.spendAckText = options.spendAckText || ko.observable<string>();
this.spendAckVisible = options.spendAckVisible || ko.observable<boolean>(false);
this.showAsMandatory = !!options.showAsMandatory;
this.isFixed = !!options.isFixed;
this.infoBubbleText = options.infoBubbleText || ko.observable<string>();
this.label = options.label || ko.observable<string>();
this.showAutoPilot = options.showAutoPilot !== undefined ? options.showAutoPilot : ko.observable<boolean>(true);
this.isAutoPilotSelected = options.isAutoPilotSelected || ko.observable<boolean>(false);
this.throughputAutoPilotRadioId = options.throughputAutoPilotRadioId;
this.throughputProvisionedRadioId = options.throughputProvisionedRadioId;
this.throughputModeRadioName = options.throughputModeRadioName;
this.autoPilotTiersList = options.autoPilotTiersList;
this.selectedAutoPilotTier = options.selectedAutoPilotTier;
this.autoPilotUsageCost = options.autoPilotUsageCost;
}
public decreaseThroughput() {
let offerThroughput: number = this._getSanitizedValue();
if (offerThroughput > this.minimum()) {
offerThroughput -= this.step;
if (offerThroughput < this.minimum()) {
offerThroughput = this.minimum();
}
this.value(offerThroughput);
}
}
public increaseThroughput() {
let offerThroughput: number = this._getSanitizedValue();
if (offerThroughput < this.maximum() || this.canExceedMaximumValue()) {
offerThroughput += this.step;
if (offerThroughput > this.maximum() && !this.canExceedMaximumValue()) {
offerThroughput = this.maximum();
}
this.value(offerThroughput);
}
}
public onIncreaseKeyDown = (source: any, event: KeyboardEvent): boolean => {
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
this.increaseThroughput();
event.stopPropagation();
return false;
}
return true;
};
public onDecreaseKeyDown = (source: any, event: KeyboardEvent): boolean => {
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
this.decreaseThroughput();
event.stopPropagation();
return false;
}
return true;
};
private _getSanitizedValue(): number {
const throughput = this.value();
return isNaN(throughput) ? 0 : Number(throughput);
}
private static _defaultStep: number = 100;
}
export const ThroughputInputComponent = {
viewModel: ThroughputInputViewModel,
template: ThroughputInputComponentTemplate
};

View File

@@ -19,6 +19,7 @@ describe("ContainerSampleGenerator", () => {
explorerStub.isPreferredApiTable = ko.computed<boolean>(() => false); explorerStub.isPreferredApiTable = ko.computed<boolean>(() => false);
explorerStub.isPreferredApiCassandra = ko.computed<boolean>(() => false); explorerStub.isPreferredApiCassandra = ko.computed<boolean>(() => false);
explorerStub.canExceedMaximumValue = ko.computed<boolean>(() => false); explorerStub.canExceedMaximumValue = ko.computed<boolean>(() => false);
explorerStub.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
explorerStub.findDatabaseWithId = () => database; explorerStub.findDatabaseWithId = () => database;
explorerStub.refreshAllDatabases = () => Q.resolve(); explorerStub.refreshAllDatabases = () => Q.resolve();
return explorerStub; return explorerStub;

View File

@@ -212,6 +212,7 @@ export default class Explorer {
public isHostedDataExplorerEnabled: ko.Computed<boolean>; public isHostedDataExplorerEnabled: ko.Computed<boolean>;
public isRightPanelV2Enabled: ko.Computed<boolean>; public isRightPanelV2Enabled: ko.Computed<boolean>;
public canExceedMaximumValue: ko.Computed<boolean>; public canExceedMaximumValue: ko.Computed<boolean>;
public hasAutoPilotV2FeatureFlag: ko.Computed<boolean>;
public shouldShowShareDialogContents: ko.Observable<boolean>; public shouldShowShareDialogContents: ko.Observable<boolean>;
public shareAccessData: ko.Observable<AdHocAccessData>; public shareAccessData: ko.Observable<AdHocAccessData>;
@@ -421,6 +422,13 @@ export default class Explorer {
this.isFeatureEnabled(Constants.Features.canExceedMaximumValue) this.isFeatureEnabled(Constants.Features.canExceedMaximumValue)
); );
this.hasAutoPilotV2FeatureFlag = ko.computed(() => {
if (this.isFeatureEnabled(Constants.Features.enableAutoPilotV2)) {
return true;
}
return false;
});
this.isNotificationConsoleExpanded = ko.observable<boolean>(false); this.isNotificationConsoleExpanded = ko.observable<boolean>(false);
this.databases = ko.observableArray<ViewModels.Database>(); this.databases = ko.observableArray<ViewModels.Database>();

View File

@@ -20,7 +20,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isSparkEnabled = ko.observable(true); mockExplorer.isSparkEnabled = ko.observable(true);
mockExplorer.isSynapseLinkUpdating = ko.observable(false); mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false); mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true; mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isNotebookEnabled = ko.observable(false); mockExplorer.isNotebookEnabled = ko.observable(false);
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false); mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
@@ -62,7 +62,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isSparkEnabled = ko.observable(true); mockExplorer.isSparkEnabled = ko.observable(true);
mockExplorer.isSynapseLinkUpdating = ko.observable(false); mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false); mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true; mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false); mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
}); });
@@ -126,7 +126,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isSparkEnabled = ko.observable(true); mockExplorer.isSparkEnabled = ko.observable(true);
mockExplorer.isSynapseLinkUpdating = ko.observable(false); mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false); mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true; mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false); mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
}); });
@@ -208,7 +208,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isSynapseLinkUpdating = ko.observable(false); mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isSparkEnabled = ko.observable(true); mockExplorer.isSparkEnabled = ko.observable(true);
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false); mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true; mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false); mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
}); });
@@ -289,7 +289,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isPreferredApiTable = ko.computed(() => true); mockExplorer.isPreferredApiTable = ko.computed(() => true);
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false); mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false); mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false);
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
mockExplorer.isSynapseLinkUpdating = ko.observable(false); mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isSparkEnabled = ko.observable(true); mockExplorer.isSparkEnabled = ko.observable(true);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true; mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
@@ -348,7 +348,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isAuthWithResourceToken = ko.observable(true); mockExplorer.isAuthWithResourceToken = ko.observable(true);
mockExplorer.isPreferredApiDocumentDB = ko.computed(() => true); mockExplorer.isPreferredApiDocumentDB = ko.computed(() => true);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true; mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
mockExplorer.isResourceTokenCollectionNodeSelected = ko.computed(() => true); mockExplorer.isResourceTokenCollectionNodeSelected = ko.computed(() => true);
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false); mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
}); });

View File

@@ -127,6 +127,7 @@
be shared across all containers within the database.</span> be shared across all containers within the database.</span>
</span> </span>
</div> </div>
<!-- ko if: hasAutoPilotV2FeatureFlag && !hasAutoPilotV2FeatureFlag() -->
<div data-bind="visible: databaseCreateNewShared() && databaseCreateNew()"> <div data-bind="visible: databaseCreateNewShared() && databaseCreateNew()">
<!-- 1 --> <!-- 1 -->
<throughput-input-autopilot-v3 params="{ <throughput-input-autopilot-v3 params="{
@@ -157,6 +158,38 @@
</throughput-input-autopilot-v3> </throughput-input-autopilot-v3>
</div> </div>
<!-- /ko --> <!-- /ko -->
<!-- ko if: hasAutoPilotV2FeatureFlag && hasAutoPilotV2FeatureFlag() -->
<div data-bind="visible: databaseCreateNewShared() && databaseCreateNew()">
<!-- 1 -->
<throughput-input params="{
testId: 'databaseThroughputValue',
value: throughputDatabase,
minimum: minThroughputRU,
maximum: maxThroughputRU,
isEnabled: databaseCreateNewShared() && databaseCreateNew(),
label: sharedThroughputRangeText,
ariaLabel: sharedThroughputRangeText,
costsVisible: costsVisible,
requestUnitsUsageCost: requestUnitsUsageCost,
spendAckChecked: throughputSpendAck,
spendAckId: 'throughputSpendAck',
spendAckText: throughputSpendAckText,
spendAckVisible: throughputSpendAckVisible,
showAsMandatory: true,
infoBubbleText: ruToolTipText,
throughputAutoPilotRadioId: 'newContainer-databaseThroughput-autoPilotRadio',
throughputProvisionedRadioId: 'newContainer-databaseThroughput-manualRadio',
throughputModeRadioName: 'sharedThroughputModeRadio',
isAutoPilotSelected: isSharedAutoPilotSelected,
autoPilotUsageCost: autoPilotUsageCost,
canExceedMaximumValue: canExceedMaximumValue,
autoPilotTiersList: sharedAutoPilotTiersList,
selectedAutoPilotTier: selectedSharedAutoPilotTier
}">
</throughput-input>
</div>
<!-- /ko -->
<!-- /ko -->
<!-- Database provisioned throughput - End --> <!-- Database provisioned throughput - End -->
</div> </div>
@@ -178,6 +211,19 @@
data-bind="value: collectionId, attr: { 'aria-label': collectionIdTitle }"> data-bind="value: collectionId, attr: { 'aria-label': collectionIdTitle }">
</div> </div>
<!-- <p class="seconddivpadding" data-bind="visible:(container.isPreferredApiDocumentDB() || container.isPreferredApiGraph()) && !databaseHasSharedOffer()">
Where did 'fixed' containers go?
<span class="infoTooltip" role="tooltip" tabindex="0">
<img class="infoImg" src="/info-bubble.svg" alt="More information">
<span class="tooltiptext noFixedCollectionsTooltipWidth">
We lowered the minimum throughput for partitioned containers to 400 RU/s, removing the only drawback partitioned containers had. <br/><br/>
We are planning to deprecate ability to create non-partitioned containers, as they do not allow you to scale elastically.
If for some reason you still need a container without partition key, you can use our SDKs to create one. <br/><br/>
Please <a href="https://aka.ms/cosmosdbfeedback?subject=Cosmos%20DB%20Hosted%20Data%20Explorer%20Feedback">contact us</a> if you have questions or concerns.
</span>
</span>
</p> -->
<!-- Indexing For Shared Throughput - start --> <!-- Indexing For Shared Throughput - start -->
<div class="seconddivpadding" <div class="seconddivpadding"
data-bind="visible: showIndexingOptionsForSharedThroughput() && !container.isPreferredApiMongoDB()"> data-bind="visible: showIndexingOptionsForSharedThroughput() && !container.isPreferredApiMongoDB()">
@@ -241,8 +287,96 @@
</div> </div>
<!-- Unlimited option button - End --> <!-- Unlimited option button - End -->
</div> </div>
<!-- Fixed Button Content - Start -->
<div class="tabcontent" data-bind="visible: isFixedStorageSelected() && !databaseHasSharedOffer()">
<!-- 2 -->
<!-- note: this is used when creating a fixed collection without shared throughput. only manual throughput is available. -->
<throughput-input params="{
testId: 'fixedThroughputValue',
value: throughputSinglePartition,
minimum: minThroughputRU,
maximum: maxThroughputRU,
isEnabled: isFixedStorageSelected() && !databaseHasSharedOffer(),
label: throughputRangeText,
ariaLabel: throughputRangeText,
costsVisible: costsVisible,
requestUnitsUsageCost: requestUnitsUsageCost
showAsMandatory: true,
isFixed: true,
infoBubbleText: ruToolTipText,
canExceedMaximumValue: canExceedMaximumValue
}">
</throughput-input>
<div data-bind="visible: rupmVisible">
<div class="tabs">
<p class="pkPadding">
<span class="mandatoryStar">*</span>
<span class="addCollectionLabel">RU/m</span>
<span class="infoTooltip" role="tooltip" tabindex="0">
<img class="infoImg" src="/info-bubble.svg" alt="More information">
<span class="tooltiptext throughputRuInfo">
For each 100 Request Units per second (RU/s) provisioned, 1,000 Request Units
per
minute
(RU/m) can be provisioned. E.g.: for a container with 5,000 RU/s provisioned
with
RU/m
enabled, the RU/m budget will be 50,000 RU/m.
</span>
</span>
</p>
<div tabindex="0" data-bind="event: { keydown: onRupmOptionsKeyDown }" aria-label="RU/m">
<div class="tab">
<input type="radio" id="rupmOn" name="rupmcoll" value="on" class="radio"
data-bind="checked: rupm">
<label for="rupmOn">ON</label>
</div>
<div class="tab">
<input type="radio" id="rupmOff" name="rupmcoll" value="off" class="radio"
data-bind="checked: rupm">
<label for="rupmOff">OFF</label>
</div>
</div>
</div>
</div>
</div>
<!-- Fixed Button Content - End -->
<!-- Unlimited Button Content - Start --> <!-- Unlimited Button Content - Start -->
<div class="tabcontent" data-bind="visible: isUnlimitedStorageSelected() || databaseHasSharedOffer()"> <div class="tabcontent" data-bind="visible: isUnlimitedStorageSelected() || databaseHasSharedOffer()">
<div data-bind="visible: rupmVisible">
<div class="tabs">
<p>
<span class="mandatoryStar">*</span>
<span class="addCollectionLabel">RU/m</span>
<span class="infoTooltip" role="tooltip" tabindex="0">
<img class="infoImg" src="/info-bubble.svg" alt="More information">
<span class="tooltiptext throughputRuInfo">
For each 100 Request Units per second (RU/s) provisioned, 1,000 Request Units
per
minute
(RU/m) can be provisioned. E.g.: for a container with 5,000 RU/s provisioned
with
RU/m
enabled, the RU/m budget will be 50,000 RU/m.
</span>
</span>
</p>
<div tabindex="0" data-bind="event: { keydown: onRupmOptionsKeyDown }" aria-label="RU/m">
<div class="tab">
<input type="radio" id="rupmOn2" name="rupmcoll2" value="on" class="radio"
data-bind="checked: rupm">
<label for="rupmOn2">ON</label>
</div>
<div class="tab">
<input type="radio" id="rupmOff2" name="rupmcoll2" value="off" class="radio"
data-bind="checked: rupm">
<label for="rupmOff2">OFF</label>
</div>
</div>
</div>
</div>
<div data-bind="visible: partitionKeyVisible"> <div data-bind="visible: partitionKeyVisible">
<p> <p>
<span class="mandatoryStar">*</span> <span class="mandatoryStar">*</span>
@@ -308,6 +442,7 @@
<!-- Provision collection throughput checkbox - end --> <!-- Provision collection throughput checkbox - end -->
<!-- Provision collection throughput spinner - start --> <!-- Provision collection throughput spinner - start -->
<!-- ko if: hasAutoPilotV2FeatureFlag && !hasAutoPilotV2FeatureFlag() -->
<div data-bind="visible: displayCollectionThroughput" data-test="addCollection-displayCollectionThroughput"> <div data-bind="visible: displayCollectionThroughput" data-test="addCollection-displayCollectionThroughput">
<!-- 3 --> <!-- 3 -->
<throughput-input-autopilot-v3 params="{ <throughput-input-autopilot-v3 params="{
@@ -337,6 +472,40 @@
}"> }">
</throughput-input-autopilot-v3> </throughput-input-autopilot-v3>
</div> </div>
<!-- /ko -->
<!-- ko if: hasAutoPilotV2FeatureFlag && hasAutoPilotV2FeatureFlag() -->
<div data-bind="visible: displayCollectionThroughput" data-test="addCollection-displayCollectionThroughput">
<!-- 3 -->
<throughput-input params="{
testId: 'collectionThroughputValue',
value: throughputMultiPartition,
minimum: minThroughputRU,
maximum: maxThroughputRU,
isEnabled: displayCollectionThroughput,
label: throughputRangeText,
ariaLabel: throughputRangeText,
costsVisible: costsVisible,
requestUnitsUsageCost: dedicatedRequestUnitsUsageCost,
spendAckChecked: throughputSpendAck,
spendAckId: 'throughputSpendAckCollection',
spendAckText: throughputSpendAckText,
spendAckVisible: throughputSpendAckVisible,
showAsMandatory: true,
infoBubbleText: ruToolTipText,
throughputAutoPilotRadioId: 'newContainer-containerThroughput-autoPilotRadio',
throughputProvisionedRadioId: 'newContainer-containerThroughput-manualRadio',
throughputModeRadioName: 'throughputModeRadioName',
isAutoPilotSelected: isAutoPilotSelected,
autoPilotUsageCost: autoPilotUsageCost,
canExceedMaximumValue: canExceedMaximumValue,
autoPilotTiersList: autoPilotTiersList,
selectedAutoPilotTier: selectedAutoPilotTier,
showAutoPilot: !isFixedStorageSelected()
}">
</throughput-input>
</div>
<!-- /ko -->
<!-- Provision collection throughput spinner - end --> <!-- Provision collection throughput spinner - end -->
<!-- /ko --> <!-- /ko -->
<!-- Provision collection throughput - end --> <!-- Provision collection throughput - end -->

View File

@@ -1,7 +1,8 @@
import * as Constants from "../../Common/Constants"; import * as Constants from "../../Common/Constants";
import AddCollectionPane from "./AddCollectionPane"; import AddCollectionPane from "./AddCollectionPane";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import { DatabaseAccount } from "../../Contracts/DataModels"; import ko from "knockout";
import { AutopilotTier, DatabaseAccount } from "../../Contracts/DataModels";
describe("Add Collection Pane", () => { describe("Add Collection Pane", () => {
describe("isValid()", () => { describe("isValid()", () => {
@@ -40,6 +41,25 @@ describe("Add Collection Pane", () => {
beforeEach(() => { beforeEach(() => {
explorer = new Explorer(); explorer = new Explorer();
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
});
it("should be true if autopilot enabled and select valid tier", () => {
explorer.databaseAccount(mockDatabaseAccount);
const addCollectionPane = explorer.addCollectionPane as AddCollectionPane;
addCollectionPane.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
addCollectionPane.isAutoPilotSelected(true);
addCollectionPane.selectedAutoPilotTier(AutopilotTier.Tier2);
expect(addCollectionPane.isValid()).toBe(true);
});
it("should be false if autopilot enabled and select invalid tier", () => {
explorer.databaseAccount(mockDatabaseAccount);
const addCollectionPane = explorer.addCollectionPane as AddCollectionPane;
addCollectionPane.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
addCollectionPane.isAutoPilotSelected(true);
addCollectionPane.selectedAutoPilotTier(0);
expect(addCollectionPane.isValid()).toBe(false);
}); });
it("should be true if graph API and partition key is not /id nor /label", () => { it("should be true if graph API and partition key is not /id nor /label", () => {

View File

@@ -41,6 +41,8 @@ export default class AddCollectionPane extends ContextualPaneBase {
public partitionKeyVisible: ko.Computed<boolean>; public partitionKeyVisible: ko.Computed<boolean>;
public partitionKeyPattern: ko.Computed<string>; public partitionKeyPattern: ko.Computed<string>;
public partitionKeyTitle: ko.Computed<string>; public partitionKeyTitle: ko.Computed<string>;
public rupm: ko.Observable<string>;
public rupmVisible: ko.Observable<boolean>;
public storage: ko.Observable<string>; public storage: ko.Observable<string>;
public throughputSinglePartition: ViewModels.Editable<number>; public throughputSinglePartition: ViewModels.Editable<number>;
public throughputMultiPartition: ViewModels.Editable<number>; public throughputMultiPartition: ViewModels.Editable<number>;
@@ -73,6 +75,10 @@ export default class AddCollectionPane extends ContextualPaneBase {
public debugstring: ko.Computed<string>; public debugstring: ko.Computed<string>;
public displayCollectionThroughput: ko.Computed<boolean>; public displayCollectionThroughput: ko.Computed<boolean>;
public isAutoPilotSelected: ko.Observable<boolean>; public isAutoPilotSelected: ko.Observable<boolean>;
public selectedAutoPilotTier: ko.Observable<DataModels.AutopilotTier>;
public selectedSharedAutoPilotTier: ko.Observable<DataModels.AutopilotTier>;
public autoPilotTiersList: ko.ObservableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>;
public sharedAutoPilotTiersList: ko.ObservableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>;
public isSharedAutoPilotSelected: ko.Observable<boolean>; public isSharedAutoPilotSelected: ko.Observable<boolean>;
public autoPilotThroughput: ko.Observable<number>; public autoPilotThroughput: ko.Observable<number>;
public sharedAutoPilotThroughput: ko.Observable<number>; public sharedAutoPilotThroughput: ko.Observable<number>;
@@ -86,6 +92,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
public isAnalyticalStorageOn: ko.Observable<boolean>; public isAnalyticalStorageOn: ko.Observable<boolean>;
public isSynapseLinkUpdating: ko.Computed<boolean>; public isSynapseLinkUpdating: ko.Computed<boolean>;
public canExceedMaximumValue: ko.PureComputed<boolean>; public canExceedMaximumValue: ko.PureComputed<boolean>;
public hasAutoPilotV2FeatureFlag: ko.PureComputed<boolean>;
public ruToolTipText: ko.Computed<string>; public ruToolTipText: ko.Computed<string>;
public canConfigureThroughput: ko.PureComputed<boolean>; public canConfigureThroughput: ko.PureComputed<boolean>;
public showUpsellMessage: ko.PureComputed<boolean>; public showUpsellMessage: ko.PureComputed<boolean>;
@@ -95,7 +102,8 @@ export default class AddCollectionPane extends ContextualPaneBase {
constructor(options: AddCollectionPaneOptions) { constructor(options: AddCollectionPaneOptions) {
super(options); super(options);
this.ruToolTipText = ko.pureComputed(() => PricingUtils.getRuToolTipText()); this.hasAutoPilotV2FeatureFlag = ko.pureComputed(() => this.container.hasAutoPilotV2FeatureFlag());
this.ruToolTipText = ko.pureComputed(() => PricingUtils.getRuToolTipText(this.hasAutoPilotV2FeatureFlag()));
this.canConfigureThroughput = ko.pureComputed(() => !this.container.isServerlessEnabled()); this.canConfigureThroughput = ko.pureComputed(() => !this.container.isServerlessEnabled());
this.showUpsellMessage = ko.pureComputed(() => !this.container.isServerlessEnabled()); this.showUpsellMessage = ko.pureComputed(() => !this.container.isServerlessEnabled());
this.formWarnings = ko.observable<string>(); this.formWarnings = ko.observable<string>();
@@ -140,6 +148,12 @@ export default class AddCollectionPane extends ContextualPaneBase {
} }
return ""; return "";
}); });
this.rupm = ko.observable<string>(Constants.RUPMStates.off);
this.rupmVisible = ko.observable<boolean>(false);
const featureSubcription = this.container.features.subscribe(() => {
this.rupmVisible(this.container.isFeatureEnabled(Constants.Features.enableRupm));
featureSubcription.dispose();
});
this.canExceedMaximumValue = ko.pureComputed(() => this.container.canExceedMaximumValue()); this.canExceedMaximumValue = ko.pureComputed(() => this.container.canExceedMaximumValue());
@@ -157,13 +171,13 @@ export default class AddCollectionPane extends ContextualPaneBase {
this.databaseHasSharedOffer = ko.observable<boolean>(true); this.databaseHasSharedOffer = ko.observable<boolean>(true);
this.throughputRangeText = ko.pureComputed<string>(() => { this.throughputRangeText = ko.pureComputed<string>(() => {
if (this.isAutoPilotSelected()) { if (this.isAutoPilotSelected()) {
return AutoPilotUtils.getAutoPilotHeaderText(); return AutoPilotUtils.getAutoPilotHeaderText(this.hasAutoPilotV2FeatureFlag());
} }
return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`; return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`;
}); });
this.sharedThroughputRangeText = ko.pureComputed<string>(() => { this.sharedThroughputRangeText = ko.pureComputed<string>(() => {
if (this.isSharedAutoPilotSelected()) { if (this.isSharedAutoPilotSelected()) {
return AutoPilotUtils.getAutoPilotHeaderText(); return AutoPilotUtils.getAutoPilotHeaderText(this.hasAutoPilotV2FeatureFlag());
} }
return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`; return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`;
}); });
@@ -192,6 +206,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
account.properties.readLocations.length) || account.properties.readLocations.length) ||
1; 1;
const multimaster = (account && account.properties && account.properties.enableMultipleWriteLocations) || false; const multimaster = (account && account.properties && account.properties.enableMultipleWriteLocations) || false;
const rupmEnabled: boolean = this.rupm() === Constants.RUPMStates.on;
let throughputSpendAckText: string; let throughputSpendAckText: string;
let estimatedSpend: string; let estimatedSpend: string;
@@ -201,15 +216,23 @@ export default class AddCollectionPane extends ContextualPaneBase {
serverId, serverId,
regions, regions,
multimaster, multimaster,
rupmEnabled,
this.isSharedAutoPilotSelected() this.isSharedAutoPilotSelected()
); );
estimatedSpend = PricingUtils.getEstimatedSpendHtml(offerThroughput, serverId, regions, multimaster); estimatedSpend = PricingUtils.getEstimatedSpendHtml(
offerThroughput,
serverId,
regions,
multimaster,
rupmEnabled
);
} else { } else {
throughputSpendAckText = PricingUtils.getEstimatedSpendAcknowledgeString( throughputSpendAckText = PricingUtils.getEstimatedSpendAcknowledgeString(
this.sharedAutoPilotThroughput(), this.sharedAutoPilotThroughput(),
serverId, serverId,
regions, regions,
multimaster, multimaster,
rupmEnabled,
this.isSharedAutoPilotSelected() this.isSharedAutoPilotSelected()
); );
estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml( estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml(
@@ -246,6 +269,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
account.properties.readLocations.length) || account.properties.readLocations.length) ||
1; 1;
const multimaster = (account && account.properties && account.properties.enableMultipleWriteLocations) || false; const multimaster = (account && account.properties && account.properties.enableMultipleWriteLocations) || false;
const rupmEnabled: boolean = this.rupm() === Constants.RUPMStates.on;
let throughputSpendAckText: string; let throughputSpendAckText: string;
let estimatedSpend: string; let estimatedSpend: string;
@@ -255,13 +279,15 @@ export default class AddCollectionPane extends ContextualPaneBase {
serverId, serverId,
regions, regions,
multimaster, multimaster,
rupmEnabled,
this.isAutoPilotSelected() this.isAutoPilotSelected()
); );
estimatedSpend = PricingUtils.getEstimatedSpendHtml( estimatedSpend = PricingUtils.getEstimatedSpendHtml(
this.throughputMultiPartition(), this.throughputMultiPartition(),
serverId, serverId,
regions, regions,
multimaster multimaster,
rupmEnabled
); );
} else { } else {
throughputSpendAckText = PricingUtils.getEstimatedSpendAcknowledgeString( throughputSpendAckText = PricingUtils.getEstimatedSpendAcknowledgeString(
@@ -269,6 +295,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
serverId, serverId,
regions, regions,
multimaster, multimaster,
rupmEnabled,
this.isAutoPilotSelected() this.isAutoPilotSelected()
); );
estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml( estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml(
@@ -420,7 +447,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
this.throughputSpendAckVisible = ko.pureComputed<boolean>(() => { this.throughputSpendAckVisible = ko.pureComputed<boolean>(() => {
const autoscaleThroughput = this.autoPilotThroughput() * 1; const autoscaleThroughput = this.autoPilotThroughput() * 1;
if (this.isAutoPilotSelected()) { if (!this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected()) {
return autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K; return autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K;
} }
const selectedThroughput: number = this._getThroughput(); const selectedThroughput: number = this._getThroughput();
@@ -463,6 +490,14 @@ export default class AddCollectionPane extends ContextualPaneBase {
this.isAutoPilotSelected = ko.observable<boolean>(false); this.isAutoPilotSelected = ko.observable<boolean>(false);
this.isSharedAutoPilotSelected = ko.observable<boolean>(false); this.isSharedAutoPilotSelected = ko.observable<boolean>(false);
this.selectedAutoPilotTier = ko.observable<DataModels.AutopilotTier>();
this.selectedSharedAutoPilotTier = ko.observable<DataModels.AutopilotTier>();
this.autoPilotTiersList = ko.observableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>(
AutoPilotUtils.getAvailableAutoPilotTiersOptions()
);
this.sharedAutoPilotTiersList = ko.observableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>(
AutoPilotUtils.getAvailableAutoPilotTiersOptions()
);
this.autoPilotThroughput = ko.observable<number>(AutoPilotUtils.minAutoPilotThroughput); this.autoPilotThroughput = ko.observable<number>(AutoPilotUtils.minAutoPilotThroughput);
this.sharedAutoPilotThroughput = ko.observable<number>(AutoPilotUtils.minAutoPilotThroughput); this.sharedAutoPilotThroughput = ko.observable<number>(AutoPilotUtils.minAutoPilotThroughput);
this.autoPilotUsageCost = ko.pureComputed<string>(() => { this.autoPilotUsageCost = ko.pureComputed<string>(() => {
@@ -471,7 +506,9 @@ export default class AddCollectionPane extends ContextualPaneBase {
return ""; return "";
} }
const isDatabaseThroughput: boolean = this.databaseCreateNewShared(); const isDatabaseThroughput: boolean = this.databaseCreateNewShared();
return PricingUtils.getAutoPilotV3SpendHtml(autoPilot.maxThroughput, isDatabaseThroughput); return !this.hasAutoPilotV2FeatureFlag()
? PricingUtils.getAutoPilotV3SpendHtml(autoPilot.maxThroughput, isDatabaseThroughput)
: PricingUtils.getAutoPilotV2SpendHtml(autoPilot.autopilotTier, isDatabaseThroughput);
}); });
this.resetData(); this.resetData();
@@ -666,7 +703,8 @@ export default class AddCollectionPane extends ContextualPaneBase {
storage: this.storage(), storage: this.storage(),
offerThroughput: this._getThroughput(), offerThroughput: this._getThroughput(),
partitionKey: this.partitionKey(), partitionKey: this.partitionKey(),
databaseId: this.databaseId() databaseId: this.databaseId(),
rupm: this.rupm()
}), }),
subscriptionType: ViewModels.SubscriptionType[this.container.subscriptionType()], subscriptionType: ViewModels.SubscriptionType[this.container.subscriptionType()],
subscriptionQuotaId: this.container.quotaId(), subscriptionQuotaId: this.container.quotaId(),
@@ -767,7 +805,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
id: this.collectionId(), id: this.collectionId(),
storage: this.storage(), storage: this.storage(),
partitionKey, partitionKey,
rupm: this.rupm(),
uniqueKeyPolicy, uniqueKeyPolicy,
collectionWithThroughputInShared: this.collectionWithThroughputInShared() collectionWithThroughputInShared: this.collectionWithThroughputInShared()
}), }),
@@ -842,7 +880,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
id: this.collectionId(), id: this.collectionId(),
storage: this.storage(), storage: this.storage(),
partitionKey, partitionKey,
rupm: this.rupm(),
uniqueKeyPolicy, uniqueKeyPolicy,
collectionWithThroughputInShared: this.collectionWithThroughputInShared() collectionWithThroughputInShared: this.collectionWithThroughputInShared()
}), }),
@@ -878,7 +916,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
id: this.collectionId(), id: this.collectionId(),
storage: this.storage(), storage: this.storage(),
partitionKey, partitionKey,
rupm: this.rupm(),
uniqueKeyPolicy, uniqueKeyPolicy,
collectionWithThroughputInShared: this.collectionWithThroughputInShared() collectionWithThroughputInShared: this.collectionWithThroughputInShared()
}, },
@@ -904,9 +942,13 @@ export default class AddCollectionPane extends ContextualPaneBase {
this.throughputSpendAck(false); this.throughputSpendAck(false);
this.isAutoPilotSelected(false); this.isAutoPilotSelected(false);
this.isSharedAutoPilotSelected(false); this.isSharedAutoPilotSelected(false);
if (!this.hasAutoPilotV2FeatureFlag()) {
this.autoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput); this.autoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
this.sharedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput); this.sharedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
} else {
this.selectedAutoPilotTier(undefined);
this.selectedSharedAutoPilotTier(undefined);
}
this.uniqueKeys([]); this.uniqueKeys([]);
this.useIndexingForSharedThroughput(true); this.useIndexingForSharedThroughput(true);
@@ -960,6 +1002,20 @@ export default class AddCollectionPane extends ContextualPaneBase {
return true; return true;
} }
public onRupmOptionsKeyDown(source: any, event: KeyboardEvent): boolean {
if (event.key === "ArrowRight") {
this.rupm("off");
return false;
}
if (event.key === "ArrowLeft") {
this.rupm("on");
return false;
}
return true;
}
public onEnableSynapseLinkButtonClicked() { public onEnableSynapseLinkButtonClicked() {
this.container.openEnableSynapseLinkDialog(); this.container.openEnableSynapseLinkDialog();
} }
@@ -971,18 +1027,32 @@ export default class AddCollectionPane extends ContextualPaneBase {
if ((this.databaseCreateNewShared() && this.isSharedAutoPilotSelected()) || this.isAutoPilotSelected()) { if ((this.databaseCreateNewShared() && this.isSharedAutoPilotSelected()) || this.isAutoPilotSelected()) {
const autoPilot = this._getAutoPilot(); const autoPilot = this._getAutoPilot();
if ( if (
!autoPilot || (!this.hasAutoPilotV2FeatureFlag() &&
(!autoPilot ||
!autoPilot.maxThroughput || !autoPilot.maxThroughput ||
!AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput) !AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput))) ||
(this.hasAutoPilotV2FeatureFlag() &&
(!autoPilot || !autoPilot.autopilotTier || !AutoPilotUtils.isValidAutoPilotTier(autoPilot.autopilotTier)))
) { ) {
this.formErrors( this.formErrors(
`Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput` !this.hasAutoPilotV2FeatureFlag()
? `Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput`
: "Please select an Autopilot tier from the list."
); );
return false; return false;
} }
} }
const throughput = this._getThroughput(); const throughput = this._getThroughput();
const maxThroughputWithRUPM =
SharedConstants.CollectionCreation.MaxRUPMPerPartition * this._calculateNumberOfPartitions();
if (this.rupm() === Constants.RUPMStates.on && throughput > maxThroughputWithRUPM) {
this.formErrors(
`The maximum supported provisioned throughput with RU/m enabled is ${maxThroughputWithRUPM} RU/s. Please turn off RU/m to incease thoughput above ${maxThroughputWithRUPM} RU/s.`
);
return false;
}
if (throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && !this.throughputSpendAck()) { if (throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && !this.throughputSpendAck()) {
this.formErrors(`Please acknowledge the estimated daily spend.`); this.formErrors(`Please acknowledge the estimated daily spend.`);
@@ -997,6 +1067,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
const autoscaleThroughput = this.autoPilotThroughput() * 1; const autoscaleThroughput = this.autoPilotThroughput() * 1;
if ( if (
!this.hasAutoPilotV2FeatureFlag() &&
this.isAutoPilotSelected() && this.isAutoPilotSelected() &&
autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K &&
!this.throughputSpendAck() !this.throughputSpendAck()
@@ -1043,15 +1114,31 @@ export default class AddCollectionPane extends ContextualPaneBase {
} }
private _getAutoPilot(): DataModels.AutoPilotCreationSettings { private _getAutoPilot(): DataModels.AutoPilotCreationSettings {
if (this.databaseCreateNewShared() && this.isSharedAutoPilotSelected() && this.sharedAutoPilotThroughput()) { if (
return { (!this.hasAutoPilotV2FeatureFlag() &&
this.databaseCreateNewShared() &&
this.isSharedAutoPilotSelected() &&
this.sharedAutoPilotThroughput()) ||
(this.hasAutoPilotV2FeatureFlag() &&
this.databaseCreateNewShared() &&
this.isSharedAutoPilotSelected() &&
this.selectedSharedAutoPilotTier())
) {
return !this.hasAutoPilotV2FeatureFlag()
? {
maxThroughput: this.sharedAutoPilotThroughput() * 1 maxThroughput: this.sharedAutoPilotThroughput() * 1
};
} }
if (this.isAutoPilotSelected() && this.autoPilotThroughput()) { : { autopilotTier: this.selectedSharedAutoPilotTier() };
return { }
if (
(!this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected() && this.autoPilotThroughput()) ||
(this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected() && this.selectedAutoPilotTier())
) {
return !this.hasAutoPilotV2FeatureFlag()
? {
maxThroughput: this.autoPilotThroughput() * 1 maxThroughput: this.autoPilotThroughput() * 1
}; }
: { autopilotTier: this.selectedAutoPilotTier() };
} }
return undefined; return undefined;

View File

@@ -89,6 +89,7 @@
data-bind="text: databaseLevelThroughputTooltipText"></span> data-bind="text: databaseLevelThroughputTooltipText"></span>
</span> </span>
</div> </div>
<!-- ko if: hasAutoPilotV2FeatureFlag && !hasAutoPilotV2FeatureFlag() -->
<div data-bind="visible: databaseCreateNewShared"> <div data-bind="visible: databaseCreateNewShared">
<throughput-input-autopilot-v3 params="{ <throughput-input-autopilot-v3 params="{
step: 100, step: 100,
@@ -123,6 +124,42 @@
support</a> for more than <span data-bind="text: maxThroughputRUText"></span> RU/s.</p> support</a> for more than <span data-bind="text: maxThroughputRUText"></span> RU/s.</p>
</div> </div>
<!-- /ko --> <!-- /ko -->
<!-- ko if: hasAutoPilotV2FeatureFlag && hasAutoPilotV2FeatureFlag() -->
<div data-bind="visible: databaseCreateNewShared">
<throughput-input params="{
step: 100,
value: throughput,
testId: 'sharedThroughputValue',
minimum: minThroughputRU,
maximum: maxThroughputRU,
isEnabled: databaseCreateNewShared,
label: throughputRangeText,
ariaLabel: throughputRangeText,
costsVisible: costsVisible,
requestUnitsUsageCost: requestUnitsUsageCost,
spendAckChecked: throughputSpendAck,
spendAckId: 'throughputSpendAckDatabase',
spendAckText: throughputSpendAckText,
spendAckVisible: throughputSpendAckVisible,
showAsMandatory: true,
infoBubbleText: ruToolTipText,
throughputAutoPilotRadioId: 'newDatabase-databaseThroughput-autoPilotRadio',
throughputProvisionedRadioId: 'newDatabase-databaseThroughput-manualRadio',
throughputModeRadioName: 'throughputModeRadioName',
isAutoPilotSelected: isAutoPilotSelected,
autoPilotTiersList: autoPilotTiersList,
selectedAutoPilotTier: selectedAutoPilotTier,
autoPilotUsageCost: autoPilotUsageCost,
canExceedMaximumValue: canExceedMaximumValue
}">
</throughput-input>
<p data-bind="visible: canRequestSupport">
<!-- TODO: Replace link with call to the Azure Support blade --><a
href="https://aka.ms/cosmosdbfeedback?subject=Cosmos%20DB%20More%20Throughput%20Request">Contact
support</a> for more than <span data-bind="text: maxThroughputRUText"></span> RU/s.</p>
</div>
<!-- /ko -->
<!-- /ko -->
<!-- Database provisioned throughput - End --> <!-- Database provisioned throughput - End -->
</div> </div>
</div> </div>

View File

@@ -38,9 +38,12 @@ export default class AddDatabasePane extends ContextualPaneBase {
public upsellAnchorUrl: ko.PureComputed<string>; public upsellAnchorUrl: ko.PureComputed<string>;
public upsellAnchorText: ko.PureComputed<string>; public upsellAnchorText: ko.PureComputed<string>;
public isAutoPilotSelected: ko.Observable<boolean>; public isAutoPilotSelected: ko.Observable<boolean>;
public selectedAutoPilotTier: ko.Observable<DataModels.AutopilotTier>;
public autoPilotTiersList: ko.ObservableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>;
public maxAutoPilotThroughputSet: ko.Observable<number>; public maxAutoPilotThroughputSet: ko.Observable<number>;
public autoPilotUsageCost: ko.Computed<string>; public autoPilotUsageCost: ko.Computed<string>;
public canExceedMaximumValue: ko.PureComputed<boolean>; public canExceedMaximumValue: ko.PureComputed<boolean>;
public hasAutoPilotV2FeatureFlag: ko.PureComputed<boolean>;
public ruToolTipText: ko.Computed<string>; public ruToolTipText: ko.Computed<string>;
public isFreeTierAccount: ko.Computed<boolean>; public isFreeTierAccount: ko.Computed<boolean>;
public canConfigureThroughput: ko.PureComputed<boolean>; public canConfigureThroughput: ko.PureComputed<boolean>;
@@ -50,7 +53,8 @@ export default class AddDatabasePane extends ContextualPaneBase {
super(options); super(options);
this.title((this.container && this.container.addDatabaseText()) || "New Database"); this.title((this.container && this.container.addDatabaseText()) || "New Database");
this.databaseId = ko.observable<string>(); this.databaseId = ko.observable<string>();
this.ruToolTipText = ko.pureComputed(() => PricingUtils.getRuToolTipText()); this.hasAutoPilotV2FeatureFlag = ko.pureComputed(() => this.container.hasAutoPilotV2FeatureFlag());
this.ruToolTipText = ko.pureComputed(() => PricingUtils.getRuToolTipText(this.hasAutoPilotV2FeatureFlag()));
this.canConfigureThroughput = ko.pureComputed(() => !this.container.isServerlessEnabled()); this.canConfigureThroughput = ko.pureComputed(() => !this.container.isServerlessEnabled());
this.showUpsellMessage = ko.pureComputed(() => !this.container.isServerlessEnabled()); this.showUpsellMessage = ko.pureComputed(() => !this.container.isServerlessEnabled());
@@ -90,6 +94,10 @@ export default class AddDatabasePane extends ContextualPaneBase {
this.minThroughputRU = ko.observable<number>(); this.minThroughputRU = ko.observable<number>();
this.throughputSpendAckText = ko.observable<string>(); this.throughputSpendAckText = ko.observable<string>();
this.throughputSpendAck = ko.observable<boolean>(false); this.throughputSpendAck = ko.observable<boolean>(false);
this.selectedAutoPilotTier = ko.observable<DataModels.AutopilotTier>();
this.autoPilotTiersList = ko.observableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>(
AutoPilotUtils.getAvailableAutoPilotTiersOptions()
);
this.isAutoPilotSelected = ko.observable<boolean>(false); this.isAutoPilotSelected = ko.observable<boolean>(false);
this.maxAutoPilotThroughputSet = ko.observable<number>(AutoPilotUtils.minAutoPilotThroughput); this.maxAutoPilotThroughputSet = ko.observable<number>(AutoPilotUtils.minAutoPilotThroughput);
this.autoPilotUsageCost = ko.pureComputed<string>(() => { this.autoPilotUsageCost = ko.pureComputed<string>(() => {
@@ -97,11 +105,13 @@ export default class AddDatabasePane extends ContextualPaneBase {
if (!autoPilot) { if (!autoPilot) {
return ""; return "";
} }
return PricingUtils.getAutoPilotV3SpendHtml(autoPilot.maxThroughput, true /* isDatabaseThroughput */); return !this.hasAutoPilotV2FeatureFlag()
? PricingUtils.getAutoPilotV3SpendHtml(autoPilot.maxThroughput, true /* isDatabaseThroughput */)
: PricingUtils.getAutoPilotV2SpendHtml(autoPilot.autopilotTier, true /* isDatabaseThroughput */);
}); });
this.throughputRangeText = ko.pureComputed<string>(() => { this.throughputRangeText = ko.pureComputed<string>(() => {
if (this.isAutoPilotSelected()) { if (this.isAutoPilotSelected()) {
return AutoPilotUtils.getAutoPilotHeaderText(); return AutoPilotUtils.getAutoPilotHeaderText(this.hasAutoPilotV2FeatureFlag());
} }
return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`; return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`;
}); });
@@ -132,12 +142,19 @@ export default class AddDatabasePane extends ContextualPaneBase {
let estimatedSpendAcknowledge: string; let estimatedSpendAcknowledge: string;
let estimatedSpend: string; let estimatedSpend: string;
if (!this.isAutoPilotSelected()) { if (!this.isAutoPilotSelected()) {
estimatedSpend = PricingUtils.getEstimatedSpendHtml(offerThroughput, serverId, regions, multimaster); estimatedSpend = PricingUtils.getEstimatedSpendHtml(
offerThroughput,
serverId,
regions,
multimaster,
false /*rupmEnabled*/
);
estimatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString( estimatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
offerThroughput, offerThroughput,
serverId, serverId,
regions, regions,
multimaster, multimaster,
false /*rupmEnabled*/,
this.isAutoPilotSelected() this.isAutoPilotSelected()
); );
} else { } else {
@@ -152,6 +169,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
serverId, serverId,
regions, regions,
multimaster, multimaster,
false /*rupmEnabled*/,
this.isAutoPilotSelected() this.isAutoPilotSelected()
); );
} }
@@ -190,7 +208,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
this.throughputSpendAckVisible = ko.pureComputed<boolean>(() => { this.throughputSpendAckVisible = ko.pureComputed<boolean>(() => {
const autoscaleThroughput = this.maxAutoPilotThroughputSet() * 1; const autoscaleThroughput = this.maxAutoPilotThroughputSet() * 1;
if (this.isAutoPilotSelected()) { if (!this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected()) {
return autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K; return autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K;
} }
@@ -307,6 +325,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
public resetData() { public resetData() {
this.databaseId(""); this.databaseId("");
this.databaseCreateNewShared(this.getSharedThroughputDefault()); this.databaseCreateNewShared(this.getSharedThroughputDefault());
this.selectedAutoPilotTier(undefined);
this.isAutoPilotSelected(false); this.isAutoPilotSelected(false);
this.maxAutoPilotThroughputSet(AutoPilotUtils.minAutoPilotThroughput); this.maxAutoPilotThroughputSet(AutoPilotUtils.minAutoPilotThroughput);
this._updateThroughputLimitByDatabase(); this._updateThroughputLimitByDatabase();
@@ -394,12 +413,17 @@ export default class AddDatabasePane extends ContextualPaneBase {
if (this.isAutoPilotSelected()) { if (this.isAutoPilotSelected()) {
const autoPilot = this._isAutoPilotSelectedAndWhatTier(); const autoPilot = this._isAutoPilotSelectedAndWhatTier();
if ( if (
!autoPilot || (!this.hasAutoPilotV2FeatureFlag() &&
(!autoPilot ||
!autoPilot.maxThroughput || !autoPilot.maxThroughput ||
!AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput) !AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput))) ||
(this.hasAutoPilotV2FeatureFlag() &&
(!autoPilot || !autoPilot.autopilotTier || !AutoPilotUtils.isValidAutoPilotTier(autoPilot.autopilotTier)))
) { ) {
this.formErrors( this.formErrors(
`Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput` !this.hasAutoPilotV2FeatureFlag()
? `Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput`
: "Please select an Autopilot tier from the list."
); );
return false; return false;
} }
@@ -414,6 +438,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
const autoscaleThroughput = this.maxAutoPilotThroughputSet() * 1; const autoscaleThroughput = this.maxAutoPilotThroughputSet() * 1;
if ( if (
!this.hasAutoPilotV2FeatureFlag() &&
this.isAutoPilotSelected() && this.isAutoPilotSelected() &&
autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K &&
!this.throughputSpendAck() !this.throughputSpendAck()
@@ -426,10 +451,15 @@ export default class AddDatabasePane extends ContextualPaneBase {
} }
private _isAutoPilotSelectedAndWhatTier(): DataModels.AutoPilotCreationSettings { private _isAutoPilotSelectedAndWhatTier(): DataModels.AutoPilotCreationSettings {
if (this.isAutoPilotSelected() && this.maxAutoPilotThroughputSet()) { if (
return { (!this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected() && this.maxAutoPilotThroughputSet()) ||
(this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected() && this.selectedAutoPilotTier())
) {
return !this.hasAutoPilotV2FeatureFlag()
? {
maxThroughput: this.maxAutoPilotThroughputSet() * 1 maxThroughput: this.maxAutoPilotThroughputSet() * 1
}; }
: { autopilotTier: this.selectedAutoPilotTier() };
} }
return undefined; return undefined;
} }

View File

@@ -142,6 +142,7 @@
</span> </span>
</div> </div>
<!-- 1 --> <!-- 1 -->
<!-- ko if: hasAutoPilotV2FeatureFlag && !hasAutoPilotV2FeatureFlag() -->
<div data-bind="visible: keyspaceCreateNew() && keyspaceHasSharedOffer()"> <div data-bind="visible: keyspaceCreateNew() && keyspaceHasSharedOffer()">
<throughput-input-autopilot-v3 <throughput-input-autopilot-v3
params="{ params="{
@@ -172,6 +173,38 @@
</throughput-input-autopilot-v3> </throughput-input-autopilot-v3>
</div> </div>
<!-- /ko --> <!-- /ko -->
<!-- ko if: hasAutoPilotV2FeatureFlag && hasAutoPilotV2FeatureFlag() -->
<div data-bind="visible: keyspaceCreateNew() && keyspaceHasSharedOffer()">
<throughput-input
params="{
testId: 'cassandraThroughputValue-v2-shared',
value: keyspaceThroughput,
minimum: minThroughputRU,
maximum: maxThroughputRU,
isEnabled: keyspaceCreateNew() && keyspaceHasSharedOffer(),
label: sharedThroughputRangeText,
ariaLabel: sharedThroughputRangeText,
requestUnitsUsageCost: requestUnitsUsageCostShared,
spendAckChecked: sharedThroughputSpendAck,
spendAckId: 'sharedThroughputSpendAck-v2-shared',
spendAckText: sharedThroughputSpendAckText,
spendAckVisible: sharedThroughputSpendAckVisible,
showAsMandatory: true,
infoBubbleText: ruToolTipText,
throughputAutoPilotRadioId: 'newKeyspace-databaseThroughput-autoPilotRadio-v2-shared',
throughputProvisionedRadioId: 'newKeyspace-databaseThroughput-manualRadio-v2-shared',
isAutoPilotSelected: isSharedAutoPilotSelected,
autoPilotTiersList: sharedAutoPilotTiersList,
costsVisible: costsVisible,
selectedAutoPilotTier: selectedSharedAutoPilotTier,
autoPilotUsageCost: autoPilotUsageCost,
canExceedMaximumValue: canExceedMaximumValue
}"
>
</throughput-input>
</div>
<!-- /ko -->
<!-- /ko -->
<!-- Database provisioned throughput - End --> <!-- Database provisioned throughput - End -->
</div> </div>
<div class="seconddivpadding"> <div class="seconddivpadding">
@@ -224,6 +257,7 @@
</span> </span>
</div> </div>
<!-- 2 --> <!-- 2 -->
<!-- ko if: hasAutoPilotV2FeatureFlag && !hasAutoPilotV2FeatureFlag() -->
<div data-bind="visible: !keyspaceHasSharedOffer() || dedicateTableThroughput()"> <div data-bind="visible: !keyspaceHasSharedOffer() || dedicateTableThroughput()">
<throughput-input-autopilot-v3 <throughput-input-autopilot-v3
params="{ params="{
@@ -255,6 +289,40 @@
</throughput-input-autopilot-v3> </throughput-input-autopilot-v3>
</div> </div>
<!-- /ko --> <!-- /ko -->
<!-- ko if: hasAutoPilotV2FeatureFlag && hasAutoPilotV2FeatureFlag() -->
<div data-bind="visible: !keyspaceHasSharedOffer() || dedicateTableThroughput()">
<throughput-input
params="{
testId: 'cassandraSharedThroughputValue-v2-dedicated',
value: throughput,
minimum: minThroughputRU,
maximum: maxThroughputRU,
isEnabled: !keyspaceHasSharedOffer() || dedicateTableThroughput(),
label: throughputRangeText,
ariaLabel: throughputRangeText,
costsVisible: costsVisible,
requestUnitsUsageCost: requestUnitsUsageCostDedicated,
spendAckChecked: throughputSpendAck,
spendAckId: 'throughputSpendAckCassandra-v2-dedicated',
spendAckText: throughputSpendAckText,
spendAckVisible: throughputSpendAckVisible,
showAsMandatory: true,
infoBubbleText: ruToolTipText,
throughputAutoPilotRadioId: 'newKeyspace-containerThroughput-autoPilotRadio-v2-dedicated',
throughputProvisionedRadioId: 'newKeyspace-containerThroughput-manualRadio-v2-dedicated',
isAutoPilotSelected: isAutoPilotSelected,
autoPilotTiersList: autoPilotTiersList,
selectedAutoPilotTier: selectedAutoPilotTier,
autoPilotUsageCost: autoPilotUsageCost,
showAutoPilot: false,
canExceedMaximumValue: canExceedMaximumValue
}"
>
</throughput-input>
</div>
<!-- /ko -->
<!-- /ko -->
<!-- Provision table throughput - end --> <!-- Provision table throughput - end -->
</div> </div>
<div class="paneFooter"> <div class="paneFooter">

View File

@@ -38,6 +38,10 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
public sharedThroughputSpendAck: ko.Observable<boolean>; public sharedThroughputSpendAck: ko.Observable<boolean>;
public sharedThroughputSpendAckText: ko.Observable<string>; public sharedThroughputSpendAckText: ko.Observable<string>;
public isAutoPilotSelected: ko.Observable<boolean>; public isAutoPilotSelected: ko.Observable<boolean>;
public selectedAutoPilotTier: ko.Observable<DataModels.AutopilotTier>;
public selectedSharedAutoPilotTier: ko.Observable<DataModels.AutopilotTier>;
public autoPilotTiersList: ko.ObservableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>;
public sharedAutoPilotTiersList: ko.ObservableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>;
public isSharedAutoPilotSelected: ko.Observable<boolean>; public isSharedAutoPilotSelected: ko.Observable<boolean>;
public selectedAutoPilotThroughput: ko.Observable<number>; public selectedAutoPilotThroughput: ko.Observable<number>;
public sharedAutoPilotThroughput: ko.Observable<number>; public sharedAutoPilotThroughput: ko.Observable<number>;
@@ -45,6 +49,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
public sharedThroughputSpendAckVisible: ko.Computed<boolean>; public sharedThroughputSpendAckVisible: ko.Computed<boolean>;
public throughputSpendAckVisible: ko.Computed<boolean>; public throughputSpendAckVisible: ko.Computed<boolean>;
public canExceedMaximumValue: ko.PureComputed<boolean>; public canExceedMaximumValue: ko.PureComputed<boolean>;
public hasAutoPilotV2FeatureFlag: ko.PureComputed<boolean>;
public isFreeTierAccount: ko.Computed<boolean>; public isFreeTierAccount: ko.Computed<boolean>;
public ruToolTipText: ko.Computed<string>; public ruToolTipText: ko.Computed<string>;
public canConfigureThroughput: ko.PureComputed<boolean>; public canConfigureThroughput: ko.PureComputed<boolean>;
@@ -56,7 +61,8 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
this.title("Add Table"); this.title("Add Table");
this.createTableQuery = ko.observable<string>("CREATE TABLE "); this.createTableQuery = ko.observable<string>("CREATE TABLE ");
this.keyspaceCreateNew = ko.observable<boolean>(true); this.keyspaceCreateNew = ko.observable<boolean>(true);
this.ruToolTipText = ko.pureComputed(() => PricingUtils.getRuToolTipText()); this.hasAutoPilotV2FeatureFlag = ko.pureComputed(() => this.container.hasAutoPilotV2FeatureFlag());
this.ruToolTipText = ko.pureComputed(() => PricingUtils.getRuToolTipText(this.hasAutoPilotV2FeatureFlag()));
this.canConfigureThroughput = ko.pureComputed(() => !this.container.isServerlessEnabled()); this.canConfigureThroughput = ko.pureComputed(() => !this.container.isServerlessEnabled());
this.keyspaceOffers = new HashMap<DataModels.Offer>(); this.keyspaceOffers = new HashMap<DataModels.Offer>();
this.keyspaceIds = ko.observableArray<string>(); this.keyspaceIds = ko.observableArray<string>();
@@ -84,6 +90,8 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
}); });
this.tableId = ko.observable<string>(""); this.tableId = ko.observable<string>("");
this.selectedAutoPilotTier = ko.observable<DataModels.AutopilotTier>();
this.selectedSharedAutoPilotTier = ko.observable<DataModels.AutopilotTier>();
this.isAutoPilotSelected = ko.observable<boolean>(false); this.isAutoPilotSelected = ko.observable<boolean>(false);
this.isSharedAutoPilotSelected = ko.observable<boolean>(false); this.isSharedAutoPilotSelected = ko.observable<boolean>(false);
this.selectedAutoPilotThroughput = ko.observable<number>(); this.selectedAutoPilotThroughput = ko.observable<number>();
@@ -94,11 +102,11 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
if (!enableAutoPilot) { if (!enableAutoPilot) {
return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`; return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`;
} }
return AutoPilotUtils.getAutoPilotHeaderText(); return AutoPilotUtils.getAutoPilotHeaderText(this.hasAutoPilotV2FeatureFlag());
}); });
this.sharedThroughputRangeText = ko.pureComputed<string>(() => { this.sharedThroughputRangeText = ko.pureComputed<string>(() => {
if (this.isSharedAutoPilotSelected()) { if (this.isSharedAutoPilotSelected()) {
return AutoPilotUtils.getAutoPilotHeaderText(); return AutoPilotUtils.getAutoPilotHeaderText(this.hasAutoPilotV2FeatureFlag());
} }
return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`; return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`;
}); });
@@ -136,12 +144,19 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
let estimatedSpend: string; let estimatedSpend: string;
let estimatedDedicatedSpendAcknowledge: string; let estimatedDedicatedSpendAcknowledge: string;
if (!this.isAutoPilotSelected()) { if (!this.isAutoPilotSelected()) {
estimatedSpend = PricingUtils.getEstimatedSpendHtml(offerThroughput, serverId, regions, multimaster); estimatedSpend = PricingUtils.getEstimatedSpendHtml(
offerThroughput,
serverId,
regions,
multimaster,
false /*rupmEnabled*/
);
estimatedDedicatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString( estimatedDedicatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
offerThroughput, offerThroughput,
serverId, serverId,
regions, regions,
multimaster, multimaster,
false /*rupmEnabled*/,
this.isAutoPilotSelected() this.isAutoPilotSelected()
); );
} else { } else {
@@ -156,6 +171,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
serverId, serverId,
regions, regions,
multimaster, multimaster,
false /*rupmEnabled*/,
this.isAutoPilotSelected() this.isAutoPilotSelected()
); );
} }
@@ -180,12 +196,19 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
let estimatedSpend: string; let estimatedSpend: string;
let estimatedSharedSpendAcknowledge: string; let estimatedSharedSpendAcknowledge: string;
if (!this.isSharedAutoPilotSelected()) { if (!this.isSharedAutoPilotSelected()) {
estimatedSpend = PricingUtils.getEstimatedSpendHtml(this.keyspaceThroughput(), serverId, regions, multimaster); estimatedSpend = PricingUtils.getEstimatedSpendHtml(
this.keyspaceThroughput(),
serverId,
regions,
multimaster,
false /*rupmEnabled*/
);
estimatedSharedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString( estimatedSharedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
this.keyspaceThroughput(), this.keyspaceThroughput(),
serverId, serverId,
regions, regions,
multimaster, multimaster,
false /*rupmEnabled*/,
this.isSharedAutoPilotSelected() this.isSharedAutoPilotSelected()
); );
} else { } else {
@@ -200,6 +223,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
serverId, serverId,
regions, regions,
multimaster, multimaster,
false /*rupmEnabled*/,
this.isSharedAutoPilotSelected() this.isSharedAutoPilotSelected()
); );
} }
@@ -222,7 +246,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
this.sharedThroughputSpendAckVisible = ko.computed<boolean>(() => { this.sharedThroughputSpendAckVisible = ko.computed<boolean>(() => {
const autoscaleThroughput = this.sharedAutoPilotThroughput() * 1; const autoscaleThroughput = this.sharedAutoPilotThroughput() * 1;
if (this.isSharedAutoPilotSelected()) { if (!this.hasAutoPilotV2FeatureFlag() && this.isSharedAutoPilotSelected()) {
return autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K; return autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K;
} }
@@ -231,7 +255,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
this.throughputSpendAckVisible = ko.pureComputed<boolean>(() => { this.throughputSpendAckVisible = ko.pureComputed<boolean>(() => {
const autoscaleThroughput = this.selectedAutoPilotThroughput() * 1; const autoscaleThroughput = this.selectedAutoPilotThroughput() * 1;
if (this.isAutoPilotSelected()) { if (!this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected()) {
return autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K; return autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K;
} }
@@ -256,13 +280,22 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
updateKeyspaceIds(this.container.nonSystemDatabases()); updateKeyspaceIds(this.container.nonSystemDatabases());
} }
this.autoPilotTiersList = ko.observableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>(
AutoPilotUtils.getAvailableAutoPilotTiersOptions()
);
this.sharedAutoPilotTiersList = ko.observableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>(
AutoPilotUtils.getAvailableAutoPilotTiersOptions()
);
this.autoPilotUsageCost = ko.pureComputed<string>(() => { this.autoPilotUsageCost = ko.pureComputed<string>(() => {
const autoPilot = this._getAutoPilot(); const autoPilot = this._getAutoPilot();
if (!autoPilot) { if (!autoPilot) {
return ""; return "";
} }
const isDatabaseThroughput: boolean = this.keyspaceCreateNew(); const isDatabaseThroughput: boolean = this.keyspaceCreateNew();
return PricingUtils.getAutoPilotV3SpendHtml(autoPilot.maxThroughput, isDatabaseThroughput); return !this.hasAutoPilotV2FeatureFlag()
? PricingUtils.getAutoPilotV3SpendHtml(autoPilot.maxThroughput, isDatabaseThroughput)
: PricingUtils.getAutoPilotV2SpendHtml(autoPilot.autopilotTier, isDatabaseThroughput);
}); });
} }
@@ -294,7 +327,8 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
storage: Constants.BackendDefaults.multiPartitionStorageInGb, storage: Constants.BackendDefaults.multiPartitionStorageInGb,
offerThroughput: this.throughput(), offerThroughput: this.throughput(),
partitionKey: "", partitionKey: "",
databaseId: this.keyspaceId() databaseId: this.keyspaceId(),
rupm: false
}), }),
subscriptionType: ViewModels.SubscriptionType[this.container.subscriptionType()], subscriptionType: ViewModels.SubscriptionType[this.container.subscriptionType()],
subscriptionQuotaId: this.container.quotaId(), subscriptionQuotaId: this.container.quotaId(),
@@ -318,11 +352,15 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
const autoPilotCommand = `cosmosdb_autoscale_max_throughput`; const autoPilotCommand = `cosmosdb_autoscale_max_throughput`;
let createTableAndKeyspacePromise: Q.Promise<any>; let createTableAndKeyspacePromise: Q.Promise<any>;
const toCreateKeyspace: boolean = this.keyspaceCreateNew(); const toCreateKeyspace: boolean = this.keyspaceCreateNew();
const useAutoPilotForKeyspace: boolean = this.isSharedAutoPilotSelected() && !!this.sharedAutoPilotThroughput(); const useAutoPilotForKeyspace: boolean =
(!this.hasAutoPilotV2FeatureFlag() && this.isSharedAutoPilotSelected() && !!this.sharedAutoPilotThroughput()) ||
(this.hasAutoPilotV2FeatureFlag() && this.isSharedAutoPilotSelected() && !!this.selectedSharedAutoPilotTier());
const createKeyspaceQueryPrefix: string = `CREATE KEYSPACE ${this.keyspaceId().trim()} WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 3 }`; const createKeyspaceQueryPrefix: string = `CREATE KEYSPACE ${this.keyspaceId().trim()} WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 3 }`;
const createKeyspaceQuery: string = this.keyspaceHasSharedOffer() const createKeyspaceQuery: string = this.keyspaceHasSharedOffer()
? useAutoPilotForKeyspace ? useAutoPilotForKeyspace
? !this.hasAutoPilotV2FeatureFlag()
? `${createKeyspaceQueryPrefix} AND ${autoPilotCommand}=${this.sharedAutoPilotThroughput()};` ? `${createKeyspaceQueryPrefix} AND ${autoPilotCommand}=${this.sharedAutoPilotThroughput()};`
: `${createKeyspaceQueryPrefix} AND ${autoPilotCommand}=${this.selectedSharedAutoPilotTier()};`
: `${createKeyspaceQueryPrefix} AND cosmosdb_provisioned_throughput=${this.keyspaceThroughput()};` : `${createKeyspaceQueryPrefix} AND cosmosdb_provisioned_throughput=${this.keyspaceThroughput()};`
: `${createKeyspaceQueryPrefix};`; : `${createKeyspaceQueryPrefix};`;
const createTableQueryPrefix: string = `${this.createTableQuery()}${this.tableId().trim()} ${this.userTableQuery()}`; const createTableQueryPrefix: string = `${this.createTableQuery()}${this.tableId().trim()} ${this.userTableQuery()}`;
@@ -347,6 +385,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
offerThroughput: this.throughput(), offerThroughput: this.throughput(),
partitionKey: "", partitionKey: "",
databaseId: this.keyspaceId(), databaseId: this.keyspaceId(),
rupm: false,
hasDedicatedThroughput: this.dedicateTableThroughput() hasDedicatedThroughput: this.dedicateTableThroughput()
}), }),
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(), keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
@@ -393,6 +432,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
offerThroughput: this.throughput(), offerThroughput: this.throughput(),
partitionKey: "", partitionKey: "",
databaseId: this.keyspaceId(), databaseId: this.keyspaceId(),
rupm: false,
hasDedicatedThroughput: this.dedicateTableThroughput() hasDedicatedThroughput: this.dedicateTableThroughput()
}), }),
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(), keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
@@ -422,6 +462,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
offerThroughput: this.throughput(), offerThroughput: this.throughput(),
partitionKey: "", partitionKey: "",
databaseId: this.keyspaceId(), databaseId: this.keyspaceId(),
rupm: false,
hasDedicatedThroughput: this.dedicateTableThroughput() hasDedicatedThroughput: this.dedicateTableThroughput()
}, },
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(), keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
@@ -448,6 +489,8 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
const throughputDefaults = this.container.collectionCreationDefaults.throughput; const throughputDefaults = this.container.collectionCreationDefaults.throughput;
this.isAutoPilotSelected(false); this.isAutoPilotSelected(false);
this.isSharedAutoPilotSelected(false); this.isSharedAutoPilotSelected(false);
this.selectedAutoPilotTier(null);
this.selectedSharedAutoPilotTier(null);
this.selectedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput); this.selectedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
this.sharedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput); this.sharedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
this.throughput(AddCollectionUtility.getMaxThroughput(this.container.collectionCreationDefaults, this.container)); this.throughput(AddCollectionUtility.getMaxThroughput(this.container.collectionCreationDefaults, this.container));
@@ -469,6 +512,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
const sharedAutoscaleThroughput = this.sharedAutoPilotThroughput() * 1; const sharedAutoscaleThroughput = this.sharedAutoPilotThroughput() * 1;
if ( if (
!this.hasAutoPilotV2FeatureFlag() &&
this.isSharedAutoPilotSelected() && this.isSharedAutoPilotSelected() &&
sharedAutoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && sharedAutoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K &&
!this.sharedThroughputSpendAck() !this.sharedThroughputSpendAck()
@@ -479,6 +523,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
const dedicatedAutoscaleThroughput = this.selectedAutoPilotThroughput() * 1; const dedicatedAutoscaleThroughput = this.selectedAutoPilotThroughput() * 1;
if ( if (
!this.hasAutoPilotV2FeatureFlag() &&
this.isAutoPilotSelected() && this.isAutoPilotSelected() &&
dedicatedAutoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && dedicatedAutoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K &&
!this.throughputSpendAck() !this.throughputSpendAck()
@@ -493,12 +538,17 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
) { ) {
const autoPilot = this._getAutoPilot(); const autoPilot = this._getAutoPilot();
if ( if (
!autoPilot || (!this.hasAutoPilotV2FeatureFlag() &&
(!autoPilot ||
!autoPilot.maxThroughput || !autoPilot.maxThroughput ||
!AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput) !AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput))) ||
(this.hasAutoPilotV2FeatureFlag() &&
(!autoPilot || !autoPilot.autopilotTier || !AutoPilotUtils.isValidAutoPilotTier(autoPilot.autopilotTier)))
) { ) {
this.formErrors( this.formErrors(
`Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput` !this.hasAutoPilotV2FeatureFlag()
? `Please enter a value greater than ${AutoPilotUtils.minAutoPilotThroughput} for autopilot throughput`
: "Please select an Autopilot tier from the list."
); );
return false; return false;
} }
@@ -525,20 +575,33 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
private _getAutoPilot(): DataModels.AutoPilotCreationSettings { private _getAutoPilot(): DataModels.AutoPilotCreationSettings {
if ( if (
(!this.hasAutoPilotV2FeatureFlag() &&
this.keyspaceCreateNew() && this.keyspaceCreateNew() &&
this.keyspaceHasSharedOffer() && this.keyspaceHasSharedOffer() &&
this.isSharedAutoPilotSelected() && this.isSharedAutoPilotSelected() &&
this.sharedAutoPilotThroughput() this.sharedAutoPilotThroughput()) ||
(this.hasAutoPilotV2FeatureFlag() &&
this.keyspaceCreateNew() &&
this.keyspaceHasSharedOffer() &&
this.isSharedAutoPilotSelected() &&
this.selectedSharedAutoPilotTier())
) { ) {
return { return !this.hasAutoPilotV2FeatureFlag()
? {
maxThroughput: this.sharedAutoPilotThroughput() * 1 maxThroughput: this.sharedAutoPilotThroughput() * 1
}; }
: { autopilotTier: this.selectedSharedAutoPilotTier() };
} }
if (this.selectedAutoPilotThroughput()) { if (
return { (!this.hasAutoPilotV2FeatureFlag() && this.selectedAutoPilotThroughput()) ||
(this.hasAutoPilotV2FeatureFlag() && this.selectedAutoPilotTier())
) {
return !this.hasAutoPilotV2FeatureFlag()
? {
maxThroughput: this.selectedAutoPilotThroughput() * 1 maxThroughput: this.selectedAutoPilotThroughput() * 1
}; }
: { autopilotTier: this.selectedAutoPilotTier() };
} }
return undefined; return undefined;

View File

@@ -24,6 +24,7 @@
<span class="scaleSettingTitle">Scale</span> <span class="scaleSettingTitle">Scale</span>
</div> </div>
<div class="ssTextAllignment" id="scaleRegion"> <div class="ssTextAllignment" id="scaleRegion">
<!-- ko if: hasAutoPilotV2FeatureFlag && !hasAutoPilotV2FeatureFlag() -->
<throughput-input-autopilot-v3 <throughput-input-autopilot-v3
params="{ params="{
testId: testId, testId: testId,
@@ -50,6 +51,34 @@
}" }"
> >
</throughput-input-autopilot-v3> </throughput-input-autopilot-v3>
<!-- /ko -->
<!-- ko if: hasAutoPilotV2FeatureFlag && hasAutoPilotV2FeatureFlag() -->
<throughput-input
params="{
testId: testId,
class: 'scaleForm dirty',
value: throughput,
minimum: minRUs,
maximum: maxRUThroughputInputLimit,
canExceedMaximumValue: canThroughputExceedMaximumValue,
step: throughputIncreaseFactor,
label: throughputTitle,
ariaLabel: throughputAriaLabel,
costsVisible: costsVisible,
requestUnitsUsageCost: requestUnitsUsageCost,
throughputAutoPilotRadioId: throughputAutoPilotRadioId,
throughputProvisionedRadioId: throughputProvisionedRadioId,
throughputModeRadioName: throughputModeRadioName,
showAutoPilot: userCanChangeProvisioningTypes,
isAutoPilotSelected: isAutoPilotSelected,
autoPilotTiersList: autoPilotTiersList,
selectedAutoPilotTier: selectedAutoPilotTier,
autoPilotUsageCost: autoPilotUsageCost,
canExceedMaximumValue: canExceedMaximumValue
}"
>
</throughput-input>
<!-- /ko -->
<div class="estimatedCost" data-bind="visible: costsVisible"> <div class="estimatedCost" data-bind="visible: costsVisible">
<p data-bind="visible: minRUAnotationVisible"> <p data-bind="visible: minRUAnotationVisible">

View File

@@ -54,6 +54,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
// editables // editables
public isAutoPilotSelected: ViewModels.Editable<boolean>; public isAutoPilotSelected: ViewModels.Editable<boolean>;
public throughput: ViewModels.Editable<number>; public throughput: ViewModels.Editable<number>;
public selectedAutoPilotTier: ViewModels.Editable<DataModels.AutopilotTier>;
public autoPilotThroughput: ViewModels.Editable<number>; public autoPilotThroughput: ViewModels.Editable<number>;
public throughputIncreaseFactor: number = Constants.ClientDefaults.databaseThroughputIncreaseFactor; public throughputIncreaseFactor: number = Constants.ClientDefaults.databaseThroughputIncreaseFactor;
@@ -80,9 +81,11 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
public throughputTitle: ko.PureComputed<string>; public throughputTitle: ko.PureComputed<string>;
public throughputAriaLabel: ko.PureComputed<string>; public throughputAriaLabel: ko.PureComputed<string>;
public userCanChangeProvisioningTypes: ko.Observable<boolean>; public userCanChangeProvisioningTypes: ko.Observable<boolean>;
public autoPilotTiersList: ko.ObservableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>;
public autoPilotUsageCost: ko.PureComputed<string>; public autoPilotUsageCost: ko.PureComputed<string>;
public warningMessage: ko.Computed<string>; public warningMessage: ko.Computed<string>;
public canExceedMaximumValue: ko.PureComputed<boolean>; public canExceedMaximumValue: ko.PureComputed<boolean>;
public hasAutoPilotV2FeatureFlag: ko.PureComputed<boolean>;
public overrideWithAutoPilotSettings: ko.Computed<boolean>; public overrideWithAutoPilotSettings: ko.Computed<boolean>;
public overrideWithProvisionedThroughputSettings: ko.Computed<boolean>; public overrideWithProvisionedThroughputSettings: ko.Computed<boolean>;
public testId: string; public testId: string;
@@ -99,6 +102,9 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
super(options); super(options);
this.container = options.node && (options.node as ViewModels.Database).container; this.container = options.node && (options.node as ViewModels.Database).container;
this.hasAutoPilotV2FeatureFlag = ko.pureComputed(() => this.container.hasAutoPilotV2FeatureFlag());
this.selectedAutoPilotTier = editable.observable<DataModels.AutopilotTier>();
this.autoPilotTiersList = ko.observableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>();
this.canExceedMaximumValue = ko.pureComputed(() => this.container.canExceedMaximumValue()); this.canExceedMaximumValue = ko.pureComputed(() => this.container.canExceedMaximumValue());
// html element ids // html element ids
@@ -113,8 +119,8 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
this.autoPilotThroughput = editable.observable<number>(); this.autoPilotThroughput = editable.observable<number>();
const offer = this.database && this.database.offer && this.database.offer(); const offer = this.database && this.database.offer && this.database.offer();
const offerAutopilotSettings = offer && offer.content && offer.content.offerAutopilotSettings; const offerAutopilotSettings = offer && offer.content && offer.content.offerAutopilotSettings;
this.userCanChangeProvisioningTypes = ko.observable(true); this.userCanChangeProvisioningTypes = ko.observable(!!offerAutopilotSettings || !this.hasAutoPilotV2FeatureFlag());
if (!this.hasAutoPilotV2FeatureFlag()) {
if (offerAutopilotSettings && offerAutopilotSettings.maxThroughput) { if (offerAutopilotSettings && offerAutopilotSettings.maxThroughput) {
if (AutoPilotUtils.isValidAutoPilotThroughput(offerAutopilotSettings.maxThroughput)) { if (AutoPilotUtils.isValidAutoPilotThroughput(offerAutopilotSettings.maxThroughput)) {
this._wasAutopilotOriginallySet(true); this._wasAutopilotOriginallySet(true);
@@ -122,6 +128,16 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
this.autoPilotThroughput(offerAutopilotSettings.maxThroughput); this.autoPilotThroughput(offerAutopilotSettings.maxThroughput);
} }
} }
} else {
if (offerAutopilotSettings && offerAutopilotSettings.tier) {
if (AutoPilotUtils.isValidAutoPilotTier(offerAutopilotSettings.tier)) {
this._wasAutopilotOriginallySet(true);
this.isAutoPilotSelected(true);
this.selectedAutoPilotTier(offerAutopilotSettings.tier);
this.autoPilotTiersList(AutoPilotUtils.getAvailableAutoPilotTiersOptions(offerAutopilotSettings.tier));
}
}
}
this._hasProvisioningTypeChanged = ko.pureComputed<boolean>(() => { this._hasProvisioningTypeChanged = ko.pureComputed<boolean>(() => {
if (!this.userCanChangeProvisioningTypes()) { if (!this.userCanChangeProvisioningTypes()) {
@@ -134,11 +150,13 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
}); });
this.autoPilotUsageCost = ko.pureComputed<string>(() => { this.autoPilotUsageCost = ko.pureComputed<string>(() => {
const autoPilot = this.autoPilotThroughput(); const autoPilot = !this.hasAutoPilotV2FeatureFlag() ? this.autoPilotThroughput() : this.selectedAutoPilotTier();
if (!autoPilot) { if (!autoPilot) {
return ""; return "";
} }
return PricingUtils.getAutoPilotV3SpendHtml(autoPilot, true /* isDatabaseThroughput */); return !this.hasAutoPilotV2FeatureFlag()
? PricingUtils.getAutoPilotV3SpendHtml(autoPilot, true /* isDatabaseThroughput */)
: PricingUtils.getAutoPilotV2SpendHtml(autoPilot, true /* isDatabaseThroughput */);
}); });
this.requestUnitsUsageCost = ko.pureComputed(() => { this.requestUnitsUsageCost = ko.pureComputed(() => {
@@ -163,7 +181,8 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
this.overrideWithAutoPilotSettings() ? this.autoPilotThroughput() : this.throughput(), this.overrideWithAutoPilotSettings() ? this.autoPilotThroughput() : this.throughput(),
serverId, serverId,
regions, regions,
multimaster multimaster,
false /*rupmEnabled*/
); );
} else { } else {
estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml( estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml(
@@ -197,10 +216,16 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
}); });
this.overrideWithAutoPilotSettings = ko.pureComputed(() => { this.overrideWithAutoPilotSettings = ko.pureComputed(() => {
if (this.hasAutoPilotV2FeatureFlag()) {
return false;
}
return this._hasProvisioningTypeChanged() && this._wasAutopilotOriginallySet(); return this._hasProvisioningTypeChanged() && this._wasAutopilotOriginallySet();
}); });
this.overrideWithProvisionedThroughputSettings = ko.pureComputed(() => { this.overrideWithProvisionedThroughputSettings = ko.pureComputed(() => {
if (this.hasAutoPilotV2FeatureFlag()) {
return false;
}
return this._hasProvisioningTypeChanged() && !this._wasAutopilotOriginallySet(); return this._hasProvisioningTypeChanged() && !this._wasAutopilotOriginallySet();
}); });
@@ -258,7 +283,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
this.throughputTitle = ko.pureComputed<string>(() => { this.throughputTitle = ko.pureComputed<string>(() => {
if (this.isAutoPilotSelected()) { if (this.isAutoPilotSelected()) {
return AutoPilotUtils.getAutoPilotHeaderText(); return AutoPilotUtils.getAutoPilotHeaderText(this.hasAutoPilotV2FeatureFlag());
} }
return `Throughput (${this.minRUs().toLocaleString()} - unlimited RU/s)`; return `Throughput (${this.minRUs().toLocaleString()} - unlimited RU/s)`;
@@ -281,7 +306,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
this.warningMessage = ko.computed<string>(() => { this.warningMessage = ko.computed<string>(() => {
const offer = this.database && this.database.offer && this.database.offer(); const offer = this.database && this.database.offer && this.database.offer();
if (this.overrideWithProvisionedThroughputSettings()) { if (!this.hasAutoPilotV2FeatureFlag() && this.overrideWithProvisionedThroughputSettings()) {
return AutoPilotUtils.manualToAutoscaleDisclaimer; return AutoPilotUtils.manualToAutoscaleDisclaimer;
} }
@@ -291,7 +316,9 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
!!(offer as DataModels.OfferWithHeaders).headers[Constants.HttpHeaders.offerReplacePending] !!(offer as DataModels.OfferWithHeaders).headers[Constants.HttpHeaders.offerReplacePending]
) { ) {
const throughput = offer.content.offerAutopilotSettings const throughput = offer.content.offerAutopilotSettings
? !this.hasAutoPilotV2FeatureFlag()
? offer.content.offerAutopilotSettings.maxThroughput ? offer.content.offerAutopilotSettings.maxThroughput
: offer.content.offerAutopilotSettings.maximumTierThroughput
: offer.content.offerThroughput; : offer.content.offerThroughput;
return throughputApplyShortDelayMessage(this.isAutoPilotSelected(), throughput, this.database.id()); return throughputApplyShortDelayMessage(this.isAutoPilotSelected(), throughput, this.database.id());
@@ -348,13 +375,20 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
const isAutoPilot = this.isAutoPilotSelected(); const isAutoPilot = this.isAutoPilotSelected();
const isManual = !this.isAutoPilotSelected(); const isManual = !this.isAutoPilotSelected();
if (isAutoPilot) { if (isAutoPilot) {
if (!AutoPilotUtils.isValidAutoPilotThroughput(this.autoPilotThroughput())) { if (
(!this.hasAutoPilotV2FeatureFlag() &&
!AutoPilotUtils.isValidAutoPilotThroughput(this.autoPilotThroughput())) ||
(this.hasAutoPilotV2FeatureFlag() && !AutoPilotUtils.isValidAutoPilotTier(this.selectedAutoPilotTier()))
) {
return false; return false;
} }
if (this.isAutoPilotSelected.editableIsDirty()) { if (this.isAutoPilotSelected.editableIsDirty()) {
return true; return true;
} }
if (this.autoPilotThroughput.editableIsDirty()) { if (
(!this.hasAutoPilotV2FeatureFlag() && this.autoPilotThroughput.editableIsDirty()) ||
(this.hasAutoPilotV2FeatureFlag() && this.selectedAutoPilotTier.editableIsDirty())
) {
return true; return true;
} }
} }
@@ -378,7 +412,10 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
return true; return true;
} }
if (this.isAutoPilotSelected.editableIsDirty()) { if (
(!this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected.editableIsDirty()) ||
(this.hasAutoPilotV2FeatureFlag() && this.selectedAutoPilotTier.editableIsDirty())
) {
return true; return true;
} }
} }
@@ -459,7 +496,8 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
databaseAccountName: userContext.databaseAccount.name, databaseAccountName: userContext.databaseAccount.name,
resourceGroup: userContext.resourceGroup, resourceGroup: userContext.resourceGroup,
databaseName: this.database.id(), databaseName: this.database.id(),
throughput: newThroughput throughput: newThroughput,
offerIsRUPerMinuteThroughputEnabled: false
}; };
await updateOfferThroughputBeyondLimit(requestPayload); await updateOfferThroughputBeyondLimit(requestPayload);
this.database.offer().content.offerThroughput = originalThroughputValue; this.database.offer().content.offerThroughput = originalThroughputValue;
@@ -509,7 +547,11 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
public onRevertClick = (): Q.Promise<any> => { public onRevertClick = (): Q.Promise<any> => {
this.throughput.setBaseline(this.throughput.getEditableOriginalValue()); this.throughput.setBaseline(this.throughput.getEditableOriginalValue());
this.isAutoPilotSelected.setBaseline(this.isAutoPilotSelected.getEditableOriginalValue()); this.isAutoPilotSelected.setBaseline(this.isAutoPilotSelected.getEditableOriginalValue());
if (!this.hasAutoPilotV2FeatureFlag()) {
this.autoPilotThroughput.setBaseline(this.autoPilotThroughput.getEditableOriginalValue()); this.autoPilotThroughput.setBaseline(this.autoPilotThroughput.getEditableOriginalValue());
} else {
this.selectedAutoPilotTier.setBaseline(this.selectedAutoPilotTier.getEditableOriginalValue());
}
return Q(); return Q();
}; };
@@ -527,12 +569,18 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
const offerAutopilotSettings = offer && offer.content && offer.content.offerAutopilotSettings; const offerAutopilotSettings = offer && offer.content && offer.content.offerAutopilotSettings;
this.throughput.setBaseline(offerThroughput); this.throughput.setBaseline(offerThroughput);
this.userCanChangeProvisioningTypes(true); this.userCanChangeProvisioningTypes(!!offerAutopilotSettings || !this.hasAutoPilotV2FeatureFlag());
if (this.hasAutoPilotV2FeatureFlag()) {
const selectedAutoPilotTier = offerAutopilotSettings && offerAutopilotSettings.tier;
this.isAutoPilotSelected.setBaseline(AutoPilotUtils.isValidAutoPilotTier(selectedAutoPilotTier));
this.selectedAutoPilotTier.setBaseline(selectedAutoPilotTier);
} else {
const maxThroughputForAutoPilot = offerAutopilotSettings && offerAutopilotSettings.maxThroughput; const maxThroughputForAutoPilot = offerAutopilotSettings && offerAutopilotSettings.maxThroughput;
this.isAutoPilotSelected.setBaseline(AutoPilotUtils.isValidAutoPilotThroughput(maxThroughputForAutoPilot)); this.isAutoPilotSelected.setBaseline(AutoPilotUtils.isValidAutoPilotThroughput(maxThroughputForAutoPilot));
this.autoPilotThroughput.setBaseline(maxThroughputForAutoPilot || AutoPilotUtils.minAutoPilotThroughput); this.autoPilotThroughput.setBaseline(maxThroughputForAutoPilot || AutoPilotUtils.minAutoPilotThroughput);
} }
}
protected getTabsButtons(): CommandButtonComponentProps[] { protected getTabsButtons(): CommandButtonComponentProps[] {
const buttons: CommandButtonComponentProps[] = []; const buttons: CommandButtonComponentProps[] = [];

View File

@@ -51,6 +51,7 @@
<div class="ssTextAllignment" data-bind="visible: scaleExpanded" id="scaleRegion"> <div class="ssTextAllignment" data-bind="visible: scaleExpanded" id="scaleRegion">
<!-- ko ifnot: isAutoScaleEnabled --> <!-- ko ifnot: isAutoScaleEnabled -->
<!-- ko if: hasAutoPilotV2FeatureFlag && !hasAutoPilotV2FeatureFlag() -->
<throughput-input-autopilot-v3 <throughput-input-autopilot-v3
params="{ params="{
testId: testId, testId: testId,
@@ -76,10 +77,94 @@
}" }"
> >
</throughput-input-autopilot-v3> </throughput-input-autopilot-v3>
<!-- /ko -->
<!-- ko if: hasAutoPilotV2FeatureFlag && hasAutoPilotV2FeatureFlag() -->
<throughput-input
params="{
testId: testId,
class: 'scaleForm dirty',
value: throughput,
minimum: minRUs,
maximum: maxRUThroughputInputLimit,
isEnabled: !hasDatabaseSharedThroughput(),
canExceedMaximumValue: canThroughputExceedMaximumValue,
label: throughputTitle,
ariaLabel: throughputAriaLabel,
costsVisible: costsVisible,
requestUnitsUsageCost: requestUnitsUsageCost,
throughputAutoPilotRadioId: throughputAutoPilotRadioId,
throughputProvisionedRadioId: throughputProvisionedRadioId,
throughputModeRadioName: throughputModeRadioName,
showAutoPilot: userCanChangeProvisioningTypes,
isAutoPilotSelected: isAutoPilotSelected,
autoPilotTiersList: autoPilotTiersList,
selectedAutoPilotTier: selectedAutoPilotTier,
autoPilotUsageCost: autoPilotUsageCost,
canExceedMaximumValue: canExceedMaximumValue
}"
>
</throughput-input>
<!-- /ko -->
<div class="storageCapacityTitle throughputStorageValue" data-bind="html: storageCapacityTitle"></div> <div class="storageCapacityTitle throughputStorageValue" data-bind="html: storageCapacityTitle"></div>
<!-- /ko --> <!-- /ko -->
<div data-bind="visible: rupmVisible">
<div class="formTitle">RU/m</div>
<div class="tabs" aria-label="RU/m">
<div class="tab">
<label
data-bind="
attr:{
for: rupmOnId
},
css: {
dirty: rupm.editableIsDirty,
selectedRadio: rupm() === 'on',
unselectedRadio: rupm() !== 'on'
}"
>On</label
>
<input
type="radio"
name="rupm"
value="on"
class="radio"
data-bind="
attr:{
id: rupmOnId
},
checked: rupm"
/>
</div>
<div class="tab">
<label
data-bind="
attr:{
for: rupmOffId
},
css: {
dirty: rupm.editableIsDirty,
selectedRadio: rupm() === 'off',
unselectedRadio: rupm() !== 'off'
}"
>Off</label
>
<input
type="radio"
name="rupm"
value="off"
class="radio"
data-bind="
attr:{
id: rupmOffId
},
checked: rupm"
/>
</div>
</div>
</div>
<!-- TODO: Replace link with call to the Azure Support blade --> <!-- TODO: Replace link with call to the Azure Support blade -->
<div data-bind="visible: isAutoScaleEnabled"> <div data-bind="visible: isAutoScaleEnabled">
<div class="autoScaleThroughputTitle">Throughput (RU/s)</div> <div class="autoScaleThroughputTitle">Throughput (RU/s)</div>

View File

@@ -79,6 +79,7 @@ describe("Settings tab", () => {
beforeEach(() => { beforeEach(() => {
explorer = new Explorer(); explorer = new Explorer();
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
}); });
it("single master, should not show conflict resolution", () => { it("single master, should not show conflict resolution", () => {
@@ -177,6 +178,7 @@ describe("Settings tab", () => {
beforeEach(() => { beforeEach(() => {
explorer = new Explorer(); explorer = new Explorer();
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
}); });
it("On TTL changed", () => { it("On TTL changed", () => {
@@ -249,6 +251,7 @@ describe("Settings tab", () => {
beforeEach(() => { beforeEach(() => {
explorer = new Explorer(); explorer = new Explorer();
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
}); });
it("null if it didnt change", () => { it("null if it didnt change", () => {
@@ -324,6 +327,7 @@ describe("Settings tab", () => {
function getCollection(defaultApi: string, partitionKeyOption: PartitionKeyOption) { function getCollection(defaultApi: string, partitionKeyOption: PartitionKeyOption) {
const explorer = new Explorer(); const explorer = new Explorer();
explorer.defaultExperience(defaultApi); explorer.defaultExperience(defaultApi);
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
const offer: DataModels.Offer = null; const offer: DataModels.Offer = null;
const defaultTtl = 200; const defaultTtl = 200;
@@ -446,4 +450,158 @@ describe("Settings tab", () => {
expect(settingsTab.partitionKeyVisible()).toBe(false); expect(settingsTab.partitionKeyVisible()).toBe(false);
}); });
}); });
describe("AutoPilot", () => {
function getCollection(autoPilotTier: DataModels.AutopilotTier) {
const explorer = new Explorer();
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
explorer.databaseAccount({
id: "test",
kind: "",
location: "",
name: "",
tags: "",
type: "",
properties: {
enableMultipleWriteLocations: true,
documentEndpoint: "",
cassandraEndpoint: "",
gremlinEndpoint: "",
tableEndpoint: ""
}
});
const offer: DataModels.Offer = {
id: "test",
_etag: "_etag",
_rid: "_rid",
_self: "_self",
_ts: "_ts",
content: {
offerThroughput: 0,
offerIsRUPerMinuteThroughputEnabled: false,
offerAutopilotSettings: {
tier: autoPilotTier
}
}
};
const container: DataModels.Collection = {
_rid: "_rid",
_self: "",
_etag: "",
_ts: 0,
id: "mycoll",
conflictResolutionPolicy: {
mode: DataModels.ConflictResolutionMode.LastWriterWins,
conflictResolutionPath: "/_ts"
}
};
return new Collection(explorer, "mydb", container, quotaInfo, offer);
}
function getSettingsTab(autoPilotTier: DataModels.AutopilotTier = DataModels.AutopilotTier.Tier1): SettingsTab {
return new SettingsTab({
tabKind: ViewModels.CollectionTabKind.Settings,
title: "Scale & Settings",
tabPath: "",
hashLocation: "",
isActive: ko.observable(false),
collection: getCollection(autoPilotTier),
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {}
});
}
describe("Visible", () => {
it("no autopilot configured, should not be visible", () => {
const settingsTab1 = getSettingsTab(0);
expect(settingsTab1.isAutoPilotSelected()).toBe(false);
const settingsTab2 = getSettingsTab(2);
expect(settingsTab2.isAutoPilotSelected()).toBe(true);
});
});
describe("Autopilot Save", () => {
it("edit with valid new tier, save should be enabled", () => {
const settingsTab = getSettingsTab(DataModels.AutopilotTier.Tier2);
expect(settingsTab.saveSettingsButton.enabled()).toBe(false);
settingsTab.selectedAutoPilotTier(DataModels.AutopilotTier.Tier3);
expect(settingsTab.saveSettingsButton.enabled()).toBe(true);
settingsTab.selectedAutoPilotTier(DataModels.AutopilotTier.Tier2);
expect(settingsTab.saveSettingsButton.enabled()).toBe(false);
});
it("edit with same tier, save should be disabled", () => {
const settingsTab = getSettingsTab(DataModels.AutopilotTier.Tier2);
expect(settingsTab.saveSettingsButton.enabled()).toBe(false);
settingsTab.selectedAutoPilotTier(DataModels.AutopilotTier.Tier2);
expect(settingsTab.saveSettingsButton.enabled()).toBe(false);
});
it("edit with invalid tier, save should be disabled", () => {
const settingsTab = getSettingsTab(DataModels.AutopilotTier.Tier2);
expect(settingsTab.saveSettingsButton.enabled()).toBe(false);
settingsTab.selectedAutoPilotTier(5);
expect(settingsTab.saveSettingsButton.enabled()).toBe(false);
});
});
describe("Autopilot Discard", () => {
it("edit tier, discard should be enabled and correctly dicard", () => {
const settingsTab = getSettingsTab(DataModels.AutopilotTier.Tier2);
expect(settingsTab.discardSettingsChangesButton.enabled()).toBe(false);
settingsTab.selectedAutoPilotTier(DataModels.AutopilotTier.Tier3);
expect(settingsTab.discardSettingsChangesButton.enabled()).toBe(true);
settingsTab.onRevertClick();
expect(settingsTab.selectedAutoPilotTier()).toBe(DataModels.AutopilotTier.Tier2);
settingsTab.selectedAutoPilotTier(0);
expect(settingsTab.discardSettingsChangesButton.enabled()).toBe(true);
settingsTab.onRevertClick();
expect(settingsTab.selectedAutoPilotTier()).toBe(DataModels.AutopilotTier.Tier2);
});
});
it("On TTL changed", () => {
const settingsTab = getSettingsTab(DataModels.AutopilotTier.Tier1);
expect(settingsTab.saveSettingsButton.enabled()).toBe(false);
settingsTab.timeToLive("on");
expect(settingsTab.saveSettingsButton.enabled()).toBe(true);
expect(settingsTab.discardSettingsChangesButton.enabled()).toBe(true);
});
it("On Index Policy changed", () => {
const settingsTab = getSettingsTab(DataModels.AutopilotTier.Tier1);
expect(settingsTab.saveSettingsButton.enabled()).toBe(false);
settingsTab.indexingPolicyContent({ somethingDifferent: "" });
expect(settingsTab.saveSettingsButton.enabled()).toBe(true);
expect(settingsTab.discardSettingsChangesButton.enabled()).toBe(true);
});
it("On Conflict Resolution Mode changed", () => {
const settingsTab = getSettingsTab(DataModels.AutopilotTier.Tier1);
expect(settingsTab.saveSettingsButton.enabled()).toBe(false);
settingsTab.conflictResolutionPolicyPath("/somethingElse");
expect(settingsTab.saveSettingsButton.enabled()).toBe(true);
expect(settingsTab.discardSettingsChangesButton.enabled()).toBe(true);
settingsTab.onRevertClick();
expect(settingsTab.saveSettingsButton.enabled()).toBe(false);
settingsTab.conflictResolutionPolicyMode(DataModels.ConflictResolutionMode.Custom);
settingsTab.conflictResolutionPolicyProcedure("resolver");
expect(settingsTab.saveSettingsButton.enabled()).toBe(true);
expect(settingsTab.discardSettingsChangesButton.enabled()).toBe(true);
});
});
}); });

View File

@@ -143,9 +143,11 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
public geospatialVisible: ko.Computed<boolean>; public geospatialVisible: ko.Computed<boolean>;
public indexingPolicyContent: ViewModels.Editable<any>; public indexingPolicyContent: ViewModels.Editable<any>;
public isIndexingPolicyEditorInitializing: ko.Observable<boolean>; public isIndexingPolicyEditorInitializing: ko.Observable<boolean>;
public rupm: ViewModels.Editable<string>;
public conflictResolutionPolicyMode: ViewModels.Editable<string>; public conflictResolutionPolicyMode: ViewModels.Editable<string>;
public conflictResolutionPolicyPath: ViewModels.Editable<string>; public conflictResolutionPolicyPath: ViewModels.Editable<string>;
public conflictResolutionPolicyProcedure: ViewModels.Editable<string>; public conflictResolutionPolicyProcedure: ViewModels.Editable<string>;
public hasAutoPilotV2FeatureFlag: ko.PureComputed<boolean>;
public saveSettingsButton: ViewModels.Button; public saveSettingsButton: ViewModels.Button;
public discardSettingsChangesButton: ViewModels.Button; public discardSettingsChangesButton: ViewModels.Button;
@@ -181,6 +183,9 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
public partitionKeyValue: ko.Observable<string>; public partitionKeyValue: ko.Observable<string>;
public isLargePartitionKeyEnabled: ko.Computed<boolean>; public isLargePartitionKeyEnabled: ko.Computed<boolean>;
public requestUnitsUsageCost: ko.Computed<string>; public requestUnitsUsageCost: ko.Computed<string>;
public rupmOnId: string;
public rupmOffId: string;
public rupmVisible: ko.Computed<boolean>;
public scaleExpanded: ko.Observable<boolean>; public scaleExpanded: ko.Observable<boolean>;
public settingsExpanded: ko.Observable<boolean>; public settingsExpanded: ko.Observable<boolean>;
public shouldDisplayPortalUsePrompt: ko.Computed<boolean>; public shouldDisplayPortalUsePrompt: ko.Computed<boolean>;
@@ -200,6 +205,8 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
public userCanChangeProvisioningTypes: ko.Observable<boolean>; public userCanChangeProvisioningTypes: ko.Observable<boolean>;
public warningMessage: ko.Computed<string>; public warningMessage: ko.Computed<string>;
public shouldShowKeyspaceSharedThroughputMessage: ko.Computed<boolean>; public shouldShowKeyspaceSharedThroughputMessage: ko.Computed<boolean>;
public autoPilotTiersList: ko.ObservableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>;
public selectedAutoPilotTier: ko.Observable<DataModels.AutopilotTier>;
public isAutoPilotSelected: ko.Observable<boolean>; public isAutoPilotSelected: ko.Observable<boolean>;
public autoPilotThroughput: ko.Observable<number>; public autoPilotThroughput: ko.Observable<number>;
public autoPilotUsageCost: ko.Computed<string>; public autoPilotUsageCost: ko.Computed<string>;
@@ -225,6 +232,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
super(options); super(options);
this.container = options.collection && options.collection.container; this.container = options.collection && options.collection.container;
this.isIndexingPolicyEditorInitializing = ko.observable<boolean>(false); this.isIndexingPolicyEditorInitializing = ko.observable<boolean>(false);
this.hasAutoPilotV2FeatureFlag = ko.pureComputed(() => this.container.hasAutoPilotV2FeatureFlag());
this.canExceedMaximumValue = ko.pureComputed(() => this.container.canExceedMaximumValue()); this.canExceedMaximumValue = ko.pureComputed(() => this.container.canExceedMaximumValue());
@@ -237,6 +245,8 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
this.ttlOnId = `ttlOn${this.tabId}`; this.ttlOnId = `ttlOn${this.tabId}`;
this.changeFeedPolicyOffId = `changeFeedOff${this.tabId}`; this.changeFeedPolicyOffId = `changeFeedOff${this.tabId}`;
this.changeFeedPolicyOnId = `changeFeedOn${this.tabId}`; this.changeFeedPolicyOnId = `changeFeedOn${this.tabId}`;
this.rupmOnId = `rupmOn${this.tabId}`;
this.rupmOffId = `rupmOff${this.tabId}`;
this.conflictResolutionPolicyModeCustom = `conflictResolutionPolicyModeCustom${this.tabId}`; this.conflictResolutionPolicyModeCustom = `conflictResolutionPolicyModeCustom${this.tabId}`;
this.conflictResolutionPolicyModeLWW = `conflictResolutionPolicyModeLWW${this.tabId}`; this.conflictResolutionPolicyModeLWW = `conflictResolutionPolicyModeLWW${this.tabId}`;
this.conflictResolutionPolicyModeCRDT = `conflictResolutionPolicyModeCRDT${this.tabId}`; this.conflictResolutionPolicyModeCRDT = `conflictResolutionPolicyModeCRDT${this.tabId}`;
@@ -268,6 +278,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
this.analyticalStorageTtlSelection = editable.observable<string>(); this.analyticalStorageTtlSelection = editable.observable<string>();
this.analyticalStorageTtlSeconds = editable.observable<number>(); this.analyticalStorageTtlSeconds = editable.observable<number>();
this.indexingPolicyContent = editable.observable<any>(); this.indexingPolicyContent = editable.observable<any>();
this.rupm = editable.observable<string>();
// Mongo container with system partition key still treat as "Fixed" // Mongo container with system partition key still treat as "Fixed"
this._isFixedContainer = ko.pureComputed( this._isFixedContainer = ko.pureComputed(
() => () =>
@@ -277,12 +288,15 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
this.isAutoPilotSelected = ko.observable(false); this.isAutoPilotSelected = ko.observable(false);
this._wasAutopilotOriginallySet = ko.observable(false); this._wasAutopilotOriginallySet = ko.observable(false);
this.selectedAutoPilotTier = ko.observable<DataModels.AutopilotTier>();
this.autoPilotTiersList = ko.observableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>();
this.autoPilotThroughput = ko.observable<number>(AutoPilotUtils.minAutoPilotThroughput); this.autoPilotThroughput = ko.observable<number>(AutoPilotUtils.minAutoPilotThroughput);
const offer = this.collection && this.collection.offer && this.collection.offer(); const offer = this.collection && this.collection.offer && this.collection.offer();
const offerAutopilotSettings = offer && offer.content && offer.content.offerAutopilotSettings; const offerAutopilotSettings = offer && offer.content && offer.content.offerAutopilotSettings;
this.userCanChangeProvisioningTypes = ko.observable(true); this.userCanChangeProvisioningTypes = ko.observable(!!offerAutopilotSettings || !this.hasAutoPilotV2FeatureFlag());
if (!this.hasAutoPilotV2FeatureFlag()) {
if (offerAutopilotSettings && offerAutopilotSettings.maxThroughput) { if (offerAutopilotSettings && offerAutopilotSettings.maxThroughput) {
if (AutoPilotUtils.isValidAutoPilotThroughput(offerAutopilotSettings.maxThroughput)) { if (AutoPilotUtils.isValidAutoPilotThroughput(offerAutopilotSettings.maxThroughput)) {
this.isAutoPilotSelected(true); this.isAutoPilotSelected(true);
@@ -290,6 +304,17 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
this.autoPilotThroughput(offerAutopilotSettings.maxThroughput); this.autoPilotThroughput(offerAutopilotSettings.maxThroughput);
} }
} }
} else {
if (offerAutopilotSettings && offerAutopilotSettings.tier) {
if (AutoPilotUtils.isValidAutoPilotTier(offerAutopilotSettings.tier)) {
this.isAutoPilotSelected(true);
this._wasAutopilotOriginallySet(true);
this.selectedAutoPilotTier(offerAutopilotSettings.tier);
const availableAutoPilotTiers = AutoPilotUtils.getAvailableAutoPilotTiersOptions(offerAutopilotSettings.tier);
this.autoPilotTiersList(availableAutoPilotTiers);
}
}
}
this._hasProvisioningTypeChanged = ko.pureComputed<boolean>(() => { this._hasProvisioningTypeChanged = ko.pureComputed<boolean>(() => {
if (!this.userCanChangeProvisioningTypes()) { if (!this.userCanChangeProvisioningTypes()) {
@@ -302,10 +327,16 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
}); });
this.overrideWithAutoPilotSettings = ko.pureComputed(() => { this.overrideWithAutoPilotSettings = ko.pureComputed(() => {
if (this.hasAutoPilotV2FeatureFlag()) {
return false;
}
return this._hasProvisioningTypeChanged() && this._wasAutopilotOriginallySet(); return this._hasProvisioningTypeChanged() && this._wasAutopilotOriginallySet();
}); });
this.overrideWithProvisionedThroughputSettings = ko.pureComputed(() => { this.overrideWithProvisionedThroughputSettings = ko.pureComputed(() => {
if (this.hasAutoPilotV2FeatureFlag()) {
return false;
}
return this._hasProvisioningTypeChanged() && !this._wasAutopilotOriginallySet(); return this._hasProvisioningTypeChanged() && !this._wasAutopilotOriginallySet();
}); });
@@ -317,18 +348,25 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
if (!originalAutoPilotSettings) { if (!originalAutoPilotSettings) {
return false; return false;
} }
const originalAutoPilotSetting = originalAutoPilotSettings && originalAutoPilotSettings.maxThroughput; const originalAutoPilotSetting = !this.hasAutoPilotV2FeatureFlag()
if (this.autoPilotThroughput() !== originalAutoPilotSetting) { ? originalAutoPilotSettings && originalAutoPilotSettings.maxThroughput
: originalAutoPilotSettings && originalAutoPilotSettings.tier;
if (
(!this.hasAutoPilotV2FeatureFlag() && this.autoPilotThroughput() != originalAutoPilotSetting) ||
(this.hasAutoPilotV2FeatureFlag() && this.selectedAutoPilotTier() !== originalAutoPilotSetting)
) {
return true; return true;
} }
return false; return false;
}); });
this.autoPilotUsageCost = ko.pureComputed<string>(() => { this.autoPilotUsageCost = ko.pureComputed<string>(() => {
const autoPilot = this.autoPilotThroughput(); const autoPilot = !this.hasAutoPilotV2FeatureFlag() ? this.autoPilotThroughput() : this.selectedAutoPilotTier();
if (!autoPilot) { if (!autoPilot) {
return ""; return "";
} }
return PricingUtils.getAutoPilotV3SpendHtml(autoPilot, false /* isDatabaseThroughput */); return !this.hasAutoPilotV2FeatureFlag()
? PricingUtils.getAutoPilotV3SpendHtml(autoPilot, false /* isDatabaseThroughput */)
: PricingUtils.getAutoPilotV2SpendHtml(autoPilot, false /* isDatabaseThroughput */);
}); });
this.requestUnitsUsageCost = ko.pureComputed(() => { this.requestUnitsUsageCost = ko.pureComputed(() => {
@@ -339,6 +377,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
const serverId: string = this.container.serverId(); const serverId: string = this.container.serverId();
const offerThroughput: number = this.throughput(); const offerThroughput: number = this.throughput();
const rupmEnabled = this.rupm() === Constants.RUPMStates.on;
const regions = const regions =
(account && (account &&
@@ -356,7 +395,8 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
this.overrideWithAutoPilotSettings() ? this.autoPilotThroughput() : offerThroughput, this.overrideWithAutoPilotSettings() ? this.autoPilotThroughput() : offerThroughput,
serverId, serverId,
regions, regions,
multimaster multimaster,
rupmEnabled
); );
} else { } else {
estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml( estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml(
@@ -413,6 +453,32 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
); );
}); });
this.rupmVisible = ko.computed(() => {
if (configContext.platform === Platform.Emulator) {
return false;
}
if (this.container.isFeatureEnabled(Constants.Features.enableRupm)) {
return true;
}
for (let i = 0, len = this.container.databases().length; i < len; i++) {
for (let j = 0, len2 = this.container.databases()[i].collections().length; j < len2; j++) {
const collectionOffer = this.container
.databases()
[i].collections()
[j].offer();
if (
collectionOffer &&
collectionOffer.content &&
collectionOffer.content.offerIsRUPerMinuteThroughputEnabled
) {
return true;
}
}
}
return false;
});
this.ttlVisible = ko.computed(() => { this.ttlVisible = ko.computed(() => {
return (this.container && !this.container.isPreferredApiCassandra()) || false; return (this.container && !this.container.isPreferredApiCassandra()) || false;
}); });
@@ -543,7 +609,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
this.throughputTitle = ko.pureComputed<string>(() => { this.throughputTitle = ko.pureComputed<string>(() => {
if (this.isAutoPilotSelected()) { if (this.isAutoPilotSelected()) {
return AutoPilotUtils.getAutoPilotHeaderText(); return AutoPilotUtils.getAutoPilotHeaderText(this.hasAutoPilotV2FeatureFlag());
} }
const minThroughput: string = this.minRUs().toLocaleString(); const minThroughput: string = this.minRUs().toLocaleString();
@@ -624,7 +690,12 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
return true; return true;
} else if (this.isAutoPilotSelected()) { } else if (this.isAutoPilotSelected()) {
const validAutopilotChange = const validAutopilotChange =
this._isAutoPilotDirty() && AutoPilotUtils.isValidAutoPilotThroughput(this.autoPilotThroughput()); (!this.hasAutoPilotV2FeatureFlag() &&
this._isAutoPilotDirty() &&
AutoPilotUtils.isValidAutoPilotThroughput(this.autoPilotThroughput())) ||
(this.hasAutoPilotV2FeatureFlag() &&
this._isAutoPilotDirty() &&
AutoPilotUtils.isValidAutoPilotTier(this.selectedAutoPilotTier()));
if (validAutopilotChange) { if (validAutopilotChange) {
return true; return true;
} }
@@ -678,6 +749,14 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
return false; return false;
} }
if (
this.rupm() === Constants.RUPMStates.on &&
this.throughput() >
SharedConstants.CollectionCreation.MaxRUPMPerPartition * this.collection.quotaInfo()?.numPartitions
) {
return false;
}
if (this.timeToLive.editableIsDirty()) { if (this.timeToLive.editableIsDirty()) {
return true; return true;
} }
@@ -706,6 +785,10 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
return true; return true;
} }
if (this.rupm.editableIsDirty()) {
return true;
}
return false; return false;
}), }),
@@ -755,6 +838,10 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
return true; return true;
} }
if (this.rupm.editableIsDirty()) {
return true;
}
if ( if (
this.conflictResolutionPolicyMode.editableIsDirty() || this.conflictResolutionPolicyMode.editableIsDirty() ||
this.conflictResolutionPolicyPath.editableIsDirty() || this.conflictResolutionPolicyPath.editableIsDirty() ||
@@ -814,8 +901,14 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
offer.hasOwnProperty("headers") && offer.hasOwnProperty("headers") &&
!!(offer as DataModels.OfferWithHeaders).headers[Constants.HttpHeaders.offerReplacePending] !!(offer as DataModels.OfferWithHeaders).headers[Constants.HttpHeaders.offerReplacePending]
) { ) {
if (AutoPilotUtils.isValidV2AutoPilotOffer(offer)) {
return "Tier upgrade will take some time to complete.";
}
const throughput = offer.content.offerAutopilotSettings const throughput = offer.content.offerAutopilotSettings
? !this.hasAutoPilotV2FeatureFlag()
? offer.content.offerAutopilotSettings.maxThroughput ? offer.content.offerAutopilotSettings.maxThroughput
: offer.content.offerAutopilotSettings.maximumTierThroughput
: undefined; : undefined;
const targetThroughput = const targetThroughput =
@@ -834,7 +927,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
); );
} }
if (this.overrideWithProvisionedThroughputSettings()) { if (!this.hasAutoPilotV2FeatureFlag() && this.overrideWithProvisionedThroughputSettings()) {
return AutoPilotUtils.manualToAutoscaleDisclaimer; return AutoPilotUtils.manualToAutoscaleDisclaimer;
} }
@@ -998,17 +1091,25 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
} }
} }
if (this.throughput.editableIsDirty() || this._isAutoPilotDirty() || this._hasProvisioningTypeChanged()) { if (
this.throughput.editableIsDirty() ||
this.rupm.editableIsDirty() ||
this._isAutoPilotDirty() ||
this._hasProvisioningTypeChanged()
) {
const newThroughput = this.throughput(); const newThroughput = this.throughput();
const isRUPerMinuteThroughputEnabled: boolean = this.rupm() === Constants.RUPMStates.on;
let newOffer: DataModels.Offer = _.extend({}, this.collection.offer()); let newOffer: DataModels.Offer = _.extend({}, this.collection.offer());
const originalThroughputValue: number = this.throughput.getEditableOriginalValue(); const originalThroughputValue: number = this.throughput.getEditableOriginalValue();
if (newOffer.content) { if (newOffer.content) {
newOffer.content.offerThroughput = newThroughput; newOffer.content.offerThroughput = newThroughput;
newOffer.content.offerIsRUPerMinuteThroughputEnabled = isRUPerMinuteThroughputEnabled;
} else { } else {
newOffer = _.extend({}, newOffer, { newOffer = _.extend({}, newOffer, {
content: { content: {
offerThroughput: newThroughput offerThroughput: newThroughput,
offerIsRUPerMinuteThroughputEnabled: isRUPerMinuteThroughputEnabled
} }
}); });
} }
@@ -1016,12 +1117,18 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
const headerOptions: RequestOptions = { initialHeaders: {} }; const headerOptions: RequestOptions = { initialHeaders: {} };
if (this.isAutoPilotSelected()) { if (this.isAutoPilotSelected()) {
if (!this.hasAutoPilotV2FeatureFlag()) {
newOffer.content.offerAutopilotSettings = { newOffer.content.offerAutopilotSettings = {
maxThroughput: this.autoPilotThroughput() maxThroughput: this.autoPilotThroughput()
}; };
} else {
newOffer.content.offerAutopilotSettings = {
tier: this.selectedAutoPilotTier()
};
}
// user has changed from provisioned --> autoscale // user has changed from provisioned --> autoscale
if (this._hasProvisioningTypeChanged()) { if (!this.hasAutoPilotV2FeatureFlag() && this._hasProvisioningTypeChanged()) {
headerOptions.initialHeaders[Constants.HttpHeaders.migrateOfferToAutopilot] = "true"; headerOptions.initialHeaders[Constants.HttpHeaders.migrateOfferToAutopilot] = "true";
delete newOffer.content.offerAutopilotSettings; delete newOffer.content.offerAutopilotSettings;
} else { } else {
@@ -1029,10 +1136,10 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
} }
} else { } else {
this.isAutoPilotSelected(false); this.isAutoPilotSelected(false);
this.userCanChangeProvisioningTypes(true); this.userCanChangeProvisioningTypes(false || !this.hasAutoPilotV2FeatureFlag());
// user has changed from autoscale --> provisioned // user has changed from autoscale --> provisioned
if (this._hasProvisioningTypeChanged()) { if (!this.hasAutoPilotV2FeatureFlag() && this._hasProvisioningTypeChanged()) {
headerOptions.initialHeaders[Constants.HttpHeaders.migrateOfferToManualThroughput] = "true"; headerOptions.initialHeaders[Constants.HttpHeaders.migrateOfferToManualThroughput] = "true";
} else { } else {
delete newOffer.content.offerAutopilotSettings; delete newOffer.content.offerAutopilotSettings;
@@ -1050,7 +1157,8 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
resourceGroup: userContext.resourceGroup, resourceGroup: userContext.resourceGroup,
databaseName: this.collection.databaseId, databaseName: this.collection.databaseId,
collectionName: this.collection.id(), collectionName: this.collection.id(),
throughput: newThroughput throughput: newThroughput,
offerIsRUPerMinuteThroughputEnabled: isRUPerMinuteThroughputEnabled
}; };
await updateOfferThroughputBeyondLimit(requestPayload); await updateOfferThroughputBeyondLimit(requestPayload);
@@ -1114,7 +1222,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
defaultExperience: this.container.defaultExperience(), defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab, dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(), tabTitle: this.tabTitle(),
error: error.message error: error
}, },
startKey startKey
); );
@@ -1133,6 +1241,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
this.geospatialConfigType.setBaseline(this.geospatialConfigType.getEditableOriginalValue()); this.geospatialConfigType.setBaseline(this.geospatialConfigType.getEditableOriginalValue());
this.analyticalStorageTtlSelection.setBaseline(this.analyticalStorageTtlSelection.getEditableOriginalValue()); this.analyticalStorageTtlSelection.setBaseline(this.analyticalStorageTtlSelection.getEditableOriginalValue());
this.analyticalStorageTtlSeconds.setBaseline(this.analyticalStorageTtlSeconds.getEditableOriginalValue()); this.analyticalStorageTtlSeconds.setBaseline(this.analyticalStorageTtlSeconds.getEditableOriginalValue());
this.rupm.setBaseline(this.rupm.getEditableOriginalValue());
this.changeFeedPolicyToggled.setBaseline(this.changeFeedPolicyToggled.getEditableOriginalValue()); this.changeFeedPolicyToggled.setBaseline(this.changeFeedPolicyToggled.getEditableOriginalValue());
this.conflictResolutionPolicyMode.setBaseline(this.conflictResolutionPolicyMode.getEditableOriginalValue()); this.conflictResolutionPolicyMode.setBaseline(this.conflictResolutionPolicyMode.getEditableOriginalValue());
@@ -1157,8 +1266,13 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
if (this.isAutoPilotSelected()) { if (this.isAutoPilotSelected()) {
const originalAutoPilotSettings = this.collection.offer().content.offerAutopilotSettings; const originalAutoPilotSettings = this.collection.offer().content.offerAutopilotSettings;
if (!this.hasAutoPilotV2FeatureFlag()) {
const originalAutoPilotMaxThroughput = originalAutoPilotSettings && originalAutoPilotSettings.maxThroughput; const originalAutoPilotMaxThroughput = originalAutoPilotSettings && originalAutoPilotSettings.maxThroughput;
this.autoPilotThroughput(originalAutoPilotMaxThroughput); this.autoPilotThroughput(originalAutoPilotMaxThroughput);
} else {
const originalAutoPilotTier = originalAutoPilotSettings && originalAutoPilotSettings.tier;
this.selectedAutoPilotTier(originalAutoPilotTier);
}
} }
return Q(); return Q();
@@ -1339,7 +1453,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
} }
private _getThroughputUnit(): string { private _getThroughputUnit(): string {
return "RU/s"; return this.rupm() === Constants.RUPMStates.on ? "RU/m" : "RU/s";
} }
public getUpdatedConflictResolutionPolicy(): DataModels.ConflictResolutionPolicy { public getUpdatedConflictResolutionPolicy(): DataModels.ConflictResolutionPolicy {
@@ -1456,6 +1570,13 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
this.collection.offer().content && this.collection.offer().content &&
this.collection.offer().content.offerThroughput; this.collection.offer().content.offerThroughput;
const offerIsRUPerMinuteThroughputEnabled =
this.collection &&
this.collection.offer &&
this.collection.offer() &&
this.collection.offer().content &&
this.collection.offer().content.offerIsRUPerMinuteThroughputEnabled;
const changeFeedPolicyToggled: ChangeFeedPolicyToggledState = this.changeFeedPolicyToggled(); const changeFeedPolicyToggled: ChangeFeedPolicyToggledState = this.changeFeedPolicyToggled();
this.changeFeedPolicyToggled.setBaseline(changeFeedPolicyToggled); this.changeFeedPolicyToggled.setBaseline(changeFeedPolicyToggled);
this.throughput.setBaseline(offerThroughput); this.throughput.setBaseline(offerThroughput);
@@ -1475,6 +1596,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
conflictResolutionPolicy && conflictResolutionPolicy.conflictResolutionProcedure conflictResolutionPolicy && conflictResolutionPolicy.conflictResolutionProcedure
) )
); );
this.rupm.setBaseline(offerIsRUPerMinuteThroughputEnabled ? Constants.RUPMStates.on : Constants.RUPMStates.off);
const indexingPolicyContent = this.collection.indexingPolicy(); const indexingPolicyContent = this.collection.indexingPolicy();
const value: string = JSON.stringify(indexingPolicyContent, null, 4); const value: string = JSON.stringify(indexingPolicyContent, null, 4);
@@ -1495,6 +1617,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
this.GEOMETRY; this.GEOMETRY;
this.geospatialConfigType.setBaseline(geospatialConfigType); this.geospatialConfigType.setBaseline(geospatialConfigType);
if (!this.hasAutoPilotV2FeatureFlag()) {
const maxThroughput = const maxThroughput =
this.collection && this.collection &&
this.collection.offer && this.collection.offer &&
@@ -1505,6 +1628,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
this.autoPilotThroughput(maxThroughput || AutoPilotUtils.minAutoPilotThroughput); this.autoPilotThroughput(maxThroughput || AutoPilotUtils.minAutoPilotThroughput);
} }
}
private _createIndexingPolicyEditor() { private _createIndexingPolicyEditor() {
this.isIndexingPolicyEditorInitializing(true); this.isIndexingPolicyEditorInitializing(true);

View File

@@ -1,7 +0,0 @@
<div style="width: 100%; height: 100%; margin-left: 3px;" data-bind="attr: { id: tabId }">
<!-- This runs the NotebookApp hosted by DataExplorer -->
<iframe
style="width:100%; height: 100%; border:none"
data-bind="setTemplateReady: true, attr: { src: sparkMasterSrc }, visible: !!sparkMasterSrc()"
></iframe>
</div>

View File

@@ -1,35 +0,0 @@
import * as ko from "knockout";
import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels";
import TabsBase from "./TabsBase";
import Explorer from "../Explorer";
interface SparkMasterTabOptions extends ViewModels.TabOptions {
clusterConnectionInfo: DataModels.SparkClusterConnectionInfo;
container: Explorer;
}
export default class SparkMasterTab extends TabsBase {
public sparkMasterSrc: ko.Observable<string>;
private _clusterConnectionInfo: DataModels.SparkClusterConnectionInfo;
private _container: Explorer;
constructor(options: SparkMasterTabOptions) {
super(options);
super.onActivate.bind(this);
this._container = options.container;
this._clusterConnectionInfo = options.clusterConnectionInfo;
const sparkMasterEndpoint =
this._clusterConnectionInfo &&
this._clusterConnectionInfo.endpoints &&
this._clusterConnectionInfo.endpoints.find(
endpoint => endpoint.kind === DataModels.SparkClusterEndpointKind.SparkUI
);
this.sparkMasterSrc = ko.observable<string>(sparkMasterEndpoint && sparkMasterEndpoint.endpoint);
}
protected getContainer() {
return this._container;
}
}

View File

@@ -1,7 +1,6 @@
import DocumentsTabTemplate from "./DocumentsTab.html"; import DocumentsTabTemplate from "./DocumentsTab.html";
import ConflictsTabTemplate from "./ConflictsTab.html"; import ConflictsTabTemplate from "./ConflictsTab.html";
import GraphTabTemplate from "./GraphTab.html"; import GraphTabTemplate from "./GraphTab.html";
import SparkMasterTabTemplate from "./SparkMasterTab.html";
import NotebookV2TabTemplate from "./NotebookV2Tab.html"; import NotebookV2TabTemplate from "./NotebookV2Tab.html";
import TerminalTabTemplate from "./TerminalTab.html"; import TerminalTabTemplate from "./TerminalTab.html";
import MongoDocumentsTabTemplate from "./MongoDocumentsTab.html"; import MongoDocumentsTabTemplate from "./MongoDocumentsTab.html";
@@ -61,15 +60,6 @@ export class GraphTab {
} }
} }
export class SparkMasterTab {
constructor() {
return {
viewModel: TabComponent,
template: SparkMasterTabTemplate
};
}
}
export class NotebookV2Tab { export class NotebookV2Tab {
constructor() { constructor() {
return { return {

View File

@@ -85,7 +85,6 @@ export default class TabsBase extends WaitsForTemplateViewModel {
explorer.tabsManager.closeTab(this.tabId, explorer); explorer.tabsManager.closeTab(this.tabId, explorer);
TelemetryProcessor.trace(Action.Tab, ActionModifiers.Close, { TelemetryProcessor.trace(Action.Tab, ActionModifiers.Close, {
tabName: this.constructor.name,
databaseAccountName: this.getContainer().databaseAccount().name, databaseAccountName: this.getContainer().databaseAccount().name,
defaultExperience: this.getContainer().defaultExperience(), defaultExperience: this.getContainer().defaultExperience(),
dataExplorerArea: Constants.Areas.Tab, dataExplorerArea: Constants.Areas.Tab,
@@ -144,7 +143,6 @@ export default class TabsBase extends WaitsForTemplateViewModel {
this.updateNavbarWithTabsButtons(); this.updateNavbarWithTabsButtons();
TelemetryProcessor.trace(Action.Tab, ActionModifiers.Open, { TelemetryProcessor.trace(Action.Tab, ActionModifiers.Open, {
tabName: this.constructor.name,
databaseAccountName: this.getContainer().databaseAccount().name, databaseAccountName: this.getContainer().databaseAccount().name,
defaultExperience: this.getContainer().defaultExperience(), defaultExperience: this.getContainer().defaultExperience(),
dataExplorerArea: Constants.Areas.Tab, dataExplorerArea: Constants.Areas.Tab,

View File

@@ -126,6 +126,7 @@ export class OfferPricing {
Standard: { Standard: {
StartingPrice: 24 / hoursInAMonth, // per hour StartingPrice: 24 / hoursInAMonth, // per hour
PricePerRU: 0.00008, PricePerRU: 0.00008,
PricePerRUPM: (10 * 2) / 1000 / hoursInAMonth, // preview price: $2 per 1000 RU/m per month -> 100 RU/s
PricePerGB: 0.25 / hoursInAMonth PricePerGB: 0.25 / hoursInAMonth
} }
}, },
@@ -138,6 +139,7 @@ export class OfferPricing {
Standard: { Standard: {
StartingPrice: OfferPricing.MonthlyPricing.mooncake.Standard.StartingPrice / hoursInAMonth, // per hour StartingPrice: OfferPricing.MonthlyPricing.mooncake.Standard.StartingPrice / hoursInAMonth, // per hour
PricePerRU: 0.00051, PricePerRU: 0.00051,
PricePerRUPM: (10 * 20) / 1000 / hoursInAMonth, // preview price: 20rmb per 1000 RU/m per month -> 100 RU/s
PricePerGB: OfferPricing.MonthlyPricing.mooncake.Standard.PricePerGB / hoursInAMonth PricePerGB: OfferPricing.MonthlyPricing.mooncake.Standard.PricePerGB / hoursInAMonth
} }
} }
@@ -154,6 +156,7 @@ export class CollectionCreation {
public static readonly MinRU7PartitionsTo25Partitions: number = 2500; public static readonly MinRU7PartitionsTo25Partitions: number = 2500;
public static readonly MinRUPerPartitionAbove25Partitions: number = 100; public static readonly MinRUPerPartitionAbove25Partitions: number = 100;
public static readonly MaxRUPerPartition: number = 10000; public static readonly MaxRUPerPartition: number = 10000;
public static readonly MaxRUPMPerPartition: number = 5000;
public static readonly MinPartitionedCollectionRUs: number = 2500; public static readonly MinPartitionedCollectionRUs: number = 2500;
public static readonly NumberOfPartitionsInFixedCollection: number = 1; public static readonly NumberOfPartitionsInFixedCollection: number = 1;

View File

@@ -1,13 +1,17 @@
import * as Constants from "./Constants"; import * as Constants from "./Constants";
export function computeRUUsagePrice(serverId: string, requestUnits: number): string { export function computeRUUsagePrice(serverId: string, rupmEnabled: boolean, requestUnits: number): string {
if (serverId === "mooncake") { if (serverId === "mooncake") {
let ruCharge = requestUnits * Constants.OfferPricing.HourlyPricing.mooncake.Standard.PricePerRU; let ruCharge = requestUnits * Constants.OfferPricing.HourlyPricing.mooncake.Standard.PricePerRU,
return calculateEstimateNumber(ruCharge) + " " + Constants.OfferPricing.HourlyPricing.mooncake.Currency; rupmCharge = rupmEnabled ? requestUnits * Constants.OfferPricing.HourlyPricing.mooncake.Standard.PricePerRUPM : 0;
return (
calculateEstimateNumber(ruCharge + rupmCharge) + " " + Constants.OfferPricing.HourlyPricing.mooncake.Currency
);
} }
let ruCharge = requestUnits * Constants.OfferPricing.HourlyPricing.default.Standard.PricePerRU; let ruCharge = requestUnits * Constants.OfferPricing.HourlyPricing.default.Standard.PricePerRU,
return calculateEstimateNumber(ruCharge) + " " + Constants.OfferPricing.HourlyPricing.default.Currency; rupmCharge = rupmEnabled ? requestUnits * Constants.OfferPricing.HourlyPricing.default.Standard.PricePerRUPM : 0;
return calculateEstimateNumber(ruCharge + rupmCharge) + " " + Constants.OfferPricing.HourlyPricing.default.Currency;
} }
export function computeStorageUsagePrice(serverId: string, storageUsedRoundUpToGB: number): string { export function computeStorageUsagePrice(serverId: string, storageUsedRoundUpToGB: number): string {

View File

@@ -89,8 +89,7 @@ export enum Action {
ClickResourceTreeNodeContextMenuItem, ClickResourceTreeNodeContextMenuItem,
DiscardSettings, DiscardSettings,
SettingsV2Updated, SettingsV2Updated,
SettingsV2Discarded, SettingsV2Discarded
MongoIndexUpdated
} }
export const ActionModifiers = { export const ActionModifiers = {

View File

@@ -0,0 +1,119 @@
import * as AutoPilotUtils from "./AutoPilotUtils";
import * as Constants from "../Common/Constants";
import { AutopilotTier, Offer } from "../Contracts/DataModels";
describe("AutoPilotUtils", () => {
describe("isAutoPilotOfferUpgradedToV3", () => {
const legacyAutopilotOffer = {
tier: 1,
maximumTierThroughput: 20000,
maxThroughput: 20000
};
const v3AutopilotOffer = {
maximumTierThroughput: 20000,
maxThroughput: 20000
};
const v3AutopilotOfferDuringTransitionPhase = {
tier: 0,
maximumTierThroughput: 20000,
maxThroughput: 20000
};
it("should return false if the offer has a tier level and the tier level >= 1", () => {
expect(AutoPilotUtils.isAutoPilotOfferUpgradedToV3(legacyAutopilotOffer)).toEqual(false);
});
it("should return true if the autopilot offer does not have a tier level", () => {
expect(AutoPilotUtils.isAutoPilotOfferUpgradedToV3(v3AutopilotOffer)).toEqual(true);
});
it("should return true if the autopilot offer has a tier level and the tier level is === 0", () => {
expect(AutoPilotUtils.isAutoPilotOfferUpgradedToV3(v3AutopilotOfferDuringTransitionPhase)).toEqual(true);
});
});
describe("isValidAutoPilotOffer", () => {
function getOffer(): Offer {
const commonOffer: Offer = {
_etag: "_etag",
_rid: "_rid",
_self: "_self",
_ts: "_ts",
id: "id",
content: {
offerThroughput: 0,
offerIsRUPerMinuteThroughputEnabled: false,
offerAutopilotSettings: undefined
}
};
return commonOffer;
}
it("offer with autopilot", () => {
let offer = getOffer();
offer.content.offerAutopilotSettings = {
tier: 1
};
const isValid = AutoPilotUtils.isValidV2AutoPilotOffer(offer);
expect(isValid).toBe(true);
});
it("offer without autopilot", () => {
let offer = getOffer();
const isValid = AutoPilotUtils.isValidV2AutoPilotOffer(offer);
expect(isValid).toBe(false);
});
});
describe("isValidAutoPilotTier", () => {
it("invalid input, should return false", () => {
const isValid1 = AutoPilotUtils.isValidAutoPilotTier(0);
expect(isValid1).toBe(false);
const isValid2 = AutoPilotUtils.isValidAutoPilotTier(5);
expect(isValid2).toBe(false);
const isValid3 = AutoPilotUtils.isValidAutoPilotTier(undefined);
expect(isValid3).toBe(false);
});
it("valid input, should return true", () => {
const isValid1 = AutoPilotUtils.isValidAutoPilotTier(1);
expect(isValid1).toBe(true);
const isValid3 = AutoPilotUtils.isValidAutoPilotTier(AutopilotTier.Tier3);
expect(isValid3).toBe(true);
});
});
describe("getAutoPilotTextWithTier", () => {
it("invalid input, should return undefined", () => {
const text1 = AutoPilotUtils.getAutoPilotTextWithTier(0);
expect(text1).toBe(undefined);
const text2 = AutoPilotUtils.getAutoPilotTextWithTier(undefined);
expect(text2).toBe(undefined);
});
it("valid input, should return coreponding text", () => {
const text1 = AutoPilotUtils.getAutoPilotTextWithTier(1);
expect(text1).toBe(Constants.AutoPilot.tier1Text);
const text4 = AutoPilotUtils.getAutoPilotTextWithTier(AutopilotTier.Tier4);
expect(text4).toBe(Constants.AutoPilot.tier4Text);
});
});
describe("getAvailableAutoPilotTiersOptions", () => {
it("invalid input should return all options", () => {
const option1 = AutoPilotUtils.getAvailableAutoPilotTiersOptions(undefined);
expect(option1.length).toBe(4);
const option2 = AutoPilotUtils.getAvailableAutoPilotTiersOptions(5);
expect(option2.length).toBe(4);
});
it("valid input should return all available options", () => {
const option1 = AutoPilotUtils.getAvailableAutoPilotTiersOptions();
expect(option1.length).toBe(4);
const option2 = AutoPilotUtils.getAvailableAutoPilotTiersOptions(AutopilotTier.Tier3);
expect(option2.length).toBe(4);
});
});
});

View File

@@ -1,5 +1,6 @@
import * as Constants from "../Common/Constants"; import * as Constants from "../Common/Constants";
import { AutoPilotOfferSettings, Offer } from "../Contracts/DataModels"; import { AutoPilotOfferSettings, AutopilotTier, Offer } from "../Contracts/DataModels";
import { DropdownOption } from "../Contracts/ViewModels";
export const manualToAutoscaleDisclaimer = `The starting autoscale max RU/s will be determined by the system, based on the current manual throughput settings and storage of your resource. After autoscale has been enabled, you can change the max RU/s. <a href="${Constants.Urls.autoscaleMigration}">Learn more</a>.`; export const manualToAutoscaleDisclaimer = `The starting autoscale max RU/s will be determined by the system, based on the current manual throughput settings and storage of your resource. After autoscale has been enabled, you can change the max RU/s. <a href="${Constants.Urls.autoscaleMigration}">Learn more</a>.`;
@@ -7,6 +8,24 @@ export const minAutoPilotThroughput = 4000;
export const autoPilotIncrementStep = 1000; export const autoPilotIncrementStep = 1000;
const autoPilotTiers: Array<AutopilotTier> = [
AutopilotTier.Tier1,
AutopilotTier.Tier2,
AutopilotTier.Tier3,
AutopilotTier.Tier4
];
const autoPilotTierTextMap = {
[AutopilotTier.Tier1]: Constants.AutoPilot.tier1Text,
[AutopilotTier.Tier2]: Constants.AutoPilot.tier2Text,
[AutopilotTier.Tier3]: Constants.AutoPilot.tier3Text,
[AutopilotTier.Tier4]: Constants.AutoPilot.tier4Text
};
export function isAutoPilotOfferUpgradedToV3(offer: AutoPilotOfferSettings): boolean {
return offer && !offer.tier;
}
export function isValidV3AutoPilotOffer(offer: Offer): boolean { export function isValidV3AutoPilotOffer(offer: Offer): boolean {
const maxThroughput = const maxThroughput =
offer && offer &&
@@ -16,6 +35,36 @@ export function isValidV3AutoPilotOffer(offer: Offer): boolean {
return isValidAutoPilotThroughput(maxThroughput); return isValidAutoPilotThroughput(maxThroughput);
} }
export function isValidV2AutoPilotOffer(offer: Offer): boolean {
const tier =
offer && offer.content && offer.content.offerAutopilotSettings && offer.content.offerAutopilotSettings.tier;
if (!tier) {
return false;
}
return isValidAutoPilotTier(tier);
}
export function isValidAutoPilotTier(tier: number | AutopilotTier): boolean {
if (autoPilotTiers.indexOf(tier) >= 0) {
return true;
}
return false;
}
export function getAutoPilotTextWithTier(tier: AutopilotTier): string {
return !!autoPilotTierTextMap[tier] ? autoPilotTierTextMap[tier] : undefined;
}
export function getAvailableAutoPilotTiersOptions(
tier: AutopilotTier = AutopilotTier.Tier1
): DropdownOption<AutopilotTier>[] {
if (!isValidAutoPilotTier(tier)) {
tier = AutopilotTier.Tier1;
}
return autoPilotTiers.map((t: AutopilotTier) => ({ value: t, text: getAutoPilotTextWithTier(t) }));
}
export function isValidAutoPilotThroughput(maxThroughput: number): boolean { export function isValidAutoPilotThroughput(maxThroughput: number): boolean {
if (!maxThroughput) { if (!maxThroughput) {
return false; return false;
@@ -37,6 +86,9 @@ export function getStorageBasedOnUserInput(throughput: number): number {
return Math.round(throughput && throughput * 0.01); return Math.round(throughput && throughput * 0.01);
} }
export function getAutoPilotHeaderText(): string { export function getAutoPilotHeaderText(isV2Model: boolean): string {
if (isV2Model) {
return "Throughput (Autopilot)";
}
return "Throughput (autoscale)"; return "Throughput (autoscale)";
} }

View File

@@ -25,37 +25,37 @@ describe("PricingUtils Tests", () => {
describe("computeRUUsagePriceHourly()", () => { describe("computeRUUsagePriceHourly()", () => {
it("should return 0 for NaN regions default cloud", () => { it("should return 0 for NaN regions default cloud", () => {
const value = PricingUtils.computeRUUsagePriceHourly("default", 1, null, false); const value = PricingUtils.computeRUUsagePriceHourly("default", false, 1, null, false);
expect(value).toBe(0); expect(value).toBe(0);
}); });
it("should return 0 for -1 regions", () => { it("should return 0 for -1 regions", () => {
const value = PricingUtils.computeRUUsagePriceHourly("default", 1, -1, false); const value = PricingUtils.computeRUUsagePriceHourly("default", false, 1, -1, false);
expect(value).toBe(0); expect(value).toBe(0);
}); });
it("should return 0.00008 for default cloud, 1RU, 1 region, multimaster disabled", () => { it("should return 0.00008 for default cloud, rupm disabled, 1RU, 1 region, multimaster disabled", () => {
const value = PricingUtils.computeRUUsagePriceHourly("default", 1, 1, false); const value = PricingUtils.computeRUUsagePriceHourly("default", false, 1, 1, false);
expect(value).toBe(0.00008); expect(value).toBe(0.00008);
}); });
it("should return 0.00051 for Mooncake cloud, 1RU, 1 region, multimaster disabled", () => { it("should return 0.00051 for Mooncake cloud, rupm disabled, 1RU, 1 region, multimaster disabled", () => {
const value = PricingUtils.computeRUUsagePriceHourly("mooncake", 1, 1, false); const value = PricingUtils.computeRUUsagePriceHourly("mooncake", false, 1, 1, false);
expect(value).toBe(0.00051); expect(value).toBe(0.00051);
}); });
it("should return 0.00016 for default cloud, 1RU, 2 regions, multimaster disabled", () => { it("should return 0.00016 for default cloud, rupm disabled, 1RU, 2 regions, multimaster disabled", () => {
const value = PricingUtils.computeRUUsagePriceHourly("default", 1, 2, false); const value = PricingUtils.computeRUUsagePriceHourly("default", false, 1, 2, false);
expect(value).toBe(0.00016); expect(value).toBe(0.00016);
}); });
it("should return 0.00008 for default cloud, 1RU, 1 region, multimaster enabled", () => { it("should return 0.00008 for default cloud, rupm disabled, 1RU, 1 region, multimaster enabled", () => {
const value = PricingUtils.computeRUUsagePriceHourly("default", 1, 1, true); const value = PricingUtils.computeRUUsagePriceHourly("default", false, 1, 1, true);
expect(value).toBe(0.00008); expect(value).toBe(0.00008);
}); });
it("should return 0.00048 for default cloud, 1RU, 2 region, multimaster enabled", () => { it("should return 0.00048 for default cloud, rupm disabled, 1RU, 2 region, multimaster enabled", () => {
const value = PricingUtils.computeRUUsagePriceHourly("default", 1, 2, true); const value = PricingUtils.computeRUUsagePriceHourly("default", false, 1, 2, true);
expect(value).toBe(0.00048); expect(value).toBe(0.00048);
}); });
}); });
@@ -150,6 +150,18 @@ describe("PricingUtils Tests", () => {
}); });
}); });
describe("getPricePerRuPm()", () => {
it("should return 0.000027397260273972603 for default clouds", () => {
const value = PricingUtils.getPricePerRuPm("default");
expect(value).toBe(0.000027397260273972603);
});
it("should return 0.00027397260273972606 for mooncake", () => {
const value = PricingUtils.getPricePerRuPm("mooncake");
expect(value).toBe(0.00027397260273972606);
});
});
describe("getRegionMultiplier()", () => { describe("getRegionMultiplier()", () => {
describe("without multimaster", () => { describe("without multimaster", () => {
it("should return 0 for null", () => { it("should return 0 for null", () => {
@@ -242,48 +254,52 @@ describe("PricingUtils Tests", () => {
}); });
describe("getEstimatedSpendHtml()", () => { describe("getEstimatedSpendHtml()", () => {
it("should return 'Estimated cost (USD): <b>$0.000080 hourly / $0.0019 daily / $0.058 monthly </b> (1 region, 1RU/s, $0.00008/RU)' for 1RU/s on default cloud, 1 region, with multimaster", () => { it("should return 'Estimated cost (USD): <b>$0.000080 hourly / $0.0019 daily / $0.058 monthly </b> (1 region, 1RU/s, $0.00008/RU)' for 1RU/s on default cloud, 1 region, with multimaster, and no rupm", () => {
const value = PricingUtils.getEstimatedSpendHtml( const value = PricingUtils.getEstimatedSpendHtml(
1 /*RU/s*/, 1 /*RU/s*/,
"default" /* cloud */, "default" /* cloud */,
1 /* region */, 1 /* region */,
true /* multimaster */ true /* multimaster */,
false /* rupm */
); );
expect(value).toBe( expect(value).toBe(
"Estimated cost (USD): <b>$0.000080 hourly / $0.0019 daily / $0.058 monthly </b> (1 region, 1RU/s, $0.00008/RU)" "Estimated cost (USD): <b>$0.000080 hourly / $0.0019 daily / $0.058 monthly </b> (1 region, 1RU/s, $0.00008/RU)"
); );
}); });
it("should return 'Estimated cost (RMB): <b>¥0.00051 hourly / ¥0.012 daily / ¥0.37 monthly </b> (1 region, 1RU/s, ¥0.00051/RU)' for 1RU/s on mooncake, 1 region, with multimaster", () => { it("should return 'Estimated cost (RMB): <b>¥0.00051 hourly / ¥0.012 daily / ¥0.37 monthly </b> (1 region, 1RU/s, ¥0.00051/RU)' for 1RU/s on mooncake, 1 region, with multimaster, and no rupm", () => {
const value = PricingUtils.getEstimatedSpendHtml( const value = PricingUtils.getEstimatedSpendHtml(
1 /*RU/s*/, 1 /*RU/s*/,
"mooncake" /* cloud */, "mooncake" /* cloud */,
1 /* region */, 1 /* region */,
true /* multimaster */ true /* multimaster */,
false /* rupm */
); );
expect(value).toBe( expect(value).toBe(
"Estimated cost (RMB): <b>¥0.00051 hourly / ¥0.012 daily / ¥0.37 monthly </b> (1 region, 1RU/s, ¥0.00051/RU)" "Estimated cost (RMB): <b>¥0.00051 hourly / ¥0.012 daily / ¥0.37 monthly </b> (1 region, 1RU/s, ¥0.00051/RU)"
); );
}); });
it("should return 'Estimated cost (USD): <b>$0.13 hourly / $3.07 daily / $140.16 monthly </b> (2 regions, 400RU/s, $0.00016/RU)' for 400RU/s on default cloud, 2 region, with multimaster", () => { it("should return 'Estimated cost (USD): <b>$0.13 hourly / $3.07 daily / $140.16 monthly </b> (2 regions, 400RU/s, $0.00016/RU)' for 400RU/s on default cloud, 2 region, with multimaster, and no rupm", () => {
const value = PricingUtils.getEstimatedSpendHtml( const value = PricingUtils.getEstimatedSpendHtml(
400 /*RU/s*/, 400 /*RU/s*/,
"default" /* cloud */, "default" /* cloud */,
2 /* region */, 2 /* region */,
true /* multimaster */ true /* multimaster */,
false /* rupm */
); );
expect(value).toBe( expect(value).toBe(
"Estimated cost (USD): <b>$0.19 hourly / $4.61 daily / $140.16 monthly </b> (2 regions, 400RU/s, $0.00016/RU)" "Estimated cost (USD): <b>$0.19 hourly / $4.61 daily / $140.16 monthly </b> (2 regions, 400RU/s, $0.00016/RU)"
); );
}); });
it("should return 'Estimated cost (USD): <b>$0.064 hourly / $1.54 daily / $46.72 monthly </b> (2 regions, 400RU/s, $0.00008/RU)' for 400RU/s on default cloud, 2 region, without multimaster", () => { it("should return 'Estimated cost (USD): <b>$0.064 hourly / $1.54 daily / $46.72 monthly </b> (2 regions, 400RU/s, $0.00008/RU)' for 400RU/s on default cloud, 2 region, without multimaster, and no rupm", () => {
const value = PricingUtils.getEstimatedSpendHtml( const value = PricingUtils.getEstimatedSpendHtml(
400 /*RU/s*/, 400 /*RU/s*/,
"default" /* cloud */, "default" /* cloud */,
2 /* region */, 2 /* region */,
false /* multimaster */ false /* multimaster */,
false /* rupm */
); );
expect(value).toBe( expect(value).toBe(
"Estimated cost (USD): <b>$0.064 hourly / $1.54 daily / $46.72 monthly </b> (2 regions, 400RU/s, $0.00008/RU)" "Estimated cost (USD): <b>$0.064 hourly / $1.54 daily / $46.72 monthly </b> (2 regions, 400RU/s, $0.00008/RU)"
@@ -292,45 +308,49 @@ describe("PricingUtils Tests", () => {
}); });
describe("getEstimatedSpendAcknowledgeString()", () => { describe("getEstimatedSpendAcknowledgeString()", () => {
it("should return 'I acknowledge the estimated $0.0019 daily cost for the throughput above.' for 1RU/s on default cloud, 1 region, with multimaster", () => { it("should return 'I acknowledge the estimated $0.0019 daily cost for the throughput above.' for 1RU/s on default cloud, 1 region, with multimaster, and no rupm", () => {
const value = PricingUtils.getEstimatedSpendAcknowledgeString( const value = PricingUtils.getEstimatedSpendAcknowledgeString(
1 /*RU/s*/, 1 /*RU/s*/,
"default" /* cloud */, "default" /* cloud */,
1 /* region */, 1 /* region */,
true /* multimaster */, true /* multimaster */,
false /* rupm */,
false false
); );
expect(value).toBe("I acknowledge the estimated $0.0019 daily cost for the throughput above."); expect(value).toBe("I acknowledge the estimated $0.0019 daily cost for the throughput above.");
}); });
it("should return 'I acknowledge the estimated ¥0.012 daily cost for the throughput above.' for 1RU/s on mooncake, 1 region, with multimaster", () => { it("should return 'I acknowledge the estimated ¥0.012 daily cost for the throughput above.' for 1RU/s on mooncake, 1 region, with multimaster, and no rupm", () => {
const value = PricingUtils.getEstimatedSpendAcknowledgeString( const value = PricingUtils.getEstimatedSpendAcknowledgeString(
1 /*RU/s*/, 1 /*RU/s*/,
"mooncake" /* cloud */, "mooncake" /* cloud */,
1 /* region */, 1 /* region */,
true /* multimaster */, true /* multimaster */,
false /* rupm */,
false false
); );
expect(value).toBe("I acknowledge the estimated ¥0.012 daily cost for the throughput above."); expect(value).toBe("I acknowledge the estimated ¥0.012 daily cost for the throughput above.");
}); });
it("should return 'I acknowledge the estimated $3.07 daily cost for the throughput above.' for 400RU/s on default cloud, 2 region, with multimaster", () => { it("should return 'I acknowledge the estimated $3.07 daily cost for the throughput above.' for 400RU/s on default cloud, 2 region, with multimaster, and no rupm", () => {
const value = PricingUtils.getEstimatedSpendAcknowledgeString( const value = PricingUtils.getEstimatedSpendAcknowledgeString(
400 /*RU/s*/, 400 /*RU/s*/,
"default" /* cloud */, "default" /* cloud */,
2 /* region */, 2 /* region */,
true /* multimaster */, true /* multimaster */,
false /* rupm */,
false false
); );
expect(value).toBe("I acknowledge the estimated $4.61 daily cost for the throughput above."); expect(value).toBe("I acknowledge the estimated $4.61 daily cost for the throughput above.");
}); });
it("should return 'I acknowledge the estimated $1.54 daily cost for the throughput above.' for 400RU/s on default cloud, 2 region, without multimaster", () => { it("should return 'I acknowledge the estimated $1.54 daily cost for the throughput above.' for 400RU/s on default cloud, 2 region, without multimaster, and no rupm", () => {
const value = PricingUtils.getEstimatedSpendAcknowledgeString( const value = PricingUtils.getEstimatedSpendAcknowledgeString(
400 /*RU/s*/, 400 /*RU/s*/,
"default" /* cloud */, "default" /* cloud */,
2 /* region */, 2 /* region */,
false /* multimaster */, false /* multimaster */,
false /* rupm */,
false false
); );
expect(value).toBe("I acknowledge the estimated $1.54 daily cost for the throughput above."); expect(value).toBe("I acknowledge the estimated $1.54 daily cost for the throughput above.");

View File

@@ -1,5 +1,6 @@
import * as AutoPilotUtils from "../Utils/AutoPilotUtils"; import * as AutoPilotUtils from "../Utils/AutoPilotUtils";
import * as Constants from "../Shared/Constants"; import * as Constants from "../Shared/Constants";
import { AutopilotTier } from "../Contracts/DataModels";
/** /**
* Anything that is not a number should return 0 * Anything that is not a number should return 0
@@ -13,7 +14,10 @@ export function normalizeNumber(number: null | undefined | string | number): num
return Math.floor(Number(number)); return Math.floor(Number(number));
} }
export function getRuToolTipText(): string { export function getRuToolTipText(isV2AutoPilot: boolean): string {
if (isV2AutoPilot) {
return "Provisioned throughput is measured in Request Units per second (RU/s). 1 RU corresponds to the throughput of a read of a 1 KB document.";
}
return `Set the throughput — Request Units per second (RU/s) — required for the workload. A read of a 1 KB document uses 1 RU. Select manual if you plan to scale RU/s yourself. Select autoscale to allow the system to scale RU/s based on usage.`; return `Set the throughput — Request Units per second (RU/s) — required for the workload. A read of a 1 KB document uses 1 RU. Select manual if you plan to scale RU/s yourself. Select autoscale to allow the system to scale RU/s based on usage.`;
} }
@@ -49,6 +53,7 @@ export function getMultimasterMultiplier(numberOfRegions: number, multimasterEna
export function computeRUUsagePriceHourly( export function computeRUUsagePriceHourly(
serverId: string, serverId: string,
rupmEnabled: boolean,
requestUnits: number, requestUnits: number,
numberOfRegions: number, numberOfRegions: number,
multimasterEnabled: boolean multimasterEnabled: boolean
@@ -57,10 +62,12 @@ export function computeRUUsagePriceHourly(
const multimasterMultiplier: number = getMultimasterMultiplier(numberOfRegions, multimasterEnabled); const multimasterMultiplier: number = getMultimasterMultiplier(numberOfRegions, multimasterEnabled);
const pricePerRu = getPricePerRu(serverId); const pricePerRu = getPricePerRu(serverId);
const pricePerRuPm = getPricePerRuPm(serverId);
const ruCharge = requestUnits * pricePerRu * multimasterMultiplier * regionMultiplier; const ruCharge = requestUnits * pricePerRu * multimasterMultiplier * regionMultiplier;
const rupmCharge = rupmEnabled ? requestUnits * pricePerRuPm : 0;
return Number(ruCharge.toFixed(5)); return Number((ruCharge + rupmCharge).toFixed(5));
} }
export function getPriceCurrency(serverId: string): string { export function getPriceCurrency(serverId: string): string {
@@ -146,6 +153,34 @@ export function getPricePerRu(serverId: string): number {
return Constants.OfferPricing.HourlyPricing.default.Standard.PricePerRU; return Constants.OfferPricing.HourlyPricing.default.Standard.PricePerRU;
} }
export function getPricePerRuPm(serverId: string): number {
if (serverId === "mooncake") {
return Constants.OfferPricing.HourlyPricing.mooncake.Standard.PricePerRUPM;
}
return Constants.OfferPricing.HourlyPricing.default.Standard.PricePerRUPM;
}
export function getAutoPilotV2SpendHtml(autoPilotTier: AutopilotTier, isDatabaseThroughput: boolean): string {
if (!autoPilotTier) {
return "";
}
const resource: string = isDatabaseThroughput ? "database" : "container";
switch (autoPilotTier) {
case AutopilotTier.Tier1:
return `Your ${resource} throughput will automatically scale between 400 RU/s and 4,000 RU/s based on the workload needs, as long as your storage does not exceed 50GB. If your storage exceeds 50GB, we will upgrade the maximum (and minimum) throughput thresholds to the next available value. For more details, see <a href='${Constants.AutopilotDocumentation.Url}' target='_blank'>documentation</a>.`;
case AutopilotTier.Tier2:
return `Your ${resource} throughput will automatically scale between 2,000 RU/s and 20,000 RU/s based on the workload needs, as long as your storage does not exceed 200GB. If your storage exceeds 200GB, we will upgrade the maximum (and minimum) throughput thresholds to the next available value. For more details, see <a href='${Constants.AutopilotDocumentation.Url}' target='_blank'>documentation</a>.`;
case AutopilotTier.Tier3:
return `Your ${resource} throughput will automatically scale between 10,000 RU/s and 100,000 RU/s based on the workload needs, as long as your storage does not exceed 1TB. If your storage exceeds 1TB, we will upgrade the maximum (and minimum) throughput thresholds to the next available value. For more details, see <a href='${Constants.AutopilotDocumentation.Url}' target='_blank'>documentation</a>.`;
case AutopilotTier.Tier4:
return `Your ${resource} throughput will automatically scale between 50,000 RU/s and 500,000 RU/s based on the workload needs, as long as your storage does not exceed 5TB. If your storage exceeds 5TB, we will upgrade the maximum (and minimum) throughput thresholds to the next available value. For more details, see <a href='${Constants.AutopilotDocumentation.Url}' target='_blank'>documentation</a>.`;
default:
return `Your ${resource} throughput will automatically scale based on the workload needs.`;
}
}
export function getAutoPilotV3SpendHtml(maxAutoPilotThroughputSet: number, isDatabaseThroughput: boolean): string { export function getAutoPilotV3SpendHtml(maxAutoPilotThroughputSet: number, isDatabaseThroughput: boolean): string {
if (!maxAutoPilotThroughputSet) { if (!maxAutoPilotThroughputSet) {
return ""; return "";
@@ -203,9 +238,10 @@ export function getEstimatedSpendHtml(
throughput: number, throughput: number,
serverId: string, serverId: string,
regions: number, regions: number,
multimaster: boolean multimaster: boolean,
rupmEnabled: boolean
): string { ): string {
const hourlyPrice: number = computeRUUsagePriceHourly(serverId, throughput, regions, multimaster); const hourlyPrice: number = computeRUUsagePriceHourly(serverId, rupmEnabled, throughput, regions, multimaster);
const dailyPrice: number = hourlyPrice * 24; const dailyPrice: number = hourlyPrice * 24;
const monthlyPrice: number = hourlyPrice * Constants.hoursInAMonth; const monthlyPrice: number = hourlyPrice * Constants.hoursInAMonth;
const currency: string = getPriceCurrency(serverId); const currency: string = getPriceCurrency(serverId);
@@ -226,11 +262,12 @@ export function getEstimatedSpendAcknowledgeString(
serverId: string, serverId: string,
regions: number, regions: number,
multimaster: boolean, multimaster: boolean,
rupmEnabled: boolean,
isAutoscale: boolean isAutoscale: boolean
): string { ): string {
const hourlyPrice: number = isAutoscale const hourlyPrice: number = isAutoscale
? computeAutoscaleUsagePriceHourly(serverId, throughput, regions, multimaster) ? computeAutoscaleUsagePriceHourly(serverId, throughput, regions, multimaster)
: computeRUUsagePriceHourly(serverId, throughput, regions, multimaster); : computeRUUsagePriceHourly(serverId, rupmEnabled, throughput, regions, multimaster);
const dailyPrice: number = hourlyPrice * 24; const dailyPrice: number = hourlyPrice * 24;
const monthlyPrice: number = hourlyPrice * Constants.hoursInAMonth; const monthlyPrice: number = hourlyPrice * Constants.hoursInAMonth;
const currencySign: string = getCurrencySign(serverId); const currencySign: string = getCurrencySign(serverId);