Compare commits

..

2 Commits

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

View File

@@ -206,7 +206,6 @@ src/Explorer/Tabs/QueryTablesTab.ts
src/Explorer/Tabs/ScriptTabBase.ts
src/Explorer/Tabs/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

View File

@@ -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)",

View File

@@ -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==";

View File

@@ -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,

View File

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

View File

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

View File

@@ -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;
}

View File

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

View File

@@ -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);
});
});

View File

@@ -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());

View File

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

View File

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

View File

@@ -44,6 +44,7 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
onChange?: (_?: React.FormEvent<HTMLElement | HTMLInputElement>, checked?: boolean) => void;
}[] = [
{ 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" },

View File

@@ -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"

View File

@@ -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,

View File

@@ -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)}

View File

@@ -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);

View File

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

View File

@@ -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();

View File

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

View File

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

View File

@@ -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],

View File

@@ -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>

View File

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

View File

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

View File

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

View File

@@ -19,6 +19,7 @@ describe("ContainerSampleGenerator", () => {
explorerStub.isPreferredApiTable = ko.computed<boolean>(() => false);
explorerStub.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;

View File

@@ -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>();

View File

@@ -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);
});

View File

@@ -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 -->

View File

@@ -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", () => {

View File

@@ -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;

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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">

View File

@@ -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;

View File

@@ -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">

View File

@@ -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[] {

View File

@@ -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>

View File

@@ -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);
});
});
});

View File

@@ -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() {

View File

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

View File

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

View File

@@ -1,7 +1,6 @@
import DocumentsTabTemplate from "./DocumentsTab.html";
import 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 {

View File

@@ -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,

View File

@@ -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;

View File

@@ -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 {

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
import * as Constants from "../Common/Constants";
import { 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)";
}

View File

@@ -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.");

View File

@@ -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);