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 TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import { Action } from "../../Shared/Telemetry/TelemetryConstants"; import { PlatformType } from "../../PlatformType"; import { RequestOptions } from "@azure/cosmos/dist-esm"; import Explorer from "../Explorer"; import { updateOffer } from "../../Common/DocumentClientUtilityBase"; import { updateCollection } from "../../Common/dataAccess/updateCollection"; import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent"; import { userContext } from "../../UserContext"; import { updateOfferThroughputBeyondLimit } from "../../Common/dataAccess/updateOfferThroughputBeyondLimit"; 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 hasAutoPilotV2FeatureFlag: ko.PureComputed; 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 pendingNotification: ko.Observable; 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 autoPilotTiersList: ko.ObservableArray>; public selectedAutoPilotTier: ko.Observable; 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.hasAutoPilotV2FeatureFlag = ko.pureComputed(() => this.container.hasAutoPilotV2FeatureFlag()); 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.selectedAutoPilotTier = ko.observable(); this.autoPilotTiersList = ko.observableArray>(); 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(!!offerAutopilotSettings || !this.hasAutoPilotV2FeatureFlag()); 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); } } } this._hasProvisioningTypeChanged = ko.pureComputed(() => { if (!this.userCanChangeProvisioningTypes()) { return false; } if (this._wasAutopilotOriginallySet() !== this.isAutoPilotSelected()) { return true; } return false; }); 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(); }); this._isAutoPilotDirty = ko.pureComputed(() => { if (!this.isAutoPilotSelected()) { return false; } const originalAutoPilotSettings = this.collection?.offer()?.content?.offerAutopilotSettings; if (!originalAutoPilotSettings) { return false; } 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(() => { const autoPilot = !this.hasAutoPilotV2FeatureFlag() ? this.autoPilotThroughput() : this.selectedAutoPilotTier(); if (!autoPilot) { return ""; } return !this.hasAutoPilotV2FeatureFlag() ? PricingUtils.getAutoPilotV3SpendHtml(autoPilot, false /* isDatabaseThroughput */) : PricingUtils.getAutoPilotV2SpendHtml(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 (this.container.isEmulator) { 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 !this.container.isEmulator; }); this.isTryCosmosDBSubscription = ko.computed(() => { return (this.container && this.container.isTryCosmosDBSubscription()) || false; }); this.canThroughputExceedMaximumValue = ko.pureComputed(() => { const isPublicAzurePortal: boolean = this.container.getPlatformType() === PlatformType.Portal && !this.container.isRunningOnNationalCloud(); const hasPartitionKey = !!this.collection.partitionKey; return isPublicAzurePortal && hasPartitionKey; }); this.canRequestSupport = ko.pureComputed(() => { if (this.container.isEmulator) { return false; } if (this.isTryCosmosDBSubscription()) { return false; } if (this.canThroughputExceedMaximumValue()) { return false; } if (this.container.getPlatformType() === PlatformType.Hosted) { return false; } const numPartitions = this.collection.quotaInfo().numPartitions; return !!this.collection.partitionKeyProperty || numPartitions > 1; }); this.shouldDisplayPortalUsePrompt = ko.pureComputed( () => this.container.getPlatformType() === PlatformType.Hosted && !!this.collection.partitionKey ); this.minRUs = ko.computed(() => { if (this.isTryCosmosDBSubscription()) { 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) { 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 (this.container && this.container.getPlatformType() === PlatformType.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(this.hasAutoPilotV2FeatureFlag()); } 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.hasAutoPilotV2FeatureFlag() && this._isAutoPilotDirty() && AutoPilotUtils.isValidAutoPilotThroughput(this.autoPilotThroughput())) || (this.hasAutoPilotV2FeatureFlag() && this._isAutoPilotDirty() && AutoPilotUtils.isValidAutoPilotTier(this.selectedAutoPilotTier())); 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 = this.container.isEmulator; 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.pendingNotification = ko.observable(undefined); 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 = !this.container.isEmulator && 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] ) { if (AutoPilotUtils.isValidV2AutoPilotOffer(offer)) { return "Tier upgrade will take some time to complete."; } const throughput = offer.content.offerAutopilotSettings ? !this.hasAutoPilotV2FeatureFlag() ? offer.content.offerAutopilotSettings.maxThroughput : offer.content.offerAutopilotSettings.maximumTierThroughput : 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.hasAutoPilotV2FeatureFlag() && 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()) { if (!this.hasAutoPilotV2FeatureFlag()) { newOffer.content.offerAutopilotSettings = { maxThroughput: this.autoPilotThroughput() }; } else { newOffer.content.offerAutopilotSettings = { tier: this.selectedAutoPilotTier() }; } // user has changed from provisioned --> autoscale if (!this.hasAutoPilotV2FeatureFlag() && this._hasProvisioningTypeChanged()) { headerOptions.initialHeaders[Constants.HttpHeaders.migrateOfferToAutopilot] = "true"; delete newOffer.content.offerAutopilotSettings; } else { delete newOffer.content.offerThroughput; } } else { this.isAutoPilotSelected(false); this.userCanChangeProvisioningTypes(false || !this.hasAutoPilotV2FeatureFlag()); // user has changed from autoscale --> provisioned if (!this.hasAutoPilotV2FeatureFlag() && 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 updatedOffer: DataModels.Offer = await updateOffer(this.collection.offer(), newOffer, headerOptions); this.collection.offer(updatedOffer); this.collection.offer.valueHasMutated(); } } this.container.isRefreshingExplorer(false); this._setBaseline(); this.collection.readSettings(); 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: error }, startKey ); } this.isExecuting(false); }; public onRevertClick = (): Q.Promise => { 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; if (!this.hasAutoPilotV2FeatureFlag()) { const originalAutoPilotMaxThroughput = originalAutoPilotSettings && originalAutoPilotSettings.maxThroughput; this.autoPilotThroughput(originalAutoPilotMaxThroughput); } else { const originalAutoPilotTier = originalAutoPilotSettings && originalAutoPilotSettings.tier; this.selectedAutoPilotTier(originalAutoPilotTier); } } 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(() => { this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Settings); }); } 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); 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); } } 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(); } }