mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-28 13:21:42 +00:00
Compare commits
2 Commits
remove-rup
...
remove-unu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a636c70ce8 | ||
|
|
92891a4878 |
@@ -206,7 +206,6 @@ src/Explorer/Tabs/QueryTablesTab.ts
|
||||
src/Explorer/Tabs/ScriptTabBase.ts
|
||||
src/Explorer/Tabs/SettingsTab.test.ts
|
||||
src/Explorer/Tabs/SettingsTab.ts
|
||||
src/Explorer/Tabs/SparkMasterTab.ts
|
||||
src/Explorer/Tabs/StoredProcedureTab.ts
|
||||
src/Explorer/Tabs/TabComponents.ts
|
||||
src/Explorer/Tabs/TabsBase.ts
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"offerThroughput": 400,
|
||||
"databaseLevelThroughput": false,
|
||||
"collectionId": "Persons",
|
||||
"rupmEnabled": false,
|
||||
"partitionKey": { "kind": "Hash", "paths": ["/name"] },
|
||||
"data": [
|
||||
"g.addV('person').property(id, '1').property('name', 'Eva').property('age', 44)",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AutopilotTier } from "../Contracts/DataModels";
|
||||
import { HashMap } from "./HashMap";
|
||||
|
||||
export class AuthorizationEndpoints {
|
||||
@@ -108,6 +109,7 @@ export class CapabilityNames {
|
||||
export class Features {
|
||||
public static readonly cosmosdb = "cosmosdb";
|
||||
public static readonly enableChangeFeedPolicy = "enablechangefeedpolicy";
|
||||
public static readonly enableRupm = "enablerupm";
|
||||
public static readonly executeSproc = "dataexplorerexecutesproc";
|
||||
public static readonly hostedDataExplorer = "hosteddataexplorerenabled";
|
||||
public static readonly enableTtl = "enablettl";
|
||||
@@ -122,6 +124,7 @@ export class Features {
|
||||
public static readonly notebookBasePath = "notebookbasepath";
|
||||
public static readonly canExceedMaximumValue = "canexceedmaximumvalue";
|
||||
public static readonly enableFixedCollectionWithSharedThroughput = "enablefixedcollectionwithsharedthroughput";
|
||||
public static readonly enableAutoPilotV2 = "enableautopilotv2";
|
||||
public static readonly ttl90Days = "ttl90days";
|
||||
public static readonly enableRightPanelV2 = "enablerightpanelv2";
|
||||
public static readonly enableSDKoperations = "enablesdkoperations";
|
||||
@@ -177,6 +180,12 @@ export class CassandraBackend {
|
||||
public static readonly schemaApi: string = "api/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 {
|
||||
public static CustomPageOption: string = "custom";
|
||||
public static UnlimitedPageOption: string = "unlimited";
|
||||
@@ -253,6 +262,7 @@ export class HttpHeaders {
|
||||
public static usePolygonsSmallerThanAHemisphere = "x-ms-documentdb-usepolygonssmallerthanahemisphere";
|
||||
public static autoPilotThroughput = "autoscaleSettings";
|
||||
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 migrateOfferToManualThroughput: string = "x-ms-cosmos-migrate-offer-to-manual-throughput";
|
||||
public static migrateOfferToAutopilot: string = "x-ms-cosmos-migrate-offer-to-autopilot";
|
||||
@@ -397,6 +407,54 @@ export enum ConflictOperationType {
|
||||
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 =
|
||||
//[SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Well known public masterKey for emulator")]
|
||||
"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
|
||||
|
||||
@@ -376,7 +376,8 @@ const updateOfferWithSDK = async (params: UpdateOfferParams): Promise<Offer> =>
|
||||
const currentOffer = params.currentOffer;
|
||||
const newOffer: Offer = {
|
||||
content: {
|
||||
offerThroughput: undefined
|
||||
offerThroughput: undefined,
|
||||
offerIsRUPerMinuteThroughputEnabled: false
|
||||
},
|
||||
_etag: undefined,
|
||||
_ts: undefined,
|
||||
|
||||
@@ -17,7 +17,8 @@ describe("updateOfferThroughputBeyondLimit", () => {
|
||||
resourceGroup: "foo",
|
||||
databaseAccountName: "foo",
|
||||
databaseName: "foo",
|
||||
throughput: 1000000000
|
||||
throughput: 1000000000,
|
||||
offerIsRUPerMinuteThroughputEnabled: false
|
||||
});
|
||||
expect(window.fetch).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@ interface UpdateOfferThroughputRequest {
|
||||
databaseName: string;
|
||||
collectionName?: string;
|
||||
throughput: number;
|
||||
offerIsRUPerMinuteThroughputEnabled: boolean;
|
||||
offerAutopilotSettings?: AutoPilotOfferSettings;
|
||||
}
|
||||
|
||||
|
||||
@@ -179,6 +179,7 @@ export interface Offer extends Resource {
|
||||
offerType?: string;
|
||||
content?: {
|
||||
offerThroughput: number;
|
||||
offerIsRUPerMinuteThroughputEnabled: boolean;
|
||||
collectionThroughputInfo?: OfferThroughputInfo;
|
||||
offerAutopilotSettings?: AutoPilotOfferSettings;
|
||||
};
|
||||
@@ -232,17 +233,27 @@ export interface CreateDatabaseAndCollectionRequest {
|
||||
collectionId: string;
|
||||
offerThroughput: number;
|
||||
databaseLevelThroughput: boolean;
|
||||
rupmEnabled?: boolean;
|
||||
partitionKey?: PartitionKey;
|
||||
indexingPolicy?: IndexingPolicy;
|
||||
uniqueKeyPolicy?: UniqueKeyPolicy;
|
||||
autoPilot?: AutoPilotCreationSettings;
|
||||
analyticalStorageTtl?: number;
|
||||
hasAutoPilotV2FeatureFlag?: boolean;
|
||||
}
|
||||
|
||||
export interface AutoPilotCreationSettings {
|
||||
autopilotTier?: AutopilotTier;
|
||||
maxThroughput?: number;
|
||||
}
|
||||
|
||||
export enum AutopilotTier {
|
||||
Tier1 = 1,
|
||||
Tier2 = 2,
|
||||
Tier3 = 3,
|
||||
Tier4 = 4
|
||||
}
|
||||
|
||||
export interface Query {
|
||||
id: string;
|
||||
resourceId: string;
|
||||
@@ -251,7 +262,9 @@ export interface Query {
|
||||
}
|
||||
|
||||
export interface AutoPilotOfferSettings {
|
||||
tier?: AutopilotTier;
|
||||
maximumTierThroughput?: number;
|
||||
targetTier?: AutopilotTier;
|
||||
maxThroughput?: number;
|
||||
targetMaxThroughput?: number;
|
||||
}
|
||||
@@ -478,6 +491,7 @@ export interface MongoParameters extends RpParameters {
|
||||
rid?: string;
|
||||
rtype?: string;
|
||||
isAutoPilot?: Boolean;
|
||||
autoPilotTier?: string;
|
||||
autoPilotThroughput?: string;
|
||||
analyticalStorageTtl?: number;
|
||||
}
|
||||
|
||||
@@ -355,7 +355,6 @@ export enum CollectionTabKind {
|
||||
Notebook = 13 /* Deprecated */,
|
||||
Terminal = 14,
|
||||
NotebookV2 = 15,
|
||||
SparkMasterTab = 16,
|
||||
Gallery = 17,
|
||||
NotebookViewer = 18,
|
||||
SettingsV2 = 19
|
||||
|
||||
@@ -20,10 +20,6 @@ describe("Component Registerer", () => {
|
||||
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", () => {
|
||||
expect(ko.components.isRegistered("json-editor")).toBe(true);
|
||||
});
|
||||
@@ -123,4 +119,8 @@ describe("Component Registerer", () => {
|
||||
it("should register dynamic-list component", () => {
|
||||
expect(ko.components.isRegistered("dynamic-list")).toBe(true);
|
||||
});
|
||||
|
||||
it("should register throughput-input component", () => {
|
||||
expect(ko.components.isRegistered("throughput-input")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import * as ko from "knockout";
|
||||
import * as PaneComponents from "./Panes/PaneComponents";
|
||||
import * as TabComponents from "./Tabs/TabComponents";
|
||||
import { CollapsiblePanelComponent } from "./Controls/CollapsiblePanel/CollapsiblePanelComponent";
|
||||
import { DiffEditorComponent } from "./Controls/DiffEditor/DiffEditorComponent";
|
||||
import { DynamicListComponent } from "./Controls/DynamicList/DynamicListComponent";
|
||||
import { EditorComponent } from "./Controls/Editor/EditorComponent";
|
||||
@@ -11,17 +10,18 @@ import { InputTypeaheadComponent } from "./Controls/InputTypeahead/InputTypeahea
|
||||
import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent";
|
||||
import { NewVertexComponent } from "./Graph/NewVertexComponent/NewVertexComponent";
|
||||
import { TabsManagerKOComponent } from "./Tabs/TabsManager";
|
||||
import { ThroughputInputComponent } from "./Controls/ThroughputInput/ThroughputInputComponent";
|
||||
import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3";
|
||||
|
||||
ko.components.register("input-typeahead", new InputTypeaheadComponent());
|
||||
ko.components.register("new-vertex-form", NewVertexComponent);
|
||||
ko.components.register("error-display", new ErrorDisplayComponent());
|
||||
ko.components.register("graph-style", GraphStyleComponent);
|
||||
ko.components.register("collapsible-panel", new CollapsiblePanelComponent());
|
||||
ko.components.register("editor", new EditorComponent());
|
||||
ko.components.register("json-editor", new JsonEditorComponent());
|
||||
ko.components.register("diff-editor", new DiffEditorComponent());
|
||||
ko.components.register("dynamic-list", DynamicListComponent);
|
||||
ko.components.register("throughput-input", ThroughputInputComponent);
|
||||
ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3);
|
||||
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("notebookv2-tab", new TabComponents.NotebookV2Tab());
|
||||
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("notebook-viewer-tab", new TabComponents.NotebookViewerTab());
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -44,6 +44,7 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
|
||||
onChange?: (_?: React.FormEvent<HTMLElement | HTMLInputElement>, checked?: boolean) => void;
|
||||
}[] = [
|
||||
{ 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.hosteddataexplorerenabled", label: "Hosted Data Explorer (deprecated?)", value: "true" },
|
||||
{ key: "feature.enablettl", label: "Enable TTL", value: "true" },
|
||||
|
||||
@@ -131,6 +131,12 @@ exports[`Feature panel renders all flags 1`] = `
|
||||
label="Enable change feed policy"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
<StyledCheckboxBase
|
||||
checked={false}
|
||||
key="feature.enablerupm"
|
||||
label="Enable RUPM"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
<StyledCheckboxBase
|
||||
checked={false}
|
||||
key="feature.dataexplorerexecutesproc"
|
||||
|
||||
@@ -125,6 +125,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
private container: Explorer;
|
||||
private changeFeedPolicyVisible: boolean;
|
||||
private isFixedContainer: boolean;
|
||||
private autoPilotTiersList: ViewModels.DropdownOption<DataModels.AutopilotTier>[];
|
||||
private shouldShowIndexingPolicyEditor: boolean;
|
||||
public mongoDBCollectionResource: MongoDBCollectionResource;
|
||||
|
||||
@@ -229,7 +230,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
if (
|
||||
this.container.isMongoIndexEditorEnabled() &&
|
||||
this.container.isPreferredApiMongoDB() &&
|
||||
this.container.isEnableMongoCapabilityPresent() &&
|
||||
this.container.databaseAccount()
|
||||
) {
|
||||
await this.refreshIndexTransformationProgress();
|
||||
@@ -395,54 +395,24 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
}
|
||||
|
||||
if (this.state.isMongoIndexingPolicySaveable && this.mongoDBCollectionResource) {
|
||||
try {
|
||||
const newMongoIndexes = this.getMongoIndexesToSave();
|
||||
const newMongoCollection: MongoDBCollectionResource = {
|
||||
...this.mongoDBCollectionResource,
|
||||
indexes: newMongoIndexes
|
||||
};
|
||||
const newMongoIndexes = this.getMongoIndexesToSave();
|
||||
const newMongoCollection: MongoDBCollectionResource = {
|
||||
...this.mongoDBCollectionResource,
|
||||
indexes: newMongoIndexes
|
||||
};
|
||||
this.mongoDBCollectionResource = await updateMongoDBCollectionThroughRP(
|
||||
this.collection.databaseId,
|
||||
this.collection.id(),
|
||||
newMongoCollection
|
||||
);
|
||||
|
||||
this.mongoDBCollectionResource = await updateMongoDBCollectionThroughRP(
|
||||
this.collection.databaseId,
|
||||
this.collection.id(),
|
||||
newMongoCollection
|
||||
);
|
||||
|
||||
await this.refreshIndexTransformationProgress();
|
||||
this.setState({
|
||||
isMongoIndexingPolicySaveable: false,
|
||||
indexesToDrop: [],
|
||||
indexesToAdd: [],
|
||||
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;
|
||||
}
|
||||
await this.refreshIndexTransformationProgress();
|
||||
this.setState({
|
||||
isMongoIndexingPolicySaveable: false,
|
||||
indexesToDrop: [],
|
||||
indexesToAdd: [],
|
||||
currentMongoIndexes: [...this.mongoDBCollectionResource.indexes]
|
||||
});
|
||||
}
|
||||
|
||||
if (this.state.isScaleSaveable) {
|
||||
@@ -454,7 +424,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
newOffer.content.offerThroughput = newThroughput;
|
||||
} else {
|
||||
newOffer.content = {
|
||||
offerThroughput: newThroughput
|
||||
offerThroughput: newThroughput,
|
||||
offerIsRUPerMinuteThroughputEnabled: false
|
||||
};
|
||||
}
|
||||
|
||||
@@ -497,7 +468,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
resourceGroup: userContext.resourceGroup,
|
||||
databaseName: this.collection.databaseId,
|
||||
collectionName: this.collection.id(),
|
||||
throughput: newThroughput
|
||||
throughput: newThroughput,
|
||||
offerIsRUPerMinuteThroughputEnabled: false
|
||||
};
|
||||
|
||||
await updateOfferThroughputBeyondLimit(requestPayload);
|
||||
@@ -570,7 +542,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
tabTitle: this.props.settingsTab.tabTitle(),
|
||||
error: reason.message
|
||||
error: reason
|
||||
},
|
||||
startKey
|
||||
);
|
||||
@@ -895,6 +867,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
||||
collection: this.collection,
|
||||
container: this.container,
|
||||
isFixedContainer: this.isFixedContainer,
|
||||
autoPilotTiersList: this.autoPilotTiersList,
|
||||
onThroughputChange: this.onThroughputChange,
|
||||
throughput: this.state.throughput,
|
||||
throughputBaseline: this.state.throughputBaseline,
|
||||
|
||||
@@ -31,7 +31,7 @@ class SettingsRenderUtilsTestComponent extends React.Component {
|
||||
{getAutoPilotV3SpendElement(1000, true)}
|
||||
{getAutoPilotV3SpendElement(undefined, true)}
|
||||
|
||||
{getEstimatedSpendElement(1000, "mooncake", 2, false)}
|
||||
{getEstimatedSpendElement(1000, "mooncake", 2, false, true)}
|
||||
|
||||
{getEstimatedAutoscaleSpendElement(1000, "mooncake", 2, false)}
|
||||
|
||||
|
||||
@@ -199,9 +199,10 @@ export const getEstimatedSpendElement = (
|
||||
throughput: number,
|
||||
serverId: string,
|
||||
regions: number,
|
||||
multimaster: boolean
|
||||
multimaster: boolean,
|
||||
rupmEnabled: boolean
|
||||
): 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 monthlyPrice: number = hourlyPrice * hoursInAMonth;
|
||||
const currency: string = getPriceCurrency(serverId);
|
||||
|
||||
@@ -20,6 +20,7 @@ describe("ScaleComponent", () => {
|
||||
collection: collection,
|
||||
container: container,
|
||||
isFixedContainer: false,
|
||||
autoPilotTiersList: [],
|
||||
onThroughputChange: () => {
|
||||
return;
|
||||
},
|
||||
|
||||
@@ -24,6 +24,7 @@ export interface ScaleComponentProps {
|
||||
collection: ViewModels.Collection;
|
||||
container: Explorer;
|
||||
isFixedContainer: boolean;
|
||||
autoPilotTiersList: ViewModels.DropdownOption<DataModels.AutopilotTier>[];
|
||||
onThroughputChange: (newThroughput: number) => void;
|
||||
throughput: number;
|
||||
throughputBaseline: number;
|
||||
@@ -85,7 +86,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
||||
|
||||
public getThroughputTitle = (): string => {
|
||||
if (this.props.isAutoPilotSelected) {
|
||||
return AutoPilotUtils.getAutoPilotHeaderText();
|
||||
return AutoPilotUtils.getAutoPilotHeaderText(false);
|
||||
}
|
||||
|
||||
const minThroughput: string = getMinRUs(this.props.collection, this.props.container).toLocaleString();
|
||||
|
||||
@@ -174,7 +174,8 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
||||
this.overrideWithAutoPilotSettings() ? this.props.maxAutoPilotThroughput : offerThroughput,
|
||||
serverId,
|
||||
regions,
|
||||
multimaster
|
||||
multimaster,
|
||||
false
|
||||
);
|
||||
} else {
|
||||
estimatedSpend = getEstimatedAutoscaleSpendElement(
|
||||
|
||||
@@ -22,6 +22,7 @@ export const collection = ({
|
||||
offer: ko.observable<DataModels.Offer>({
|
||||
content: {
|
||||
offerThroughput: 10000,
|
||||
offerIsRUPerMinuteThroughputEnabled: false,
|
||||
collectionThroughputInfo: {
|
||||
minimumRUForCollection: 6000,
|
||||
numPhysicalPartitions: 4
|
||||
|
||||
@@ -40,6 +40,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"_openShareDialog": [Function],
|
||||
"_panes": Array [
|
||||
AddDatabasePane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -55,6 +56,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "adddatabasepane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -67,6 +69,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"onMoreDetailsKeyPress": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"showUpsellMessage": [Function],
|
||||
"throughput": [Function],
|
||||
"throughputRangeText": [Function],
|
||||
@@ -83,6 +86,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
AddCollectionPane {
|
||||
"_isSynapseLinkEnabled": [Function],
|
||||
"autoPilotThroughput": [Function],
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -104,6 +108,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"formWarnings": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "addcollectionpane",
|
||||
"isAnalyticalStorageOn": [Function],
|
||||
"isAutoPilotSelected": [Function],
|
||||
@@ -133,7 +138,12 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"partitionKeyVisible": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"rupm": [Function],
|
||||
"rupmVisible": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"shouldCreateMongoWildcardIndex": [Function],
|
||||
"shouldUseDatabaseThroughput": [Function],
|
||||
@@ -344,6 +354,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"visible": [Function],
|
||||
},
|
||||
CassandraAddCollectionPane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -355,6 +366,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "cassandraaddcollectionpane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -375,7 +387,10 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"requestUnitsUsageCostShared": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotThroughput": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"sharedThroughputSpendAck": [Function],
|
||||
"sharedThroughputSpendAckText": [Function],
|
||||
@@ -570,6 +585,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"addCollectionPane": AddCollectionPane {
|
||||
"_isSynapseLinkEnabled": [Function],
|
||||
"autoPilotThroughput": [Function],
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -591,6 +607,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"formWarnings": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "addcollectionpane",
|
||||
"isAnalyticalStorageOn": [Function],
|
||||
"isAutoPilotSelected": [Function],
|
||||
@@ -620,7 +637,12 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"partitionKeyVisible": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"rupm": [Function],
|
||||
"rupmVisible": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"shouldCreateMongoWildcardIndex": [Function],
|
||||
"shouldUseDatabaseThroughput": [Function],
|
||||
@@ -650,6 +672,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
},
|
||||
"addCollectionText": [Function],
|
||||
"addDatabasePane": AddDatabasePane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -665,6 +688,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "adddatabasepane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -677,6 +701,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"onMoreDetailsKeyPress": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"showUpsellMessage": [Function],
|
||||
"throughput": [Function],
|
||||
"throughputRangeText": [Function],
|
||||
@@ -753,6 +778,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"canExceedMaximumValue": [Function],
|
||||
"canSaveQueries": [Function],
|
||||
"cassandraAddCollectionPane": CassandraAddCollectionPane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -764,6 +790,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "cassandraaddcollectionpane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -784,7 +811,10 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"requestUnitsUsageCostShared": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotThroughput": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"sharedThroughputSpendAck": [Function],
|
||||
"sharedThroughputSpendAckText": [Function],
|
||||
@@ -939,6 +969,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"title": [Function],
|
||||
"visible": [Function],
|
||||
},
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"hasStorageAnalyticsAfecFeature": [Function],
|
||||
"hasWriteAccess": [Function],
|
||||
"isAccountReady": [Function],
|
||||
@@ -1316,6 +1347,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"_openShareDialog": [Function],
|
||||
"_panes": Array [
|
||||
AddDatabasePane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -1331,6 +1363,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "adddatabasepane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -1343,6 +1376,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"onMoreDetailsKeyPress": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"showUpsellMessage": [Function],
|
||||
"throughput": [Function],
|
||||
"throughputRangeText": [Function],
|
||||
@@ -1359,6 +1393,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
AddCollectionPane {
|
||||
"_isSynapseLinkEnabled": [Function],
|
||||
"autoPilotThroughput": [Function],
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -1380,6 +1415,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"formWarnings": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "addcollectionpane",
|
||||
"isAnalyticalStorageOn": [Function],
|
||||
"isAutoPilotSelected": [Function],
|
||||
@@ -1409,7 +1445,12 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"partitionKeyVisible": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"rupm": [Function],
|
||||
"rupmVisible": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"shouldCreateMongoWildcardIndex": [Function],
|
||||
"shouldUseDatabaseThroughput": [Function],
|
||||
@@ -1620,6 +1661,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"visible": [Function],
|
||||
},
|
||||
CassandraAddCollectionPane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -1631,6 +1673,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "cassandraaddcollectionpane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -1651,7 +1694,10 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"requestUnitsUsageCostShared": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotThroughput": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"sharedThroughputSpendAck": [Function],
|
||||
"sharedThroughputSpendAckText": [Function],
|
||||
@@ -1846,6 +1892,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"addCollectionPane": AddCollectionPane {
|
||||
"_isSynapseLinkEnabled": [Function],
|
||||
"autoPilotThroughput": [Function],
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -1867,6 +1914,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"formWarnings": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "addcollectionpane",
|
||||
"isAnalyticalStorageOn": [Function],
|
||||
"isAutoPilotSelected": [Function],
|
||||
@@ -1896,7 +1944,12 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"partitionKeyVisible": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"rupm": [Function],
|
||||
"rupmVisible": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"shouldCreateMongoWildcardIndex": [Function],
|
||||
"shouldUseDatabaseThroughput": [Function],
|
||||
@@ -1926,6 +1979,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
},
|
||||
"addCollectionText": [Function],
|
||||
"addDatabasePane": AddDatabasePane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -1941,6 +1995,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "adddatabasepane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -1953,6 +2008,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"onMoreDetailsKeyPress": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"showUpsellMessage": [Function],
|
||||
"throughput": [Function],
|
||||
"throughputRangeText": [Function],
|
||||
@@ -2029,6 +2085,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"canExceedMaximumValue": [Function],
|
||||
"canSaveQueries": [Function],
|
||||
"cassandraAddCollectionPane": CassandraAddCollectionPane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -2040,6 +2097,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "cassandraaddcollectionpane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -2060,7 +2118,10 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"requestUnitsUsageCostShared": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotThroughput": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"sharedThroughputSpendAck": [Function],
|
||||
"sharedThroughputSpendAckText": [Function],
|
||||
@@ -2215,6 +2276,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"title": [Function],
|
||||
"visible": [Function],
|
||||
},
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"hasStorageAnalyticsAfecFeature": [Function],
|
||||
"hasWriteAccess": [Function],
|
||||
"isAccountReady": [Function],
|
||||
@@ -2605,6 +2667,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"_openShareDialog": [Function],
|
||||
"_panes": Array [
|
||||
AddDatabasePane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -2620,6 +2683,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "adddatabasepane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -2632,6 +2696,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"onMoreDetailsKeyPress": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"showUpsellMessage": [Function],
|
||||
"throughput": [Function],
|
||||
"throughputRangeText": [Function],
|
||||
@@ -2648,6 +2713,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
AddCollectionPane {
|
||||
"_isSynapseLinkEnabled": [Function],
|
||||
"autoPilotThroughput": [Function],
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -2669,6 +2735,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"formWarnings": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "addcollectionpane",
|
||||
"isAnalyticalStorageOn": [Function],
|
||||
"isAutoPilotSelected": [Function],
|
||||
@@ -2698,7 +2765,12 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"partitionKeyVisible": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"rupm": [Function],
|
||||
"rupmVisible": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"shouldCreateMongoWildcardIndex": [Function],
|
||||
"shouldUseDatabaseThroughput": [Function],
|
||||
@@ -2909,6 +2981,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"visible": [Function],
|
||||
},
|
||||
CassandraAddCollectionPane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -2920,6 +2993,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "cassandraaddcollectionpane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -2940,7 +3014,10 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"requestUnitsUsageCostShared": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotThroughput": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"sharedThroughputSpendAck": [Function],
|
||||
"sharedThroughputSpendAckText": [Function],
|
||||
@@ -3135,6 +3212,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"addCollectionPane": AddCollectionPane {
|
||||
"_isSynapseLinkEnabled": [Function],
|
||||
"autoPilotThroughput": [Function],
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -3156,6 +3234,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"formWarnings": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "addcollectionpane",
|
||||
"isAnalyticalStorageOn": [Function],
|
||||
"isAutoPilotSelected": [Function],
|
||||
@@ -3185,7 +3264,12 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"partitionKeyVisible": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"rupm": [Function],
|
||||
"rupmVisible": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"shouldCreateMongoWildcardIndex": [Function],
|
||||
"shouldUseDatabaseThroughput": [Function],
|
||||
@@ -3215,6 +3299,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
},
|
||||
"addCollectionText": [Function],
|
||||
"addDatabasePane": AddDatabasePane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -3230,6 +3315,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "adddatabasepane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -3242,6 +3328,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"onMoreDetailsKeyPress": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"showUpsellMessage": [Function],
|
||||
"throughput": [Function],
|
||||
"throughputRangeText": [Function],
|
||||
@@ -3318,6 +3405,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"canExceedMaximumValue": [Function],
|
||||
"canSaveQueries": [Function],
|
||||
"cassandraAddCollectionPane": CassandraAddCollectionPane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -3329,6 +3417,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "cassandraaddcollectionpane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -3349,7 +3438,10 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"requestUnitsUsageCostShared": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotThroughput": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"sharedThroughputSpendAck": [Function],
|
||||
"sharedThroughputSpendAckText": [Function],
|
||||
@@ -3504,6 +3596,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"title": [Function],
|
||||
"visible": [Function],
|
||||
},
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"hasStorageAnalyticsAfecFeature": [Function],
|
||||
"hasWriteAccess": [Function],
|
||||
"isAccountReady": [Function],
|
||||
@@ -3881,6 +3974,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"_openShareDialog": [Function],
|
||||
"_panes": Array [
|
||||
AddDatabasePane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -3896,6 +3990,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "adddatabasepane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -3908,6 +4003,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"onMoreDetailsKeyPress": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"showUpsellMessage": [Function],
|
||||
"throughput": [Function],
|
||||
"throughputRangeText": [Function],
|
||||
@@ -3924,6 +4020,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
AddCollectionPane {
|
||||
"_isSynapseLinkEnabled": [Function],
|
||||
"autoPilotThroughput": [Function],
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -3945,6 +4042,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"formWarnings": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "addcollectionpane",
|
||||
"isAnalyticalStorageOn": [Function],
|
||||
"isAutoPilotSelected": [Function],
|
||||
@@ -3974,7 +4072,12 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"partitionKeyVisible": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"rupm": [Function],
|
||||
"rupmVisible": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"shouldCreateMongoWildcardIndex": [Function],
|
||||
"shouldUseDatabaseThroughput": [Function],
|
||||
@@ -4185,6 +4288,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"visible": [Function],
|
||||
},
|
||||
CassandraAddCollectionPane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -4196,6 +4300,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "cassandraaddcollectionpane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -4216,7 +4321,10 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"requestUnitsUsageCostShared": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotThroughput": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"sharedThroughputSpendAck": [Function],
|
||||
"sharedThroughputSpendAckText": [Function],
|
||||
@@ -4411,6 +4519,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"addCollectionPane": AddCollectionPane {
|
||||
"_isSynapseLinkEnabled": [Function],
|
||||
"autoPilotThroughput": [Function],
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -4432,6 +4541,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"formWarnings": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "addcollectionpane",
|
||||
"isAnalyticalStorageOn": [Function],
|
||||
"isAutoPilotSelected": [Function],
|
||||
@@ -4461,7 +4571,12 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"partitionKeyVisible": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"rupm": [Function],
|
||||
"rupmVisible": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"shouldCreateMongoWildcardIndex": [Function],
|
||||
"shouldUseDatabaseThroughput": [Function],
|
||||
@@ -4491,6 +4606,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
},
|
||||
"addCollectionText": [Function],
|
||||
"addDatabasePane": AddDatabasePane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -4506,6 +4622,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "adddatabasepane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -4518,6 +4635,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"onMoreDetailsKeyPress": [Function],
|
||||
"requestUnitsUsageCost": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"showUpsellMessage": [Function],
|
||||
"throughput": [Function],
|
||||
"throughputRangeText": [Function],
|
||||
@@ -4594,6 +4712,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"canExceedMaximumValue": [Function],
|
||||
"canSaveQueries": [Function],
|
||||
"cassandraAddCollectionPane": CassandraAddCollectionPane {
|
||||
"autoPilotTiersList": [Function],
|
||||
"autoPilotUsageCost": [Function],
|
||||
"canConfigureThroughput": [Function],
|
||||
"canExceedMaximumValue": [Function],
|
||||
@@ -4605,6 +4724,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"firstFieldHasFocus": [Function],
|
||||
"formErrors": [Function],
|
||||
"formErrorsDetails": [Function],
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"id": "cassandraaddcollectionpane",
|
||||
"isAutoPilotSelected": [Function],
|
||||
"isExecuting": [Function],
|
||||
@@ -4625,7 +4745,10 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"requestUnitsUsageCostShared": [Function],
|
||||
"ruToolTipText": [Function],
|
||||
"selectedAutoPilotThroughput": [Function],
|
||||
"selectedAutoPilotTier": [Function],
|
||||
"selectedSharedAutoPilotTier": [Function],
|
||||
"sharedAutoPilotThroughput": [Function],
|
||||
"sharedAutoPilotTiersList": [Function],
|
||||
"sharedThroughputRangeText": [Function],
|
||||
"sharedThroughputSpendAck": [Function],
|
||||
"sharedThroughputSpendAckText": [Function],
|
||||
@@ -4780,6 +4903,7 @@ exports[`SettingsComponent renders 1`] = `
|
||||
"title": [Function],
|
||||
"visible": [Function],
|
||||
},
|
||||
"hasAutoPilotV2FeatureFlag": [Function],
|
||||
"hasStorageAnalyticsAfecFeature": [Function],
|
||||
"hasWriteAccess": [Function],
|
||||
"isAccountReady": [Function],
|
||||
|
||||
@@ -69,15 +69,15 @@ exports[`SettingsUtils functions render 1`] = `
|
||||
|
||||
<b>
|
||||
¥
|
||||
1.02
|
||||
1.29
|
||||
hourly
|
||||
/
|
||||
¥
|
||||
24.48
|
||||
31.06
|
||||
daily
|
||||
/
|
||||
¥
|
||||
744.60
|
||||
944.60
|
||||
monthly
|
||||
|
||||
</b>
|
||||
|
||||
222
src/Explorer/Controls/ThroughputInput/ThroughputInput.test.ts
Normal file
222
src/Explorer/Controls/ThroughputInput/ThroughputInput.test.ts
Normal 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");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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>
|
||||
@@ -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
|
||||
};
|
||||
@@ -19,6 +19,7 @@ describe("ContainerSampleGenerator", () => {
|
||||
explorerStub.isPreferredApiTable = ko.computed<boolean>(() => false);
|
||||
explorerStub.isPreferredApiCassandra = ko.computed<boolean>(() => false);
|
||||
explorerStub.canExceedMaximumValue = ko.computed<boolean>(() => false);
|
||||
explorerStub.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||
explorerStub.findDatabaseWithId = () => database;
|
||||
explorerStub.refreshAllDatabases = () => Q.resolve();
|
||||
return explorerStub;
|
||||
|
||||
@@ -212,6 +212,7 @@ export default class Explorer {
|
||||
public isHostedDataExplorerEnabled: ko.Computed<boolean>;
|
||||
public isRightPanelV2Enabled: ko.Computed<boolean>;
|
||||
public canExceedMaximumValue: ko.Computed<boolean>;
|
||||
public hasAutoPilotV2FeatureFlag: ko.Computed<boolean>;
|
||||
|
||||
public shouldShowShareDialogContents: ko.Observable<boolean>;
|
||||
public shareAccessData: ko.Observable<AdHocAccessData>;
|
||||
@@ -421,6 +422,13 @@ export default class Explorer {
|
||||
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.databases = ko.observableArray<ViewModels.Database>();
|
||||
|
||||
@@ -20,7 +20,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
mockExplorer.isSparkEnabled = ko.observable(true);
|
||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
|
||||
|
||||
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
||||
mockExplorer.isNotebookEnabled = ko.observable(false);
|
||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
||||
@@ -62,7 +62,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
mockExplorer.isSparkEnabled = ko.observable(true);
|
||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
|
||||
|
||||
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
||||
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
|
||||
});
|
||||
@@ -126,7 +126,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
mockExplorer.isSparkEnabled = ko.observable(true);
|
||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
|
||||
|
||||
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
||||
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
|
||||
});
|
||||
@@ -208,7 +208,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||
mockExplorer.isSparkEnabled = ko.observable(true);
|
||||
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
|
||||
|
||||
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
||||
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
|
||||
});
|
||||
@@ -289,7 +289,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
mockExplorer.isPreferredApiTable = ko.computed(() => true);
|
||||
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
|
||||
mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false);
|
||||
|
||||
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||
mockExplorer.isSparkEnabled = ko.observable(true);
|
||||
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
||||
@@ -348,7 +348,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
||||
mockExplorer.isAuthWithResourceToken = ko.observable(true);
|
||||
mockExplorer.isPreferredApiDocumentDB = ko.computed(() => true);
|
||||
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
||||
|
||||
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||
mockExplorer.isResourceTokenCollectionNodeSelected = ko.computed(() => true);
|
||||
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
|
||||
});
|
||||
|
||||
@@ -127,6 +127,7 @@
|
||||
be shared across all containers within the database.</span>
|
||||
</span>
|
||||
</div>
|
||||
<!-- ko if: hasAutoPilotV2FeatureFlag && !hasAutoPilotV2FeatureFlag() -->
|
||||
<div data-bind="visible: databaseCreateNewShared() && databaseCreateNew()">
|
||||
<!-- 1 -->
|
||||
<throughput-input-autopilot-v3 params="{
|
||||
@@ -157,6 +158,38 @@
|
||||
</throughput-input-autopilot-v3>
|
||||
</div>
|
||||
<!-- /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 -->
|
||||
</div>
|
||||
|
||||
@@ -178,6 +211,19 @@
|
||||
data-bind="value: collectionId, attr: { 'aria-label': collectionIdTitle }">
|
||||
</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 -->
|
||||
<div class="seconddivpadding"
|
||||
data-bind="visible: showIndexingOptionsForSharedThroughput() && !container.isPreferredApiMongoDB()">
|
||||
@@ -241,8 +287,96 @@
|
||||
</div>
|
||||
<!-- Unlimited option button - End -->
|
||||
</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 -->
|
||||
<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">
|
||||
<p>
|
||||
<span class="mandatoryStar">*</span>
|
||||
@@ -308,6 +442,7 @@
|
||||
<!-- Provision collection throughput checkbox - end -->
|
||||
|
||||
<!-- Provision collection throughput spinner - start -->
|
||||
<!-- ko if: hasAutoPilotV2FeatureFlag && !hasAutoPilotV2FeatureFlag() -->
|
||||
<div data-bind="visible: displayCollectionThroughput" data-test="addCollection-displayCollectionThroughput">
|
||||
<!-- 3 -->
|
||||
<throughput-input-autopilot-v3 params="{
|
||||
@@ -337,6 +472,40 @@
|
||||
}">
|
||||
</throughput-input-autopilot-v3>
|
||||
</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 -->
|
||||
<!-- /ko -->
|
||||
<!-- Provision collection throughput - end -->
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import AddCollectionPane from "./AddCollectionPane";
|
||||
import Explorer from "../Explorer";
|
||||
import { DatabaseAccount } from "../../Contracts/DataModels";
|
||||
import ko from "knockout";
|
||||
import { AutopilotTier, DatabaseAccount } from "../../Contracts/DataModels";
|
||||
|
||||
describe("Add Collection Pane", () => {
|
||||
describe("isValid()", () => {
|
||||
@@ -40,6 +41,25 @@ describe("Add Collection Pane", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
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", () => {
|
||||
|
||||
@@ -41,6 +41,8 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
public partitionKeyVisible: ko.Computed<boolean>;
|
||||
public partitionKeyPattern: ko.Computed<string>;
|
||||
public partitionKeyTitle: ko.Computed<string>;
|
||||
public rupm: ko.Observable<string>;
|
||||
public rupmVisible: ko.Observable<boolean>;
|
||||
public storage: ko.Observable<string>;
|
||||
public throughputSinglePartition: ViewModels.Editable<number>;
|
||||
public throughputMultiPartition: ViewModels.Editable<number>;
|
||||
@@ -73,6 +75,10 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
public debugstring: ko.Computed<string>;
|
||||
public displayCollectionThroughput: ko.Computed<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 autoPilotThroughput: ko.Observable<number>;
|
||||
public sharedAutoPilotThroughput: ko.Observable<number>;
|
||||
@@ -86,6 +92,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
public isAnalyticalStorageOn: ko.Observable<boolean>;
|
||||
public isSynapseLinkUpdating: ko.Computed<boolean>;
|
||||
public canExceedMaximumValue: ko.PureComputed<boolean>;
|
||||
public hasAutoPilotV2FeatureFlag: ko.PureComputed<boolean>;
|
||||
public ruToolTipText: ko.Computed<string>;
|
||||
public canConfigureThroughput: ko.PureComputed<boolean>;
|
||||
public showUpsellMessage: ko.PureComputed<boolean>;
|
||||
@@ -95,7 +102,8 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
|
||||
constructor(options: AddCollectionPaneOptions) {
|
||||
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.showUpsellMessage = ko.pureComputed(() => !this.container.isServerlessEnabled());
|
||||
this.formWarnings = ko.observable<string>();
|
||||
@@ -140,6 +148,12 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
}
|
||||
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());
|
||||
|
||||
@@ -157,13 +171,13 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
this.databaseHasSharedOffer = ko.observable<boolean>(true);
|
||||
this.throughputRangeText = ko.pureComputed<string>(() => {
|
||||
if (this.isAutoPilotSelected()) {
|
||||
return AutoPilotUtils.getAutoPilotHeaderText();
|
||||
return AutoPilotUtils.getAutoPilotHeaderText(this.hasAutoPilotV2FeatureFlag());
|
||||
}
|
||||
return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`;
|
||||
});
|
||||
this.sharedThroughputRangeText = ko.pureComputed<string>(() => {
|
||||
if (this.isSharedAutoPilotSelected()) {
|
||||
return AutoPilotUtils.getAutoPilotHeaderText();
|
||||
return AutoPilotUtils.getAutoPilotHeaderText(this.hasAutoPilotV2FeatureFlag());
|
||||
}
|
||||
return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`;
|
||||
});
|
||||
@@ -192,6 +206,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
account.properties.readLocations.length) ||
|
||||
1;
|
||||
const multimaster = (account && account.properties && account.properties.enableMultipleWriteLocations) || false;
|
||||
const rupmEnabled: boolean = this.rupm() === Constants.RUPMStates.on;
|
||||
|
||||
let throughputSpendAckText: string;
|
||||
let estimatedSpend: string;
|
||||
@@ -201,15 +216,23 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
serverId,
|
||||
regions,
|
||||
multimaster,
|
||||
rupmEnabled,
|
||||
this.isSharedAutoPilotSelected()
|
||||
);
|
||||
estimatedSpend = PricingUtils.getEstimatedSpendHtml(offerThroughput, serverId, regions, multimaster);
|
||||
estimatedSpend = PricingUtils.getEstimatedSpendHtml(
|
||||
offerThroughput,
|
||||
serverId,
|
||||
regions,
|
||||
multimaster,
|
||||
rupmEnabled
|
||||
);
|
||||
} else {
|
||||
throughputSpendAckText = PricingUtils.getEstimatedSpendAcknowledgeString(
|
||||
this.sharedAutoPilotThroughput(),
|
||||
serverId,
|
||||
regions,
|
||||
multimaster,
|
||||
rupmEnabled,
|
||||
this.isSharedAutoPilotSelected()
|
||||
);
|
||||
estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml(
|
||||
@@ -246,6 +269,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
account.properties.readLocations.length) ||
|
||||
1;
|
||||
const multimaster = (account && account.properties && account.properties.enableMultipleWriteLocations) || false;
|
||||
const rupmEnabled: boolean = this.rupm() === Constants.RUPMStates.on;
|
||||
|
||||
let throughputSpendAckText: string;
|
||||
let estimatedSpend: string;
|
||||
@@ -255,13 +279,15 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
serverId,
|
||||
regions,
|
||||
multimaster,
|
||||
rupmEnabled,
|
||||
this.isAutoPilotSelected()
|
||||
);
|
||||
estimatedSpend = PricingUtils.getEstimatedSpendHtml(
|
||||
this.throughputMultiPartition(),
|
||||
serverId,
|
||||
regions,
|
||||
multimaster
|
||||
multimaster,
|
||||
rupmEnabled
|
||||
);
|
||||
} else {
|
||||
throughputSpendAckText = PricingUtils.getEstimatedSpendAcknowledgeString(
|
||||
@@ -269,6 +295,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
serverId,
|
||||
regions,
|
||||
multimaster,
|
||||
rupmEnabled,
|
||||
this.isAutoPilotSelected()
|
||||
);
|
||||
estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml(
|
||||
@@ -420,7 +447,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
|
||||
this.throughputSpendAckVisible = ko.pureComputed<boolean>(() => {
|
||||
const autoscaleThroughput = this.autoPilotThroughput() * 1;
|
||||
if (this.isAutoPilotSelected()) {
|
||||
if (!this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected()) {
|
||||
return autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K;
|
||||
}
|
||||
const selectedThroughput: number = this._getThroughput();
|
||||
@@ -463,6 +490,14 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
|
||||
this.isAutoPilotSelected = 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.sharedAutoPilotThroughput = ko.observable<number>(AutoPilotUtils.minAutoPilotThroughput);
|
||||
this.autoPilotUsageCost = ko.pureComputed<string>(() => {
|
||||
@@ -471,7 +506,9 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
return "";
|
||||
}
|
||||
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();
|
||||
@@ -666,7 +703,8 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
storage: this.storage(),
|
||||
offerThroughput: this._getThroughput(),
|
||||
partitionKey: this.partitionKey(),
|
||||
databaseId: this.databaseId()
|
||||
databaseId: this.databaseId(),
|
||||
rupm: this.rupm()
|
||||
}),
|
||||
subscriptionType: ViewModels.SubscriptionType[this.container.subscriptionType()],
|
||||
subscriptionQuotaId: this.container.quotaId(),
|
||||
@@ -767,7 +805,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
id: this.collectionId(),
|
||||
storage: this.storage(),
|
||||
partitionKey,
|
||||
|
||||
rupm: this.rupm(),
|
||||
uniqueKeyPolicy,
|
||||
collectionWithThroughputInShared: this.collectionWithThroughputInShared()
|
||||
}),
|
||||
@@ -842,7 +880,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
id: this.collectionId(),
|
||||
storage: this.storage(),
|
||||
partitionKey,
|
||||
|
||||
rupm: this.rupm(),
|
||||
uniqueKeyPolicy,
|
||||
collectionWithThroughputInShared: this.collectionWithThroughputInShared()
|
||||
}),
|
||||
@@ -878,7 +916,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
id: this.collectionId(),
|
||||
storage: this.storage(),
|
||||
partitionKey,
|
||||
|
||||
rupm: this.rupm(),
|
||||
uniqueKeyPolicy,
|
||||
collectionWithThroughputInShared: this.collectionWithThroughputInShared()
|
||||
},
|
||||
@@ -904,9 +942,13 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
this.throughputSpendAck(false);
|
||||
this.isAutoPilotSelected(false);
|
||||
this.isSharedAutoPilotSelected(false);
|
||||
this.autoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
|
||||
this.sharedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
|
||||
|
||||
if (!this.hasAutoPilotV2FeatureFlag()) {
|
||||
this.autoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
|
||||
this.sharedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
|
||||
} else {
|
||||
this.selectedAutoPilotTier(undefined);
|
||||
this.selectedSharedAutoPilotTier(undefined);
|
||||
}
|
||||
this.uniqueKeys([]);
|
||||
this.useIndexingForSharedThroughput(true);
|
||||
|
||||
@@ -960,6 +1002,20 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
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() {
|
||||
this.container.openEnableSynapseLinkDialog();
|
||||
}
|
||||
@@ -971,18 +1027,32 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
if ((this.databaseCreateNewShared() && this.isSharedAutoPilotSelected()) || this.isAutoPilotSelected()) {
|
||||
const autoPilot = this._getAutoPilot();
|
||||
if (
|
||||
!autoPilot ||
|
||||
!autoPilot.maxThroughput ||
|
||||
!AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput)
|
||||
(!this.hasAutoPilotV2FeatureFlag() &&
|
||||
(!autoPilot ||
|
||||
!autoPilot.maxThroughput ||
|
||||
!AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput))) ||
|
||||
(this.hasAutoPilotV2FeatureFlag() &&
|
||||
(!autoPilot || !autoPilot.autopilotTier || !AutoPilotUtils.isValidAutoPilotTier(autoPilot.autopilotTier)))
|
||||
) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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()) {
|
||||
this.formErrors(`Please acknowledge the estimated daily spend.`);
|
||||
@@ -997,6 +1067,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
const autoscaleThroughput = this.autoPilotThroughput() * 1;
|
||||
|
||||
if (
|
||||
!this.hasAutoPilotV2FeatureFlag() &&
|
||||
this.isAutoPilotSelected() &&
|
||||
autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K &&
|
||||
!this.throughputSpendAck()
|
||||
@@ -1043,15 +1114,31 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
||||
}
|
||||
|
||||
private _getAutoPilot(): DataModels.AutoPilotCreationSettings {
|
||||
if (this.databaseCreateNewShared() && this.isSharedAutoPilotSelected() && this.sharedAutoPilotThroughput()) {
|
||||
return {
|
||||
maxThroughput: this.sharedAutoPilotThroughput() * 1
|
||||
};
|
||||
if (
|
||||
(!this.hasAutoPilotV2FeatureFlag() &&
|
||||
this.databaseCreateNewShared() &&
|
||||
this.isSharedAutoPilotSelected() &&
|
||||
this.sharedAutoPilotThroughput()) ||
|
||||
(this.hasAutoPilotV2FeatureFlag() &&
|
||||
this.databaseCreateNewShared() &&
|
||||
this.isSharedAutoPilotSelected() &&
|
||||
this.selectedSharedAutoPilotTier())
|
||||
) {
|
||||
return !this.hasAutoPilotV2FeatureFlag()
|
||||
? {
|
||||
maxThroughput: this.sharedAutoPilotThroughput() * 1
|
||||
}
|
||||
: { autopilotTier: this.selectedSharedAutoPilotTier() };
|
||||
}
|
||||
if (this.isAutoPilotSelected() && this.autoPilotThroughput()) {
|
||||
return {
|
||||
maxThroughput: this.autoPilotThroughput() * 1
|
||||
};
|
||||
if (
|
||||
(!this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected() && this.autoPilotThroughput()) ||
|
||||
(this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected() && this.selectedAutoPilotTier())
|
||||
) {
|
||||
return !this.hasAutoPilotV2FeatureFlag()
|
||||
? {
|
||||
maxThroughput: this.autoPilotThroughput() * 1
|
||||
}
|
||||
: { autopilotTier: this.selectedAutoPilotTier() };
|
||||
}
|
||||
|
||||
return undefined;
|
||||
|
||||
@@ -89,6 +89,7 @@
|
||||
data-bind="text: databaseLevelThroughputTooltipText"></span>
|
||||
</span>
|
||||
</div>
|
||||
<!-- ko if: hasAutoPilotV2FeatureFlag && !hasAutoPilotV2FeatureFlag() -->
|
||||
<div data-bind="visible: databaseCreateNewShared">
|
||||
<throughput-input-autopilot-v3 params="{
|
||||
step: 100,
|
||||
@@ -123,6 +124,42 @@
|
||||
support</a> for more than <span data-bind="text: maxThroughputRUText"></span> RU/s.</p>
|
||||
</div>
|
||||
<!-- /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 -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -38,9 +38,12 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
||||
public upsellAnchorUrl: ko.PureComputed<string>;
|
||||
public upsellAnchorText: ko.PureComputed<string>;
|
||||
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 autoPilotUsageCost: ko.Computed<string>;
|
||||
public canExceedMaximumValue: ko.PureComputed<boolean>;
|
||||
public hasAutoPilotV2FeatureFlag: ko.PureComputed<boolean>;
|
||||
public ruToolTipText: ko.Computed<string>;
|
||||
public isFreeTierAccount: ko.Computed<boolean>;
|
||||
public canConfigureThroughput: ko.PureComputed<boolean>;
|
||||
@@ -50,7 +53,8 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
||||
super(options);
|
||||
this.title((this.container && this.container.addDatabaseText()) || "New Database");
|
||||
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.showUpsellMessage = ko.pureComputed(() => !this.container.isServerlessEnabled());
|
||||
|
||||
@@ -90,6 +94,10 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
||||
this.minThroughputRU = ko.observable<number>();
|
||||
this.throughputSpendAckText = ko.observable<string>();
|
||||
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.maxAutoPilotThroughputSet = ko.observable<number>(AutoPilotUtils.minAutoPilotThroughput);
|
||||
this.autoPilotUsageCost = ko.pureComputed<string>(() => {
|
||||
@@ -97,11 +105,13 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
||||
if (!autoPilot) {
|
||||
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>(() => {
|
||||
if (this.isAutoPilotSelected()) {
|
||||
return AutoPilotUtils.getAutoPilotHeaderText();
|
||||
return AutoPilotUtils.getAutoPilotHeaderText(this.hasAutoPilotV2FeatureFlag());
|
||||
}
|
||||
return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`;
|
||||
});
|
||||
@@ -132,12 +142,19 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
||||
let estimatedSpendAcknowledge: string;
|
||||
let estimatedSpend: string;
|
||||
if (!this.isAutoPilotSelected()) {
|
||||
estimatedSpend = PricingUtils.getEstimatedSpendHtml(offerThroughput, serverId, regions, multimaster);
|
||||
estimatedSpend = PricingUtils.getEstimatedSpendHtml(
|
||||
offerThroughput,
|
||||
serverId,
|
||||
regions,
|
||||
multimaster,
|
||||
false /*rupmEnabled*/
|
||||
);
|
||||
estimatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
|
||||
offerThroughput,
|
||||
serverId,
|
||||
regions,
|
||||
multimaster,
|
||||
false /*rupmEnabled*/,
|
||||
this.isAutoPilotSelected()
|
||||
);
|
||||
} else {
|
||||
@@ -152,6 +169,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
||||
serverId,
|
||||
regions,
|
||||
multimaster,
|
||||
false /*rupmEnabled*/,
|
||||
this.isAutoPilotSelected()
|
||||
);
|
||||
}
|
||||
@@ -190,7 +208,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
||||
|
||||
this.throughputSpendAckVisible = ko.pureComputed<boolean>(() => {
|
||||
const autoscaleThroughput = this.maxAutoPilotThroughputSet() * 1;
|
||||
if (this.isAutoPilotSelected()) {
|
||||
if (!this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected()) {
|
||||
return autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K;
|
||||
}
|
||||
|
||||
@@ -307,6 +325,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
||||
public resetData() {
|
||||
this.databaseId("");
|
||||
this.databaseCreateNewShared(this.getSharedThroughputDefault());
|
||||
this.selectedAutoPilotTier(undefined);
|
||||
this.isAutoPilotSelected(false);
|
||||
this.maxAutoPilotThroughputSet(AutoPilotUtils.minAutoPilotThroughput);
|
||||
this._updateThroughputLimitByDatabase();
|
||||
@@ -394,12 +413,17 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
||||
if (this.isAutoPilotSelected()) {
|
||||
const autoPilot = this._isAutoPilotSelectedAndWhatTier();
|
||||
if (
|
||||
!autoPilot ||
|
||||
!autoPilot.maxThroughput ||
|
||||
!AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput)
|
||||
(!this.hasAutoPilotV2FeatureFlag() &&
|
||||
(!autoPilot ||
|
||||
!autoPilot.maxThroughput ||
|
||||
!AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput))) ||
|
||||
(this.hasAutoPilotV2FeatureFlag() &&
|
||||
(!autoPilot || !autoPilot.autopilotTier || !AutoPilotUtils.isValidAutoPilotTier(autoPilot.autopilotTier)))
|
||||
) {
|
||||
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;
|
||||
}
|
||||
@@ -414,6 +438,7 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
||||
const autoscaleThroughput = this.maxAutoPilotThroughputSet() * 1;
|
||||
|
||||
if (
|
||||
!this.hasAutoPilotV2FeatureFlag() &&
|
||||
this.isAutoPilotSelected() &&
|
||||
autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K &&
|
||||
!this.throughputSpendAck()
|
||||
@@ -426,10 +451,15 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
||||
}
|
||||
|
||||
private _isAutoPilotSelectedAndWhatTier(): DataModels.AutoPilotCreationSettings {
|
||||
if (this.isAutoPilotSelected() && this.maxAutoPilotThroughputSet()) {
|
||||
return {
|
||||
maxThroughput: this.maxAutoPilotThroughputSet() * 1
|
||||
};
|
||||
if (
|
||||
(!this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected() && this.maxAutoPilotThroughputSet()) ||
|
||||
(this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected() && this.selectedAutoPilotTier())
|
||||
) {
|
||||
return !this.hasAutoPilotV2FeatureFlag()
|
||||
? {
|
||||
maxThroughput: this.maxAutoPilotThroughputSet() * 1
|
||||
}
|
||||
: { autopilotTier: this.selectedAutoPilotTier() };
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -142,6 +142,7 @@
|
||||
</span>
|
||||
</div>
|
||||
<!-- 1 -->
|
||||
<!-- ko if: hasAutoPilotV2FeatureFlag && !hasAutoPilotV2FeatureFlag() -->
|
||||
<div data-bind="visible: keyspaceCreateNew() && keyspaceHasSharedOffer()">
|
||||
<throughput-input-autopilot-v3
|
||||
params="{
|
||||
@@ -172,6 +173,38 @@
|
||||
</throughput-input-autopilot-v3>
|
||||
</div>
|
||||
<!-- /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 -->
|
||||
</div>
|
||||
<div class="seconddivpadding">
|
||||
@@ -224,6 +257,7 @@
|
||||
</span>
|
||||
</div>
|
||||
<!-- 2 -->
|
||||
<!-- ko if: hasAutoPilotV2FeatureFlag && !hasAutoPilotV2FeatureFlag() -->
|
||||
<div data-bind="visible: !keyspaceHasSharedOffer() || dedicateTableThroughput()">
|
||||
<throughput-input-autopilot-v3
|
||||
params="{
|
||||
@@ -255,6 +289,40 @@
|
||||
</throughput-input-autopilot-v3>
|
||||
</div>
|
||||
<!-- /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 -->
|
||||
</div>
|
||||
<div class="paneFooter">
|
||||
|
||||
@@ -38,6 +38,10 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
public sharedThroughputSpendAck: ko.Observable<boolean>;
|
||||
public sharedThroughputSpendAckText: ko.Observable<string>;
|
||||
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 selectedAutoPilotThroughput: ko.Observable<number>;
|
||||
public sharedAutoPilotThroughput: ko.Observable<number>;
|
||||
@@ -45,6 +49,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
public sharedThroughputSpendAckVisible: ko.Computed<boolean>;
|
||||
public throughputSpendAckVisible: ko.Computed<boolean>;
|
||||
public canExceedMaximumValue: ko.PureComputed<boolean>;
|
||||
public hasAutoPilotV2FeatureFlag: ko.PureComputed<boolean>;
|
||||
public isFreeTierAccount: ko.Computed<boolean>;
|
||||
public ruToolTipText: ko.Computed<string>;
|
||||
public canConfigureThroughput: ko.PureComputed<boolean>;
|
||||
@@ -56,7 +61,8 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
this.title("Add Table");
|
||||
this.createTableQuery = ko.observable<string>("CREATE TABLE ");
|
||||
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.keyspaceOffers = new HashMap<DataModels.Offer>();
|
||||
this.keyspaceIds = ko.observableArray<string>();
|
||||
@@ -84,6 +90,8 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
});
|
||||
|
||||
this.tableId = ko.observable<string>("");
|
||||
this.selectedAutoPilotTier = ko.observable<DataModels.AutopilotTier>();
|
||||
this.selectedSharedAutoPilotTier = ko.observable<DataModels.AutopilotTier>();
|
||||
this.isAutoPilotSelected = ko.observable<boolean>(false);
|
||||
this.isSharedAutoPilotSelected = ko.observable<boolean>(false);
|
||||
this.selectedAutoPilotThroughput = ko.observable<number>();
|
||||
@@ -94,11 +102,11 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
if (!enableAutoPilot) {
|
||||
return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`;
|
||||
}
|
||||
return AutoPilotUtils.getAutoPilotHeaderText();
|
||||
return AutoPilotUtils.getAutoPilotHeaderText(this.hasAutoPilotV2FeatureFlag());
|
||||
});
|
||||
this.sharedThroughputRangeText = ko.pureComputed<string>(() => {
|
||||
if (this.isSharedAutoPilotSelected()) {
|
||||
return AutoPilotUtils.getAutoPilotHeaderText();
|
||||
return AutoPilotUtils.getAutoPilotHeaderText(this.hasAutoPilotV2FeatureFlag());
|
||||
}
|
||||
return `Throughput (${this.minThroughputRU().toLocaleString()} - ${this.maxThroughputRU().toLocaleString()} RU/s)`;
|
||||
});
|
||||
@@ -136,12 +144,19 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
let estimatedSpend: string;
|
||||
let estimatedDedicatedSpendAcknowledge: string;
|
||||
if (!this.isAutoPilotSelected()) {
|
||||
estimatedSpend = PricingUtils.getEstimatedSpendHtml(offerThroughput, serverId, regions, multimaster);
|
||||
estimatedSpend = PricingUtils.getEstimatedSpendHtml(
|
||||
offerThroughput,
|
||||
serverId,
|
||||
regions,
|
||||
multimaster,
|
||||
false /*rupmEnabled*/
|
||||
);
|
||||
estimatedDedicatedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
|
||||
offerThroughput,
|
||||
serverId,
|
||||
regions,
|
||||
multimaster,
|
||||
false /*rupmEnabled*/,
|
||||
this.isAutoPilotSelected()
|
||||
);
|
||||
} else {
|
||||
@@ -156,6 +171,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
serverId,
|
||||
regions,
|
||||
multimaster,
|
||||
false /*rupmEnabled*/,
|
||||
this.isAutoPilotSelected()
|
||||
);
|
||||
}
|
||||
@@ -180,12 +196,19 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
let estimatedSpend: string;
|
||||
let estimatedSharedSpendAcknowledge: string;
|
||||
if (!this.isSharedAutoPilotSelected()) {
|
||||
estimatedSpend = PricingUtils.getEstimatedSpendHtml(this.keyspaceThroughput(), serverId, regions, multimaster);
|
||||
estimatedSpend = PricingUtils.getEstimatedSpendHtml(
|
||||
this.keyspaceThroughput(),
|
||||
serverId,
|
||||
regions,
|
||||
multimaster,
|
||||
false /*rupmEnabled*/
|
||||
);
|
||||
estimatedSharedSpendAcknowledge = PricingUtils.getEstimatedSpendAcknowledgeString(
|
||||
this.keyspaceThroughput(),
|
||||
serverId,
|
||||
regions,
|
||||
multimaster,
|
||||
false /*rupmEnabled*/,
|
||||
this.isSharedAutoPilotSelected()
|
||||
);
|
||||
} else {
|
||||
@@ -200,6 +223,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
serverId,
|
||||
regions,
|
||||
multimaster,
|
||||
false /*rupmEnabled*/,
|
||||
this.isSharedAutoPilotSelected()
|
||||
);
|
||||
}
|
||||
@@ -222,7 +246,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
|
||||
this.sharedThroughputSpendAckVisible = ko.computed<boolean>(() => {
|
||||
const autoscaleThroughput = this.sharedAutoPilotThroughput() * 1;
|
||||
if (this.isSharedAutoPilotSelected()) {
|
||||
if (!this.hasAutoPilotV2FeatureFlag() && this.isSharedAutoPilotSelected()) {
|
||||
return autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K;
|
||||
}
|
||||
|
||||
@@ -231,7 +255,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
|
||||
this.throughputSpendAckVisible = ko.pureComputed<boolean>(() => {
|
||||
const autoscaleThroughput = this.selectedAutoPilotThroughput() * 1;
|
||||
if (this.isAutoPilotSelected()) {
|
||||
if (!this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected()) {
|
||||
return autoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K;
|
||||
}
|
||||
|
||||
@@ -256,13 +280,22 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
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>(() => {
|
||||
const autoPilot = this._getAutoPilot();
|
||||
if (!autoPilot) {
|
||||
return "";
|
||||
}
|
||||
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,
|
||||
offerThroughput: this.throughput(),
|
||||
partitionKey: "",
|
||||
databaseId: this.keyspaceId()
|
||||
databaseId: this.keyspaceId(),
|
||||
rupm: false
|
||||
}),
|
||||
subscriptionType: ViewModels.SubscriptionType[this.container.subscriptionType()],
|
||||
subscriptionQuotaId: this.container.quotaId(),
|
||||
@@ -318,11 +352,15 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
const autoPilotCommand = `cosmosdb_autoscale_max_throughput`;
|
||||
let createTableAndKeyspacePromise: Q.Promise<any>;
|
||||
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 createKeyspaceQuery: string = this.keyspaceHasSharedOffer()
|
||||
? useAutoPilotForKeyspace
|
||||
? `${createKeyspaceQueryPrefix} AND ${autoPilotCommand}=${this.sharedAutoPilotThroughput()};`
|
||||
? !this.hasAutoPilotV2FeatureFlag()
|
||||
? `${createKeyspaceQueryPrefix} AND ${autoPilotCommand}=${this.sharedAutoPilotThroughput()};`
|
||||
: `${createKeyspaceQueryPrefix} AND ${autoPilotCommand}=${this.selectedSharedAutoPilotTier()};`
|
||||
: `${createKeyspaceQueryPrefix} AND cosmosdb_provisioned_throughput=${this.keyspaceThroughput()};`
|
||||
: `${createKeyspaceQueryPrefix};`;
|
||||
const createTableQueryPrefix: string = `${this.createTableQuery()}${this.tableId().trim()} ${this.userTableQuery()}`;
|
||||
@@ -347,6 +385,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
offerThroughput: this.throughput(),
|
||||
partitionKey: "",
|
||||
databaseId: this.keyspaceId(),
|
||||
rupm: false,
|
||||
hasDedicatedThroughput: this.dedicateTableThroughput()
|
||||
}),
|
||||
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
|
||||
@@ -393,6 +432,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
offerThroughput: this.throughput(),
|
||||
partitionKey: "",
|
||||
databaseId: this.keyspaceId(),
|
||||
rupm: false,
|
||||
hasDedicatedThroughput: this.dedicateTableThroughput()
|
||||
}),
|
||||
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
|
||||
@@ -422,6 +462,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
offerThroughput: this.throughput(),
|
||||
partitionKey: "",
|
||||
databaseId: this.keyspaceId(),
|
||||
rupm: false,
|
||||
hasDedicatedThroughput: this.dedicateTableThroughput()
|
||||
},
|
||||
keyspaceHasSharedOffer: this.keyspaceHasSharedOffer(),
|
||||
@@ -448,6 +489,8 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
const throughputDefaults = this.container.collectionCreationDefaults.throughput;
|
||||
this.isAutoPilotSelected(false);
|
||||
this.isSharedAutoPilotSelected(false);
|
||||
this.selectedAutoPilotTier(null);
|
||||
this.selectedSharedAutoPilotTier(null);
|
||||
this.selectedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
|
||||
this.sharedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
|
||||
this.throughput(AddCollectionUtility.getMaxThroughput(this.container.collectionCreationDefaults, this.container));
|
||||
@@ -469,6 +512,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
|
||||
const sharedAutoscaleThroughput = this.sharedAutoPilotThroughput() * 1;
|
||||
if (
|
||||
!this.hasAutoPilotV2FeatureFlag() &&
|
||||
this.isSharedAutoPilotSelected() &&
|
||||
sharedAutoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K &&
|
||||
!this.sharedThroughputSpendAck()
|
||||
@@ -479,6 +523,7 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
|
||||
const dedicatedAutoscaleThroughput = this.selectedAutoPilotThroughput() * 1;
|
||||
if (
|
||||
!this.hasAutoPilotV2FeatureFlag() &&
|
||||
this.isAutoPilotSelected() &&
|
||||
dedicatedAutoscaleThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K &&
|
||||
!this.throughputSpendAck()
|
||||
@@ -493,12 +538,17 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
) {
|
||||
const autoPilot = this._getAutoPilot();
|
||||
if (
|
||||
!autoPilot ||
|
||||
!autoPilot.maxThroughput ||
|
||||
!AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput)
|
||||
(!this.hasAutoPilotV2FeatureFlag() &&
|
||||
(!autoPilot ||
|
||||
!autoPilot.maxThroughput ||
|
||||
!AutoPilotUtils.isValidAutoPilotThroughput(autoPilot.maxThroughput))) ||
|
||||
(this.hasAutoPilotV2FeatureFlag() &&
|
||||
(!autoPilot || !autoPilot.autopilotTier || !AutoPilotUtils.isValidAutoPilotTier(autoPilot.autopilotTier)))
|
||||
) {
|
||||
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;
|
||||
}
|
||||
@@ -525,20 +575,33 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
|
||||
|
||||
private _getAutoPilot(): DataModels.AutoPilotCreationSettings {
|
||||
if (
|
||||
this.keyspaceCreateNew() &&
|
||||
this.keyspaceHasSharedOffer() &&
|
||||
this.isSharedAutoPilotSelected() &&
|
||||
this.sharedAutoPilotThroughput()
|
||||
(!this.hasAutoPilotV2FeatureFlag() &&
|
||||
this.keyspaceCreateNew() &&
|
||||
this.keyspaceHasSharedOffer() &&
|
||||
this.isSharedAutoPilotSelected() &&
|
||||
this.sharedAutoPilotThroughput()) ||
|
||||
(this.hasAutoPilotV2FeatureFlag() &&
|
||||
this.keyspaceCreateNew() &&
|
||||
this.keyspaceHasSharedOffer() &&
|
||||
this.isSharedAutoPilotSelected() &&
|
||||
this.selectedSharedAutoPilotTier())
|
||||
) {
|
||||
return {
|
||||
maxThroughput: this.sharedAutoPilotThroughput() * 1
|
||||
};
|
||||
return !this.hasAutoPilotV2FeatureFlag()
|
||||
? {
|
||||
maxThroughput: this.sharedAutoPilotThroughput() * 1
|
||||
}
|
||||
: { autopilotTier: this.selectedSharedAutoPilotTier() };
|
||||
}
|
||||
|
||||
if (this.selectedAutoPilotThroughput()) {
|
||||
return {
|
||||
maxThroughput: this.selectedAutoPilotThroughput() * 1
|
||||
};
|
||||
if (
|
||||
(!this.hasAutoPilotV2FeatureFlag() && this.selectedAutoPilotThroughput()) ||
|
||||
(this.hasAutoPilotV2FeatureFlag() && this.selectedAutoPilotTier())
|
||||
) {
|
||||
return !this.hasAutoPilotV2FeatureFlag()
|
||||
? {
|
||||
maxThroughput: this.selectedAutoPilotThroughput() * 1
|
||||
}
|
||||
: { autopilotTier: this.selectedAutoPilotTier() };
|
||||
}
|
||||
|
||||
return undefined;
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
<span class="scaleSettingTitle">Scale</span>
|
||||
</div>
|
||||
<div class="ssTextAllignment" id="scaleRegion">
|
||||
<!-- ko if: hasAutoPilotV2FeatureFlag && !hasAutoPilotV2FeatureFlag() -->
|
||||
<throughput-input-autopilot-v3
|
||||
params="{
|
||||
testId: testId,
|
||||
@@ -50,6 +51,34 @@
|
||||
}"
|
||||
>
|
||||
</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">
|
||||
<p data-bind="visible: minRUAnotationVisible">
|
||||
|
||||
@@ -54,6 +54,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
||||
// editables
|
||||
public isAutoPilotSelected: ViewModels.Editable<boolean>;
|
||||
public throughput: ViewModels.Editable<number>;
|
||||
public selectedAutoPilotTier: ViewModels.Editable<DataModels.AutopilotTier>;
|
||||
public autoPilotThroughput: ViewModels.Editable<number>;
|
||||
public throughputIncreaseFactor: number = Constants.ClientDefaults.databaseThroughputIncreaseFactor;
|
||||
|
||||
@@ -80,9 +81,11 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
||||
public throughputTitle: ko.PureComputed<string>;
|
||||
public throughputAriaLabel: ko.PureComputed<string>;
|
||||
public userCanChangeProvisioningTypes: ko.Observable<boolean>;
|
||||
public autoPilotTiersList: ko.ObservableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>;
|
||||
public autoPilotUsageCost: ko.PureComputed<string>;
|
||||
public warningMessage: ko.Computed<string>;
|
||||
public canExceedMaximumValue: ko.PureComputed<boolean>;
|
||||
public hasAutoPilotV2FeatureFlag: ko.PureComputed<boolean>;
|
||||
public overrideWithAutoPilotSettings: ko.Computed<boolean>;
|
||||
public overrideWithProvisionedThroughputSettings: ko.Computed<boolean>;
|
||||
public testId: string;
|
||||
@@ -99,6 +102,9 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
||||
super(options);
|
||||
|
||||
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());
|
||||
|
||||
// html element ids
|
||||
@@ -113,13 +119,23 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
||||
this.autoPilotThroughput = editable.observable<number>();
|
||||
const offer = this.database && this.database.offer && this.database.offer();
|
||||
const offerAutopilotSettings = offer && offer.content && offer.content.offerAutopilotSettings;
|
||||
this.userCanChangeProvisioningTypes = ko.observable(true);
|
||||
|
||||
if (offerAutopilotSettings && offerAutopilotSettings.maxThroughput) {
|
||||
if (AutoPilotUtils.isValidAutoPilotThroughput(offerAutopilotSettings.maxThroughput)) {
|
||||
this._wasAutopilotOriginallySet(true);
|
||||
this.isAutoPilotSelected(true);
|
||||
this.autoPilotThroughput(offerAutopilotSettings.maxThroughput);
|
||||
this.userCanChangeProvisioningTypes = ko.observable(!!offerAutopilotSettings || !this.hasAutoPilotV2FeatureFlag());
|
||||
if (!this.hasAutoPilotV2FeatureFlag()) {
|
||||
if (offerAutopilotSettings && offerAutopilotSettings.maxThroughput) {
|
||||
if (AutoPilotUtils.isValidAutoPilotThroughput(offerAutopilotSettings.maxThroughput)) {
|
||||
this._wasAutopilotOriginallySet(true);
|
||||
this.isAutoPilotSelected(true);
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,11 +150,13 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
||||
});
|
||||
|
||||
this.autoPilotUsageCost = ko.pureComputed<string>(() => {
|
||||
const autoPilot = this.autoPilotThroughput();
|
||||
const autoPilot = !this.hasAutoPilotV2FeatureFlag() ? this.autoPilotThroughput() : this.selectedAutoPilotTier();
|
||||
if (!autoPilot) {
|
||||
return "";
|
||||
}
|
||||
return PricingUtils.getAutoPilotV3SpendHtml(autoPilot, true /* isDatabaseThroughput */);
|
||||
return !this.hasAutoPilotV2FeatureFlag()
|
||||
? PricingUtils.getAutoPilotV3SpendHtml(autoPilot, true /* isDatabaseThroughput */)
|
||||
: PricingUtils.getAutoPilotV2SpendHtml(autoPilot, true /* isDatabaseThroughput */);
|
||||
});
|
||||
|
||||
this.requestUnitsUsageCost = ko.pureComputed(() => {
|
||||
@@ -163,7 +181,8 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
||||
this.overrideWithAutoPilotSettings() ? this.autoPilotThroughput() : this.throughput(),
|
||||
serverId,
|
||||
regions,
|
||||
multimaster
|
||||
multimaster,
|
||||
false /*rupmEnabled*/
|
||||
);
|
||||
} else {
|
||||
estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml(
|
||||
@@ -197,10 +216,16 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
||||
});
|
||||
|
||||
this.overrideWithAutoPilotSettings = ko.pureComputed(() => {
|
||||
if (this.hasAutoPilotV2FeatureFlag()) {
|
||||
return false;
|
||||
}
|
||||
return this._hasProvisioningTypeChanged() && this._wasAutopilotOriginallySet();
|
||||
});
|
||||
|
||||
this.overrideWithProvisionedThroughputSettings = ko.pureComputed(() => {
|
||||
if (this.hasAutoPilotV2FeatureFlag()) {
|
||||
return false;
|
||||
}
|
||||
return this._hasProvisioningTypeChanged() && !this._wasAutopilotOriginallySet();
|
||||
});
|
||||
|
||||
@@ -258,7 +283,7 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
||||
|
||||
this.throughputTitle = ko.pureComputed<string>(() => {
|
||||
if (this.isAutoPilotSelected()) {
|
||||
return AutoPilotUtils.getAutoPilotHeaderText();
|
||||
return AutoPilotUtils.getAutoPilotHeaderText(this.hasAutoPilotV2FeatureFlag());
|
||||
}
|
||||
|
||||
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>(() => {
|
||||
const offer = this.database && this.database.offer && this.database.offer();
|
||||
|
||||
if (this.overrideWithProvisionedThroughputSettings()) {
|
||||
if (!this.hasAutoPilotV2FeatureFlag() && this.overrideWithProvisionedThroughputSettings()) {
|
||||
return AutoPilotUtils.manualToAutoscaleDisclaimer;
|
||||
}
|
||||
|
||||
@@ -291,7 +316,9 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
||||
!!(offer as DataModels.OfferWithHeaders).headers[Constants.HttpHeaders.offerReplacePending]
|
||||
) {
|
||||
const throughput = offer.content.offerAutopilotSettings
|
||||
? offer.content.offerAutopilotSettings.maxThroughput
|
||||
? !this.hasAutoPilotV2FeatureFlag()
|
||||
? offer.content.offerAutopilotSettings.maxThroughput
|
||||
: offer.content.offerAutopilotSettings.maximumTierThroughput
|
||||
: offer.content.offerThroughput;
|
||||
|
||||
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 isManual = !this.isAutoPilotSelected();
|
||||
if (isAutoPilot) {
|
||||
if (!AutoPilotUtils.isValidAutoPilotThroughput(this.autoPilotThroughput())) {
|
||||
if (
|
||||
(!this.hasAutoPilotV2FeatureFlag() &&
|
||||
!AutoPilotUtils.isValidAutoPilotThroughput(this.autoPilotThroughput())) ||
|
||||
(this.hasAutoPilotV2FeatureFlag() && !AutoPilotUtils.isValidAutoPilotTier(this.selectedAutoPilotTier()))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (this.isAutoPilotSelected.editableIsDirty()) {
|
||||
return true;
|
||||
}
|
||||
if (this.autoPilotThroughput.editableIsDirty()) {
|
||||
if (
|
||||
(!this.hasAutoPilotV2FeatureFlag() && this.autoPilotThroughput.editableIsDirty()) ||
|
||||
(this.hasAutoPilotV2FeatureFlag() && this.selectedAutoPilotTier.editableIsDirty())
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -378,7 +412,10 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isAutoPilotSelected.editableIsDirty()) {
|
||||
if (
|
||||
(!this.hasAutoPilotV2FeatureFlag() && this.isAutoPilotSelected.editableIsDirty()) ||
|
||||
(this.hasAutoPilotV2FeatureFlag() && this.selectedAutoPilotTier.editableIsDirty())
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -459,7 +496,8 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
||||
databaseAccountName: userContext.databaseAccount.name,
|
||||
resourceGroup: userContext.resourceGroup,
|
||||
databaseName: this.database.id(),
|
||||
throughput: newThroughput
|
||||
throughput: newThroughput,
|
||||
offerIsRUPerMinuteThroughputEnabled: false
|
||||
};
|
||||
await updateOfferThroughputBeyondLimit(requestPayload);
|
||||
this.database.offer().content.offerThroughput = originalThroughputValue;
|
||||
@@ -509,7 +547,11 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
||||
public onRevertClick = (): Q.Promise<any> => {
|
||||
this.throughput.setBaseline(this.throughput.getEditableOriginalValue());
|
||||
this.isAutoPilotSelected.setBaseline(this.isAutoPilotSelected.getEditableOriginalValue());
|
||||
this.autoPilotThroughput.setBaseline(this.autoPilotThroughput.getEditableOriginalValue());
|
||||
if (!this.hasAutoPilotV2FeatureFlag()) {
|
||||
this.autoPilotThroughput.setBaseline(this.autoPilotThroughput.getEditableOriginalValue());
|
||||
} else {
|
||||
this.selectedAutoPilotTier.setBaseline(this.selectedAutoPilotTier.getEditableOriginalValue());
|
||||
}
|
||||
|
||||
return Q();
|
||||
};
|
||||
@@ -527,11 +569,17 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
||||
const offerAutopilotSettings = offer && offer.content && offer.content.offerAutopilotSettings;
|
||||
|
||||
this.throughput.setBaseline(offerThroughput);
|
||||
this.userCanChangeProvisioningTypes(true);
|
||||
this.userCanChangeProvisioningTypes(!!offerAutopilotSettings || !this.hasAutoPilotV2FeatureFlag());
|
||||
|
||||
const maxThroughputForAutoPilot = offerAutopilotSettings && offerAutopilotSettings.maxThroughput;
|
||||
this.isAutoPilotSelected.setBaseline(AutoPilotUtils.isValidAutoPilotThroughput(maxThroughputForAutoPilot));
|
||||
this.autoPilotThroughput.setBaseline(maxThroughputForAutoPilot || AutoPilotUtils.minAutoPilotThroughput);
|
||||
if (this.hasAutoPilotV2FeatureFlag()) {
|
||||
const selectedAutoPilotTier = offerAutopilotSettings && offerAutopilotSettings.tier;
|
||||
this.isAutoPilotSelected.setBaseline(AutoPilotUtils.isValidAutoPilotTier(selectedAutoPilotTier));
|
||||
this.selectedAutoPilotTier.setBaseline(selectedAutoPilotTier);
|
||||
} else {
|
||||
const maxThroughputForAutoPilot = offerAutopilotSettings && offerAutopilotSettings.maxThroughput;
|
||||
this.isAutoPilotSelected.setBaseline(AutoPilotUtils.isValidAutoPilotThroughput(maxThroughputForAutoPilot));
|
||||
this.autoPilotThroughput.setBaseline(maxThroughputForAutoPilot || AutoPilotUtils.minAutoPilotThroughput);
|
||||
}
|
||||
}
|
||||
|
||||
protected getTabsButtons(): CommandButtonComponentProps[] {
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
|
||||
<div class="ssTextAllignment" data-bind="visible: scaleExpanded" id="scaleRegion">
|
||||
<!-- ko ifnot: isAutoScaleEnabled -->
|
||||
<!-- ko if: hasAutoPilotV2FeatureFlag && !hasAutoPilotV2FeatureFlag() -->
|
||||
<throughput-input-autopilot-v3
|
||||
params="{
|
||||
testId: testId,
|
||||
@@ -76,10 +77,94 @@
|
||||
}"
|
||||
>
|
||||
</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>
|
||||
<!-- /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 -->
|
||||
<div data-bind="visible: isAutoScaleEnabled">
|
||||
<div class="autoScaleThroughputTitle">Throughput (RU/s)</div>
|
||||
|
||||
@@ -79,6 +79,7 @@ describe("Settings tab", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
explorer = new Explorer();
|
||||
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||
});
|
||||
|
||||
it("single master, should not show conflict resolution", () => {
|
||||
@@ -177,6 +178,7 @@ describe("Settings tab", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
explorer = new Explorer();
|
||||
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||
});
|
||||
|
||||
it("On TTL changed", () => {
|
||||
@@ -249,6 +251,7 @@ describe("Settings tab", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
explorer = new Explorer();
|
||||
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||
});
|
||||
|
||||
it("null if it didnt change", () => {
|
||||
@@ -324,6 +327,7 @@ describe("Settings tab", () => {
|
||||
function getCollection(defaultApi: string, partitionKeyOption: PartitionKeyOption) {
|
||||
const explorer = new Explorer();
|
||||
explorer.defaultExperience(defaultApi);
|
||||
explorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
|
||||
|
||||
const offer: DataModels.Offer = null;
|
||||
const defaultTtl = 200;
|
||||
@@ -446,4 +450,158 @@ describe("Settings tab", () => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -143,9 +143,11 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
public geospatialVisible: ko.Computed<boolean>;
|
||||
public indexingPolicyContent: ViewModels.Editable<any>;
|
||||
public isIndexingPolicyEditorInitializing: ko.Observable<boolean>;
|
||||
public rupm: ViewModels.Editable<string>;
|
||||
public conflictResolutionPolicyMode: ViewModels.Editable<string>;
|
||||
public conflictResolutionPolicyPath: ViewModels.Editable<string>;
|
||||
public conflictResolutionPolicyProcedure: ViewModels.Editable<string>;
|
||||
public hasAutoPilotV2FeatureFlag: ko.PureComputed<boolean>;
|
||||
|
||||
public saveSettingsButton: 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 isLargePartitionKeyEnabled: ko.Computed<boolean>;
|
||||
public requestUnitsUsageCost: ko.Computed<string>;
|
||||
public rupmOnId: string;
|
||||
public rupmOffId: string;
|
||||
public rupmVisible: ko.Computed<boolean>;
|
||||
public scaleExpanded: ko.Observable<boolean>;
|
||||
public settingsExpanded: ko.Observable<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 warningMessage: ko.Computed<string>;
|
||||
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 autoPilotThroughput: ko.Observable<number>;
|
||||
public autoPilotUsageCost: ko.Computed<string>;
|
||||
@@ -225,6 +232,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
super(options);
|
||||
this.container = options.collection && options.collection.container;
|
||||
this.isIndexingPolicyEditorInitializing = ko.observable<boolean>(false);
|
||||
this.hasAutoPilotV2FeatureFlag = ko.pureComputed(() => this.container.hasAutoPilotV2FeatureFlag());
|
||||
|
||||
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.changeFeedPolicyOffId = `changeFeedOff${this.tabId}`;
|
||||
this.changeFeedPolicyOnId = `changeFeedOn${this.tabId}`;
|
||||
this.rupmOnId = `rupmOn${this.tabId}`;
|
||||
this.rupmOffId = `rupmOff${this.tabId}`;
|
||||
this.conflictResolutionPolicyModeCustom = `conflictResolutionPolicyModeCustom${this.tabId}`;
|
||||
this.conflictResolutionPolicyModeLWW = `conflictResolutionPolicyModeLWW${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.analyticalStorageTtlSeconds = editable.observable<number>();
|
||||
this.indexingPolicyContent = editable.observable<any>();
|
||||
this.rupm = editable.observable<string>();
|
||||
// Mongo container with system partition key still treat as "Fixed"
|
||||
this._isFixedContainer = ko.pureComputed(
|
||||
() =>
|
||||
@@ -277,17 +288,31 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
|
||||
this.isAutoPilotSelected = 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);
|
||||
const offer = this.collection && this.collection.offer && this.collection.offer();
|
||||
const offerAutopilotSettings = offer && offer.content && offer.content.offerAutopilotSettings;
|
||||
|
||||
this.userCanChangeProvisioningTypes = ko.observable(true);
|
||||
this.userCanChangeProvisioningTypes = ko.observable(!!offerAutopilotSettings || !this.hasAutoPilotV2FeatureFlag());
|
||||
|
||||
if (offerAutopilotSettings && offerAutopilotSettings.maxThroughput) {
|
||||
if (AutoPilotUtils.isValidAutoPilotThroughput(offerAutopilotSettings.maxThroughput)) {
|
||||
this.isAutoPilotSelected(true);
|
||||
this._wasAutopilotOriginallySet(true);
|
||||
this.autoPilotThroughput(offerAutopilotSettings.maxThroughput);
|
||||
if (!this.hasAutoPilotV2FeatureFlag()) {
|
||||
if (offerAutopilotSettings && offerAutopilotSettings.maxThroughput) {
|
||||
if (AutoPilotUtils.isValidAutoPilotThroughput(offerAutopilotSettings.maxThroughput)) {
|
||||
this.isAutoPilotSelected(true);
|
||||
this._wasAutopilotOriginallySet(true);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,10 +327,16 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
});
|
||||
|
||||
this.overrideWithAutoPilotSettings = ko.pureComputed(() => {
|
||||
if (this.hasAutoPilotV2FeatureFlag()) {
|
||||
return false;
|
||||
}
|
||||
return this._hasProvisioningTypeChanged() && this._wasAutopilotOriginallySet();
|
||||
});
|
||||
|
||||
this.overrideWithProvisionedThroughputSettings = ko.pureComputed(() => {
|
||||
if (this.hasAutoPilotV2FeatureFlag()) {
|
||||
return false;
|
||||
}
|
||||
return this._hasProvisioningTypeChanged() && !this._wasAutopilotOriginallySet();
|
||||
});
|
||||
|
||||
@@ -317,18 +348,25 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
if (!originalAutoPilotSettings) {
|
||||
return false;
|
||||
}
|
||||
const originalAutoPilotSetting = originalAutoPilotSettings && originalAutoPilotSettings.maxThroughput;
|
||||
if (this.autoPilotThroughput() !== originalAutoPilotSetting) {
|
||||
const originalAutoPilotSetting = !this.hasAutoPilotV2FeatureFlag()
|
||||
? originalAutoPilotSettings && originalAutoPilotSettings.maxThroughput
|
||||
: originalAutoPilotSettings && originalAutoPilotSettings.tier;
|
||||
if (
|
||||
(!this.hasAutoPilotV2FeatureFlag() && this.autoPilotThroughput() != originalAutoPilotSetting) ||
|
||||
(this.hasAutoPilotV2FeatureFlag() && this.selectedAutoPilotTier() !== originalAutoPilotSetting)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
this.autoPilotUsageCost = ko.pureComputed<string>(() => {
|
||||
const autoPilot = this.autoPilotThroughput();
|
||||
const autoPilot = !this.hasAutoPilotV2FeatureFlag() ? this.autoPilotThroughput() : this.selectedAutoPilotTier();
|
||||
if (!autoPilot) {
|
||||
return "";
|
||||
}
|
||||
return PricingUtils.getAutoPilotV3SpendHtml(autoPilot, false /* isDatabaseThroughput */);
|
||||
return !this.hasAutoPilotV2FeatureFlag()
|
||||
? PricingUtils.getAutoPilotV3SpendHtml(autoPilot, false /* isDatabaseThroughput */)
|
||||
: PricingUtils.getAutoPilotV2SpendHtml(autoPilot, false /* isDatabaseThroughput */);
|
||||
});
|
||||
|
||||
this.requestUnitsUsageCost = ko.pureComputed(() => {
|
||||
@@ -339,6 +377,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
|
||||
const serverId: string = this.container.serverId();
|
||||
const offerThroughput: number = this.throughput();
|
||||
const rupmEnabled = this.rupm() === Constants.RUPMStates.on;
|
||||
|
||||
const regions =
|
||||
(account &&
|
||||
@@ -356,7 +395,8 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
this.overrideWithAutoPilotSettings() ? this.autoPilotThroughput() : offerThroughput,
|
||||
serverId,
|
||||
regions,
|
||||
multimaster
|
||||
multimaster,
|
||||
rupmEnabled
|
||||
);
|
||||
} else {
|
||||
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(() => {
|
||||
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>(() => {
|
||||
if (this.isAutoPilotSelected()) {
|
||||
return AutoPilotUtils.getAutoPilotHeaderText();
|
||||
return AutoPilotUtils.getAutoPilotHeaderText(this.hasAutoPilotV2FeatureFlag());
|
||||
}
|
||||
|
||||
const minThroughput: string = this.minRUs().toLocaleString();
|
||||
@@ -624,7 +690,12 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
return true;
|
||||
} else if (this.isAutoPilotSelected()) {
|
||||
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) {
|
||||
return true;
|
||||
}
|
||||
@@ -678,6 +749,14 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
this.rupm() === Constants.RUPMStates.on &&
|
||||
this.throughput() >
|
||||
SharedConstants.CollectionCreation.MaxRUPMPerPartition * this.collection.quotaInfo()?.numPartitions
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.timeToLive.editableIsDirty()) {
|
||||
return true;
|
||||
}
|
||||
@@ -706,6 +785,10 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.rupm.editableIsDirty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}),
|
||||
|
||||
@@ -755,6 +838,10 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.rupm.editableIsDirty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
this.conflictResolutionPolicyMode.editableIsDirty() ||
|
||||
this.conflictResolutionPolicyPath.editableIsDirty() ||
|
||||
@@ -814,8 +901,14 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
offer.hasOwnProperty("headers") &&
|
||||
!!(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
|
||||
? offer.content.offerAutopilotSettings.maxThroughput
|
||||
? !this.hasAutoPilotV2FeatureFlag()
|
||||
? offer.content.offerAutopilotSettings.maxThroughput
|
||||
: offer.content.offerAutopilotSettings.maximumTierThroughput
|
||||
: undefined;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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 isRUPerMinuteThroughputEnabled: boolean = this.rupm() === Constants.RUPMStates.on;
|
||||
let newOffer: DataModels.Offer = _.extend({}, this.collection.offer());
|
||||
const originalThroughputValue: number = this.throughput.getEditableOriginalValue();
|
||||
|
||||
if (newOffer.content) {
|
||||
newOffer.content.offerThroughput = newThroughput;
|
||||
newOffer.content.offerIsRUPerMinuteThroughputEnabled = isRUPerMinuteThroughputEnabled;
|
||||
} else {
|
||||
newOffer = _.extend({}, newOffer, {
|
||||
content: {
|
||||
offerThroughput: newThroughput
|
||||
offerThroughput: newThroughput,
|
||||
offerIsRUPerMinuteThroughputEnabled: isRUPerMinuteThroughputEnabled
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1016,12 +1117,18 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
const headerOptions: RequestOptions = { initialHeaders: {} };
|
||||
|
||||
if (this.isAutoPilotSelected()) {
|
||||
newOffer.content.offerAutopilotSettings = {
|
||||
maxThroughput: this.autoPilotThroughput()
|
||||
};
|
||||
if (!this.hasAutoPilotV2FeatureFlag()) {
|
||||
newOffer.content.offerAutopilotSettings = {
|
||||
maxThroughput: this.autoPilotThroughput()
|
||||
};
|
||||
} else {
|
||||
newOffer.content.offerAutopilotSettings = {
|
||||
tier: this.selectedAutoPilotTier()
|
||||
};
|
||||
}
|
||||
|
||||
// user has changed from provisioned --> autoscale
|
||||
if (this._hasProvisioningTypeChanged()) {
|
||||
if (!this.hasAutoPilotV2FeatureFlag() && this._hasProvisioningTypeChanged()) {
|
||||
headerOptions.initialHeaders[Constants.HttpHeaders.migrateOfferToAutopilot] = "true";
|
||||
delete newOffer.content.offerAutopilotSettings;
|
||||
} else {
|
||||
@@ -1029,10 +1136,10 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
}
|
||||
} else {
|
||||
this.isAutoPilotSelected(false);
|
||||
this.userCanChangeProvisioningTypes(true);
|
||||
this.userCanChangeProvisioningTypes(false || !this.hasAutoPilotV2FeatureFlag());
|
||||
|
||||
// user has changed from autoscale --> provisioned
|
||||
if (this._hasProvisioningTypeChanged()) {
|
||||
if (!this.hasAutoPilotV2FeatureFlag() && this._hasProvisioningTypeChanged()) {
|
||||
headerOptions.initialHeaders[Constants.HttpHeaders.migrateOfferToManualThroughput] = "true";
|
||||
} else {
|
||||
delete newOffer.content.offerAutopilotSettings;
|
||||
@@ -1050,7 +1157,8 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
resourceGroup: userContext.resourceGroup,
|
||||
databaseName: this.collection.databaseId,
|
||||
collectionName: this.collection.id(),
|
||||
throughput: newThroughput
|
||||
throughput: newThroughput,
|
||||
offerIsRUPerMinuteThroughputEnabled: isRUPerMinuteThroughputEnabled
|
||||
};
|
||||
|
||||
await updateOfferThroughputBeyondLimit(requestPayload);
|
||||
@@ -1114,7 +1222,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
defaultExperience: this.container.defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
tabTitle: this.tabTitle(),
|
||||
error: error.message
|
||||
error: error
|
||||
},
|
||||
startKey
|
||||
);
|
||||
@@ -1133,6 +1241,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
this.geospatialConfigType.setBaseline(this.geospatialConfigType.getEditableOriginalValue());
|
||||
this.analyticalStorageTtlSelection.setBaseline(this.analyticalStorageTtlSelection.getEditableOriginalValue());
|
||||
this.analyticalStorageTtlSeconds.setBaseline(this.analyticalStorageTtlSeconds.getEditableOriginalValue());
|
||||
this.rupm.setBaseline(this.rupm.getEditableOriginalValue());
|
||||
this.changeFeedPolicyToggled.setBaseline(this.changeFeedPolicyToggled.getEditableOriginalValue());
|
||||
|
||||
this.conflictResolutionPolicyMode.setBaseline(this.conflictResolutionPolicyMode.getEditableOriginalValue());
|
||||
@@ -1157,8 +1266,13 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
|
||||
if (this.isAutoPilotSelected()) {
|
||||
const originalAutoPilotSettings = this.collection.offer().content.offerAutopilotSettings;
|
||||
const originalAutoPilotMaxThroughput = originalAutoPilotSettings && originalAutoPilotSettings.maxThroughput;
|
||||
this.autoPilotThroughput(originalAutoPilotMaxThroughput);
|
||||
if (!this.hasAutoPilotV2FeatureFlag()) {
|
||||
const originalAutoPilotMaxThroughput = originalAutoPilotSettings && originalAutoPilotSettings.maxThroughput;
|
||||
this.autoPilotThroughput(originalAutoPilotMaxThroughput);
|
||||
} else {
|
||||
const originalAutoPilotTier = originalAutoPilotSettings && originalAutoPilotSettings.tier;
|
||||
this.selectedAutoPilotTier(originalAutoPilotTier);
|
||||
}
|
||||
}
|
||||
|
||||
return Q();
|
||||
@@ -1339,7 +1453,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
}
|
||||
|
||||
private _getThroughputUnit(): string {
|
||||
return "RU/s";
|
||||
return this.rupm() === Constants.RUPMStates.on ? "RU/m" : "RU/s";
|
||||
}
|
||||
|
||||
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.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();
|
||||
this.changeFeedPolicyToggled.setBaseline(changeFeedPolicyToggled);
|
||||
this.throughput.setBaseline(offerThroughput);
|
||||
@@ -1475,6 +1596,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
conflictResolutionPolicy && conflictResolutionPolicy.conflictResolutionProcedure
|
||||
)
|
||||
);
|
||||
this.rupm.setBaseline(offerIsRUPerMinuteThroughputEnabled ? Constants.RUPMStates.on : Constants.RUPMStates.off);
|
||||
|
||||
const indexingPolicyContent = this.collection.indexingPolicy();
|
||||
const value: string = JSON.stringify(indexingPolicyContent, null, 4);
|
||||
@@ -1495,15 +1617,17 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
|
||||
this.GEOMETRY;
|
||||
this.geospatialConfigType.setBaseline(geospatialConfigType);
|
||||
|
||||
const maxThroughput =
|
||||
this.collection &&
|
||||
this.collection.offer &&
|
||||
this.collection.offer() &&
|
||||
this.collection.offer().content &&
|
||||
this.collection.offer().content.offerAutopilotSettings &&
|
||||
this.collection.offer().content.offerAutopilotSettings.maxThroughput;
|
||||
if (!this.hasAutoPilotV2FeatureFlag()) {
|
||||
const maxThroughput =
|
||||
this.collection &&
|
||||
this.collection.offer &&
|
||||
this.collection.offer() &&
|
||||
this.collection.offer().content &&
|
||||
this.collection.offer().content.offerAutopilotSettings &&
|
||||
this.collection.offer().content.offerAutopilotSettings.maxThroughput;
|
||||
|
||||
this.autoPilotThroughput(maxThroughput || AutoPilotUtils.minAutoPilotThroughput);
|
||||
this.autoPilotThroughput(maxThroughput || AutoPilotUtils.minAutoPilotThroughput);
|
||||
}
|
||||
}
|
||||
|
||||
private _createIndexingPolicyEditor() {
|
||||
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import DocumentsTabTemplate from "./DocumentsTab.html";
|
||||
import ConflictsTabTemplate from "./ConflictsTab.html";
|
||||
import GraphTabTemplate from "./GraphTab.html";
|
||||
import SparkMasterTabTemplate from "./SparkMasterTab.html";
|
||||
import NotebookV2TabTemplate from "./NotebookV2Tab.html";
|
||||
import TerminalTabTemplate from "./TerminalTab.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 {
|
||||
constructor() {
|
||||
return {
|
||||
|
||||
@@ -85,7 +85,6 @@ export default class TabsBase extends WaitsForTemplateViewModel {
|
||||
explorer.tabsManager.closeTab(this.tabId, explorer);
|
||||
|
||||
TelemetryProcessor.trace(Action.Tab, ActionModifiers.Close, {
|
||||
tabName: this.constructor.name,
|
||||
databaseAccountName: this.getContainer().databaseAccount().name,
|
||||
defaultExperience: this.getContainer().defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
@@ -144,7 +143,6 @@ export default class TabsBase extends WaitsForTemplateViewModel {
|
||||
this.updateNavbarWithTabsButtons();
|
||||
|
||||
TelemetryProcessor.trace(Action.Tab, ActionModifiers.Open, {
|
||||
tabName: this.constructor.name,
|
||||
databaseAccountName: this.getContainer().databaseAccount().name,
|
||||
defaultExperience: this.getContainer().defaultExperience(),
|
||||
dataExplorerArea: Constants.Areas.Tab,
|
||||
|
||||
@@ -126,6 +126,7 @@ export class OfferPricing {
|
||||
Standard: {
|
||||
StartingPrice: 24 / hoursInAMonth, // per hour
|
||||
PricePerRU: 0.00008,
|
||||
PricePerRUPM: (10 * 2) / 1000 / hoursInAMonth, // preview price: $2 per 1000 RU/m per month -> 100 RU/s
|
||||
PricePerGB: 0.25 / hoursInAMonth
|
||||
}
|
||||
},
|
||||
@@ -138,6 +139,7 @@ export class OfferPricing {
|
||||
Standard: {
|
||||
StartingPrice: OfferPricing.MonthlyPricing.mooncake.Standard.StartingPrice / hoursInAMonth, // per hour
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -154,6 +156,7 @@ export class CollectionCreation {
|
||||
public static readonly MinRU7PartitionsTo25Partitions: number = 2500;
|
||||
public static readonly MinRUPerPartitionAbove25Partitions: number = 100;
|
||||
public static readonly MaxRUPerPartition: number = 10000;
|
||||
public static readonly MaxRUPMPerPartition: number = 5000;
|
||||
public static readonly MinPartitionedCollectionRUs: number = 2500;
|
||||
|
||||
public static readonly NumberOfPartitionsInFixedCollection: number = 1;
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
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") {
|
||||
let ruCharge = requestUnits * Constants.OfferPricing.HourlyPricing.mooncake.Standard.PricePerRU;
|
||||
return calculateEstimateNumber(ruCharge) + " " + Constants.OfferPricing.HourlyPricing.mooncake.Currency;
|
||||
let ruCharge = requestUnits * Constants.OfferPricing.HourlyPricing.mooncake.Standard.PricePerRU,
|
||||
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;
|
||||
return calculateEstimateNumber(ruCharge) + " " + Constants.OfferPricing.HourlyPricing.default.Currency;
|
||||
let ruCharge = requestUnits * Constants.OfferPricing.HourlyPricing.default.Standard.PricePerRU,
|
||||
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 {
|
||||
|
||||
@@ -89,8 +89,7 @@ export enum Action {
|
||||
ClickResourceTreeNodeContextMenuItem,
|
||||
DiscardSettings,
|
||||
SettingsV2Updated,
|
||||
SettingsV2Discarded,
|
||||
MongoIndexUpdated
|
||||
SettingsV2Discarded
|
||||
}
|
||||
|
||||
export const ActionModifiers = {
|
||||
|
||||
119
src/Utils/AutoPilotUtils.test.ts
Normal file
119
src/Utils/AutoPilotUtils.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
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>.`;
|
||||
|
||||
@@ -7,6 +8,24 @@ export const minAutoPilotThroughput = 4000;
|
||||
|
||||
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 {
|
||||
const maxThroughput =
|
||||
offer &&
|
||||
@@ -16,6 +35,36 @@ export function isValidV3AutoPilotOffer(offer: Offer): boolean {
|
||||
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 {
|
||||
if (!maxThroughput) {
|
||||
return false;
|
||||
@@ -37,6 +86,9 @@ export function getStorageBasedOnUserInput(throughput: number): number {
|
||||
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)";
|
||||
}
|
||||
|
||||
@@ -25,37 +25,37 @@ describe("PricingUtils Tests", () => {
|
||||
|
||||
describe("computeRUUsagePriceHourly()", () => {
|
||||
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);
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
it("should return 0.00008 for default cloud, 1RU, 1 region, multimaster disabled", () => {
|
||||
const value = PricingUtils.computeRUUsagePriceHourly("default", 1, 1, false);
|
||||
it("should return 0.00008 for default cloud, rupm disabled, 1RU, 1 region, multimaster disabled", () => {
|
||||
const value = PricingUtils.computeRUUsagePriceHourly("default", false, 1, 1, false);
|
||||
expect(value).toBe(0.00008);
|
||||
});
|
||||
|
||||
it("should return 0.00051 for Mooncake cloud, 1RU, 1 region, multimaster disabled", () => {
|
||||
const value = PricingUtils.computeRUUsagePriceHourly("mooncake", 1, 1, false);
|
||||
it("should return 0.00051 for Mooncake cloud, rupm disabled, 1RU, 1 region, multimaster disabled", () => {
|
||||
const value = PricingUtils.computeRUUsagePriceHourly("mooncake", false, 1, 1, false);
|
||||
expect(value).toBe(0.00051);
|
||||
});
|
||||
|
||||
it("should return 0.00016 for default cloud, 1RU, 2 regions, multimaster disabled", () => {
|
||||
const value = PricingUtils.computeRUUsagePriceHourly("default", 1, 2, false);
|
||||
it("should return 0.00016 for default cloud, rupm disabled, 1RU, 2 regions, multimaster disabled", () => {
|
||||
const value = PricingUtils.computeRUUsagePriceHourly("default", false, 1, 2, false);
|
||||
expect(value).toBe(0.00016);
|
||||
});
|
||||
|
||||
it("should return 0.00008 for default cloud, 1RU, 1 region, multimaster enabled", () => {
|
||||
const value = PricingUtils.computeRUUsagePriceHourly("default", 1, 1, true);
|
||||
it("should return 0.00008 for default cloud, rupm disabled, 1RU, 1 region, multimaster enabled", () => {
|
||||
const value = PricingUtils.computeRUUsagePriceHourly("default", false, 1, 1, true);
|
||||
expect(value).toBe(0.00008);
|
||||
});
|
||||
|
||||
it("should return 0.00048 for default cloud, 1RU, 2 region, multimaster enabled", () => {
|
||||
const value = PricingUtils.computeRUUsagePriceHourly("default", 1, 2, true);
|
||||
it("should return 0.00048 for default cloud, rupm disabled, 1RU, 2 region, multimaster enabled", () => {
|
||||
const value = PricingUtils.computeRUUsagePriceHourly("default", false, 1, 2, true);
|
||||
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("without multimaster", () => {
|
||||
it("should return 0 for null", () => {
|
||||
@@ -242,48 +254,52 @@ describe("PricingUtils Tests", () => {
|
||||
});
|
||||
|
||||
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(
|
||||
1 /*RU/s*/,
|
||||
"default" /* cloud */,
|
||||
1 /* region */,
|
||||
true /* multimaster */
|
||||
true /* multimaster */,
|
||||
false /* rupm */
|
||||
);
|
||||
expect(value).toBe(
|
||||
"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(
|
||||
1 /*RU/s*/,
|
||||
"mooncake" /* cloud */,
|
||||
1 /* region */,
|
||||
true /* multimaster */
|
||||
true /* multimaster */,
|
||||
false /* rupm */
|
||||
);
|
||||
expect(value).toBe(
|
||||
"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(
|
||||
400 /*RU/s*/,
|
||||
"default" /* cloud */,
|
||||
2 /* region */,
|
||||
true /* multimaster */
|
||||
true /* multimaster */,
|
||||
false /* rupm */
|
||||
);
|
||||
expect(value).toBe(
|
||||
"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(
|
||||
400 /*RU/s*/,
|
||||
"default" /* cloud */,
|
||||
2 /* region */,
|
||||
false /* multimaster */
|
||||
false /* multimaster */,
|
||||
false /* rupm */
|
||||
);
|
||||
expect(value).toBe(
|
||||
"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()", () => {
|
||||
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(
|
||||
1 /*RU/s*/,
|
||||
"default" /* cloud */,
|
||||
1 /* region */,
|
||||
true /* multimaster */,
|
||||
false /* rupm */,
|
||||
false
|
||||
);
|
||||
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(
|
||||
1 /*RU/s*/,
|
||||
"mooncake" /* cloud */,
|
||||
1 /* region */,
|
||||
true /* multimaster */,
|
||||
false /* rupm */,
|
||||
false
|
||||
);
|
||||
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(
|
||||
400 /*RU/s*/,
|
||||
"default" /* cloud */,
|
||||
2 /* region */,
|
||||
true /* multimaster */,
|
||||
false /* rupm */,
|
||||
false
|
||||
);
|
||||
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(
|
||||
400 /*RU/s*/,
|
||||
"default" /* cloud */,
|
||||
2 /* region */,
|
||||
false /* multimaster */,
|
||||
false /* rupm */,
|
||||
false
|
||||
);
|
||||
expect(value).toBe("I acknowledge the estimated $1.54 daily cost for the throughput above.");
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as AutoPilotUtils from "../Utils/AutoPilotUtils";
|
||||
import * as Constants from "../Shared/Constants";
|
||||
import { AutopilotTier } from "../Contracts/DataModels";
|
||||
|
||||
/**
|
||||
* 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));
|
||||
}
|
||||
|
||||
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.`;
|
||||
}
|
||||
|
||||
@@ -49,6 +53,7 @@ export function getMultimasterMultiplier(numberOfRegions: number, multimasterEna
|
||||
|
||||
export function computeRUUsagePriceHourly(
|
||||
serverId: string,
|
||||
rupmEnabled: boolean,
|
||||
requestUnits: number,
|
||||
numberOfRegions: number,
|
||||
multimasterEnabled: boolean
|
||||
@@ -57,10 +62,12 @@ export function computeRUUsagePriceHourly(
|
||||
const multimasterMultiplier: number = getMultimasterMultiplier(numberOfRegions, multimasterEnabled);
|
||||
|
||||
const pricePerRu = getPricePerRu(serverId);
|
||||
const pricePerRuPm = getPricePerRuPm(serverId);
|
||||
|
||||
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 {
|
||||
@@ -146,6 +153,34 @@ export function getPricePerRu(serverId: string): number {
|
||||
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 {
|
||||
if (!maxAutoPilotThroughputSet) {
|
||||
return "";
|
||||
@@ -203,9 +238,10 @@ export function getEstimatedSpendHtml(
|
||||
throughput: number,
|
||||
serverId: string,
|
||||
regions: number,
|
||||
multimaster: boolean
|
||||
multimaster: boolean,
|
||||
rupmEnabled: boolean
|
||||
): string {
|
||||
const hourlyPrice: number = computeRUUsagePriceHourly(serverId, throughput, regions, multimaster);
|
||||
const hourlyPrice: number = computeRUUsagePriceHourly(serverId, rupmEnabled, throughput, regions, multimaster);
|
||||
const dailyPrice: number = hourlyPrice * 24;
|
||||
const monthlyPrice: number = hourlyPrice * Constants.hoursInAMonth;
|
||||
const currency: string = getPriceCurrency(serverId);
|
||||
@@ -226,11 +262,12 @@ export function getEstimatedSpendAcknowledgeString(
|
||||
serverId: string,
|
||||
regions: number,
|
||||
multimaster: boolean,
|
||||
rupmEnabled: boolean,
|
||||
isAutoscale: boolean
|
||||
): string {
|
||||
const hourlyPrice: number = isAutoscale
|
||||
? computeAutoscaleUsagePriceHourly(serverId, throughput, regions, multimaster)
|
||||
: computeRUUsagePriceHourly(serverId, throughput, regions, multimaster);
|
||||
: computeRUUsagePriceHourly(serverId, rupmEnabled, throughput, regions, multimaster);
|
||||
const dailyPrice: number = hourlyPrice * 24;
|
||||
const monthlyPrice: number = hourlyPrice * Constants.hoursInAMonth;
|
||||
const currencySign: string = getCurrencySign(serverId);
|
||||
|
||||
Reference in New Issue
Block a user