mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-02-16 17:25:58 +00:00
Move database settings tab to react (#386)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
This commit is contained in:
parent
4210e0752b
commit
22d8a7a1be
@ -119,6 +119,7 @@ export class Features {
|
|||||||
public static readonly enableSchema = "enableschema";
|
public static readonly enableSchema = "enableschema";
|
||||||
public static readonly enableSDKoperations = "enablesdkoperations";
|
public static readonly enableSDKoperations = "enablesdkoperations";
|
||||||
public static readonly showMinRUSurvey = "showminrusurvey";
|
public static readonly showMinRUSurvey = "showminrusurvey";
|
||||||
|
public static readonly enableDatabaseSettingsTabV1 = "enabledbsettingsv1";
|
||||||
public static readonly selfServeType = "selfservetype";
|
public static readonly selfServeType = "selfservetype";
|
||||||
public static readonly enableKOPanel = "enablekopanel";
|
public static readonly enableKOPanel = "enablekopanel";
|
||||||
}
|
}
|
||||||
|
@ -91,6 +91,7 @@ export interface Database extends TreeNode {
|
|||||||
onDeleteDatabaseContextMenuClick(source: Database, event: MouseEvent | KeyboardEvent): void;
|
onDeleteDatabaseContextMenuClick(source: Database, event: MouseEvent | KeyboardEvent): void;
|
||||||
onSettingsClick: () => void;
|
onSettingsClick: () => void;
|
||||||
loadOffer(): Promise<void>;
|
loadOffer(): Promise<void>;
|
||||||
|
getPendingThroughputSplitNotification(): Promise<DataModels.Notification>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CollectionBase extends TreeNode {
|
export interface CollectionBase extends TreeNode {
|
||||||
@ -177,6 +178,7 @@ export interface Collection extends CollectionBase {
|
|||||||
uploadFiles(fileList: FileList): Promise<UploadDetails>;
|
uploadFiles(fileList: FileList): Promise<UploadDetails>;
|
||||||
|
|
||||||
getLabel(): string;
|
getLabel(): string;
|
||||||
|
getPendingThroughputSplitNotification(): Promise<DataModels.Notification>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -291,10 +293,6 @@ export interface DocumentsTabOptions extends TabOptions {
|
|||||||
resourceTokenPartitionKey?: string;
|
resourceTokenPartitionKey?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SettingsTabV2Options extends TabOptions {
|
|
||||||
getPendingNotification: Promise<DataModels.Notification>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ConflictsTabOptions extends TabOptions {
|
export interface ConflictsTabOptions extends TabOptions {
|
||||||
partitionKey: DataModels.PartitionKey;
|
partitionKey: DataModels.PartitionKey;
|
||||||
conflictIds: ko.ObservableArray<ConflictId>;
|
conflictIds: ko.ObservableArray<ConflictId>;
|
||||||
@ -361,7 +359,8 @@ export enum CollectionTabKind {
|
|||||||
Gallery = 17,
|
Gallery = 17,
|
||||||
NotebookViewer = 18,
|
NotebookViewer = 18,
|
||||||
Schema = 19,
|
Schema = 19,
|
||||||
SettingsV2 = 20,
|
CollectionSettingsV2 = 20,
|
||||||
|
DatabaseSettingsV2 = 21,
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum TerminalKind {
|
export enum TerminalKind {
|
||||||
|
@ -45,7 +45,8 @@ describe("Component Registerer", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should register settings-tab-v2 component", () => {
|
it("should register settings-tab-v2 component", () => {
|
||||||
expect(ko.components.isRegistered("settings-tab-v2")).toBe(true);
|
expect(ko.components.isRegistered("database-settings-tab-v2")).toBe(true);
|
||||||
|
expect(ko.components.isRegistered("collection-settings-tab-v2")).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should register query-tab component", () => {
|
it("should register query-tab component", () => {
|
||||||
|
@ -31,7 +31,7 @@ ko.components.register("mongo-documents-tab", new TabComponents.MongoDocumentsTa
|
|||||||
ko.components.register("stored-procedure-tab", new TabComponents.StoredProcedureTab());
|
ko.components.register("stored-procedure-tab", new TabComponents.StoredProcedureTab());
|
||||||
ko.components.register("trigger-tab", new TabComponents.TriggerTab());
|
ko.components.register("trigger-tab", new TabComponents.TriggerTab());
|
||||||
ko.components.register("user-defined-function-tab", new TabComponents.UserDefinedFunctionTab());
|
ko.components.register("user-defined-function-tab", new TabComponents.UserDefinedFunctionTab());
|
||||||
ko.components.register("settings-tab-v2", new TabComponents.SettingsTabV2());
|
ko.components.register("collection-settings-tab-v2", new TabComponents.SettingsTabV2());
|
||||||
ko.components.register("query-tab", new TabComponents.QueryTab());
|
ko.components.register("query-tab", new TabComponents.QueryTab());
|
||||||
ko.components.register("tables-query-tab", new TabComponents.QueryTablesTab());
|
ko.components.register("tables-query-tab", new TabComponents.QueryTablesTab());
|
||||||
ko.components.register("graph-tab", new TabComponents.GraphTab());
|
ko.components.register("graph-tab", new TabComponents.GraphTab());
|
||||||
@ -45,6 +45,7 @@ ko.components.register("notebook-viewer-tab", new TabComponents.NotebookViewerTa
|
|||||||
|
|
||||||
// Database Tabs
|
// Database Tabs
|
||||||
ko.components.register("database-settings-tab", new TabComponents.DatabaseSettingsTab());
|
ko.components.register("database-settings-tab", new TabComponents.DatabaseSettingsTab());
|
||||||
|
ko.components.register("database-settings-tab-v2", new TabComponents.SettingsTabV2());
|
||||||
|
|
||||||
// Panes
|
// Panes
|
||||||
ko.components.register("add-database-pane", new PaneComponents.AddDatabasePaneComponent());
|
ko.components.register("add-database-pane", new PaneComponents.AddDatabasePaneComponent());
|
||||||
|
@ -2,7 +2,7 @@ import { shallow } from "enzyme";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { SettingsComponentProps, SettingsComponent, SettingsComponentState } from "./SettingsComponent";
|
import { SettingsComponentProps, SettingsComponent, SettingsComponentState } from "./SettingsComponent";
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
import SettingsTabV2 from "../../Tabs/SettingsTabV2";
|
import { CollectionSettingsTabV2 } from "../../Tabs/SettingsTabV2";
|
||||||
import { collection } from "./TestUtils";
|
import { collection } from "./TestUtils";
|
||||||
import * as DataModels from "../../../Contracts/DataModels";
|
import * as DataModels from "../../../Contracts/DataModels";
|
||||||
import ko from "knockout";
|
import ko from "knockout";
|
||||||
@ -37,16 +37,15 @@ jest.mock("../../../Common/dataAccess/updateOffer", () => ({
|
|||||||
|
|
||||||
describe("SettingsComponent", () => {
|
describe("SettingsComponent", () => {
|
||||||
const baseProps: SettingsComponentProps = {
|
const baseProps: SettingsComponentProps = {
|
||||||
settingsTab: new SettingsTabV2({
|
settingsTab: new CollectionSettingsTabV2({
|
||||||
collection: collection,
|
collection: collection,
|
||||||
tabKind: ViewModels.CollectionTabKind.SettingsV2,
|
tabKind: ViewModels.CollectionTabKind.CollectionSettingsV2,
|
||||||
title: "Scale & Settings",
|
title: "Scale & Settings",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
node: undefined,
|
node: undefined,
|
||||||
hashLocation: "settings",
|
hashLocation: "settings",
|
||||||
isActive: ko.observable(false),
|
isActive: ko.observable(false),
|
||||||
onUpdateTabsButtons: undefined,
|
onUpdateTabsButtons: undefined,
|
||||||
getPendingNotification: Promise.resolve(undefined),
|
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -139,6 +138,7 @@ describe("SettingsComponent", () => {
|
|||||||
readSettings: undefined,
|
readSettings: undefined,
|
||||||
onSettingsClick: undefined,
|
onSettingsClick: undefined,
|
||||||
loadOffer: undefined,
|
loadOffer: undefined,
|
||||||
|
getPendingThroughputSplitNotification: undefined,
|
||||||
} as ViewModels.Database;
|
} as ViewModels.Database;
|
||||||
newCollection.getDatabase = () => newDatabase;
|
newCollection.getDatabase = () => newDatabase;
|
||||||
newCollection.offer = ko.observable(undefined);
|
newCollection.offer = ko.observable(undefined);
|
||||||
|
@ -11,7 +11,7 @@ import Explorer from "../../Explorer";
|
|||||||
import { updateOffer } from "../../../Common/dataAccess/updateOffer";
|
import { updateOffer } from "../../../Common/dataAccess/updateOffer";
|
||||||
import { updateCollection, updateMongoDBCollectionThroughRP } from "../../../Common/dataAccess/updateCollection";
|
import { updateCollection, updateMongoDBCollectionThroughRP } from "../../../Common/dataAccess/updateCollection";
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
import SettingsTab from "../../Tabs/SettingsTabV2";
|
import { SettingsTabV2 } from "../../Tabs/SettingsTabV2";
|
||||||
import { mongoIndexingPolicyAADError } from "./SettingsRenderUtils";
|
import { mongoIndexingPolicyAADError } from "./SettingsRenderUtils";
|
||||||
import { ScaleComponent, ScaleComponentProps } from "./SettingsSubComponents/ScaleComponent";
|
import { ScaleComponent, ScaleComponentProps } from "./SettingsSubComponents/ScaleComponent";
|
||||||
import {
|
import {
|
||||||
@ -58,7 +58,7 @@ interface ButtonV2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SettingsComponentProps {
|
export interface SettingsComponentProps {
|
||||||
settingsTab: SettingsTab;
|
settingsTab: SettingsTabV2;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SettingsComponentState {
|
export interface SettingsComponentState {
|
||||||
@ -116,7 +116,10 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
private discardSettingsChangesButton: ButtonV2;
|
private discardSettingsChangesButton: ButtonV2;
|
||||||
|
|
||||||
private isAnalyticalStorageEnabled: boolean;
|
private isAnalyticalStorageEnabled: boolean;
|
||||||
|
private isCollectionSettingsTab: boolean;
|
||||||
private collection: ViewModels.Collection;
|
private collection: ViewModels.Collection;
|
||||||
|
private database: ViewModels.Database;
|
||||||
|
private offer: DataModels.Offer;
|
||||||
private container: Explorer;
|
private container: Explorer;
|
||||||
private changeFeedPolicyVisible: boolean;
|
private changeFeedPolicyVisible: boolean;
|
||||||
private isFixedContainer: boolean;
|
private isFixedContainer: boolean;
|
||||||
@ -126,8 +129,11 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
constructor(props: SettingsComponentProps) {
|
constructor(props: SettingsComponentProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
this.isCollectionSettingsTab = this.props.settingsTab.tabKind === ViewModels.CollectionTabKind.CollectionSettingsV2;
|
||||||
|
if (this.isCollectionSettingsTab) {
|
||||||
this.collection = this.props.settingsTab.collection as ViewModels.Collection;
|
this.collection = this.props.settingsTab.collection as ViewModels.Collection;
|
||||||
this.container = this.collection?.container;
|
this.container = this.collection?.container;
|
||||||
|
this.offer = this.collection?.offer();
|
||||||
this.isAnalyticalStorageEnabled = !!this.collection?.analyticalStorageTtl();
|
this.isAnalyticalStorageEnabled = !!this.collection?.analyticalStorageTtl();
|
||||||
this.shouldShowIndexingPolicyEditor =
|
this.shouldShowIndexingPolicyEditor =
|
||||||
this.container && !this.container.isPreferredApiCassandra() && !this.container.isPreferredApiMongoDB();
|
this.container && !this.container.isPreferredApiCassandra() && !this.container.isPreferredApiMongoDB();
|
||||||
@ -139,7 +145,12 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
// Mongo container with system partition key still treat as "Fixed"
|
// Mongo container with system partition key still treat as "Fixed"
|
||||||
this.isFixedContainer =
|
this.isFixedContainer =
|
||||||
this.container.isPreferredApiMongoDB() &&
|
this.container.isPreferredApiMongoDB() &&
|
||||||
(!this.collection.partitionKey || this.collection.partitionKey.systemKey);
|
(!this.collection?.partitionKey || this.collection?.partitionKey.systemKey);
|
||||||
|
} else {
|
||||||
|
this.database = this.props.settingsTab.database;
|
||||||
|
this.container = this.database?.container;
|
||||||
|
this.offer = this.database?.offer();
|
||||||
|
}
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
throughput: undefined,
|
throughput: undefined,
|
||||||
@ -206,18 +217,21 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
||||||
|
if (this.isCollectionSettingsTab) {
|
||||||
this.refreshIndexTransformationProgress();
|
this.refreshIndexTransformationProgress();
|
||||||
this.loadMongoIndexes();
|
this.loadMongoIndexes();
|
||||||
|
}
|
||||||
|
|
||||||
this.setAutoPilotStates();
|
this.setAutoPilotStates();
|
||||||
this.setBaseline();
|
this.setBaseline();
|
||||||
if (this.props.settingsTab.isActive()) {
|
if (this.props.settingsTab.isActive()) {
|
||||||
this.props.settingsTab.getSettingsTabContainer().onUpdateTabsButtons(this.getTabsButtons());
|
this.props.settingsTab.getContainer().onUpdateTabsButtons(this.getTabsButtons());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(): void {
|
componentDidUpdate(): void {
|
||||||
if (this.props.settingsTab.isActive()) {
|
if (this.props.settingsTab.isActive()) {
|
||||||
this.props.settingsTab.getSettingsTabContainer().onUpdateTabsButtons(this.getTabsButtons());
|
this.props.settingsTab.getContainer().onUpdateTabsButtons(this.getTabsButtons());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,7 +284,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
};
|
};
|
||||||
|
|
||||||
private setAutoPilotStates = (): void => {
|
private setAutoPilotStates = (): void => {
|
||||||
const autoscaleMaxThroughput = this.collection?.offer()?.autoscaleMaxThroughput;
|
const autoscaleMaxThroughput = this.offer?.autoscaleMaxThroughput;
|
||||||
|
|
||||||
if (autoscaleMaxThroughput && AutoPilotUtils.isValidAutoPilotThroughput(autoscaleMaxThroughput)) {
|
if (autoscaleMaxThroughput && AutoPilotUtils.isValidAutoPilotThroughput(autoscaleMaxThroughput)) {
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -295,7 +309,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
!!this.collection.conflictResolutionPolicy();
|
!!this.collection.conflictResolutionPolicy();
|
||||||
|
|
||||||
public isOfferReplacePending = (): boolean => {
|
public isOfferReplacePending = (): boolean => {
|
||||||
return this.collection?.offer()?.offerReplacePending;
|
return this.offer?.offerReplacePending;
|
||||||
};
|
};
|
||||||
|
|
||||||
public onSaveClick = async (): Promise<void> => {
|
public onSaveClick = async (): Promise<void> => {
|
||||||
@ -309,174 +323,10 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
tabTitle: this.props.settingsTab.tabTitle(),
|
tabTitle: this.props.settingsTab.tabTitle(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const newCollection: DataModels.Collection = { ...this.collection.rawDataModel };
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (
|
await (this.isCollectionSettingsTab
|
||||||
this.state.isSubSettingsSaveable ||
|
? this.saveCollectionSettings(startKey)
|
||||||
this.state.isIndexingPolicyDirty ||
|
: this.saveDatabaseSettings(startKey));
|
||||||
this.state.isConflictResolutionDirty
|
|
||||||
) {
|
|
||||||
let defaultTtl: number;
|
|
||||||
switch (this.state.timeToLive) {
|
|
||||||
case TtlType.On:
|
|
||||||
defaultTtl = Number(this.state.timeToLiveSeconds);
|
|
||||||
break;
|
|
||||||
case TtlType.OnNoDefault:
|
|
||||||
defaultTtl = -1;
|
|
||||||
break;
|
|
||||||
case TtlType.Off:
|
|
||||||
default:
|
|
||||||
defaultTtl = undefined;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const wasIndexingPolicyModified = this.state.isIndexingPolicyDirty;
|
|
||||||
newCollection.defaultTtl = defaultTtl;
|
|
||||||
|
|
||||||
newCollection.indexingPolicy = this.state.indexingPolicyContent;
|
|
||||||
|
|
||||||
newCollection.changeFeedPolicy =
|
|
||||||
this.changeFeedPolicyVisible && this.state.changeFeedPolicy === ChangeFeedPolicyState.On
|
|
||||||
? {
|
|
||||||
retentionDuration: Constants.BackendDefaults.maxChangeFeedRetentionDuration,
|
|
||||||
}
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
newCollection.analyticalStorageTtl = this.getAnalyticalStorageTtl();
|
|
||||||
|
|
||||||
newCollection.geospatialConfig = {
|
|
||||||
type: this.state.geospatialConfigType,
|
|
||||||
};
|
|
||||||
|
|
||||||
const conflictResolutionChanges: DataModels.ConflictResolutionPolicy = this.getUpdatedConflictResolutionPolicy();
|
|
||||||
if (conflictResolutionChanges) {
|
|
||||||
newCollection.conflictResolutionPolicy = conflictResolutionChanges;
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedCollection: DataModels.Collection = await updateCollection(
|
|
||||||
this.collection.databaseId,
|
|
||||||
this.collection.id(),
|
|
||||||
newCollection
|
|
||||||
);
|
|
||||||
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 (wasIndexingPolicyModified) {
|
|
||||||
await this.refreshIndexTransformationProgress();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
isSubSettingsSaveable: false,
|
|
||||||
isSubSettingsDiscardable: false,
|
|
||||||
isIndexingPolicyDirty: false,
|
|
||||||
isConflictResolutionDirty: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.state.isMongoIndexingPolicySaveable && this.mongoDBCollectionResource) {
|
|
||||||
try {
|
|
||||||
const newMongoIndexes = this.getMongoIndexesToSave();
|
|
||||||
const newMongoCollection: MongoDBCollectionResource = {
|
|
||||||
...this.mongoDBCollectionResource,
|
|
||||||
indexes: newMongoIndexes,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.mongoDBCollectionResource = await updateMongoDBCollectionThroughRP(
|
|
||||||
this.collection.databaseId,
|
|
||||||
this.collection.id(),
|
|
||||||
newMongoCollection
|
|
||||||
);
|
|
||||||
|
|
||||||
await this.refreshIndexTransformationProgress();
|
|
||||||
this.setState({
|
|
||||||
isMongoIndexingPolicySaveable: false,
|
|
||||||
indexesToDrop: [],
|
|
||||||
indexesToAdd: [],
|
|
||||||
currentMongoIndexes: [...this.mongoDBCollectionResource.indexes],
|
|
||||||
});
|
|
||||||
traceSuccess(
|
|
||||||
Action.MongoIndexUpdated,
|
|
||||||
{
|
|
||||||
databaseAccountName: this.container.databaseAccount()?.name,
|
|
||||||
databaseName: this.collection?.databaseId,
|
|
||||||
collectionName: this.collection?.id(),
|
|
||||||
defaultExperience: this.container.defaultExperience(),
|
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
|
||||||
tabTitle: this.props.settingsTab.tabTitle(),
|
|
||||||
},
|
|
||||||
startKey
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
traceFailure(
|
|
||||||
Action.MongoIndexUpdated,
|
|
||||||
{
|
|
||||||
databaseAccountName: this.container.databaseAccount()?.name,
|
|
||||||
databaseName: this.collection?.databaseId,
|
|
||||||
collectionName: this.collection?.id(),
|
|
||||||
defaultExperience: this.container.defaultExperience(),
|
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
|
||||||
tabTitle: this.props.settingsTab.tabTitle(),
|
|
||||||
error: getErrorMessage(error),
|
|
||||||
errorStack: getErrorStack(error),
|
|
||||||
},
|
|
||||||
startKey
|
|
||||||
);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.state.isScaleSaveable) {
|
|
||||||
const updateOfferParams: DataModels.UpdateOfferParams = {
|
|
||||||
databaseId: this.collection.databaseId,
|
|
||||||
collectionId: this.collection.id(),
|
|
||||||
currentOffer: this.collection.offer(),
|
|
||||||
autopilotThroughput: this.state.isAutoPilotSelected ? this.state.autoPilotThroughput : undefined,
|
|
||||||
manualThroughput: this.state.isAutoPilotSelected ? undefined : this.state.throughput,
|
|
||||||
};
|
|
||||||
if (this.hasProvisioningTypeChanged()) {
|
|
||||||
if (this.state.isAutoPilotSelected) {
|
|
||||||
updateOfferParams.migrateToAutoPilot = true;
|
|
||||||
} else {
|
|
||||||
updateOfferParams.migrateToManual = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const updatedOffer: DataModels.Offer = await updateOffer(updateOfferParams);
|
|
||||||
this.collection.offer(updatedOffer);
|
|
||||||
this.setState({ isScaleSaveable: false, isScaleDiscardable: false });
|
|
||||||
if (this.state.isAutoPilotSelected) {
|
|
||||||
this.setState({
|
|
||||||
autoPilotThroughput: updatedOffer.autoscaleMaxThroughput,
|
|
||||||
autoPilotThroughputBaseline: updatedOffer.autoscaleMaxThroughput,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.setState({
|
|
||||||
throughput: updatedOffer.manualThroughput,
|
|
||||||
throughputBaseline: updatedOffer.manualThroughput,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.container.isRefreshingExplorer(false);
|
|
||||||
this.setBaseline();
|
|
||||||
this.setState({ wasAutopilotOriginallySet: this.state.isAutoPilotSelected });
|
|
||||||
traceSuccess(
|
|
||||||
Action.SettingsV2Updated,
|
|
||||||
{
|
|
||||||
databaseAccountName: this.container.databaseAccount()?.name,
|
|
||||||
databaseName: this.collection?.databaseId,
|
|
||||||
collectionName: this.collection?.id(),
|
|
||||||
defaultExperience: this.container.defaultExperience(),
|
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
|
||||||
tabTitle: this.props.settingsTab.tabTitle(),
|
|
||||||
},
|
|
||||||
startKey
|
|
||||||
);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.container.isRefreshingExplorer(false);
|
this.container.isRefreshingExplorer(false);
|
||||||
this.props.settingsTab.isExecutionError(true);
|
this.props.settingsTab.isExecutionError(true);
|
||||||
@ -495,8 +345,9 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
},
|
},
|
||||||
startKey
|
startKey
|
||||||
);
|
);
|
||||||
}
|
} finally {
|
||||||
this.props.settingsTab.isExecuting(false);
|
this.props.settingsTab.isExecuting(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public onRevertClick = (): void => {
|
public onRevertClick = (): void => {
|
||||||
@ -693,6 +544,17 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
};
|
};
|
||||||
|
|
||||||
public setBaseline = (): void => {
|
public setBaseline = (): void => {
|
||||||
|
const offerThroughput = this.offer?.manualThroughput;
|
||||||
|
|
||||||
|
if (!this.isCollectionSettingsTab) {
|
||||||
|
this.setState({
|
||||||
|
throughput: offerThroughput,
|
||||||
|
throughputBaseline: offerThroughput,
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const defaultTtl = this.collection.defaultTtl();
|
const defaultTtl = this.collection.defaultTtl();
|
||||||
|
|
||||||
let timeToLive: TtlType = this.state.timeToLive;
|
let timeToLive: TtlType = this.state.timeToLive;
|
||||||
@ -725,7 +587,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const offerThroughput = this.collection.offer()?.manualThroughput;
|
|
||||||
const changeFeedPolicy = this.collection.rawDataModel?.changeFeedPolicy
|
const changeFeedPolicy = this.collection.rawDataModel?.changeFeedPolicy
|
||||||
? ChangeFeedPolicyState.On
|
? ChangeFeedPolicyState.On
|
||||||
: ChangeFeedPolicyState.Off;
|
: ChangeFeedPolicyState.Off;
|
||||||
@ -811,9 +672,225 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
this.setState({ selectedTab: selectedTab });
|
this.setState({ selectedTab: selectedTab });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private saveDatabaseSettings = async (startKey: number): Promise<void> => {
|
||||||
|
if (this.state.isScaleSaveable) {
|
||||||
|
const updateOfferParams: DataModels.UpdateOfferParams = {
|
||||||
|
databaseId: this.database.id(),
|
||||||
|
currentOffer: this.database.offer(),
|
||||||
|
autopilotThroughput: this.state.isAutoPilotSelected ? this.state.autoPilotThroughput : undefined,
|
||||||
|
manualThroughput: this.state.isAutoPilotSelected ? undefined : this.state.throughput,
|
||||||
|
};
|
||||||
|
if (this.hasProvisioningTypeChanged()) {
|
||||||
|
if (this.state.isAutoPilotSelected) {
|
||||||
|
updateOfferParams.migrateToAutoPilot = true;
|
||||||
|
} else {
|
||||||
|
updateOfferParams.migrateToManual = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const updatedOffer: DataModels.Offer = await updateOffer(updateOfferParams);
|
||||||
|
this.database.offer(updatedOffer);
|
||||||
|
this.offer = updatedOffer;
|
||||||
|
this.setState({ isScaleSaveable: false, isScaleDiscardable: false });
|
||||||
|
if (this.state.isAutoPilotSelected) {
|
||||||
|
this.setState({
|
||||||
|
autoPilotThroughput: updatedOffer.autoscaleMaxThroughput,
|
||||||
|
autoPilotThroughputBaseline: updatedOffer.autoscaleMaxThroughput,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
throughput: updatedOffer.manualThroughput,
|
||||||
|
throughputBaseline: updatedOffer.manualThroughput,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.container.isRefreshingExplorer(false);
|
||||||
|
this.setBaseline();
|
||||||
|
this.setState({ wasAutopilotOriginallySet: this.state.isAutoPilotSelected });
|
||||||
|
traceSuccess(
|
||||||
|
Action.SettingsV2Updated,
|
||||||
|
{
|
||||||
|
databaseAccountName: this.container.databaseAccount()?.name,
|
||||||
|
databaseName: this.database.id(),
|
||||||
|
defaultExperience: this.container.defaultExperience(),
|
||||||
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
|
tabTitle: this.props.settingsTab.tabTitle(),
|
||||||
|
},
|
||||||
|
startKey
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
private saveCollectionSettings = async (startKey: number): Promise<void> => {
|
||||||
|
const newCollection: DataModels.Collection = { ...this.collection.rawDataModel };
|
||||||
|
|
||||||
|
if (this.state.isSubSettingsSaveable || this.state.isIndexingPolicyDirty || this.state.isConflictResolutionDirty) {
|
||||||
|
let defaultTtl: number;
|
||||||
|
switch (this.state.timeToLive) {
|
||||||
|
case TtlType.On:
|
||||||
|
defaultTtl = Number(this.state.timeToLiveSeconds);
|
||||||
|
break;
|
||||||
|
case TtlType.OnNoDefault:
|
||||||
|
defaultTtl = -1;
|
||||||
|
break;
|
||||||
|
case TtlType.Off:
|
||||||
|
default:
|
||||||
|
defaultTtl = undefined;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const wasIndexingPolicyModified = this.state.isIndexingPolicyDirty;
|
||||||
|
newCollection.defaultTtl = defaultTtl;
|
||||||
|
|
||||||
|
newCollection.indexingPolicy = this.state.indexingPolicyContent;
|
||||||
|
|
||||||
|
newCollection.changeFeedPolicy =
|
||||||
|
this.changeFeedPolicyVisible && this.state.changeFeedPolicy === ChangeFeedPolicyState.On
|
||||||
|
? {
|
||||||
|
retentionDuration: Constants.BackendDefaults.maxChangeFeedRetentionDuration,
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
newCollection.analyticalStorageTtl = this.getAnalyticalStorageTtl();
|
||||||
|
|
||||||
|
newCollection.geospatialConfig = {
|
||||||
|
type: this.state.geospatialConfigType,
|
||||||
|
};
|
||||||
|
|
||||||
|
const conflictResolutionChanges: DataModels.ConflictResolutionPolicy = this.getUpdatedConflictResolutionPolicy();
|
||||||
|
if (conflictResolutionChanges) {
|
||||||
|
newCollection.conflictResolutionPolicy = conflictResolutionChanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedCollection: DataModels.Collection = await updateCollection(
|
||||||
|
this.collection.databaseId,
|
||||||
|
this.collection.id(),
|
||||||
|
newCollection
|
||||||
|
);
|
||||||
|
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 (wasIndexingPolicyModified) {
|
||||||
|
await this.refreshIndexTransformationProgress();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
isSubSettingsSaveable: false,
|
||||||
|
isSubSettingsDiscardable: false,
|
||||||
|
isIndexingPolicyDirty: false,
|
||||||
|
isConflictResolutionDirty: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.isMongoIndexingPolicySaveable && this.mongoDBCollectionResource) {
|
||||||
|
try {
|
||||||
|
const newMongoIndexes = this.getMongoIndexesToSave();
|
||||||
|
const newMongoCollection: MongoDBCollectionResource = {
|
||||||
|
...this.mongoDBCollectionResource,
|
||||||
|
indexes: newMongoIndexes,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.mongoDBCollectionResource = await updateMongoDBCollectionThroughRP(
|
||||||
|
this.collection.databaseId,
|
||||||
|
this.collection.id(),
|
||||||
|
newMongoCollection
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.refreshIndexTransformationProgress();
|
||||||
|
this.setState({
|
||||||
|
isMongoIndexingPolicySaveable: false,
|
||||||
|
indexesToDrop: [],
|
||||||
|
indexesToAdd: [],
|
||||||
|
currentMongoIndexes: [...this.mongoDBCollectionResource.indexes],
|
||||||
|
});
|
||||||
|
traceSuccess(
|
||||||
|
Action.MongoIndexUpdated,
|
||||||
|
{
|
||||||
|
databaseAccountName: this.container.databaseAccount()?.name,
|
||||||
|
databaseName: this.collection?.databaseId,
|
||||||
|
collectionName: this.collection?.id(),
|
||||||
|
defaultExperience: this.container.defaultExperience(),
|
||||||
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
|
tabTitle: this.props.settingsTab.tabTitle(),
|
||||||
|
},
|
||||||
|
startKey
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
traceFailure(
|
||||||
|
Action.MongoIndexUpdated,
|
||||||
|
{
|
||||||
|
databaseAccountName: this.container.databaseAccount()?.name,
|
||||||
|
databaseName: this.collection?.databaseId,
|
||||||
|
collectionName: this.collection?.id(),
|
||||||
|
defaultExperience: this.container.defaultExperience(),
|
||||||
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
|
tabTitle: this.props.settingsTab.tabTitle(),
|
||||||
|
error: getErrorMessage(error),
|
||||||
|
errorStack: getErrorStack(error),
|
||||||
|
},
|
||||||
|
startKey
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.isScaleSaveable) {
|
||||||
|
const updateOfferParams: DataModels.UpdateOfferParams = {
|
||||||
|
databaseId: this.collection.databaseId,
|
||||||
|
collectionId: this.collection.id(),
|
||||||
|
currentOffer: this.collection.offer(),
|
||||||
|
autopilotThroughput: this.state.isAutoPilotSelected ? this.state.autoPilotThroughput : undefined,
|
||||||
|
manualThroughput: this.state.isAutoPilotSelected ? undefined : this.state.throughput,
|
||||||
|
};
|
||||||
|
if (this.hasProvisioningTypeChanged()) {
|
||||||
|
if (this.state.isAutoPilotSelected) {
|
||||||
|
updateOfferParams.migrateToAutoPilot = true;
|
||||||
|
} else {
|
||||||
|
updateOfferParams.migrateToManual = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const updatedOffer: DataModels.Offer = await updateOffer(updateOfferParams);
|
||||||
|
this.collection.offer(updatedOffer);
|
||||||
|
this.offer = updatedOffer;
|
||||||
|
this.setState({ isScaleSaveable: false, isScaleDiscardable: false });
|
||||||
|
if (this.state.isAutoPilotSelected) {
|
||||||
|
this.setState({
|
||||||
|
autoPilotThroughput: updatedOffer.autoscaleMaxThroughput,
|
||||||
|
autoPilotThroughputBaseline: updatedOffer.autoscaleMaxThroughput,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
throughput: updatedOffer.manualThroughput,
|
||||||
|
throughputBaseline: updatedOffer.manualThroughput,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.container.isRefreshingExplorer(false);
|
||||||
|
this.setBaseline();
|
||||||
|
this.setState({ wasAutopilotOriginallySet: this.state.isAutoPilotSelected });
|
||||||
|
traceSuccess(
|
||||||
|
Action.SettingsV2Updated,
|
||||||
|
{
|
||||||
|
databaseAccountName: this.container.databaseAccount()?.name,
|
||||||
|
databaseName: this.collection?.databaseId,
|
||||||
|
collectionName: this.collection?.id(),
|
||||||
|
defaultExperience: this.container.defaultExperience(),
|
||||||
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
|
tabTitle: this.props.settingsTab.tabTitle(),
|
||||||
|
},
|
||||||
|
startKey
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
const scaleComponentProps: ScaleComponentProps = {
|
const scaleComponentProps: ScaleComponentProps = {
|
||||||
collection: this.collection,
|
collection: this.collection,
|
||||||
|
database: this.database,
|
||||||
container: this.container,
|
container: this.container,
|
||||||
isFixedContainer: this.isFixedContainer,
|
isFixedContainer: this.isFixedContainer,
|
||||||
onThroughputChange: this.onThroughputChange,
|
onThroughputChange: this.onThroughputChange,
|
||||||
@ -830,6 +907,16 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
initialNotification: this.props.settingsTab.pendingNotification(),
|
initialNotification: this.props.settingsTab.pendingNotification(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!this.isCollectionSettingsTab) {
|
||||||
|
return (
|
||||||
|
<div className="settingsV2MainContainer">
|
||||||
|
<div className="settingsV2TabsContainer">
|
||||||
|
<ScaleComponent {...scaleComponentProps} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const subSettingsComponentProps: SubSettingsComponentProps = {
|
const subSettingsComponentProps: SubSettingsComponentProps = {
|
||||||
collection: this.collection,
|
collection: this.collection,
|
||||||
container: this.container,
|
container: this.container,
|
||||||
@ -899,7 +986,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
};
|
};
|
||||||
|
|
||||||
const tabs: SettingsV2TabInfo[] = [];
|
const tabs: SettingsV2TabInfo[] = [];
|
||||||
if (!hasDatabaseSharedThroughput(this.collection) && this.collection.offer()) {
|
if (!hasDatabaseSharedThroughput(this.collection) && this.offer) {
|
||||||
tabs.push({
|
tabs.push({
|
||||||
tab: SettingsV2TabTypes.ScaleTab,
|
tab: SettingsV2TabTypes.ScaleTab,
|
||||||
content: <ScaleComponent {...scaleComponentProps} />,
|
content: <ScaleComponent {...scaleComponentProps} />,
|
||||||
|
@ -375,7 +375,7 @@ export const getThroughputApplyShortDelayMessage = (
|
|||||||
<Text styles={infoAndToolTipTextStyle} id="throughputApplyShortDelayMessage">
|
<Text styles={infoAndToolTipTextStyle} id="throughputApplyShortDelayMessage">
|
||||||
A request to increase the throughput is currently in progress. This operation will take some time to complete.
|
A request to increase the throughput is currently in progress. This operation will take some time to complete.
|
||||||
<br />
|
<br />
|
||||||
Database: {databaseName}, Container: {collectionName}{" "}
|
{collectionName ? `Database: ${databaseName}, Container: ${collectionName} ` : `Database: ${databaseName} `}
|
||||||
{getCurrentThroughput(isAutoscale, throughput, throughputUnit)}
|
{getCurrentThroughput(isAutoscale, throughput, throughputUnit)}
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
@ -392,7 +392,7 @@ export const getThroughputApplyLongDelayMessage = (
|
|||||||
A request to increase the throughput is currently in progress. This operation will take 1-3 business days to
|
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.
|
complete. View the latest status in Notifications.
|
||||||
<br />
|
<br />
|
||||||
Database: {databaseName}, Container: {collectionName}{" "}
|
{collectionName ? `Database: ${databaseName}, Container: ${collectionName} ` : `Database: ${databaseName} `}
|
||||||
{getCurrentThroughput(isAutoscale, throughput, throughputUnit, requestedThroughput)}
|
{getCurrentThroughput(isAutoscale, throughput, throughputUnit, requestedThroughput)}
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
|
@ -18,6 +18,7 @@ describe("ScaleComponent", () => {
|
|||||||
|
|
||||||
const baseProps: ScaleComponentProps = {
|
const baseProps: ScaleComponentProps = {
|
||||||
collection: collection,
|
collection: collection,
|
||||||
|
database: undefined,
|
||||||
container: container,
|
container: container,
|
||||||
isFixedContainer: false,
|
isFixedContainer: false,
|
||||||
onThroughputChange: () => {
|
onThroughputChange: () => {
|
||||||
|
@ -21,6 +21,7 @@ import { configContext, Platform } from "../../../../ConfigContext";
|
|||||||
|
|
||||||
export interface ScaleComponentProps {
|
export interface ScaleComponentProps {
|
||||||
collection: ViewModels.Collection;
|
collection: ViewModels.Collection;
|
||||||
|
database: ViewModels.Database;
|
||||||
container: Explorer;
|
container: Explorer;
|
||||||
isFixedContainer: boolean;
|
isFixedContainer: boolean;
|
||||||
onThroughputChange: (newThroughput: number) => void;
|
onThroughputChange: (newThroughput: number) => void;
|
||||||
@ -39,9 +40,16 @@ export interface ScaleComponentProps {
|
|||||||
|
|
||||||
export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
||||||
private isEmulator: boolean;
|
private isEmulator: boolean;
|
||||||
|
private offer: DataModels.Offer;
|
||||||
|
private databaseId: string;
|
||||||
|
private collectionId: string;
|
||||||
|
|
||||||
constructor(props: ScaleComponentProps) {
|
constructor(props: ScaleComponentProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.isEmulator = configContext.platform === Platform.Emulator;
|
this.isEmulator = configContext.platform === Platform.Emulator;
|
||||||
|
this.offer = this.props.database?.offer() || this.props.collection?.offer();
|
||||||
|
this.databaseId = this.props.database?.id() || this.props.collection.databaseId;
|
||||||
|
this.collectionId = this.props.collection?.id();
|
||||||
}
|
}
|
||||||
|
|
||||||
public isAutoScaleEnabled = (): boolean => {
|
public isAutoScaleEnabled = (): boolean => {
|
||||||
@ -87,9 +95,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
|||||||
return SharedConstants.CollectionCreation.DefaultCollectionRUs400;
|
return SharedConstants.CollectionCreation.DefaultCollectionRUs400;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return this.offer?.minimumThroughput || SharedConstants.CollectionCreation.DefaultCollectionRUs400;
|
||||||
this.props.collection.offer()?.minimumThroughput || SharedConstants.CollectionCreation.DefaultCollectionRUs400
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public getThroughputTitle = (): string => {
|
public getThroughputTitle = (): string => {
|
||||||
@ -115,15 +121,14 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
|||||||
return this.getLongDelayMessage();
|
return this.getLongDelayMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
const offer = this.props.collection?.offer();
|
if (this.offer?.offerReplacePending) {
|
||||||
if (offer?.offerReplacePending) {
|
const throughput = this.offer.manualThroughput || this.offer.autoscaleMaxThroughput;
|
||||||
const throughput = offer.manualThroughput || offer.autoscaleMaxThroughput;
|
|
||||||
return getThroughputApplyShortDelayMessage(
|
return getThroughputApplyShortDelayMessage(
|
||||||
this.props.isAutoPilotSelected,
|
this.props.isAutoPilotSelected,
|
||||||
throughput,
|
throughput,
|
||||||
throughputUnit,
|
throughputUnit,
|
||||||
this.props.collection.databaseId,
|
this.databaseId,
|
||||||
this.props.collection.id()
|
this.collectionId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,7 +140,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
|||||||
this.canThroughputExceedMaximumValue() &&
|
this.canThroughputExceedMaximumValue() &&
|
||||||
this.props.throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs1Million;
|
this.props.throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs1Million;
|
||||||
|
|
||||||
if (throughputExceedsBackendLimits && !!this.props.collection.partitionKey && !this.props.isFixedContainer) {
|
if (throughputExceedsBackendLimits && !this.props.isFixedContainer) {
|
||||||
return updateThroughputBeyondLimitWarningMessage;
|
return updateThroughputBeyondLimitWarningMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,8 +159,8 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
|||||||
this.props.wasAutopilotOriginallySet,
|
this.props.wasAutopilotOriginallySet,
|
||||||
throughput,
|
throughput,
|
||||||
throughputUnit,
|
throughputUnit,
|
||||||
this.props.collection.databaseId,
|
this.databaseId,
|
||||||
this.props.collection.id(),
|
this.collectionId,
|
||||||
targetThroughput
|
targetThroughput
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -165,15 +170,15 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
|||||||
private getThroughputInputComponent = (): JSX.Element => (
|
private getThroughputInputComponent = (): JSX.Element => (
|
||||||
<ThroughputInputAutoPilotV3Component
|
<ThroughputInputAutoPilotV3Component
|
||||||
databaseAccount={this.props.container.databaseAccount()}
|
databaseAccount={this.props.container.databaseAccount()}
|
||||||
databaseName={this.props.collection.databaseId}
|
databaseName={this.databaseId}
|
||||||
collectionName={this.props.collection.id()}
|
collectionName={this.collectionId}
|
||||||
serverId={this.props.container.serverId()}
|
serverId={this.props.container.serverId()}
|
||||||
throughput={this.props.throughput}
|
throughput={this.props.throughput}
|
||||||
throughputBaseline={this.props.throughputBaseline}
|
throughputBaseline={this.props.throughputBaseline}
|
||||||
onThroughputChange={this.props.onThroughputChange}
|
onThroughputChange={this.props.onThroughputChange}
|
||||||
minimum={this.getMinRUs()}
|
minimum={this.getMinRUs()}
|
||||||
maximum={this.getMaxRUs()}
|
maximum={this.getMaxRUs()}
|
||||||
isEnabled={!hasDatabaseSharedThroughput(this.props.collection)}
|
isEnabled={!!this.props.database || !hasDatabaseSharedThroughput(this.props.collection)}
|
||||||
canExceedMaximumValue={this.canThroughputExceedMaximumValue()}
|
canExceedMaximumValue={this.canThroughputExceedMaximumValue()}
|
||||||
label={this.getThroughputTitle()}
|
label={this.getThroughputTitle()}
|
||||||
isEmulator={this.isEmulator}
|
isEmulator={this.isEmulator}
|
||||||
@ -189,7 +194,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
|||||||
onScaleSaveableChange={this.props.onScaleSaveableChange}
|
onScaleSaveableChange={this.props.onScaleSaveableChange}
|
||||||
onScaleDiscardableChange={this.props.onScaleDiscardableChange}
|
onScaleDiscardableChange={this.props.onScaleDiscardableChange}
|
||||||
getThroughputWarningMessage={this.getThroughputWarningMessage}
|
getThroughputWarningMessage={this.getThroughputWarningMessage}
|
||||||
usageSizeInKB={this.props.collection.usageSizeInKB()}
|
usageSizeInKB={this.props.collection?.usageSizeInKB()}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -230,7 +235,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
|
|||||||
{!this.isAutoScaleEnabled() && (
|
{!this.isAutoScaleEnabled() && (
|
||||||
<Stack {...subComponentStackProps}>
|
<Stack {...subComponentStackProps}>
|
||||||
{this.getThroughputInputComponent()}
|
{this.getThroughputInputComponent()}
|
||||||
{this.getStorageCapacityTitle()}
|
{!this.props.database && this.getStorageCapacityTitle()}
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -40,6 +40,7 @@ import { userContext } from "../../../../../UserContext";
|
|||||||
import { SubscriptionType } from "../../../../../Contracts/SubscriptionType";
|
import { SubscriptionType } from "../../../../../Contracts/SubscriptionType";
|
||||||
import { usageInGB, calculateEstimateNumber } from "../../../../../Utils/PricingUtils";
|
import { usageInGB, calculateEstimateNumber } from "../../../../../Utils/PricingUtils";
|
||||||
import { Features } from "../../../../../Common/Constants";
|
import { Features } from "../../../../../Common/Constants";
|
||||||
|
import { minAutoPilotThroughput } from "../../../../../Utils/AutoPilotUtils";
|
||||||
|
|
||||||
import * as TelemetryProcessor from "../../../../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { Action, ActionModifiers } from "../../../../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../../../../Shared/Telemetry/TelemetryConstants";
|
||||||
@ -541,6 +542,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
step={AutoPilotUtils.autoPilotIncrementStep}
|
step={AutoPilotUtils.autoPilotIncrementStep}
|
||||||
value={this.overrideWithProvisionedThroughputSettings() ? "" : this.props.maxAutoPilotThroughput?.toString()}
|
value={this.overrideWithProvisionedThroughputSettings() ? "" : this.props.maxAutoPilotThroughput?.toString()}
|
||||||
onChange={this.onAutoPilotThroughputChange}
|
onChange={this.onAutoPilotThroughputChange}
|
||||||
|
min={minAutoPilotThroughput}
|
||||||
/>
|
/>
|
||||||
{!this.overrideWithProvisionedThroughputSettings() && this.getAutoPilotUsageCost()}
|
{!this.overrideWithProvisionedThroughputSettings() && this.getAutoPilotUsageCost()}
|
||||||
{this.minRUperGBSurvey()}
|
{this.minRUperGBSurvey()}
|
||||||
@ -579,6 +581,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
: this.props.throughput?.toString()
|
: this.props.throughput?.toString()
|
||||||
}
|
}
|
||||||
onChange={this.onThroughputChange}
|
onChange={this.onThroughputChange}
|
||||||
|
min={this.props.minimum}
|
||||||
/>
|
/>
|
||||||
{this.state.exceedFreeTierThroughput && (
|
{this.state.exceedFreeTierThroughput && (
|
||||||
<MessageBar
|
<MessageBar
|
||||||
|
@ -142,6 +142,7 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
|
|||||||
id="autopilotInput"
|
id="autopilotInput"
|
||||||
key="auto pilot throughput input"
|
key="auto pilot throughput input"
|
||||||
label="Max RU/s"
|
label="Max RU/s"
|
||||||
|
min={4000}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
required={true}
|
required={true}
|
||||||
step={1000}
|
step={1000}
|
||||||
@ -260,6 +261,7 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
|
|||||||
disabled={false}
|
disabled={false}
|
||||||
id="throughputInput"
|
id="throughputInput"
|
||||||
key="provisioned throughput input"
|
key="provisioned throughput input"
|
||||||
|
min={10000}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
required={true}
|
required={true}
|
||||||
step={100}
|
step={100}
|
||||||
@ -533,6 +535,7 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
|
|||||||
disabled={false}
|
disabled={false}
|
||||||
id="throughputInput"
|
id="throughputInput"
|
||||||
key="provisioned throughput input"
|
key="provisioned throughput input"
|
||||||
|
min={10000}
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
required={true}
|
required={true}
|
||||||
step={100}
|
step={100}
|
||||||
|
@ -23,11 +23,7 @@ exports[`ScaleComponent renders with correct initial notification 1`] = `
|
|||||||
>
|
>
|
||||||
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.
|
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.
|
||||||
<br />
|
<br />
|
||||||
Database:
|
Database: test, Container: test
|
||||||
test
|
|
||||||
, Container:
|
|
||||||
test
|
|
||||||
|
|
||||||
, Current autoscale throughput: 100 - 1000 RU/s, Target autoscale throughput: 600 - 6000 RU/s
|
, Current autoscale throughput: 100 - 1000 RU/s, Target autoscale throughput: 600 - 6000 RU/s
|
||||||
</Text>
|
</Text>
|
||||||
</StyledMessageBarBase>
|
</StyledMessageBarBase>
|
||||||
|
@ -46,6 +46,7 @@ describe("SettingsUtils", () => {
|
|||||||
readSettings: undefined,
|
readSettings: undefined,
|
||||||
onSettingsClick: undefined,
|
onSettingsClick: undefined,
|
||||||
loadOffer: undefined,
|
loadOffer: undefined,
|
||||||
|
getPendingThroughputSplitNotification: undefined,
|
||||||
} as ViewModels.Database;
|
} as ViewModels.Database;
|
||||||
};
|
};
|
||||||
newCollection.offer(undefined);
|
newCollection.offer(undefined);
|
||||||
|
@ -256,11 +256,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
>
|
>
|
||||||
A request to increase the throughput is currently in progress. This operation will take some time to complete.
|
A request to increase the throughput is currently in progress. This operation will take some time to complete.
|
||||||
<br />
|
<br />
|
||||||
Database:
|
Database: sampleDb, Container: sampleCollection
|
||||||
sampleDb
|
|
||||||
, Container:
|
|
||||||
sampleCollection
|
|
||||||
|
|
||||||
, Current manual throughput: 1000 RU/s
|
, Current manual throughput: 1000 RU/s
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
@ -275,11 +271,7 @@ exports[`SettingsUtils functions render 1`] = `
|
|||||||
>
|
>
|
||||||
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.
|
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.
|
||||||
<br />
|
<br />
|
||||||
Database:
|
Database: sampleDb, Container: sampleCollection
|
||||||
sampleDb
|
|
||||||
, Container:
|
|
||||||
sampleCollection
|
|
||||||
|
|
||||||
, Current manual throughput: 1000 RU/s, Target manual throughput: 2000
|
, Current manual throughput: 1000 RU/s, Target manual throughput: 2000
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
|
@ -387,8 +387,6 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
|
|||||||
tabTitle: this.tabTitle(),
|
tabTitle: this.tabTitle(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const headerOptions: RequestOptions = { initialHeaders: {} };
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const updateOfferParams: DataModels.UpdateOfferParams = {
|
const updateOfferParams: DataModels.UpdateOfferParams = {
|
||||||
databaseId: this.database.id(),
|
databaseId: this.database.id(),
|
||||||
|
@ -61,7 +61,7 @@ export default class GalleryTab extends TabsBase {
|
|||||||
this.galleryAndNotebookViewerComponentAdapter.triggerRender();
|
this.galleryAndNotebookViewerComponentAdapter.triggerRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getContainer(): Explorer {
|
public getContainer(): Explorer {
|
||||||
return this.container;
|
return this.container;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,7 +117,7 @@ export default class NotebookTabV2 extends TabsBase {
|
|||||||
return await this.configureServiceEndpoints(this.notebookComponentAdapter.getCurrentKernelName());
|
return await this.configureServiceEndpoints(this.notebookComponentAdapter.getCurrentKernelName());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getContainer(): Explorer {
|
public getContainer(): Explorer {
|
||||||
return this.container;
|
return this.container;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ export default class NotebookViewerTab extends TabsBase {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getContainer(): Explorer {
|
public getContainer(): Explorer {
|
||||||
return this.container;
|
return this.container;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@ import * as DataModels from "../../Contracts/DataModels";
|
|||||||
import TabsBase from "./TabsBase";
|
import TabsBase from "./TabsBase";
|
||||||
import { SettingsComponentAdapter } from "../Controls/Settings/SettingsComponentAdapter";
|
import { SettingsComponentAdapter } from "../Controls/Settings/SettingsComponentAdapter";
|
||||||
import { SettingsComponentProps } from "../Controls/Settings/SettingsComponent";
|
import { SettingsComponentProps } from "../Controls/Settings/SettingsComponent";
|
||||||
import Explorer from "../Explorer";
|
|
||||||
import { traceFailure } from "../../Shared/Telemetry/TelemetryProcessor";
|
import { traceFailure } from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import ko from "knockout";
|
import ko from "knockout";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
@ -11,23 +10,27 @@ import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
|||||||
import { logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||||
|
|
||||||
export default class SettingsTabV2 extends TabsBase {
|
export class SettingsTabV2 extends TabsBase {
|
||||||
public settingsComponentAdapter: SettingsComponentAdapter;
|
public settingsComponentAdapter: SettingsComponentAdapter;
|
||||||
private notificationRead: ko.Observable<boolean>;
|
|
||||||
private notification: DataModels.Notification;
|
|
||||||
private offerRead: ko.Observable<boolean>;
|
|
||||||
private currentCollection: ViewModels.Collection;
|
|
||||||
private options: ViewModels.SettingsTabV2Options;
|
|
||||||
|
|
||||||
constructor(options: ViewModels.SettingsTabV2Options) {
|
constructor(options: ViewModels.TabOptions) {
|
||||||
super(options);
|
super(options);
|
||||||
this.options = options;
|
|
||||||
this.tabId = "SettingsV2-" + this.tabId;
|
|
||||||
const props: SettingsComponentProps = {
|
const props: SettingsComponentProps = {
|
||||||
settingsTab: this,
|
settingsTab: this,
|
||||||
};
|
};
|
||||||
this.settingsComponentAdapter = new SettingsComponentAdapter(props);
|
this.settingsComponentAdapter = new SettingsComponentAdapter(props);
|
||||||
this.currentCollection = this.collection as ViewModels.Collection;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CollectionSettingsTabV2 extends SettingsTabV2 {
|
||||||
|
private notificationRead: ko.Observable<boolean>;
|
||||||
|
private notification: DataModels.Notification;
|
||||||
|
private offerRead: ko.Observable<boolean>;
|
||||||
|
|
||||||
|
constructor(options: ViewModels.TabOptions) {
|
||||||
|
super(options);
|
||||||
|
|
||||||
|
this.tabId = "SettingsV2-" + this.tabId;
|
||||||
this.notificationRead = ko.observable(false);
|
this.notificationRead = ko.observable(false);
|
||||||
this.offerRead = ko.observable(false);
|
this.offerRead = ko.observable(false);
|
||||||
this.settingsComponentAdapter.parameters = ko.computed<boolean>(() => {
|
this.settingsComponentAdapter.parameters = ko.computed<boolean>(() => {
|
||||||
@ -45,49 +48,95 @@ export default class SettingsTabV2 extends TabsBase {
|
|||||||
public async onActivate(): Promise<void> {
|
public async onActivate(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
this.isExecuting(true);
|
this.isExecuting(true);
|
||||||
await this.currentCollection.loadOffer();
|
|
||||||
// passed in options and set by parent as "Settings" by default
|
|
||||||
this.tabTitle(this.currentCollection.offer() ? "Settings" : "Scale & Settings");
|
|
||||||
|
|
||||||
this.options.getPendingNotification.then(
|
const collection: ViewModels.Collection = this.collection as ViewModels.Collection;
|
||||||
(data: DataModels.Notification) => {
|
await collection.loadOffer();
|
||||||
|
// passed in options and set by parent as "Settings" by default
|
||||||
|
this.tabTitle(collection.offer() ? "Settings" : "Scale & Settings");
|
||||||
|
|
||||||
|
const data: DataModels.Notification = await collection.getPendingThroughputSplitNotification();
|
||||||
this.notification = data;
|
this.notification = data;
|
||||||
this.notificationRead(true);
|
this.notificationRead(true);
|
||||||
},
|
} catch (error) {
|
||||||
(error) => {
|
|
||||||
const errorMessage = getErrorMessage(error);
|
const errorMessage = getErrorMessage(error);
|
||||||
this.notification = undefined;
|
this.notification = undefined;
|
||||||
this.notificationRead(true);
|
this.notificationRead(true);
|
||||||
traceFailure(
|
traceFailure(
|
||||||
Action.Tab,
|
Action.Tab,
|
||||||
{
|
{
|
||||||
databaseAccountName: this.options.collection.container.databaseAccount().name,
|
databaseAccountName: this.collection.container.databaseAccount().name,
|
||||||
databaseName: this.options.collection.databaseId,
|
databaseName: this.collection.databaseId,
|
||||||
collectionName: this.options.collection.id(),
|
collectionName: this.collection.id(),
|
||||||
defaultExperience: this.options.collection.container.defaultExperience(),
|
defaultExperience: this.collection.container.defaultExperience(),
|
||||||
dataExplorerArea: Constants.Areas.Tab,
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
tabTitle: this.tabTitle,
|
tabTitle: this.tabTitle,
|
||||||
error: errorMessage,
|
error: errorMessage,
|
||||||
errorStack: getErrorStack(error),
|
errorStack: getErrorStack(error),
|
||||||
},
|
},
|
||||||
this.options.onLoadStartKey
|
this.onLoadStartKey
|
||||||
);
|
|
||||||
logConsoleError(
|
|
||||||
`Error while fetching container settings for container ${this.options.collection.id()}: ${errorMessage}`
|
|
||||||
);
|
);
|
||||||
|
logConsoleError(`Error while fetching container settings for container ${this.collection.id()}: ${errorMessage}`);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
|
||||||
);
|
|
||||||
} finally {
|
} finally {
|
||||||
this.offerRead(true);
|
this.offerRead(true);
|
||||||
this.isExecuting(false);
|
this.isExecuting(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
super.onActivate();
|
super.onActivate();
|
||||||
this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.SettingsV2);
|
this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.CollectionSettingsV2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getSettingsTabContainer(): Explorer {
|
export class DatabaseSettingsTabV2 extends SettingsTabV2 {
|
||||||
return this.getContainer();
|
private notificationRead: ko.Observable<boolean>;
|
||||||
|
private notification: DataModels.Notification;
|
||||||
|
|
||||||
|
constructor(options: ViewModels.TabOptions) {
|
||||||
|
super(options);
|
||||||
|
this.tabId = "DatabaseSettingsV2-" + this.tabId;
|
||||||
|
this.notificationRead = ko.observable(false);
|
||||||
|
this.settingsComponentAdapter.parameters = ko.computed<boolean>(() => {
|
||||||
|
if (this.notificationRead()) {
|
||||||
|
this.pendingNotification(this.notification);
|
||||||
|
this.notification = undefined;
|
||||||
|
this.notificationRead(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async onActivate(): Promise<void> {
|
||||||
|
try {
|
||||||
|
this.isExecuting(true);
|
||||||
|
|
||||||
|
const data: DataModels.Notification = await this.database.getPendingThroughputSplitNotification();
|
||||||
|
this.notification = data;
|
||||||
|
this.notificationRead(true);
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = getErrorMessage(error);
|
||||||
|
this.notification = undefined;
|
||||||
|
this.notificationRead(true);
|
||||||
|
traceFailure(
|
||||||
|
Action.Tab,
|
||||||
|
{
|
||||||
|
databaseAccountName: this.database?.container.databaseAccount().name,
|
||||||
|
databaseName: this.database.id(),
|
||||||
|
defaultExperience: this.database?.container.defaultExperience(),
|
||||||
|
dataExplorerArea: Constants.Areas.Tab,
|
||||||
|
tabTitle: this.tabTitle,
|
||||||
|
error: errorMessage,
|
||||||
|
errorStack: getErrorStack(error),
|
||||||
|
},
|
||||||
|
this.onLoadStartKey
|
||||||
|
);
|
||||||
|
logConsoleError(`Error while fetching database settings for database ${this.database.id()}: ${errorMessage}`);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
this.isExecuting(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onActivate();
|
||||||
|
this.database.selectedSubnodeKind(ViewModels.CollectionTabKind.DatabaseSettingsV2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ export default class SparkMasterTab extends TabsBase {
|
|||||||
this.sparkMasterSrc = ko.observable<string>(sparkMasterEndpoint && sparkMasterEndpoint.endpoint);
|
this.sparkMasterSrc = ko.observable<string>(sparkMasterEndpoint && sparkMasterEndpoint.endpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getContainer() {
|
public getContainer() {
|
||||||
return this._container;
|
return this._container;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -177,7 +177,7 @@ export default class TabsBase extends WaitsForTemplateViewModel {
|
|||||||
return Q();
|
return Q();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getContainer(): Explorer {
|
public getContainer(): Explorer {
|
||||||
return (this.collection && this.collection.container) || (this.database && this.database.container);
|
return (this.collection && this.collection.container) || (this.database && this.database.container);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,7 +143,11 @@
|
|||||||
<!-- /ko -->
|
<!-- /ko -->
|
||||||
|
|
||||||
<!-- ko if: $data.tabKind === 20 -->
|
<!-- ko if: $data.tabKind === 20 -->
|
||||||
<settings-tab-v2 params="{data: $data}"></settings-tab-v2>
|
<collection-settings-tab-v2 params="{data: $data}"></collection-settings-tab-v2>
|
||||||
|
<!-- /ko -->
|
||||||
|
|
||||||
|
<!-- ko if: $data.tabKind === 21 -->
|
||||||
|
<database-settings-tab-v2 params="{data: $data}"></database-settings-tab-v2>
|
||||||
<!-- /ko -->
|
<!-- /ko -->
|
||||||
</div>
|
</div>
|
||||||
<!-- /ko -->
|
<!-- /ko -->
|
||||||
|
@ -56,7 +56,7 @@ export default class TerminalTab extends TabsBase {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getContainer(): Explorer {
|
public getContainer(): Explorer {
|
||||||
return this.container;
|
return this.container;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ import MongoQueryTab from "../Tabs/MongoQueryTab";
|
|||||||
import MongoShellTab from "../Tabs/MongoShellTab";
|
import MongoShellTab from "../Tabs/MongoShellTab";
|
||||||
import QueryTab from "../Tabs/QueryTab";
|
import QueryTab from "../Tabs/QueryTab";
|
||||||
import QueryTablesTab from "../Tabs/QueryTablesTab";
|
import QueryTablesTab from "../Tabs/QueryTablesTab";
|
||||||
import SettingsTabV2 from "../Tabs/SettingsTabV2";
|
import { CollectionSettingsTabV2 } from "../Tabs/SettingsTabV2";
|
||||||
import ConflictId from "./ConflictId";
|
import ConflictId from "./ConflictId";
|
||||||
import DocumentId from "./DocumentId";
|
import DocumentId from "./DocumentId";
|
||||||
import StoredProcedure from "./StoredProcedure";
|
import StoredProcedure from "./StoredProcedure";
|
||||||
@ -544,10 +544,12 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const tabTitle = !this.offer() ? "Settings" : "Scale & Settings";
|
const tabTitle = !this.offer() ? "Settings" : "Scale & Settings";
|
||||||
const pendingNotificationsPromise: Promise<DataModels.Notification> = this._getPendingThroughputSplitNotification();
|
const matchingTabs = this.container.tabsManager.getTabs(
|
||||||
const matchingTabs = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.SettingsV2, (tab) => {
|
ViewModels.CollectionTabKind.CollectionSettingsV2,
|
||||||
|
(tab) => {
|
||||||
return tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id();
|
return tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id();
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const traceStartData = {
|
const traceStartData = {
|
||||||
databaseAccountName: this.container.databaseAccount().name,
|
databaseAccountName: this.container.databaseAccount().name,
|
||||||
@ -569,26 +571,20 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
||||||
};
|
};
|
||||||
|
|
||||||
let settingsTabV2 = matchingTabs && (matchingTabs[0] as SettingsTabV2);
|
let settingsTabV2 = matchingTabs && (matchingTabs[0] as CollectionSettingsTabV2);
|
||||||
this.launchSettingsTabV2(settingsTabV2, traceStartData, settingsTabOptions, pendingNotificationsPromise);
|
this.launchSettingsTabV2(settingsTabV2, traceStartData, settingsTabOptions);
|
||||||
};
|
};
|
||||||
|
|
||||||
private launchSettingsTabV2 = (
|
private launchSettingsTabV2 = (
|
||||||
settingsTabV2: SettingsTabV2,
|
settingsTabV2: CollectionSettingsTabV2,
|
||||||
traceStartData: any,
|
traceStartData: any,
|
||||||
settingsTabOptions: ViewModels.TabOptions,
|
settingsTabOptions: ViewModels.TabOptions
|
||||||
getPendingNotification: Promise<DataModels.Notification>
|
|
||||||
): void => {
|
): void => {
|
||||||
const settingsTabV2Options: ViewModels.SettingsTabV2Options = {
|
|
||||||
...settingsTabOptions,
|
|
||||||
getPendingNotification: getPendingNotification,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!settingsTabV2) {
|
if (!settingsTabV2) {
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, traceStartData);
|
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, traceStartData);
|
||||||
settingsTabV2Options.onLoadStartKey = startKey;
|
settingsTabOptions.onLoadStartKey = startKey;
|
||||||
settingsTabV2Options.tabKind = ViewModels.CollectionTabKind.SettingsV2;
|
settingsTabOptions.tabKind = ViewModels.CollectionTabKind.CollectionSettingsV2;
|
||||||
settingsTabV2 = new SettingsTabV2(settingsTabV2Options);
|
settingsTabV2 = new CollectionSettingsTabV2(settingsTabOptions);
|
||||||
this.container.tabsManager.activateNewTab(settingsTabV2);
|
this.container.tabsManager.activateNewTab(settingsTabV2);
|
||||||
} else {
|
} else {
|
||||||
this.container.tabsManager.activateTab(settingsTabV2);
|
this.container.tabsManager.activateTab(settingsTabV2);
|
||||||
@ -1040,6 +1036,41 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public async getPendingThroughputSplitNotification(): Promise<DataModels.Notification> {
|
||||||
|
if (!this.container) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const notifications: DataModels.Notification[] = await fetchPortalNotifications();
|
||||||
|
if (!notifications || notifications.length === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _.find(notifications, (notification: DataModels.Notification) => {
|
||||||
|
const throughputUpdateRegExp: RegExp = new RegExp("Throughput update (.*) in progress");
|
||||||
|
return (
|
||||||
|
notification.kind === "message" &&
|
||||||
|
notification.collectionName === this.id() &&
|
||||||
|
notification.description &&
|
||||||
|
throughputUpdateRegExp.test(notification.description)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
Logger.logError(
|
||||||
|
JSON.stringify({
|
||||||
|
error: getErrorMessage(error),
|
||||||
|
accountName: this.container && this.container.databaseAccount(),
|
||||||
|
databaseName: this.databaseId,
|
||||||
|
collectionName: this.id(),
|
||||||
|
}),
|
||||||
|
"Settings tree node"
|
||||||
|
);
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async _uploadFilesCors(files: FileList): Promise<UploadDetails> {
|
private async _uploadFilesCors(files: FileList): Promise<UploadDetails> {
|
||||||
const data = await Promise.all(Array.from(files).map((file) => this._uploadFile(file)));
|
const data = await Promise.all(Array.from(files).map((file) => this._uploadFile(file)));
|
||||||
|
|
||||||
@ -1100,37 +1131,6 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _getPendingThroughputSplitNotification(): Promise<DataModels.Notification> {
|
|
||||||
if (!this.container) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const throughputUpdateRegExp = new RegExp("Throughput update (.*) in progress");
|
|
||||||
try {
|
|
||||||
const notifications = await fetchPortalNotifications();
|
|
||||||
if (!notifications) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return notifications.find(
|
|
||||||
({ kind, collectionName, description = "" }) =>
|
|
||||||
kind === "message" && collectionName === this.id() && throughputUpdateRegExp.test(description)
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
Logger.logError(
|
|
||||||
JSON.stringify({
|
|
||||||
error: getErrorMessage(error),
|
|
||||||
accountName: this.container && this.container.databaseAccount(),
|
|
||||||
databaseName: this.databaseId,
|
|
||||||
collectionName: this.id(),
|
|
||||||
}),
|
|
||||||
"Settings tree node"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _logUploadDetailsInConsole(uploadDetails: UploadDetails): void {
|
private _logUploadDetailsInConsole(uploadDetails: UploadDetails): void {
|
||||||
const uploadDetailsRecords: UploadDetailsRecord[] = uploadDetails.data;
|
const uploadDetailsRecords: UploadDetailsRecord[] = uploadDetails.data;
|
||||||
const numFiles: number = uploadDetailsRecords.length;
|
const numFiles: number = uploadDetailsRecords.length;
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import * as _ from "underscore";
|
import * as _ from "underscore";
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import Q from "q";
|
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import DatabaseSettingsTab from "../Tabs/DatabaseSettingsTab";
|
import DatabaseSettingsTab from "../Tabs/DatabaseSettingsTab";
|
||||||
|
import { DatabaseSettingsTabV2 } from "../Tabs/SettingsTabV2";
|
||||||
import Collection from "./Collection";
|
import Collection from "./Collection";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||||
@ -16,7 +16,6 @@ import { readCollections } from "../../Common/dataAccess/readCollections";
|
|||||||
import { JunoClient, IJunoResponse } from "../../Juno/JunoClient";
|
import { JunoClient, IJunoResponse } from "../../Juno/JunoClient";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { readDatabaseOffer } from "../../Common/dataAccess/readDatabaseOffer";
|
import { readDatabaseOffer } from "../../Common/dataAccess/readDatabaseOffer";
|
||||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
|
||||||
import { fetchPortalNotifications } from "../../Common/PortalNotifications";
|
import { fetchPortalNotifications } from "../../Common/PortalNotifications";
|
||||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||||
|
|
||||||
@ -59,12 +58,17 @@ export default class Database implements ViewModels.Database {
|
|||||||
dataExplorerArea: Constants.Areas.ResourceTree,
|
dataExplorerArea: Constants.Areas.ResourceTree,
|
||||||
});
|
});
|
||||||
|
|
||||||
const pendingNotificationsPromise: Q.Promise<DataModels.Notification> = this._getPendingThroughputSplitNotification();
|
const pendingNotificationsPromise: Promise<DataModels.Notification> = this.getPendingThroughputSplitNotification();
|
||||||
const matchingTabs = this.container.tabsManager.getTabs(
|
const useDatabaseSettingsTabV1: boolean = this.container.isFeatureEnabled(
|
||||||
ViewModels.CollectionTabKind.DatabaseSettings,
|
Constants.Features.enableDatabaseSettingsTabV1
|
||||||
(tab) => tab.node?.id() === this.id()
|
|
||||||
);
|
);
|
||||||
let settingsTab: DatabaseSettingsTab = matchingTabs && (matchingTabs[0] as DatabaseSettingsTab);
|
const tabKind: ViewModels.CollectionTabKind = useDatabaseSettingsTabV1
|
||||||
|
? ViewModels.CollectionTabKind.DatabaseSettings
|
||||||
|
: ViewModels.CollectionTabKind.DatabaseSettingsV2;
|
||||||
|
const matchingTabs = this.container.tabsManager.getTabs(tabKind, (tab) => tab.node?.id() === this.id());
|
||||||
|
let settingsTab: DatabaseSettingsTab | DatabaseSettingsTabV2 = useDatabaseSettingsTabV1
|
||||||
|
? (matchingTabs?.[0] as DatabaseSettingsTab)
|
||||||
|
: (matchingTabs?.[0] as DatabaseSettingsTabV2);
|
||||||
if (!settingsTab) {
|
if (!settingsTab) {
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
||||||
databaseAccountName: this.container.databaseAccount().name,
|
databaseAccountName: this.container.databaseAccount().name,
|
||||||
@ -75,9 +79,11 @@ export default class Database implements ViewModels.Database {
|
|||||||
});
|
});
|
||||||
pendingNotificationsPromise.then(
|
pendingNotificationsPromise.then(
|
||||||
(data: any) => {
|
(data: any) => {
|
||||||
const pendingNotification: DataModels.Notification = data && data[0];
|
const pendingNotification: DataModels.Notification = data?.[0];
|
||||||
settingsTab = new DatabaseSettingsTab({
|
const tabOptions: ViewModels.TabOptions = {
|
||||||
tabKind: ViewModels.CollectionTabKind.DatabaseSettings,
|
tabKind: useDatabaseSettingsTabV1
|
||||||
|
? ViewModels.CollectionTabKind.DatabaseSettings
|
||||||
|
: ViewModels.CollectionTabKind.DatabaseSettingsV2,
|
||||||
title: "Scale",
|
title: "Scale",
|
||||||
tabPath: "",
|
tabPath: "",
|
||||||
node: this,
|
node: this,
|
||||||
@ -87,8 +93,10 @@ export default class Database implements ViewModels.Database {
|
|||||||
isActive: ko.observable(false),
|
isActive: ko.observable(false),
|
||||||
onLoadStartKey: startKey,
|
onLoadStartKey: startKey,
|
||||||
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
|
||||||
});
|
};
|
||||||
|
settingsTab = useDatabaseSettingsTabV1
|
||||||
|
? new DatabaseSettingsTab(tabOptions)
|
||||||
|
: new DatabaseSettingsTabV2(tabOptions);
|
||||||
settingsTab.pendingNotification(pendingNotification);
|
settingsTab.pendingNotification(pendingNotification);
|
||||||
this.container.tabsManager.activateNewTab(settingsTab);
|
this.container.tabsManager.activateNewTab(settingsTab);
|
||||||
},
|
},
|
||||||
@ -221,20 +229,18 @@ export default class Database implements ViewModels.Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getPendingThroughputSplitNotification(): Q.Promise<DataModels.Notification> {
|
public async getPendingThroughputSplitNotification(): Promise<DataModels.Notification> {
|
||||||
if (!this.container) {
|
if (!this.container) {
|
||||||
return Q.resolve(undefined);
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const deferred: Q.Deferred<DataModels.Notification> = Q.defer<DataModels.Notification>();
|
try {
|
||||||
fetchPortalNotifications().then(
|
const notifications: DataModels.Notification[] = await fetchPortalNotifications();
|
||||||
(notifications) => {
|
|
||||||
if (!notifications || notifications.length === 0) {
|
if (!notifications || notifications.length === 0) {
|
||||||
deferred.resolve(undefined);
|
return undefined;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const pendingNotification = _.find(notifications, (notification: DataModels.Notification) => {
|
return _.find(notifications, (notification: DataModels.Notification) => {
|
||||||
const throughputUpdateRegExp: RegExp = new RegExp("Throughput update (.*) in progress");
|
const throughputUpdateRegExp: RegExp = new RegExp("Throughput update (.*) in progress");
|
||||||
return (
|
return (
|
||||||
notification.kind === "message" &&
|
notification.kind === "message" &&
|
||||||
@ -244,10 +250,7 @@ export default class Database implements ViewModels.Database {
|
|||||||
throughputUpdateRegExp.test(notification.description)
|
throughputUpdateRegExp.test(notification.description)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
deferred.resolve(pendingNotification);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
Logger.logError(
|
Logger.logError(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
error: getErrorMessage(error),
|
error: getErrorMessage(error),
|
||||||
@ -257,11 +260,9 @@ export default class Database implements ViewModels.Database {
|
|||||||
}),
|
}),
|
||||||
"Settings tree node"
|
"Settings tree node"
|
||||||
);
|
);
|
||||||
deferred.resolve(undefined);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return deferred.promise;
|
return undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getDeltaCollections(
|
private getDeltaCollections(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user