diff --git a/.eslintignore b/.eslintignore index 48ae531a2..6beb83c58 100644 --- a/.eslintignore +++ b/.eslintignore @@ -202,8 +202,6 @@ src/Explorer/Tabs/QueryTab.test.ts src/Explorer/Tabs/QueryTab.ts 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 diff --git a/src/Common/PortalNotifications.ts b/src/Common/PortalNotifications.ts index e97b19891..34afd40e0 100644 --- a/src/Common/PortalNotifications.ts +++ b/src/Common/PortalNotifications.ts @@ -16,7 +16,7 @@ const notificationsPath = () => { }; export const fetchPortalNotifications = async (): Promise => { - if (configContext.platform === Platform.Emulator) { + if (configContext.platform === Platform.Emulator || configContext.platform === Platform.Hosted) { return []; } diff --git a/src/Explorer/ComponentRegisterer.test.ts b/src/Explorer/ComponentRegisterer.test.ts index 327c7252a..ed3596c0f 100644 --- a/src/Explorer/ComponentRegisterer.test.ts +++ b/src/Explorer/ComponentRegisterer.test.ts @@ -44,10 +44,6 @@ describe("Component Registerer", () => { expect(ko.components.isRegistered("user-defined-function-tab")).toBe(true); }); - it("should register settings-tab component", () => { - expect(ko.components.isRegistered("settings-tab")).toBe(true); - }); - it("should register settings-tab-v2 component", () => { expect(ko.components.isRegistered("settings-tab-v2")).toBe(true); }); diff --git a/src/Explorer/ComponentRegisterer.ts b/src/Explorer/ComponentRegisterer.ts index 1e28c2d8d..bc7c47a7d 100644 --- a/src/Explorer/ComponentRegisterer.ts +++ b/src/Explorer/ComponentRegisterer.ts @@ -31,7 +31,6 @@ ko.components.register("mongo-documents-tab", new TabComponents.MongoDocumentsTa ko.components.register("stored-procedure-tab", new TabComponents.StoredProcedureTab()); ko.components.register("trigger-tab", new TabComponents.TriggerTab()); ko.components.register("user-defined-function-tab", new TabComponents.UserDefinedFunctionTab()); -ko.components.register("settings-tab", new TabComponents.SettingsTab()); ko.components.register("settings-tab-v2", new TabComponents.SettingsTabV2()); ko.components.register("query-tab", new TabComponents.QueryTab()); ko.components.register("tables-query-tab", new TabComponents.QueryTablesTab()); diff --git a/src/Explorer/Controls/Settings/SettingsComponent.tsx b/src/Explorer/Controls/Settings/SettingsComponent.tsx index b6b1ba90a..fb5672eac 100644 --- a/src/Explorer/Controls/Settings/SettingsComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsComponent.tsx @@ -16,7 +16,7 @@ import { CommandButtonComponentProps } from "../../Controls/CommandButton/Comman import { userContext } from "../../../UserContext"; import { updateOfferThroughputBeyondLimit } from "../../../Common/dataAccess/updateOfferThroughputBeyondLimit"; import SettingsTab from "../../Tabs/SettingsTabV2"; -import { throughputUnit } from "./SettingsRenderUtils"; +import { mongoIndexingPolicyAADError, throughputUnit } from "./SettingsRenderUtils"; import { ScaleComponent, ScaleComponentProps } from "./SettingsSubComponents/ScaleComponent"; import { MongoIndexingPolicyComponent, @@ -49,6 +49,7 @@ import { MongoDBCollectionResource, MongoIndex } from "../../../Utils/arm/genera import { readMongoDBCollectionThroughRP } from "../../../Common/dataAccess/readMongoDBCollection"; import { getIndexTransformationProgress } from "../../../Common/dataAccess/getIndexTransformationProgress"; import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils"; +import { isEmpty } from "underscore"; interface SettingsV2TabInfo { tab: SettingsV2TabTypes; @@ -227,7 +228,6 @@ export class SettingsComponent extends React.Component => { if ( - this.container.isMongoIndexEditorEnabled() && this.container.isPreferredApiMongoDB() && this.container.isEnableMongoCapabilityPresent() && this.container.databaseAccount() @@ -1000,15 +1000,18 @@ export class SettingsComponent extends React.Component }); - } else if ( - this.container.isMongoIndexEditorEnabled() && - this.container.isPreferredApiMongoDB() && - this.container.isEnableMongoCapabilityPresent() - ) { - tabs.push({ - tab: SettingsV2TabTypes.IndexingPolicyTab, - content: - }); + } else if (this.container.isPreferredApiMongoDB()) { + if (isEmpty(this.container.features())) { + tabs.push({ + tab: SettingsV2TabTypes.IndexingPolicyTab, + content: mongoIndexingPolicyAADError + }); + } else if (this.container.isEnableMongoCapabilityPresent()) { + tabs.push({ + tab: SettingsV2TabTypes.IndexingPolicyTab, + content: + }); + } } if (this.hasConflictResolution()) { diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/MongoIndexingPolicy/MongoIndexingPolicyComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/MongoIndexingPolicy/MongoIndexingPolicyComponent.tsx index 435cec265..a74eaff1e 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/MongoIndexingPolicy/MongoIndexingPolicyComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/MongoIndexingPolicy/MongoIndexingPolicyComponent.tsx @@ -24,7 +24,6 @@ import { transparentDetailsRowStyles, createAndAddMongoIndexStackProps, separatorStyles, - mongoIndexingPolicyAADError, indexingPolicynUnsavedWarningMessage, infoAndToolTipTextStyle } from "../../SettingsRenderUtils"; @@ -40,7 +39,6 @@ import { } from "../../SettingsUtils"; import { AddMongoIndexComponent } from "./AddMongoIndexComponent"; import { CollapsibleSectionComponent } from "../../../CollapsiblePanel/CollapsibleSectionComponent"; -import { AuthType } from "../../../../../AuthType"; import { IndexingPolicyRefreshComponent } from "../IndexingPolicyRefresh/IndexingPolicyRefreshComponent"; export interface MongoIndexingPolicyComponentProps { @@ -321,7 +319,7 @@ export class MongoIndexingPolicyComponent extends React.Component ); } else { - return window.authType !== AuthType.AAD ? mongoIndexingPolicyAADError : ; + return ; } } } diff --git a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap index 428808056..ee5174ba4 100644 --- a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap +++ b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap @@ -956,7 +956,6 @@ exports[`SettingsComponent renders 1`] = ` "isHostedDataExplorerEnabled": [Function], "isLeftPaneExpanded": [Function], "isLinkInjectionEnabled": [Function], - "isMongoIndexEditorEnabled": [Function], "isNotebookEnabled": [Function], "isNotebooksEnabledForAccount": [Function], "isNotificationConsoleExpanded": [Function], @@ -973,7 +972,6 @@ exports[`SettingsComponent renders 1`] = ` "isRightPanelV2Enabled": [Function], "isSchemaEnabled": [Function], "isServerlessEnabled": [Function], - "isSettingsV2Enabled": [Function], "isSparkEnabled": [Function], "isSparkEnabledForAccount": [Function], "isSynapseLinkUpdating": [Function], @@ -2237,7 +2235,6 @@ exports[`SettingsComponent renders 1`] = ` "isHostedDataExplorerEnabled": [Function], "isLeftPaneExpanded": [Function], "isLinkInjectionEnabled": [Function], - "isMongoIndexEditorEnabled": [Function], "isNotebookEnabled": [Function], "isNotebooksEnabledForAccount": [Function], "isNotificationConsoleExpanded": [Function], @@ -2254,7 +2251,6 @@ exports[`SettingsComponent renders 1`] = ` "isRightPanelV2Enabled": [Function], "isSchemaEnabled": [Function], "isServerlessEnabled": [Function], - "isSettingsV2Enabled": [Function], "isSparkEnabled": [Function], "isSparkEnabledForAccount": [Function], "isSynapseLinkUpdating": [Function], @@ -3531,7 +3527,6 @@ exports[`SettingsComponent renders 1`] = ` "isHostedDataExplorerEnabled": [Function], "isLeftPaneExpanded": [Function], "isLinkInjectionEnabled": [Function], - "isMongoIndexEditorEnabled": [Function], "isNotebookEnabled": [Function], "isNotebooksEnabledForAccount": [Function], "isNotificationConsoleExpanded": [Function], @@ -3548,7 +3543,6 @@ exports[`SettingsComponent renders 1`] = ` "isRightPanelV2Enabled": [Function], "isSchemaEnabled": [Function], "isServerlessEnabled": [Function], - "isSettingsV2Enabled": [Function], "isSparkEnabled": [Function], "isSparkEnabledForAccount": [Function], "isSynapseLinkUpdating": [Function], @@ -4812,7 +4806,6 @@ exports[`SettingsComponent renders 1`] = ` "isHostedDataExplorerEnabled": [Function], "isLeftPaneExpanded": [Function], "isLinkInjectionEnabled": [Function], - "isMongoIndexEditorEnabled": [Function], "isNotebookEnabled": [Function], "isNotebooksEnabledForAccount": [Function], "isNotificationConsoleExpanded": [Function], @@ -4829,7 +4822,6 @@ exports[`SettingsComponent renders 1`] = ` "isRightPanelV2Enabled": [Function], "isSchemaEnabled": [Function], "isServerlessEnabled": [Function], - "isSettingsV2Enabled": [Function], "isSparkEnabled": [Function], "isSparkEnabledForAccount": [Function], "isSynapseLinkUpdating": [Function], diff --git a/src/Explorer/Explorer.ts b/src/Explorer/Explorer.ts index bef261953..b311cd43b 100644 --- a/src/Explorer/Explorer.ts +++ b/src/Explorer/Explorer.ts @@ -206,8 +206,6 @@ export default class Explorer { public isGalleryPublishEnabled: ko.Computed; public isCodeOfConductEnabled: ko.Computed; public isLinkInjectionEnabled: ko.Computed; - public isSettingsV2Enabled: ko.Observable; - public isMongoIndexEditorEnabled: ko.Observable; public isGitHubPaneEnabled: ko.Observable; public isPublishNotebookPaneEnabled: ko.Observable; public isCopyNotebookPaneEnabled: ko.Observable; @@ -412,8 +410,6 @@ export default class Explorer { this.isLinkInjectionEnabled = ko.computed(() => this.isFeatureEnabled(Constants.Features.enableLinkInjection) ); - this.isSettingsV2Enabled = ko.observable(false); - this.isMongoIndexEditorEnabled = ko.observable(false); this.isGitHubPaneEnabled = ko.observable(false); this.isPublishNotebookPaneEnabled = ko.observable(false); this.isCopyNotebookPaneEnabled = ko.observable(false); @@ -1913,14 +1909,6 @@ export default class Explorer { if (!flights) { return; } - - if (flights.indexOf(Constants.Flights.SettingsV2) !== -1) { - this.isSettingsV2Enabled(true); - } - - if (flights.indexOf(Constants.Flights.MongoIndexEditor) !== -1) { - this.isMongoIndexEditorEnabled(true); - } } public findSelectedCollection(): ViewModels.Collection { diff --git a/src/Explorer/Tabs/SettingsTab.html b/src/Explorer/Tabs/SettingsTab.html deleted file mode 100644 index 6c4e1bc99..000000000 --- a/src/Explorer/Tabs/SettingsTab.html +++ /dev/null @@ -1,723 +0,0 @@ -
-
-
-
- Info - -
-
- Warning - -
-
-
-
- -
This table shared throughput is configured at the keyspace
- - - -
-
- - Show scale properties - - - - Hide scale properties - - - Scale -
- -
- - - - -
- - -
-
RU/m
-
-
- - -
-
- - -
-
-
- - -
-
Throughput (RU/s)
- -
- Your account has custom settings that prevents setting throughput at the container level. Please work with - your Cosmos DB engineering team point of contact to make changes. -
-
-
-
- - -
-
- - Show conflict resolution - - - - Show conflict resolution - - Conflict resolution -
-
-
Mode
-
-
- - -
- -
- - -
-
-
-

- Conflict Resolver Property - - More information - Gets or sets the name of a integer property in your documents which is used for the Last Write Wins - (LWW) based conflict resolution scheme. By default, the system uses the system defined timestamp - property, _ts to decide the winner for the conflicting versions of the document. Specify your own - integer property if you want to override the default timestamp based conflict resolution. - -

-

- -

-
-
-

- Stored procedure - - More information - Gets or sets the name of a stored procedure (aka merge procedure) for resolving the conflicts. You can - write application defined logic to determine the winner of the conflicting versions of a document. The - stored procedure will get executed transactionally, exactly once, on the server side. If you do not - provide a stored procedure, the conflicts will be populated in the - conflicts feed. You can update/re-register the stored procedure at any time. - -

-

- -

-
-
-
-
- - Show settings - - - - Show settings - - Settings -
-
-
-
Time to Live
-
-
- - -
- -
- - -
- -
- - -
-
-
- - second(s) -
-
- - -
-
Geospatial Configuration
- -
-
- - -
- -
- - -
-
-
- - -
-
Analytical Storage Time to Live
-
-
- -
-
- - -
- -
- - -
-
-
- - second(s) -
-
-
-
- Change feed log retention policy - - More information - Enable change feed log retention policy to retain last 10 minutes of history for items in the container - by default. To support this, the request unit (RU) charge for this container will be multiplied by a - factor of two for writes. Reads are unaffected. - -
-
-
- - -
-
- - -
-
-
-
-
Partition Key
- -
-
-

- Large has been enabled -

-
-
-
Indexing Policy
-
-
-
-
-
diff --git a/src/Explorer/Tabs/SettingsTab.test.ts b/src/Explorer/Tabs/SettingsTab.test.ts deleted file mode 100644 index 990d5a8ee..000000000 --- a/src/Explorer/Tabs/SettingsTab.test.ts +++ /dev/null @@ -1,449 +0,0 @@ -import * as Constants from "../../Common/Constants"; -import * as DataModels from "../../Contracts/DataModels"; -import * as ko from "knockout"; -import * as ViewModels from "../../Contracts/ViewModels"; -import Collection from "../Tree/Collection"; -import Database from "../Tree/Database"; -import Explorer from "../Explorer"; -import SettingsTab from "./SettingsTab"; -import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent"; -import { IndexingPolicies } from "../../Shared/Constants"; - -describe("Settings tab", () => { - const baseCollection: DataModels.Collection = { - defaultTtl: 200, - partitionKey: null, - conflictResolutionPolicy: { - mode: DataModels.ConflictResolutionMode.LastWriterWins, - conflictResolutionPath: "/_ts" - }, - indexingPolicy: IndexingPolicies.SharedDatabaseDefault, - _rid: "", - _self: "", - _etag: "", - _ts: 0, - id: "mycoll" - }; - - const baseDatabase: DataModels.Database = { - _rid: "", - _self: "", - _etag: "", - _ts: 0, - id: "mydb", - collections: [baseCollection] - }; - - const quotaInfo: DataModels.CollectionQuotaInfo = { - storedProcedures: 0, - triggers: 0, - functions: 0, - documentsSize: 0, - documentsCount: 0, - collectionSize: 0, - usageSizeInKB: 0, - numPartitions: 0 - }; - - describe("Conflict Resolution", () => { - describe("should show conflict resolution", () => { - let explorer: Explorer; - const baseCollectionWithoutConflict: DataModels.Collection = { - defaultTtl: 200, - partitionKey: null, - conflictResolutionPolicy: null, - indexingPolicy: IndexingPolicies.SharedDatabaseDefault, - _rid: "", - _self: "", - _etag: "", - _ts: 0, - id: "mycoll" - }; - const getSettingsTab = (conflictResolution: boolean = true) => { - return new SettingsTab({ - tabKind: ViewModels.CollectionTabKind.Settings, - title: "Scale & Settings", - tabPath: "", - hashLocation: "", - isActive: ko.observable(false), - collection: new Collection( - explorer, - "mydb", - conflictResolution ? baseCollection : baseCollectionWithoutConflict, - quotaInfo, - null - ), - onUpdateTabsButtons: undefined - }); - }; - - beforeEach(() => { - explorer = new Explorer(); - }); - - it("single master, should not show conflict resolution", () => { - const settingsTab = getSettingsTab(); - expect(settingsTab.hasConflictResolution()).toBe(false); - }); - - it("multi master with resolution conflict, show conflict resolution", () => { - explorer.databaseAccount({ - id: "test", - kind: "", - location: "", - name: "", - tags: "", - type: "", - properties: { - enableMultipleWriteLocations: true, - documentEndpoint: "", - cassandraEndpoint: "", - gremlinEndpoint: "", - tableEndpoint: "" - } - }); - - const settingsTab = getSettingsTab(); - expect(settingsTab.hasConflictResolution()).toBe(true); - }); - - it("multi master without resolution conflict, show conflict resolution", () => { - explorer.databaseAccount({ - id: "test", - kind: "", - location: "", - name: "", - tags: "", - type: "", - properties: { - enableMultipleWriteLocations: true, - documentEndpoint: "", - cassandraEndpoint: "", - gremlinEndpoint: "", - tableEndpoint: "" - } - }); - - const settingsTab = getSettingsTab(false /* no resolution conflict*/); - expect(settingsTab.hasConflictResolution()).toBe(false); - }); - }); - - describe("Parse Conflict Resolution Mode from backend", () => { - it("should parse any casing", () => { - expect(SettingsTab.parseConflictResolutionMode("custom")).toBe(DataModels.ConflictResolutionMode.Custom); - expect(SettingsTab.parseConflictResolutionMode("Custom")).toBe(DataModels.ConflictResolutionMode.Custom); - expect(SettingsTab.parseConflictResolutionMode("lastWriterWins")).toBe( - DataModels.ConflictResolutionMode.LastWriterWins - ); - expect(SettingsTab.parseConflictResolutionMode("LastWriterWins")).toBe( - DataModels.ConflictResolutionMode.LastWriterWins - ); - }); - - it("should parse empty as null", () => { - expect(SettingsTab.parseConflictResolutionMode("")).toBe(null); - }); - - it("should parse null as null", () => { - expect(SettingsTab.parseConflictResolutionMode(null)).toBe(null); - }); - }); - - describe("Parse Conflict Resolution procedure from backend", () => { - it("should parse path as name", () => { - expect(SettingsTab.parseConflictResolutionProcedure("/dbs/xxxx/colls/xxxx/sprocs/validsproc")).toBe( - "validsproc" - ); - }); - - it("should parse name as name", () => { - expect(SettingsTab.parseConflictResolutionProcedure("validsproc")).toBe("validsproc"); - }); - - it("should parse invalid path as null", () => { - expect(SettingsTab.parseConflictResolutionProcedure("/not/a/valid/path")).toBe(null); - }); - - it("should parse empty or null as null", () => { - expect(SettingsTab.parseConflictResolutionProcedure("")).toBe(null); - expect(SettingsTab.parseConflictResolutionProcedure(null)).toBe(null); - }); - }); - }); - - describe("Should update collection", () => { - let explorer: Explorer; - - beforeEach(() => { - explorer = new Explorer(); - }); - - it("On TTL changed", () => { - const settingsTab = new SettingsTab({ - tabKind: ViewModels.CollectionTabKind.Settings, - title: "Scale & Settings", - tabPath: "", - hashLocation: "", - isActive: ko.observable(false), - collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null), - onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {} - }); - - expect(settingsTab.shouldUpdateCollection()).toBe(false); - settingsTab.timeToLive("off"); - expect(settingsTab.shouldUpdateCollection()).toBe(true); - - settingsTab.onRevertClick(); - expect(settingsTab.shouldUpdateCollection()).toBe(false); - settingsTab.timeToLiveSeconds(100); - expect(settingsTab.shouldUpdateCollection()).toBe(true); - }); - - it("On Index Policy changed", () => { - const settingsTab = new SettingsTab({ - tabKind: ViewModels.CollectionTabKind.Settings, - title: "Scale & Settings", - tabPath: "", - hashLocation: "", - isActive: ko.observable(false), - collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null), - onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {} - }); - - expect(settingsTab.shouldUpdateCollection()).toBe(false); - settingsTab.indexingPolicyContent({ somethingDifferent: "" }); - expect(settingsTab.shouldUpdateCollection()).toBe(true); - }); - - it("On Conflict Resolution Mode changed", () => { - const settingsTab = new SettingsTab({ - tabKind: ViewModels.CollectionTabKind.Settings, - title: "Scale & Settings", - tabPath: "", - hashLocation: "", - isActive: ko.observable(false), - collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null), - onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {} - }); - - expect(settingsTab.shouldUpdateCollection()).toBe(false); - settingsTab.conflictResolutionPolicyMode(DataModels.ConflictResolutionMode.Custom); - expect(settingsTab.shouldUpdateCollection()).toBe(true); - - settingsTab.onRevertClick(); - expect(settingsTab.shouldUpdateCollection()).toBe(false); - settingsTab.conflictResolutionPolicyPath("/somethingElse"); - expect(settingsTab.shouldUpdateCollection()).toBe(true); - - settingsTab.onRevertClick(); - expect(settingsTab.shouldUpdateCollection()).toBe(false); - settingsTab.conflictResolutionPolicyMode(DataModels.ConflictResolutionMode.Custom); - settingsTab.conflictResolutionPolicyProcedure("resolver"); - expect(settingsTab.shouldUpdateCollection()).toBe(true); - }); - }); - - describe("Get Conflict Resolution configuration from user", () => { - let explorer: Explorer; - - beforeEach(() => { - explorer = new Explorer(); - }); - - it("null if it didnt change", () => { - const settingsTab = new SettingsTab({ - tabKind: ViewModels.CollectionTabKind.Settings, - title: "Scale & Settings", - tabPath: "", - hashLocation: "", - isActive: ko.observable(false), - collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null), - onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {} - }); - - expect(settingsTab.getUpdatedConflictResolutionPolicy()).toBe(null); - }); - - it("Custom contains valid backend path", () => { - const settingsTab = new SettingsTab({ - tabKind: ViewModels.CollectionTabKind.Settings, - title: "Scale & Settings", - tabPath: "", - hashLocation: "", - isActive: ko.observable(false), - collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null), - onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {} - }); - - expect(settingsTab.getUpdatedConflictResolutionPolicy()).toBe(null); - settingsTab.conflictResolutionPolicyMode(DataModels.ConflictResolutionMode.Custom); - settingsTab.conflictResolutionPolicyProcedure("resolver"); - let updatedPolicy = settingsTab.getUpdatedConflictResolutionPolicy(); - expect(updatedPolicy.mode).toBe(DataModels.ConflictResolutionMode.Custom); - expect(updatedPolicy.conflictResolutionProcedure).toBe("/dbs/mydb/colls/mycoll/sprocs/resolver"); - - settingsTab.conflictResolutionPolicyProcedure(""); - updatedPolicy = settingsTab.getUpdatedConflictResolutionPolicy(); - expect(updatedPolicy.conflictResolutionProcedure).toBe(undefined); - }); - - it("LWW contains valid property path", () => { - const settingsTab = new SettingsTab({ - tabKind: ViewModels.CollectionTabKind.Settings, - title: "Scale & Settings", - tabPath: "", - hashLocation: "", - isActive: ko.observable(false), - collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null), - onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {} - }); - - expect(settingsTab.getUpdatedConflictResolutionPolicy()).toBe(null); - settingsTab.conflictResolutionPolicyPath("someAttr"); - let updatedPolicy = settingsTab.getUpdatedConflictResolutionPolicy(); - expect(updatedPolicy.conflictResolutionPath).toBe("/someAttr"); - - settingsTab.conflictResolutionPolicyPath("/someAttr"); - updatedPolicy = settingsTab.getUpdatedConflictResolutionPolicy(); - expect(updatedPolicy.conflictResolutionPath).toBe("/someAttr"); - - settingsTab.conflictResolutionPolicyPath(""); - updatedPolicy = settingsTab.getUpdatedConflictResolutionPolicy(); - expect(updatedPolicy.conflictResolutionPath).toBe(""); - }); - }); - - describe("partitionKeyVisible", () => { - enum PartitionKeyOption { - None, - System, - NonSystem - } - - function getCollection(defaultApi: string, partitionKeyOption: PartitionKeyOption) { - const explorer = new Explorer(); - explorer.defaultExperience(defaultApi); - - const offer: DataModels.Offer = null; - const defaultTtl = 200; - const conflictResolutionPolicy = { - mode: DataModels.ConflictResolutionMode.LastWriterWins, - conflictResolutionPath: "/_ts" - }; - - return new Collection( - explorer, - "mydb", - { - defaultTtl: defaultTtl, - partitionKey: - partitionKeyOption != PartitionKeyOption.None - ? { - paths: ["/foo"], - kind: "Hash", - version: 2, - systemKey: partitionKeyOption === PartitionKeyOption.System - } - : null, - conflictResolutionPolicy: conflictResolutionPolicy, - indexingPolicy: IndexingPolicies.SharedDatabaseDefault, - _rid: "", - _self: "", - _etag: "", - _ts: 0, - id: "mycoll" - }, - quotaInfo, - offer - ); - } - - function getSettingsTab(defaultApi: string, partitionKeyOption: PartitionKeyOption): SettingsTab { - return new SettingsTab({ - tabKind: ViewModels.CollectionTabKind.Settings, - title: "Scale & Settings", - tabPath: "", - hashLocation: "", - isActive: ko.observable(false), - collection: getCollection(defaultApi, partitionKeyOption), - onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {} - }); - } - - it("on SQL container with no partition key should be false", () => { - const settingsTab = getSettingsTab(Constants.DefaultAccountExperience.DocumentDB, PartitionKeyOption.None); - expect(settingsTab.partitionKeyVisible()).toBe(false); - }); - - it("on Mongo container with no partition key should be false", () => { - const settingsTab = getSettingsTab(Constants.DefaultAccountExperience.MongoDB, PartitionKeyOption.None); - expect(settingsTab.partitionKeyVisible()).toBe(false); - }); - - it("on Gremlin container with no partition key should be false", () => { - const settingsTab = getSettingsTab(Constants.DefaultAccountExperience.Graph, PartitionKeyOption.None); - expect(settingsTab.partitionKeyVisible()).toBe(false); - }); - - it("on Cassandra container with no partition key should be false", () => { - const settingsTab = getSettingsTab(Constants.DefaultAccountExperience.Cassandra, PartitionKeyOption.None); - expect(settingsTab.partitionKeyVisible()).toBe(false); - }); - - it("on Table container with no partition key should be false", () => { - const settingsTab = getSettingsTab(Constants.DefaultAccountExperience.Table, PartitionKeyOption.None); - expect(settingsTab.partitionKeyVisible()).toBe(false); - }); - - it("on SQL container with system partition key should be true", () => { - const settingsTab = getSettingsTab(Constants.DefaultAccountExperience.DocumentDB, PartitionKeyOption.System); - expect(settingsTab.partitionKeyVisible()).toBe(true); - }); - - it("on Mongo container with system partition key should be false", () => { - const settingsTab = getSettingsTab(Constants.DefaultAccountExperience.MongoDB, PartitionKeyOption.System); - expect(settingsTab.partitionKeyVisible()).toBe(false); - }); - - it("on Gremlin container with system partition key should be true", () => { - const settingsTab = getSettingsTab(Constants.DefaultAccountExperience.Graph, PartitionKeyOption.System); - expect(settingsTab.partitionKeyVisible()).toBe(true); - }); - - it("on Cassandra container with system partition key should be false", () => { - const settingsTab = getSettingsTab(Constants.DefaultAccountExperience.Cassandra, PartitionKeyOption.System); - expect(settingsTab.partitionKeyVisible()).toBe(false); - }); - - it("on Table container with system partition key should be false", () => { - const settingsTab = getSettingsTab(Constants.DefaultAccountExperience.Table, PartitionKeyOption.System); - expect(settingsTab.partitionKeyVisible()).toBe(false); - }); - - it("on SQL container with non-system partition key should be true", () => { - const settingsTab = getSettingsTab(Constants.DefaultAccountExperience.DocumentDB, PartitionKeyOption.NonSystem); - expect(settingsTab.partitionKeyVisible()).toBe(true); - }); - - it("on Mongo container with non-system partition key should be true", () => { - const settingsTab = getSettingsTab(Constants.DefaultAccountExperience.MongoDB, PartitionKeyOption.NonSystem); - expect(settingsTab.partitionKeyVisible()).toBe(true); - }); - - it("on Gremlin container with non-system partition key should be true", () => { - const settingsTab = getSettingsTab(Constants.DefaultAccountExperience.Graph, PartitionKeyOption.NonSystem); - expect(settingsTab.partitionKeyVisible()).toBe(true); - }); - - it("on Cassandra container with non-system partition key should be false", () => { - const settingsTab = getSettingsTab(Constants.DefaultAccountExperience.Cassandra, PartitionKeyOption.NonSystem); - expect(settingsTab.partitionKeyVisible()).toBe(false); - }); - - it("on Table container with non-system partition key should be false", () => { - const settingsTab = getSettingsTab(Constants.DefaultAccountExperience.Table, PartitionKeyOption.NonSystem); - expect(settingsTab.partitionKeyVisible()).toBe(false); - }); - }); -}); diff --git a/src/Explorer/Tabs/SettingsTab.ts b/src/Explorer/Tabs/SettingsTab.ts deleted file mode 100644 index 95088879b..000000000 --- a/src/Explorer/Tabs/SettingsTab.ts +++ /dev/null @@ -1,1681 +0,0 @@ -import * as _ from "underscore"; -import * as AutoPilotUtils from "../../Utils/AutoPilotUtils"; -import * as Constants from "../../Common/Constants"; -import * as DataModels from "../../Contracts/DataModels"; -import * as ko from "knockout"; -import * as monaco from "monaco-editor"; -import * as PricingUtils from "../../Utils/PricingUtils"; -import * as SharedConstants from "../../Shared/Constants"; -import * as ViewModels from "../../Contracts/ViewModels"; -import DiscardIcon from "../../../images/discard.svg"; -import editable from "../../Common/EditableUtility"; -import Q from "q"; -import SaveIcon from "../../../images/save-cosmos.svg"; -import TabsBase from "./TabsBase"; -import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; -import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; -import { RequestOptions } from "@azure/cosmos/dist-esm"; -import Explorer from "../Explorer"; -import { updateOffer } from "../../Common/dataAccess/updateOffer"; -import { updateCollection } from "../../Common/dataAccess/updateCollection"; -import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent"; -import { userContext } from "../../UserContext"; -import { updateOfferThroughputBeyondLimit } from "../../Common/dataAccess/updateOfferThroughputBeyondLimit"; -import { configContext, Platform } from "../../ConfigContext"; -import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils"; - -const ttlWarning: string = ` -The system will automatically delete items based on the TTL value (in seconds) you provide, without needing a delete operation explicitly issued by a client application. -For more information see, Time to Live (TTL) in Azure Cosmos DB.`; - -const indexingPolicyTTLWarningMessage: string = ` -Changing the Indexing Policy impacts query results while the index transformation occurs. -When a change is made and the indexing mode is set to consistent or lazy, queries return eventual results until the operation completes. -For more information see, Modifying Indexing Policies.`; - -const updateThroughputBeyondLimitWarningMessage: string = ` -You are about to request an increase in throughput beyond the pre-allocated capacity. -The service will scale out and increase throughput for the selected container. -This operation will take 1-3 business days to complete. You can track the status of this request in Notifications.`; - -const updateThroughputDelayedApplyWarningMessage: string = ` -You are about to request an increase in throughput beyond the pre-allocated capacity. -This operation will take some time to complete.`; - -// TODO: move to a utility classs and add unit tests - -const currentThroughput = ( - isAutoscale: boolean, - throughput: number, - throughputUnit: string, - targetThroughput?: number -): string => { - if (targetThroughput && throughput) { - return isAutoscale - ? `, Current autoscale throughput: ${Math.round( - throughput / 10 - )} - ${throughput} ${throughputUnit}, Target autoscale throughput: ${Math.round( - targetThroughput / 10 - )} - ${targetThroughput} ${throughputUnit}` - : `, Current manual throughput: ${throughput} ${throughputUnit}, Target manual throughput: ${targetThroughput}`; - } - - if (targetThroughput && !throughput) { - return isAutoscale - ? `, Target autoscale throughput: ${Math.round(targetThroughput / 10)} - ${targetThroughput} ${throughputUnit}` - : `, Target manual throughput: ${targetThroughput} ${throughputUnit}`; - } - - if (!targetThroughput && throughput) { - return isAutoscale - ? `, Current autoscale throughput: ${Math.round(throughput / 10)} - ${throughput} ${throughputUnit}` - : `, Current manual throughput: ${throughput} ${throughputUnit}`; - } - - return ""; -}; - -const throughputApplyDelayedMessage = ( - isAutoscale: boolean, - throughput: number, - throughputUnit: string, - databaseName: string, - collectionName: string, - requestedThroughput: number -): string => ` -The request to increase the throughput has successfully been submitted. -This operation will take 1-3 business days to complete. View the latest status in Notifications.
-Database: ${databaseName}, Container: ${collectionName} ${currentThroughput( - isAutoscale, - throughput, - throughputUnit, - requestedThroughput -)}`; - -const throughputApplyShortDelayMessage = ( - isAutoscale: boolean, - throughput: number, - throughputUnit: string, - databaseName: string, - collectionName: string, - targetThroughput: number -): string => ` -A request to increase the throughput is currently in progress. This operation will take some time to complete.
-Database: ${databaseName}, Container: ${collectionName} ${currentThroughput( - isAutoscale, - throughput, - throughputUnit, - targetThroughput -)}`; - -const throughputApplyLongDelayMessage = ( - isAutoscale: boolean, - throughput: number, - throughputUnit: string, - databaseName: string, - collectionName: string, - requestedThroughput: number -): string => ` -A request to increase the throughput is currently in progress. -This operation will take 1-3 business days to complete. View the latest status in Notifications.
-Database: ${databaseName}, Container: ${collectionName} ${currentThroughput( - isAutoscale, - throughput, - throughputUnit, - requestedThroughput -)}`; - -enum ChangeFeedPolicyToggledState { - Off = "Off", - On = "On" -} - -export default class SettingsTab extends TabsBase implements ViewModels.WaitsForTemplate { - public GEOGRAPHY: string = "Geography"; - public GEOMETRY: string = "Geometry"; - - public collection: ViewModels.Collection; - - // editable - public throughput: ViewModels.Editable; - public timeToLive: ViewModels.Editable; - public timeToLiveSeconds: ViewModels.Editable; - public geospatialConfigType: ViewModels.Editable; - public geospatialVisible: ko.Computed; - public indexingPolicyContent: ViewModels.Editable; - public isIndexingPolicyEditorInitializing: ko.Observable; - public rupm: ViewModels.Editable; - public conflictResolutionPolicyMode: ViewModels.Editable; - public conflictResolutionPolicyPath: ViewModels.Editable; - public conflictResolutionPolicyProcedure: ViewModels.Editable; - - public saveSettingsButton: ViewModels.Button; - public discardSettingsChangesButton: ViewModels.Button; - - public canRequestSupport: ko.Computed; - public canThroughputExceedMaximumValue: ko.Computed; - public changeFeedPolicyOffId: string; - public changeFeedPolicyOnId: string; - public changeFeedPolicyToggled: ViewModels.Editable; - public changeFeedPolicyVisible: ko.Computed; - public conflictResolutionExpanded: ko.Observable; - public conflictResolutionPolicyModeCustom: string; - public conflictResolutionPolicyModeCRDT: string; - public conflictResolutionPolicyModeLWW: string; - public costsVisible: ko.Computed; - public hasConflictResolution: ko.Computed; - public lowerCasePartitionKeyName: ko.Computed; - public hasDatabaseSharedThroughput: ko.Computed; - public isAutoScaleEnabled: ko.Computed; - public isTemplateReady: ko.Observable; - public isTryCosmosDBSubscription: ko.Computed; - public indexingPolicyEditor: ko.Observable; - public indexingPolicyEditorId: string; - public indexingPolicyElementFocused: ko.Observable; - public minRUs: ko.Computed; - public minRUAnotationVisible: ko.Computed; - public maxRUs: ko.Computed; - public maxRUThroughputInputLimit: ko.Computed; - public maxRUsText: ko.PureComputed; - public notificationStatusInfo: ko.Observable; - public partitionKeyName: ko.Computed; - public partitionKeyVisible: ko.PureComputed; - public partitionKeyValue: ko.Observable; - public isLargePartitionKeyEnabled: ko.Computed; - public requestUnitsUsageCost: ko.Computed; - public rupmOnId: string; - public rupmOffId: string; - public rupmVisible: ko.Computed; - public scaleExpanded: ko.Observable; - public settingsExpanded: ko.Observable; - public shouldDisplayPortalUsePrompt: ko.Computed; - public shouldShowIndexingPolicyEditor: ko.Computed; - public shouldShowNotificationStatusPrompt: ko.Computed; - public shouldShowStatusBar: ko.Computed; - public storageCapacityTitle: ko.PureComputed; - public throughputTitle: ko.PureComputed; - public throughputAriaLabel: ko.PureComputed; - public ttlOffFocused: ko.Observable; - public ttlOffId: string; - public ttlOnDefaultFocused: ko.Observable; - public ttlOnFocused: ko.Observable; - public ttlOnId: string; - public ttlOnNoDefaultId: string; - public ttlVisible: ko.Computed; - public userCanChangeProvisioningTypes: ko.Observable; - public warningMessage: ko.Computed; - public shouldShowKeyspaceSharedThroughputMessage: ko.Computed; - public isAutoPilotSelected: ko.Observable; - public autoPilotThroughput: ko.Observable; - public autoPilotUsageCost: ko.Computed; - public isAnalyticalStorageEnabled: boolean; - public analyticalStorageTtlSelection: ViewModels.Editable; - public analyticalStorageTtlSeconds: ViewModels.Editable; - public canExceedMaximumValue: ko.PureComputed; - public overrideWithAutoPilotSettings: ko.Computed; - public overrideWithProvisionedThroughputSettings: ko.Computed; - public testId: string; - public throughputAutoPilotRadioId: string; - public throughputProvisionedRadioId: string; - public throughputModeRadioName: string; - - private _offerReplacePending: ko.PureComputed; - private container: Explorer; - private _wasAutopilotOriginallySet: ko.Observable; - private _isAutoPilotDirty: ko.Computed; - private _hasProvisioningTypeChanged: ko.Computed; - private _isFixedContainer: ko.Computed; - - constructor(options: ViewModels.TabOptions) { - super(options); - this.container = options.collection && options.collection.container; - this.isIndexingPolicyEditorInitializing = ko.observable(false); - - this.canExceedMaximumValue = ko.pureComputed(() => this.container.canExceedMaximumValue()); - - this.geospatialVisible = ko.pureComputed(() => this.container.isPreferredApiDocumentDB()); - - // html element ids - this.indexingPolicyEditorId = `indexingpolicyeditor${this.tabId}`; - this.ttlOffId = `ttlOffId${this.tabId}`; - this.ttlOnNoDefaultId = `ttlOnNoDefault${this.tabId}`; - 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}`; - this.testId = `settingsThroughputValue${this.tabId}`; - this.throughputAutoPilotRadioId = `editDatabaseThroughput-autoPilotRadio${this.tabId}`; - this.throughputProvisionedRadioId = `editDatabaseThroughput-manualRadio${this.tabId}`; - this.throughputModeRadioName = `throughputModeRadio${this.tabId}`; - - this.changeFeedPolicyToggled = editable.observable( - this.collection.rawDataModel?.changeFeedPolicy != null - ? ChangeFeedPolicyToggledState.On - : ChangeFeedPolicyToggledState.Off - ); - this.changeFeedPolicyVisible = ko.computed( - () => this.collection && this.collection.container.isFeatureEnabled(Constants.Features.enableChangeFeedPolicy) - ); - this.scaleExpanded = ko.observable(true); - this.settingsExpanded = ko.observable(true); - this.conflictResolutionExpanded = ko.observable(true); - - this.throughput = editable.observable(); - this.conflictResolutionPolicyMode = editable.observable(); - this.conflictResolutionPolicyPath = editable.observable(); - this.conflictResolutionPolicyProcedure = editable.observable(); - this.timeToLive = editable.observable(); - this.timeToLiveSeconds = editable.observable(); - this.geospatialConfigType = editable.observable(); - this.isAnalyticalStorageEnabled = this.collection && !!this.collection.analyticalStorageTtl(); - this.analyticalStorageTtlSelection = editable.observable(); - this.analyticalStorageTtlSeconds = editable.observable(); - this.indexingPolicyContent = editable.observable(); - this.rupm = editable.observable(); - // Mongo container with system partition key still treat as "Fixed" - this._isFixedContainer = ko.pureComputed( - () => - !this.collection.partitionKey || - (this.container.isPreferredApiMongoDB() && this.collection.partitionKey.systemKey) - ); - - this.isAutoPilotSelected = ko.observable(false); - this._wasAutopilotOriginallySet = ko.observable(false); - this.autoPilotThroughput = ko.observable(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); - - if (offerAutopilotSettings && offerAutopilotSettings.maxThroughput) { - if (AutoPilotUtils.isValidAutoPilotThroughput(offerAutopilotSettings.maxThroughput)) { - this.isAutoPilotSelected(true); - this._wasAutopilotOriginallySet(true); - this.autoPilotThroughput(offerAutopilotSettings.maxThroughput); - } - } - - this._hasProvisioningTypeChanged = ko.pureComputed(() => { - if (!this.userCanChangeProvisioningTypes()) { - return false; - } - if (this._wasAutopilotOriginallySet() !== this.isAutoPilotSelected()) { - return true; - } - return false; - }); - - this.overrideWithAutoPilotSettings = ko.pureComputed(() => { - return this._hasProvisioningTypeChanged() && this._wasAutopilotOriginallySet(); - }); - - this.overrideWithProvisionedThroughputSettings = ko.pureComputed(() => { - return this._hasProvisioningTypeChanged() && !this._wasAutopilotOriginallySet(); - }); - - this._isAutoPilotDirty = ko.pureComputed(() => { - if (!this.isAutoPilotSelected()) { - return false; - } - const originalAutoPilotSettings = this.collection?.offer()?.content?.offerAutopilotSettings; - if (!originalAutoPilotSettings) { - return false; - } - const originalAutoPilotSetting = originalAutoPilotSettings && originalAutoPilotSettings.maxThroughput; - if (this.autoPilotThroughput() !== originalAutoPilotSetting) { - return true; - } - return false; - }); - this.autoPilotUsageCost = ko.pureComputed(() => { - const autoPilot = this.autoPilotThroughput(); - if (!autoPilot) { - return ""; - } - return PricingUtils.getAutoPilotV3SpendHtml(autoPilot, false /* isDatabaseThroughput */); - }); - - this.requestUnitsUsageCost = ko.pureComputed(() => { - const account = this.container.databaseAccount(); - if (!account) { - return ""; - } - - const serverId: string = this.container.serverId(); - const offerThroughput: number = this.throughput(); - const rupmEnabled = this.rupm() === Constants.RUPMStates.on; - - const regions = - (account && - account.properties && - account.properties.readLocations && - account.properties.readLocations.length) || - 1; - const multimaster = (account && account.properties && account.properties.enableMultipleWriteLocations) || false; - - let estimatedSpend: string; - - if (!this.isAutoPilotSelected()) { - estimatedSpend = PricingUtils.getEstimatedSpendHtml( - // if migrating from autoscale to manual, we use the autoscale RUs value as that is what will be set... - this.overrideWithAutoPilotSettings() ? this.autoPilotThroughput() : offerThroughput, - serverId, - regions, - multimaster, - rupmEnabled - ); - } else { - estimatedSpend = PricingUtils.getEstimatedAutoscaleSpendHtml( - this.autoPilotThroughput(), - serverId, - regions, - multimaster - ); - } - return estimatedSpend; - }); - - this.isAutoScaleEnabled = ko.pureComputed(() => { - const accountCapabilities: DataModels.Capability[] = - this.container && - this.container.databaseAccount() && - this.container.databaseAccount().properties && - this.container.databaseAccount().properties.capabilities; - const enableAutoScaleCapability = - accountCapabilities && - _.find(accountCapabilities, capability => { - return ( - capability && - capability.name && - capability.name.toLowerCase() === Constants.CapabilityNames.EnableAutoScale.toLowerCase() - ); - }); - - return !!enableAutoScaleCapability; - }); - - this.hasDatabaseSharedThroughput = ko.pureComputed(() => { - const database: ViewModels.Database = this.collection.getDatabase(); - return database && database.isDatabaseShared && !this.collection.offer(); - }); - - this.shouldShowKeyspaceSharedThroughputMessage = ko.pureComputed(() => { - if (!this.container || !this.container.isPreferredApiCassandra() || !this.hasDatabaseSharedThroughput()) { - return false; - } - return true; - }); - - this.hasConflictResolution = ko.pureComputed(() => { - return ( - (this.container && - this.container.databaseAccount && - this.container.databaseAccount() && - this.container.databaseAccount().properties && - this.container.databaseAccount().properties.enableMultipleWriteLocations && - this.collection.conflictResolutionPolicy && - !!this.collection.conflictResolutionPolicy()) || - false - ); - }); - - 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; - }); - - this.costsVisible = ko.computed(() => { - return configContext.platform !== Platform.Emulator; - }); - - this.isTryCosmosDBSubscription = ko.computed(() => { - return (this.container && this.container.isTryCosmosDBSubscription()) || false; - }); - - this.canThroughputExceedMaximumValue = ko.pureComputed(() => { - return ( - this._isFixedContainer() && - configContext.platform === Platform.Portal && - !this.container.isRunningOnNationalCloud() - ); - }); - - this.canRequestSupport = ko.pureComputed(() => { - if (configContext.platform === Platform.Emulator) { - return false; - } - - if (this.isTryCosmosDBSubscription()) { - return false; - } - - if (this.canThroughputExceedMaximumValue()) { - return false; - } - - if (configContext.platform === Platform.Hosted) { - return false; - } - - if (this.container.isServerlessEnabled()) { - return false; - } - - const numPartitions = this.collection.quotaInfo().numPartitions; - return !!this.collection.partitionKeyProperty || numPartitions > 1; - }); - - this.shouldDisplayPortalUsePrompt = ko.pureComputed( - () => configContext.platform === Platform.Hosted && !!this.collection.partitionKey - ); - - this.minRUs = ko.computed(() => { - if (this.isTryCosmosDBSubscription() || this.container.isServerlessEnabled()) { - return SharedConstants.CollectionCreation.DefaultCollectionRUs400; - } - - const offerContent = - this.collection && this.collection.offer && this.collection.offer() && this.collection.offer().content; - - if (offerContent && offerContent.offerAutopilotSettings) { - return 400; - } - - const collectionThroughputInfo: DataModels.OfferThroughputInfo = - offerContent && offerContent.collectionThroughputInfo; - - if ( - collectionThroughputInfo && - collectionThroughputInfo.minimumRUForCollection && - collectionThroughputInfo.minimumRUForCollection > 0 - ) { - return collectionThroughputInfo.minimumRUForCollection; - } - - const numPartitions = - (collectionThroughputInfo && collectionThroughputInfo.numPhysicalPartitions) || - this.collection.quotaInfo().numPartitions; - - if (!numPartitions || numPartitions === 1) { - return SharedConstants.CollectionCreation.DefaultCollectionRUs400; - } - - let baseRU = SharedConstants.CollectionCreation.DefaultCollectionRUs400; - - const quotaInKb = this.collection.quotaInfo().collectionSize; - const quotaInGb = PricingUtils.usageInGB(quotaInKb); - - const perPartitionGBQuota: number = Math.max(10, quotaInGb / numPartitions); - const baseRUbyPartitions: number = ((numPartitions * perPartitionGBQuota) / 10) * 100; - - return Math.max(baseRU, baseRUbyPartitions); - }); - - this.minRUAnotationVisible = ko.computed(() => { - return PricingUtils.isLargerThanDefaultMinRU(this.minRUs()); - }); - - this.maxRUs = ko.computed(() => { - const isTryCosmosDBSubscription = this.isTryCosmosDBSubscription(); - if (isTryCosmosDBSubscription || this.container.isServerlessEnabled()) { - return Constants.TryCosmosExperience.maxRU; - } - - const numPartitionsFromOffer: number = - this.collection && - this.collection.offer && - this.collection.offer() && - this.collection.offer().content && - this.collection.offer().content.collectionThroughputInfo && - this.collection.offer().content.collectionThroughputInfo.numPhysicalPartitions; - - const numPartitionsFromQuotaInfo: number = this.collection && this.collection.quotaInfo().numPartitions; - - const numPartitions = numPartitionsFromOffer || numPartitionsFromQuotaInfo || 1; - - return SharedConstants.CollectionCreation.MaxRUPerPartition * numPartitions; - }); - - this.maxRUThroughputInputLimit = ko.pureComputed(() => { - if (configContext.platform === Platform.Hosted && this.collection.partitionKey) { - return SharedConstants.CollectionCreation.DefaultCollectionRUs1Million; - } - - return this.maxRUs(); - }); - - this.maxRUsText = ko.pureComputed(() => { - return SharedConstants.CollectionCreation.DefaultCollectionRUs1Million.toLocaleString(); - }); - - this.throughputTitle = ko.pureComputed(() => { - if (this.isAutoPilotSelected()) { - return AutoPilotUtils.getAutoPilotHeaderText(); - } - - const minThroughput: string = this.minRUs().toLocaleString(); - const maxThroughput: string = - this.canThroughputExceedMaximumValue() && !this._isFixedContainer() - ? "unlimited" - : this.maxRUs().toLocaleString(); - return `Throughput (${minThroughput} - ${maxThroughput} RU/s)`; - }); - - this.throughputAriaLabel = ko.pureComputed(() => { - return this.throughputTitle() + this.requestUnitsUsageCost(); - }); - - this.storageCapacityTitle = ko.pureComputed(() => { - // Mongo container with system partition key still treat as "Fixed" - const isFixed = - !this.collection.partitionKey || - (this.container.isPreferredApiMongoDB() && this.collection.partitionKey.systemKey); - const capacity: string = isFixed ? "Fixed" : "Unlimited"; - return `Storage capacity
${capacity}`; - }); - - this.partitionKeyVisible = ko.computed(() => { - if (this.container.isPreferredApiCassandra() || this.container.isPreferredApiTable()) { - return false; - } - - if (!this.collection.partitionKeyProperty) { - return false; - } - - if (this.container.isPreferredApiMongoDB() && this.collection.partitionKey.systemKey) { - return false; - } - - return true; - }); - - this.partitionKeyValue = ko.observable("/" + this.collection.partitionKeyProperty); - - this.partitionKeyName = ko.computed(() => { - if (this.container.isPreferredApiMongoDB()) { - return "Shard key"; - } - - return "Partition key"; - }); - - this.lowerCasePartitionKeyName = ko.computed(() => this.partitionKeyName().toLowerCase()); - - this.isLargePartitionKeyEnabled = ko.computed(() => { - return ( - !!this.collection.partitionKey && - !!this.collection.partitionKey.version && - this.collection.partitionKey.version >= 2 - ); - }); - - this.indexingPolicyEditor = ko.observable(); - - this.shouldShowIndexingPolicyEditor = ko.computed( - () => this.container && !this.container.isPreferredApiCassandra() && !this.container.isPreferredApiMongoDB() - ); - - this._setBaseline(); - - this.saveSettingsButton = { - enabled: ko.computed(() => { - // TODO: move validations to editables and display validation errors - if (this._offerReplacePending && this._offerReplacePending()) { - return false; - } - - const isCollectionThroughput: boolean = !this.hasDatabaseSharedThroughput(); - if (isCollectionThroughput) { - if (this._hasProvisioningTypeChanged()) { - return true; - } else if (this.isAutoPilotSelected()) { - const validAutopilotChange = - this._isAutoPilotDirty() && AutoPilotUtils.isValidAutoPilotThroughput(this.autoPilotThroughput()); - if (validAutopilotChange) { - return true; - } - } else { - const isMissingThroughput = !this.throughput(); - if (isMissingThroughput) { - return false; - } - - const isThroughputLessThanMinRus = this.throughput() < this.minRUs(); - if (isThroughputLessThanMinRus) { - return false; - } - - const isThroughputGreaterThanMaxRus = this.throughput() > this.maxRUs(); - const isEmulator = configContext.platform === Platform.Emulator; - if (isThroughputGreaterThanMaxRus && isEmulator) { - return false; - } - - if (isThroughputGreaterThanMaxRus && this._isFixedContainer()) { - return false; - } - - const isThroughputMoreThan1Million = - this.throughput() > SharedConstants.CollectionCreation.DefaultCollectionRUs1Million; - if (!this.canThroughputExceedMaximumValue() && isThroughputMoreThan1Million) { - return false; - } - - if (this.throughput.editableIsDirty()) { - return true; - } - } - } - - if ( - this.hasConflictResolution() && - (this.conflictResolutionPolicyMode.editableIsDirty() || - this.conflictResolutionPolicyPath.editableIsDirty() || - this.conflictResolutionPolicyProcedure.editableIsDirty()) - ) { - return true; - } - - if (this.timeToLive() === "on" && !this.timeToLiveSeconds()) { - return false; - } - - if (this.analyticalStorageTtlSelection() === "on" && !this.analyticalStorageTtlSeconds()) { - 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; - } - - if (this.geospatialConfigType.editableIsDirty()) { - return true; - } - - if (this.analyticalStorageTtlSelection.editableIsDirty()) { - return true; - } - - if (this.changeFeedPolicyToggled.editableIsDirty()) { - return true; - } - - if (this.timeToLive() === "on" && this.timeToLiveSeconds.editableIsDirty()) { - return true; - } - - if (this.analyticalStorageTtlSelection() === "on" && this.analyticalStorageTtlSeconds.editableIsDirty()) { - return true; - } - - if (this.indexingPolicyContent.editableIsDirty() && this.indexingPolicyContent.editableIsValid()) { - return true; - } - - if (this.rupm.editableIsDirty()) { - return true; - } - - return false; - }), - - visible: ko.pureComputed(() => { - return true; - }) - }; - - this.discardSettingsChangesButton = { - enabled: ko.computed(() => { - if (this._hasProvisioningTypeChanged()) { - return true; - } - if (this.isAutoPilotSelected() && this._isAutoPilotDirty()) { - return true; - } - - if (this.throughput.editableIsDirty()) { - return true; - } - - if (this.timeToLive.editableIsDirty()) { - return true; - } - - if (this.geospatialConfigType.editableIsDirty()) { - return true; - } - - if (this.analyticalStorageTtlSelection.editableIsDirty()) { - return true; - } - - if (this.timeToLive() === "on" && this.timeToLiveSeconds.editableIsDirty()) { - return true; - } - - if (this.analyticalStorageTtlSelection() === "on" && this.analyticalStorageTtlSeconds.editableIsDirty()) { - return true; - } - - if (this.changeFeedPolicyToggled.editableIsDirty()) { - return true; - } - - if (this.indexingPolicyContent.editableIsDirty()) { - return true; - } - - if (this.rupm.editableIsDirty()) { - return true; - } - - if ( - this.conflictResolutionPolicyMode.editableIsDirty() || - this.conflictResolutionPolicyPath.editableIsDirty() || - this.conflictResolutionPolicyProcedure.editableIsDirty() - ) { - return true; - } - - return false; - }), - - visible: ko.computed(() => { - return true; - }) - }; - - this.ttlOffFocused = ko.observable(false); - this.ttlOnDefaultFocused = ko.observable(false); - this.ttlOnFocused = ko.observable(false); - this.indexingPolicyElementFocused = ko.observable(false); - - this._offerReplacePending = ko.pureComputed(() => { - const offer = this.collection && this.collection.offer && this.collection.offer(); - return ( - offer && - offer.hasOwnProperty("headers") && - !!(offer as DataModels.OfferWithHeaders).headers[Constants.HttpHeaders.offerReplacePending] - ); - }); - - this.notificationStatusInfo = ko.observable(""); - this.shouldShowNotificationStatusPrompt = ko.computed(() => this.notificationStatusInfo().length > 0); - - this.warningMessage = ko.computed(() => { - const throughputExceedsBackendLimits: boolean = - this.canThroughputExceedMaximumValue() && - this.maxRUs() <= SharedConstants.CollectionCreation.DefaultCollectionRUs1Million && - this.throughput() > SharedConstants.CollectionCreation.DefaultCollectionRUs1Million; - - const throughputExceedsMaxValue: boolean = - configContext.platform !== Platform.Emulator && this.throughput() > this.maxRUs(); - - const ttlOptionDirty: boolean = this.timeToLive.editableIsDirty(); - const ttlOrIndexingPolicyFieldsDirty: boolean = - this.timeToLive.editableIsDirty() || - this.indexingPolicyContent.editableIsDirty() || - this.timeToLiveSeconds.editableIsDirty(); - const ttlFieldFocused: boolean = this.ttlOffFocused() || this.ttlOnDefaultFocused() || this.ttlOnFocused(); - const offer = this.collection && this.collection.offer && this.collection.offer(); - - if (ttlOptionDirty && this.timeToLive() === "on") { - return ttlWarning; - } - - if ( - offer && - offer.hasOwnProperty("headers") && - !!(offer as DataModels.OfferWithHeaders).headers[Constants.HttpHeaders.offerReplacePending] - ) { - const throughput = offer.content.offerAutopilotSettings - ? offer.content.offerAutopilotSettings.maxThroughput - : undefined; - - const targetThroughput = - offer && - offer.content && - ((offer.content.offerAutopilotSettings && offer.content.offerAutopilotSettings.targetMaxThroughput) || - offer.content.offerThroughput); - - return throughputApplyShortDelayMessage( - this.isAutoPilotSelected(), - throughput, - this._getThroughputUnit(), - this.collection.databaseId, - this.collection.id(), - targetThroughput - ); - } - - if (this.overrideWithProvisionedThroughputSettings()) { - return AutoPilotUtils.manualToAutoscaleDisclaimer; - } - - if ( - throughputExceedsBackendLimits && - !!this.collection.partitionKey && - !this._isFixedContainer() && - !ttlFieldFocused && - !this.indexingPolicyElementFocused() - ) { - return updateThroughputBeyondLimitWarningMessage; - } - - if ( - throughputExceedsMaxValue && - !!this.collection.partitionKey && - !this._isFixedContainer() && - !ttlFieldFocused && - !this.indexingPolicyElementFocused() - ) { - return updateThroughputDelayedApplyWarningMessage; - } - - if (this.pendingNotification()) { - const throughputUnit: string = this._getThroughputUnit(); - const matches: string[] = this.pendingNotification().description.match( - `Throughput update for (.*) ${throughputUnit}` - ); - - const throughput = this.throughput(); - const targetThroughput: number = matches.length > 1 && Number(matches[1]); - if (targetThroughput) { - return throughputApplyLongDelayMessage( - this.isAutoPilotSelected(), - throughput, - throughputUnit, - this.collection.databaseId, - this.collection.id(), - targetThroughput - ); - } - } - - if (ttlOrIndexingPolicyFieldsDirty) { - return indexingPolicyTTLWarningMessage; - } - - return ""; - }); - - this.warningMessage.subscribe((warning: string) => { - if (warning.length > 0) { - this.notificationStatusInfo(""); - } - }); - - this.shouldShowStatusBar = ko.computed( - () => this.shouldShowNotificationStatusPrompt() || (this.warningMessage && this.warningMessage().length > 0) - ); - - this.isTemplateReady = ko.observable(false); - this.isTemplateReady.subscribe((isTemplateReady: boolean) => { - if (!this.indexingPolicyEditor() && !this.isIndexingPolicyEditorInitializing() && isTemplateReady) { - this._createIndexingPolicyEditor(); - } - }); - - this._buildCommandBarOptions(); - } - - public shouldUpdateCollection(): boolean { - return ( - this.timeToLive.editableIsDirty() || - (this.timeToLive() === "on" && this.timeToLiveSeconds.editableIsDirty()) || - this.geospatialConfigType.editableIsDirty() || - this.conflictResolutionPolicyMode.editableIsDirty() || - this.conflictResolutionPolicyPath.editableIsDirty() || - this.conflictResolutionPolicyProcedure.editableIsDirty() || - this.indexingPolicyContent.editableIsDirty() || - this.changeFeedPolicyToggled.editableIsDirty() || - this.analyticalStorageTtlSelection.editableIsDirty() || - (this.analyticalStorageTtlSelection() === "on" && this.analyticalStorageTtlSeconds.editableIsDirty()) - ); - } - - public onSaveClick = async (): Promise => { - this.isExecutionError(false); - - this.isExecuting(true); - const startKey: number = TelemetryProcessor.traceStart(Action.UpdateSettings, { - databaseAccountName: this.container.databaseAccount().name, - defaultExperience: this.container.defaultExperience(), - dataExplorerArea: Constants.Areas.Tab, - tabTitle: this.tabTitle() - }); - - const newCollectionAttributes: any = {}; - - try { - if (this.shouldUpdateCollection()) { - let defaultTtl: number; - switch (this.timeToLive()) { - case "on": - defaultTtl = Number(this.timeToLiveSeconds()); - break; - case "on-nodefault": - defaultTtl = -1; - break; - case "off": - default: - defaultTtl = undefined; - break; - } - - newCollectionAttributes.defaultTtl = defaultTtl; - - newCollectionAttributes.indexingPolicy = this.indexingPolicyContent(); - - newCollectionAttributes.changeFeedPolicy = - this.changeFeedPolicyVisible() && this.changeFeedPolicyToggled() === ChangeFeedPolicyToggledState.On - ? ({ - retentionDuration: Constants.BackendDefaults.maxChangeFeedRetentionDuration - } as DataModels.ChangeFeedPolicy) - : undefined; - - newCollectionAttributes.analyticalStorageTtl = this.isAnalyticalStorageEnabled - ? this.analyticalStorageTtlSelection() === "on" - ? Number(this.analyticalStorageTtlSeconds()) - : Constants.AnalyticalStorageTtl.Infinite - : undefined; - - newCollectionAttributes.geospatialConfig = { - type: this.geospatialConfigType() - }; - - const conflictResolutionChanges: DataModels.ConflictResolutionPolicy = this.getUpdatedConflictResolutionPolicy(); - if (!!conflictResolutionChanges) { - newCollectionAttributes.conflictResolutionPolicy = conflictResolutionChanges; - } - - const newCollection: DataModels.Collection = _.extend( - {}, - this.collection.rawDataModel, - newCollectionAttributes - ); - const updatedCollection: DataModels.Collection = await updateCollection( - this.collection.databaseId, - this.collection.id(), - newCollection - ); - - if (updatedCollection) { - this.collection.rawDataModel = updatedCollection; - this.collection.defaultTtl(updatedCollection.defaultTtl); - this.collection.analyticalStorageTtl(updatedCollection.analyticalStorageTtl); - this.collection.id(updatedCollection.id); - this.collection.indexingPolicy(updatedCollection.indexingPolicy); - this.collection.conflictResolutionPolicy(updatedCollection.conflictResolutionPolicy); - this.collection.changeFeedPolicy(updatedCollection.changeFeedPolicy); - this.collection.geospatialConfig(updatedCollection.geospatialConfig); - } - } - - 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, - offerIsRUPerMinuteThroughputEnabled: isRUPerMinuteThroughputEnabled - } - }); - } - - const headerOptions: RequestOptions = { initialHeaders: {} }; - - if (this.isAutoPilotSelected()) { - newOffer.content.offerAutopilotSettings = { - maxThroughput: this.autoPilotThroughput() - }; - - // user has changed from provisioned --> autoscale - if (this._hasProvisioningTypeChanged()) { - headerOptions.initialHeaders[Constants.HttpHeaders.migrateOfferToAutopilot] = "true"; - delete newOffer.content.offerAutopilotSettings; - } else { - delete newOffer.content.offerThroughput; - } - } else { - this.isAutoPilotSelected(false); - this.userCanChangeProvisioningTypes(true); - - // user has changed from autoscale --> provisioned - if (this._hasProvisioningTypeChanged()) { - headerOptions.initialHeaders[Constants.HttpHeaders.migrateOfferToManualThroughput] = "true"; - } else { - delete newOffer.content.offerAutopilotSettings; - } - } - - if ( - this.maxRUs() <= SharedConstants.CollectionCreation.DefaultCollectionRUs1Million && - newThroughput > SharedConstants.CollectionCreation.DefaultCollectionRUs1Million && - this.container != null - ) { - const requestPayload = { - subscriptionId: userContext.subscriptionId, - databaseAccountName: userContext.databaseAccount.name, - resourceGroup: userContext.resourceGroup, - databaseName: this.collection.databaseId, - collectionName: this.collection.id(), - throughput: newThroughput, - offerIsRUPerMinuteThroughputEnabled: isRUPerMinuteThroughputEnabled - }; - - await updateOfferThroughputBeyondLimit(requestPayload); - this.collection.offer().content.offerThroughput = originalThroughputValue; - this.throughput(originalThroughputValue); - this.notificationStatusInfo( - throughputApplyDelayedMessage( - this.isAutoPilotSelected(), - originalThroughputValue, - this._getThroughputUnit(), - this.collection.databaseId, - this.collection.id(), - newThroughput - ) - ); - this.throughput.valueHasMutated(); // force component re-render - } else { - const updateOfferParams: DataModels.UpdateOfferParams = { - databaseId: this.collection.databaseId, - collectionId: this.collection.id(), - currentOffer: this.collection.offer(), - autopilotThroughput: this.isAutoPilotSelected() ? this.autoPilotThroughput() : undefined, - manualThroughput: this.isAutoPilotSelected() ? undefined : newThroughput - }; - if (this._hasProvisioningTypeChanged()) { - if (this.isAutoPilotSelected()) { - updateOfferParams.migrateToAutoPilot = true; - } else { - updateOfferParams.migrateToManual = true; - } - } - const updatedOffer: DataModels.Offer = await updateOffer(updateOfferParams); - this.collection.offer(updatedOffer); - this.collection.offer.valueHasMutated(); - } - } - - this.container.isRefreshingExplorer(false); - this._setBaseline(); - this._wasAutopilotOriginallySet(this.isAutoPilotSelected()); - TelemetryProcessor.traceSuccess( - Action.UpdateSettings, - { - databaseAccountName: this.container.databaseAccount().name, - defaultExperience: this.container.defaultExperience(), - dataExplorerArea: Constants.Areas.Tab, - tabTitle: this.tabTitle() - }, - startKey - ); - } catch (error) { - this.container.isRefreshingExplorer(false); - this.isExecutionError(true); - console.error(error); - TelemetryProcessor.traceFailure( - Action.UpdateSettings, - { - databaseAccountName: this.container.databaseAccount().name, - databaseName: this.collection && this.collection.databaseId, - collectionName: this.collection && this.collection.id(), - defaultExperience: this.container.defaultExperience(), - dataExplorerArea: Constants.Areas.Tab, - tabTitle: this.tabTitle(), - error: getErrorMessage(error), - errorStack: getErrorStack(error) - }, - startKey - ); - } - - this.isExecuting(false); - }; - - public onRevertClick = (): Q.Promise => { - TelemetryProcessor.trace(Action.DiscardSettings, ActionModifiers.Mark, { - message: "Settings Discarded" - }); - this.throughput.setBaseline(this.throughput.getEditableOriginalValue()); - this.timeToLive.setBaseline(this.timeToLive.getEditableOriginalValue()); - this.timeToLiveSeconds.setBaseline(this.timeToLiveSeconds.getEditableOriginalValue()); - 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()); - this.conflictResolutionPolicyPath.setBaseline(this.conflictResolutionPolicyPath.getEditableOriginalValue()); - this.conflictResolutionPolicyProcedure.setBaseline( - this.conflictResolutionPolicyProcedure.getEditableOriginalValue() - ); - - const indexingPolicyContent = this.indexingPolicyContent.getEditableOriginalValue(); - const value: string = JSON.stringify(indexingPolicyContent, null, 4); - this.indexingPolicyContent.setBaseline(indexingPolicyContent); - - const indexingPolicyEditor = this.indexingPolicyEditor(); - if (indexingPolicyEditor) { - const indexingPolicyEditorModel = indexingPolicyEditor.getModel(); - indexingPolicyEditorModel.setValue(value); - } - - if (this.userCanChangeProvisioningTypes()) { - this.isAutoPilotSelected(this._wasAutopilotOriginallySet()); - } - - if (this.isAutoPilotSelected()) { - const originalAutoPilotSettings = this.collection.offer().content.offerAutopilotSettings; - const originalAutoPilotMaxThroughput = originalAutoPilotSettings && originalAutoPilotSettings.maxThroughput; - this.autoPilotThroughput(originalAutoPilotMaxThroughput); - } - - return Q(); - }; - - public onValidIndexingPolicyEdit(): Q.Promise { - this.indexingPolicyContent.editableIsValid(true); - return Q(); - } - - public onInvalidIndexingPolicyEdit(): Q.Promise { - this.indexingPolicyContent.editableIsValid(false); - return Q(); - } - - public onActivate(): Q.Promise { - return super.onActivate().then(async () => { - this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Settings); - const database: ViewModels.Database = this.collection.getDatabase(); - await database.loadOffer(); - }); - } - - public toggleScale(): void { - this.scaleExpanded(!this.scaleExpanded()); - } - - public toggleSettings(): void { - if (this.hasDatabaseSharedThroughput()) { - return; - } - - this.settingsExpanded(!this.settingsExpanded()); - } - - public toggleConflictResolution(): void { - this.conflictResolutionExpanded(!this.conflictResolutionExpanded()); - } - - public onScaleKeyPress(source: any, event: KeyboardEvent): boolean { - if (event.keyCode === Constants.KeyCodes.Space || event.keyCode === Constants.KeyCodes.Enter) { - this.toggleScale(); - event.stopPropagation(); - return false; - } - - return true; - } - - public onConflictResolutionKeyPress(source: any, event: KeyboardEvent): boolean { - if (event.keyCode === Constants.KeyCodes.Space || event.keyCode === Constants.KeyCodes.Enter) { - this.toggleConflictResolution(); - event.stopPropagation(); - return false; - } - - return true; - } - - public onSettingsKeyPress(source: any, event: KeyboardEvent): boolean { - if (event.keyCode === Constants.KeyCodes.Space || event.keyCode === Constants.KeyCodes.Enter) { - this.toggleSettings(); - event.stopPropagation(); - return false; - } - - return true; - } - - public onTtlOffKeyPress(source: any, event: KeyboardEvent): boolean { - if (event.keyCode === Constants.KeyCodes.Space || event.keyCode === Constants.KeyCodes.Enter) { - this.timeToLive("off"); - event.stopPropagation(); - return false; - } - - return true; - } - - public onTtlOnNoDefaultKeyPress(source: any, event: KeyboardEvent): boolean { - if (event.keyCode === Constants.KeyCodes.Space || event.keyCode === Constants.KeyCodes.Enter) { - this.timeToLive("on-nodefault"); - event.stopPropagation(); - return false; - } - - return true; - } - - public onTtlOnKeyPress(source: any, event: KeyboardEvent): boolean { - if (event.keyCode === Constants.KeyCodes.Space || event.keyCode === Constants.KeyCodes.Enter) { - this.timeToLive("on"); - event.stopPropagation(); - return false; - } - - return true; - } - - public onGeographyKeyPress(source: any, event: KeyboardEvent): boolean { - if (event.keyCode === Constants.KeyCodes.Space || event.keyCode === Constants.KeyCodes.Enter) { - this.geospatialConfigType(this.GEOGRAPHY); - event.stopPropagation(); - return false; - } - - return true; - } - - public onGeometryKeyPress(source: any, event: KeyboardEvent): boolean { - if (event.keyCode === Constants.KeyCodes.Space || event.keyCode === Constants.KeyCodes.Enter) { - this.geospatialConfigType(this.GEOMETRY); - event.stopPropagation(); - return false; - } - - return true; - } - - public onAnalyticalStorageTtlOnNoDefaultKeyPress(source: any, event: KeyboardEvent): boolean { - if (event.keyCode === Constants.KeyCodes.Space || event.keyCode === Constants.KeyCodes.Enter) { - this.analyticalStorageTtlSelection("on-nodefault"); - event.stopPropagation(); - return false; - } - - return true; - } - - public onAnalyticalStorageTtlOnKeyPress(source: any, event: KeyboardEvent): boolean { - if (event.keyCode === Constants.KeyCodes.Space || event.keyCode === Constants.KeyCodes.Enter) { - this.analyticalStorageTtlSelection("on"); - event.stopPropagation(); - return false; - } - - return true; - } - - public onChangeFeedPolicyOffKeyPress(source: any, event: KeyboardEvent): boolean { - if (event.keyCode === Constants.KeyCodes.Space || event.keyCode === Constants.KeyCodes.Enter) { - this.changeFeedPolicyToggled(ChangeFeedPolicyToggledState.Off); - event.stopPropagation(); - return false; - } - - return true; - } - - public onChangeFeedPolicyOnKeyPress(source: any, event: KeyboardEvent): boolean { - if (event.keyCode === Constants.KeyCodes.Space || event.keyCode === Constants.KeyCodes.Enter) { - this.changeFeedPolicyToggled(ChangeFeedPolicyToggledState.On); - event.stopPropagation(); - return false; - } - - return true; - } - - public onConflictResolutionCustomKeyPress(source: any, event: KeyboardEvent): boolean { - if (event.keyCode === Constants.KeyCodes.Space || event.keyCode === Constants.KeyCodes.Enter) { - this.conflictResolutionPolicyMode(DataModels.ConflictResolutionMode.Custom); - event.stopPropagation(); - return false; - } - - return true; - } - - public onConflictResolutionLWWKeyPress(source: any, event: KeyboardEvent): boolean { - if (event.keyCode === Constants.KeyCodes.Space || event.keyCode === Constants.KeyCodes.Enter) { - this.conflictResolutionPolicyMode(DataModels.ConflictResolutionMode.LastWriterWins); - event.stopPropagation(); - return false; - } - - return true; - } - - private _getThroughputUnit(): string { - return this.rupm() === Constants.RUPMStates.on ? "RU/m" : "RU/s"; - } - - public getUpdatedConflictResolutionPolicy(): DataModels.ConflictResolutionPolicy { - if ( - !this.conflictResolutionPolicyMode.editableIsDirty() && - !this.conflictResolutionPolicyPath.editableIsDirty() && - !this.conflictResolutionPolicyProcedure.editableIsDirty() - ) { - return null; - } - - const policy: DataModels.ConflictResolutionPolicy = { - mode: SettingsTab.parseConflictResolutionMode(this.conflictResolutionPolicyMode()) - }; - - if ( - policy.mode === DataModels.ConflictResolutionMode.Custom && - !!this.conflictResolutionPolicyProcedure() && - this.conflictResolutionPolicyProcedure().length > 0 - ) { - policy.conflictResolutionProcedure = Constants.HashRoutePrefixes.sprocWithIds( - this.collection.databaseId, - this.collection.id(), - this.conflictResolutionPolicyProcedure(), - false - ); - } - - if (policy.mode === DataModels.ConflictResolutionMode.LastWriterWins) { - policy.conflictResolutionPath = this.conflictResolutionPolicyPath(); - if ( - policy.conflictResolutionPath && - policy.conflictResolutionPath.length > 0 && - policy.conflictResolutionPath[0] !== "/" - ) { - policy.conflictResolutionPath = "/" + policy.conflictResolutionPath; - } - } - - return policy; - } - - public static parseConflictResolutionMode(modeFromBackend: string): DataModels.ConflictResolutionMode { - // Backend can contain different casing as it does case-insensitive comparisson - if (!modeFromBackend) { - return null; - } - - const modeAsLowerCase: string = modeFromBackend.toLowerCase(); - if (modeAsLowerCase === DataModels.ConflictResolutionMode.Custom.toLowerCase()) { - return DataModels.ConflictResolutionMode.Custom; - } - - // Default is LWW - return DataModels.ConflictResolutionMode.LastWriterWins; - } - - public static parseConflictResolutionProcedure(procedureFromBackEnd: string): string { - // Backend data comes in /dbs/xxxx/colls/xxxx/sprocs/{name}, to make it easier for users, we just use the name - if (!procedureFromBackEnd) { - return null; - } - - if (procedureFromBackEnd.indexOf("/") >= 0) { - const sprocsIndex: number = procedureFromBackEnd.indexOf(Constants.HashRoutePrefixes.sprocHash); - if (sprocsIndex === -1) { - return null; - } - - return procedureFromBackEnd.substr(sprocsIndex + Constants.HashRoutePrefixes.sprocHash.length); - } - - // No path, just a name, in case backend returns just the name - return procedureFromBackEnd; - } - - private _setBaseline() { - const sixMonthsInSeconds = 15768000; - const defaultTtl = this.collection.defaultTtl(); - - let timeToLive: string = this.timeToLive(); - let timeToLiveSeconds = this.timeToLiveSeconds(); - switch (defaultTtl) { - case null: - case undefined: - case 0: - timeToLive = "off"; - timeToLiveSeconds = sixMonthsInSeconds; - break; - case -1: - timeToLive = "on-nodefault"; - timeToLiveSeconds = sixMonthsInSeconds; - break; - default: - timeToLive = "on"; - timeToLiveSeconds = defaultTtl; - break; - } - - if (this.isAnalyticalStorageEnabled) { - const analyticalStorageTtl: number = this.collection.analyticalStorageTtl(); - if (analyticalStorageTtl === Constants.AnalyticalStorageTtl.Infinite) { - this.analyticalStorageTtlSelection.setBaseline("on-nodefault"); - } else { - this.analyticalStorageTtlSelection.setBaseline("on"); - this.analyticalStorageTtlSeconds.setBaseline(analyticalStorageTtl); - } - } - - const offerThroughput = - this.collection && - this.collection.offer && - this.collection.offer() && - 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); - this.timeToLive.setBaseline(timeToLive); - this.timeToLiveSeconds.setBaseline(timeToLiveSeconds); - this.indexingPolicyContent.setBaseline(this.collection.indexingPolicy()); - const conflictResolutionPolicy: DataModels.ConflictResolutionPolicy = - this.collection.conflictResolutionPolicy && this.collection.conflictResolutionPolicy(); - this.conflictResolutionPolicyMode.setBaseline( - SettingsTab.parseConflictResolutionMode(conflictResolutionPolicy && conflictResolutionPolicy.mode) - ); - this.conflictResolutionPolicyPath.setBaseline( - conflictResolutionPolicy && conflictResolutionPolicy.conflictResolutionPath - ); - this.conflictResolutionPolicyProcedure.setBaseline( - SettingsTab.parseConflictResolutionProcedure( - 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); - - this.indexingPolicyContent.setBaseline(indexingPolicyContent); - - if (!this.indexingPolicyEditor() && !this.isIndexingPolicyEditorInitializing()) { - this._createIndexingPolicyEditor(); - } else { - const indexingPolicyEditorModel = this.indexingPolicyEditor().getModel(); - indexingPolicyEditorModel.setValue(value); - } - - const geospatialConfigType: string = - (this.collection.geospatialConfig && - this.collection.geospatialConfig() && - this.collection.geospatialConfig().type) || - 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; - - this.autoPilotThroughput(maxThroughput || AutoPilotUtils.minAutoPilotThroughput); - } - - private _createIndexingPolicyEditor() { - this.isIndexingPolicyEditorInitializing(true); - - // TODO: Remove this check once we unify Editor creation among all tabs - if (!this._getIndexingPolicyEditorContainer()) { - setTimeout(() => { - this._createIndexingPolicyEditor(); - }, Constants.ClientDefaults.waitForDOMElementMs); - return; - } - - let value: string = JSON.stringify(this.indexingPolicyContent(), null, 4); - - // TODO: Use consistent logic to create editor throughout DataExplorer avoiding any race conditions - $(document).ready(() => { - let indexingPolicyEditor = monaco.editor.create(this._getIndexingPolicyEditorContainer(), { - value: value, - language: "json", - readOnly: false, - ariaLabel: "Indexing Policy" - }); - indexingPolicyEditor.onDidFocusEditorText(() => this.indexingPolicyElementFocused(true)); - indexingPolicyEditor.onDidBlurEditorText(() => this.indexingPolicyElementFocused(false)); - const indexingPolicyEditorModel = indexingPolicyEditor.getModel(); - indexingPolicyEditorModel.onDidChangeContent(this._onEditorContentChange.bind(this)); - this.indexingPolicyEditor(indexingPolicyEditor); - this.isIndexingPolicyEditorInitializing(false); - if (this.onLoadStartKey != null && this.onLoadStartKey != undefined) { - TelemetryProcessor.traceSuccess( - Action.Tab, - { - databaseAccountName: this.container.databaseAccount().name, - databaseName: this.collection.databaseId, - collectionName: this.collection.id(), - defaultExperience: this.container.defaultExperience(), - dataExplorerArea: Constants.Areas.Tab, - tabTitle: this.tabTitle() - }, - this.onLoadStartKey - ); - this.onLoadStartKey = null; - } - }); - } - - private _onEditorContentChange(e: monaco.editor.IModelContentChangedEvent) { - const indexingPolicyEditorModel = this.indexingPolicyEditor().getModel(); - try { - let parsed: any = JSON.parse(indexingPolicyEditorModel.getValue()); - this.indexingPolicyContent(parsed); - this.onValidIndexingPolicyEdit(); - } catch (e) { - this.onInvalidIndexingPolicyEdit(); - } - } - - private _getIndexingPolicyEditorContainer(): HTMLElement { - return document.getElementById(this.indexingPolicyEditorId); - } - - protected getTabsButtons(): CommandButtonComponentProps[] { - const buttons: CommandButtonComponentProps[] = []; - if (this.saveSettingsButton.visible()) { - const label = "Save"; - buttons.push({ - iconSrc: SaveIcon, - iconAlt: label, - onCommandClick: this.onSaveClick, - commandButtonLabel: label, - ariaLabel: label, - hasPopup: false, - disabled: !this.saveSettingsButton.enabled() - }); - } - - if (this.discardSettingsChangesButton.visible()) { - const label = "Discard"; - buttons.push({ - iconSrc: DiscardIcon, - iconAlt: label, - onCommandClick: this.onRevertClick, - commandButtonLabel: label, - ariaLabel: label, - hasPopup: false, - disabled: !this.discardSettingsChangesButton.enabled() - }); - } - return buttons; - } - - private _buildCommandBarOptions(): void { - ko.computed(() => - ko.toJSON([ - this.saveSettingsButton.visible, - this.saveSettingsButton.enabled, - this.discardSettingsChangesButton.visible, - this.discardSettingsChangesButton.enabled - ]) - ).subscribe(() => this.updateNavbarWithTabsButtons()); - this.updateNavbarWithTabsButtons(); - } -} diff --git a/src/Explorer/Tabs/SettingsTabV2.tsx b/src/Explorer/Tabs/SettingsTabV2.tsx index 634c0ac3b..256792ed4 100644 --- a/src/Explorer/Tabs/SettingsTabV2.tsx +++ b/src/Explorer/Tabs/SettingsTabV2.tsx @@ -47,7 +47,7 @@ export default class SettingsTabV2 extends TabsBase { this.currentCollection.loadOffer().then( () => { // passed in options and set by parent as "Settings" by default - this.tabTitle("Scale & Settings"); + this.tabTitle(this.currentCollection.offer() ? "Settings" : "Scale & Settings"); this.offerRead(true); this.options.getPendingNotification.then( (data: DataModels.Notification) => { diff --git a/src/Explorer/Tabs/TabComponents.ts b/src/Explorer/Tabs/TabComponents.ts index 2700bce07..8d0490ff2 100644 --- a/src/Explorer/Tabs/TabComponents.ts +++ b/src/Explorer/Tabs/TabComponents.ts @@ -9,7 +9,6 @@ import MongoQueryTabTemplate from "./MongoQueryTab.html"; import MongoShellTabTemplate from "./MongoShellTab.html"; import QueryTabTemplate from "./QueryTab.html"; import QueryTablesTabTemplate from "./QueryTablesTab.html"; -import SettingsTabTemplate from "./SettingsTab.html"; import SettingsTabV2Template from "./SettingsTabV2.html"; import DatabaseSettingsTabTemplate from "./DatabaseSettingsTab.html"; import StoredProcedureTabTemplate from "./StoredProcedureTab.html"; @@ -133,15 +132,6 @@ export class QueryTablesTab { } } -export class SettingsTab { - constructor() { - return { - viewModel: TabComponent, - template: SettingsTabTemplate - }; - } -} - export class SettingsTabV2 { constructor() { return { diff --git a/src/Explorer/Tree/Collection.ts b/src/Explorer/Tree/Collection.ts index 897752b92..307ff2ff6 100644 --- a/src/Explorer/Tree/Collection.ts +++ b/src/Explorer/Tree/Collection.ts @@ -29,7 +29,6 @@ import MongoShellTab from "../Tabs/MongoShellTab"; import QueryTab from "../Tabs/QueryTab"; import QueryTablesTab from "../Tabs/QueryTablesTab"; import SettingsTabV2 from "../Tabs/SettingsTabV2"; -import SettingsTab from "../Tabs/SettingsTab"; import ConflictId from "./ConflictId"; import DocumentId from "./DocumentId"; import StoredProcedure from "./StoredProcedure"; @@ -556,11 +555,6 @@ export default class Collection implements ViewModels.Collection { dataExplorerArea: Constants.Areas.ResourceTree }); - const isSettingsV2Enabled = this.container.isSettingsV2Enabled(); - if (!isSettingsV2Enabled) { - await this.loadOffer(); - } - const tabTitle = !this.offer() ? "Settings" : "Scale & Settings"; const pendingNotificationsPromise: Q.Promise = this._getPendingThroughputSplitNotification(); const matchingTabs = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Settings, tab => { @@ -587,68 +581,8 @@ export default class Collection implements ViewModels.Collection { onUpdateTabsButtons: this.container.onUpdateTabsButtons }; - if (isSettingsV2Enabled) { - let settingsTabV2 = matchingTabs && (matchingTabs[0] as SettingsTabV2); - this.launchSettingsTabV2(settingsTabV2, traceStartData, settingsTabOptions, pendingNotificationsPromise); - } else { - let settingsTab = matchingTabs && (matchingTabs[0] as SettingsTab); - this.launchSettingsTabV1(settingsTab, traceStartData, settingsTabOptions, pendingNotificationsPromise); - } - }; - - private launchSettingsTabV1 = ( - settingsTab: SettingsTab, - traceStartData: any, - settingsTabOptions: ViewModels.TabOptions, - getPendingNotification: Q.Promise - ): void => { - if (!settingsTab) { - const startKey: number = TelemetryProcessor.traceStart(Action.Tab, traceStartData); - settingsTabOptions.onLoadStartKey = startKey; - - getPendingNotification.then( - (data: any) => { - const pendingNotification: DataModels.Notification = data && data[0]; - settingsTabOptions.tabKind = ViewModels.CollectionTabKind.Settings; - settingsTab = new SettingsTab(settingsTabOptions); - this.container.tabsManager.activateNewTab(settingsTab); - settingsTab.pendingNotification(pendingNotification); - }, - (error: any) => { - const errorMessage = getErrorMessage(error); - TelemetryProcessor.traceFailure( - Action.Tab, - { - databaseAccountName: this.container.databaseAccount().name, - databaseName: this.databaseId, - collectionName: this.id(), - defaultExperience: this.container.defaultExperience(), - dataExplorerArea: Constants.Areas.Tab, - tabTitle: settingsTabOptions.title, - error: errorMessage, - errorStack: getErrorStack(error) - }, - startKey - ); - NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.Error, - `Error while fetching container settings for container ${this.id()}: ${errorMessage}` - ); - throw error; - } - ); - } else { - getPendingNotification.then( - (pendingNotification: DataModels.Notification) => { - settingsTab.pendingNotification(pendingNotification); - this.container.tabsManager.activateTab(settingsTab); - }, - (error: any) => { - settingsTab.pendingNotification(undefined); - this.container.tabsManager.activateTab(settingsTab); - } - ); - } + let settingsTabV2 = matchingTabs && (matchingTabs[0] as SettingsTabV2); + this.launchSettingsTabV2(settingsTabV2, traceStartData, settingsTabOptions, pendingNotificationsPromise); }; private launchSettingsTabV2 = (