2020-09-15 13:31:30 -05:00
|
|
|
import { Resource, StoredProcedureDefinition, TriggerDefinition, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
2020-05-25 21:30:55 -05:00
|
|
|
import * as ko from "knockout";
|
|
|
|
import Q from "q";
|
|
|
|
import * as _ from "underscore";
|
|
|
|
import UploadWorker from "worker-loader!../../workers/upload";
|
|
|
|
import { AuthType } from "../../AuthType";
|
|
|
|
import * as Constants from "../../Common/Constants";
|
2020-09-15 13:31:30 -05:00
|
|
|
import { readStoredProcedures } from "../../Common/dataAccess/readStoredProcedures";
|
|
|
|
import { readTriggers } from "../../Common/dataAccess/readTriggers";
|
|
|
|
import { readUserDefinedFunctions } from "../../Common/dataAccess/readUserDefinedFunctions";
|
2020-09-28 12:54:28 -07:00
|
|
|
import { createDocument } from "../../Common/DocumentClientUtilityBase";
|
|
|
|
import { readCollectionOffer } from "../../Common/dataAccess/readCollectionOffer";
|
|
|
|
import { readCollectionQuotaInfo } from "../../Common/dataAccess/readCollectionQuotaInfo";
|
2020-06-23 10:45:51 -05:00
|
|
|
import * as Logger from "../../Common/Logger";
|
2020-05-25 21:30:55 -05:00
|
|
|
import * as DataModels from "../../Contracts/DataModels";
|
|
|
|
import * as ViewModels from "../../Contracts/ViewModels";
|
|
|
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
2020-09-08 12:44:46 -05:00
|
|
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
2020-08-03 17:11:07 -05:00
|
|
|
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
2020-05-25 21:30:55 -05:00
|
|
|
import { StartUploadMessageParams, UploadDetails, UploadDetailsRecord } from "../../workers/upload/definitions";
|
|
|
|
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
|
|
|
import { CassandraAPIDataClient, CassandraTableKey, CassandraTableKeys } from "../Tables/TableDataClient";
|
2020-07-09 13:53:37 -07:00
|
|
|
import ConflictsTab from "../Tabs/ConflictsTab";
|
2020-05-25 21:30:55 -05:00
|
|
|
import DocumentsTab from "../Tabs/DocumentsTab";
|
|
|
|
import GraphTab from "../Tabs/GraphTab";
|
|
|
|
import MongoDocumentsTab from "../Tabs/MongoDocumentsTab";
|
|
|
|
import MongoQueryTab from "../Tabs/MongoQueryTab";
|
|
|
|
import MongoShellTab from "../Tabs/MongoShellTab";
|
|
|
|
import QueryTab from "../Tabs/QueryTab";
|
|
|
|
import QueryTablesTab from "../Tabs/QueryTablesTab";
|
2020-09-30 12:34:39 -07:00
|
|
|
import SettingsTabV2 from "../Tabs/SettingsTabV2";
|
2020-05-25 21:30:55 -05:00
|
|
|
import SettingsTab from "../Tabs/SettingsTab";
|
2020-09-15 13:31:30 -05:00
|
|
|
import ConflictId from "./ConflictId";
|
|
|
|
import DocumentId from "./DocumentId";
|
2020-05-25 21:30:55 -05:00
|
|
|
import StoredProcedure from "./StoredProcedure";
|
|
|
|
import Trigger from "./Trigger";
|
|
|
|
import UserDefinedFunction from "./UserDefinedFunction";
|
2020-10-14 22:25:13 -05:00
|
|
|
import { configContext, Platform } from "../../ConfigContext";
|
2020-09-30 12:34:39 -07:00
|
|
|
import Explorer from "../Explorer";
|
|
|
|
import { userContext } from "../../UserContext";
|
|
|
|
import TabsBase from "../Tabs/TabsBase";
|
2020-10-12 22:10:28 -05:00
|
|
|
import { fetchPortalNotifications } from "../../Common/PortalNotifications";
|
2020-05-25 21:30:55 -05:00
|
|
|
|
|
|
|
export default class Collection implements ViewModels.Collection {
|
|
|
|
public nodeKind: string;
|
2020-07-20 12:59:40 -05:00
|
|
|
public container: Explorer;
|
2020-05-25 21:30:55 -05:00
|
|
|
public self: string;
|
|
|
|
public rid: string;
|
|
|
|
public databaseId: string;
|
|
|
|
public partitionKey: DataModels.PartitionKey;
|
|
|
|
public partitionKeyPropertyHeader: string;
|
|
|
|
public partitionKeyProperty: string;
|
|
|
|
public id: ko.Observable<string>;
|
|
|
|
public defaultTtl: ko.Observable<number>;
|
|
|
|
public indexingPolicy: ko.Observable<DataModels.IndexingPolicy>;
|
|
|
|
public uniqueKeyPolicy: DataModels.UniqueKeyPolicy;
|
|
|
|
public quotaInfo: ko.Observable<DataModels.CollectionQuotaInfo>;
|
|
|
|
public offer: ko.Observable<DataModels.Offer>;
|
|
|
|
public conflictResolutionPolicy: ko.Observable<DataModels.ConflictResolutionPolicy>;
|
|
|
|
public changeFeedPolicy: ko.Observable<DataModels.ChangeFeedPolicy>;
|
|
|
|
public partitions: ko.Computed<number>;
|
|
|
|
public throughput: ko.Computed<number>;
|
|
|
|
public rawDataModel: DataModels.Collection;
|
|
|
|
public analyticalStorageTtl: ko.Observable<number>;
|
|
|
|
public geospatialConfig: ko.Observable<DataModels.GeospatialConfig>;
|
|
|
|
|
|
|
|
// TODO move this to API customization class
|
|
|
|
public cassandraKeys: CassandraTableKeys;
|
|
|
|
public cassandraSchema: CassandraTableKey[];
|
|
|
|
|
|
|
|
public documentIds: ko.ObservableArray<DocumentId>;
|
|
|
|
public children: ko.ObservableArray<ViewModels.TreeNode>;
|
2020-07-21 13:50:51 -05:00
|
|
|
public storedProcedures: ko.Computed<StoredProcedure[]>;
|
|
|
|
public userDefinedFunctions: ko.Computed<UserDefinedFunction[]>;
|
|
|
|
public triggers: ko.Computed<Trigger[]>;
|
2020-05-25 21:30:55 -05:00
|
|
|
|
|
|
|
public showStoredProcedures: ko.Observable<boolean>;
|
|
|
|
public showTriggers: ko.Observable<boolean>;
|
|
|
|
public showUserDefinedFunctions: ko.Observable<boolean>;
|
|
|
|
public showConflicts: ko.Observable<boolean>;
|
|
|
|
|
|
|
|
public selectedDocumentContent: ViewModels.Editable<any>;
|
|
|
|
public selectedSubnodeKind: ko.Observable<ViewModels.CollectionTabKind>;
|
|
|
|
public focusedSubnodeKind: ko.Observable<ViewModels.CollectionTabKind>;
|
|
|
|
public isCollectionExpanded: ko.Observable<boolean>;
|
|
|
|
public isStoredProceduresExpanded: ko.Observable<boolean>;
|
|
|
|
public isUserDefinedFunctionsExpanded: ko.Observable<boolean>;
|
|
|
|
public isTriggersExpanded: ko.Observable<boolean>;
|
|
|
|
|
|
|
|
public documentsFocused: ko.Observable<boolean>;
|
|
|
|
public settingsFocused: ko.Observable<boolean>;
|
|
|
|
public storedProceduresFocused: ko.Observable<boolean>;
|
|
|
|
public userDefinedFunctionsFocused: ko.Observable<boolean>;
|
|
|
|
public triggersFocused: ko.Observable<boolean>;
|
|
|
|
|
|
|
|
constructor(
|
2020-07-20 12:59:40 -05:00
|
|
|
container: Explorer,
|
2020-05-25 21:30:55 -05:00
|
|
|
databaseId: string,
|
|
|
|
data: DataModels.Collection,
|
|
|
|
quotaInfo: DataModels.CollectionQuotaInfo,
|
|
|
|
offer: DataModels.Offer
|
|
|
|
) {
|
|
|
|
this.nodeKind = "Collection";
|
|
|
|
this.container = container;
|
|
|
|
this.self = data._self;
|
|
|
|
this.rid = data._rid;
|
|
|
|
this.databaseId = databaseId;
|
|
|
|
this.rawDataModel = data;
|
|
|
|
this.partitionKey = data.partitionKey;
|
|
|
|
|
|
|
|
this.id = ko.observable(data.id);
|
|
|
|
this.defaultTtl = ko.observable(data.defaultTtl);
|
|
|
|
this.indexingPolicy = ko.observable(data.indexingPolicy);
|
|
|
|
this.quotaInfo = ko.observable(quotaInfo);
|
|
|
|
this.offer = ko.observable(offer);
|
|
|
|
this.conflictResolutionPolicy = ko.observable(data.conflictResolutionPolicy);
|
|
|
|
this.changeFeedPolicy = ko.observable<DataModels.ChangeFeedPolicy>(data.changeFeedPolicy);
|
|
|
|
this.analyticalStorageTtl = ko.observable(data.analyticalStorageTtl);
|
|
|
|
this.geospatialConfig = ko.observable(data.geospatialConfig);
|
|
|
|
|
|
|
|
// TODO fix this to only replace non-excaped single quotes
|
|
|
|
this.partitionKeyProperty =
|
|
|
|
(this.partitionKey &&
|
|
|
|
this.partitionKey.paths &&
|
|
|
|
this.partitionKey.paths.length &&
|
|
|
|
this.partitionKey.paths.length > 0 &&
|
|
|
|
this.partitionKey.paths[0]
|
|
|
|
.replace(/[/]+/g, ".")
|
|
|
|
.substr(1)
|
|
|
|
.replace(/[']+/g, "")) ||
|
|
|
|
null;
|
|
|
|
this.partitionKeyPropertyHeader =
|
|
|
|
(this.partitionKey &&
|
|
|
|
this.partitionKey.paths &&
|
|
|
|
this.partitionKey.paths.length > 0 &&
|
|
|
|
this.partitionKey.paths[0]) ||
|
|
|
|
null;
|
|
|
|
|
|
|
|
if (!!container.isPreferredApiMongoDB() && this.partitionKeyProperty && ~this.partitionKeyProperty.indexOf(`"`)) {
|
|
|
|
this.partitionKeyProperty = this.partitionKeyProperty.replace(/["]+/g, "");
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO #10738269 : Add this logic in a derived class for Mongo
|
|
|
|
if (
|
|
|
|
!!container.isPreferredApiMongoDB() &&
|
|
|
|
this.partitionKeyProperty &&
|
|
|
|
this.partitionKeyProperty.indexOf("$v") > -1
|
|
|
|
) {
|
|
|
|
// From $v.shard.$v.key.$v > shard.key
|
|
|
|
this.partitionKeyProperty = this.partitionKeyProperty.replace(/.\$v/g, "").replace(/\$v./g, "");
|
|
|
|
this.partitionKeyPropertyHeader = "/" + this.partitionKeyProperty;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.documentIds = ko.observableArray<DocumentId>([]);
|
|
|
|
this.isCollectionExpanded = ko.observable<boolean>(false);
|
|
|
|
this.selectedSubnodeKind = ko.observable<ViewModels.CollectionTabKind>();
|
|
|
|
this.focusedSubnodeKind = ko.observable<ViewModels.CollectionTabKind>();
|
|
|
|
|
|
|
|
this.documentsFocused = ko.observable<boolean>();
|
|
|
|
this.documentsFocused.subscribe(focus => {
|
|
|
|
console.log("Focus set on Documents: " + focus);
|
|
|
|
this.focusedSubnodeKind(ViewModels.CollectionTabKind.Documents);
|
|
|
|
});
|
|
|
|
|
|
|
|
this.settingsFocused = ko.observable<boolean>(false);
|
|
|
|
this.settingsFocused.subscribe(focus => {
|
|
|
|
this.focusedSubnodeKind(ViewModels.CollectionTabKind.Settings);
|
|
|
|
});
|
|
|
|
|
|
|
|
this.storedProceduresFocused = ko.observable<boolean>(false);
|
|
|
|
this.storedProceduresFocused.subscribe(focus => {
|
|
|
|
this.focusedSubnodeKind(ViewModels.CollectionTabKind.StoredProcedures);
|
|
|
|
});
|
|
|
|
|
|
|
|
this.userDefinedFunctionsFocused = ko.observable<boolean>(false);
|
|
|
|
this.userDefinedFunctionsFocused.subscribe(focus => {
|
|
|
|
this.focusedSubnodeKind(ViewModels.CollectionTabKind.UserDefinedFunctions);
|
|
|
|
});
|
|
|
|
|
|
|
|
this.triggersFocused = ko.observable<boolean>(false);
|
|
|
|
this.triggersFocused.subscribe(focus => {
|
|
|
|
this.focusedSubnodeKind(ViewModels.CollectionTabKind.Triggers);
|
|
|
|
});
|
|
|
|
|
|
|
|
this.children = ko.observableArray<ViewModels.TreeNode>([]);
|
|
|
|
|
|
|
|
this.storedProcedures = ko.computed(() => {
|
|
|
|
return this.children()
|
|
|
|
.filter(node => node.nodeKind === "StoredProcedure")
|
2020-07-21 13:50:51 -05:00
|
|
|
.map(node => <StoredProcedure>node);
|
2020-05-25 21:30:55 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
this.userDefinedFunctions = ko.computed(() => {
|
|
|
|
return this.children()
|
|
|
|
.filter(node => node.nodeKind === "UserDefinedFunction")
|
2020-07-21 13:50:51 -05:00
|
|
|
.map(node => <UserDefinedFunction>node);
|
2020-05-25 21:30:55 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
this.triggers = ko.computed(() => {
|
|
|
|
return this.children()
|
|
|
|
.filter(node => node.nodeKind === "Trigger")
|
2020-07-21 13:50:51 -05:00
|
|
|
.map(node => <Trigger>node);
|
2020-05-25 21:30:55 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
const showScriptsMenus: boolean = container.isPreferredApiDocumentDB() || container.isPreferredApiGraph();
|
|
|
|
this.showStoredProcedures = ko.observable<boolean>(showScriptsMenus);
|
|
|
|
this.showTriggers = ko.observable<boolean>(showScriptsMenus);
|
|
|
|
this.showUserDefinedFunctions = ko.observable<boolean>(showScriptsMenus);
|
|
|
|
|
|
|
|
this.showConflicts = ko.observable<boolean>(
|
|
|
|
container &&
|
|
|
|
container.databaseAccount &&
|
|
|
|
container.databaseAccount() &&
|
|
|
|
container.databaseAccount().properties &&
|
|
|
|
container.databaseAccount().properties.enableMultipleWriteLocations &&
|
|
|
|
data &&
|
|
|
|
!!data.conflictResolutionPolicy
|
|
|
|
);
|
|
|
|
|
|
|
|
this.isStoredProceduresExpanded = ko.observable<boolean>(false);
|
|
|
|
this.isUserDefinedFunctionsExpanded = ko.observable<boolean>(false);
|
|
|
|
this.isTriggersExpanded = ko.observable<boolean>(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
public expandCollapseCollection() {
|
|
|
|
this.container.selectedNode(this);
|
|
|
|
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
|
|
|
description: "Collection node",
|
|
|
|
databaseAccountName: this.container.databaseAccount().name,
|
|
|
|
databaseName: this.databaseId,
|
|
|
|
collectionName: this.id(),
|
|
|
|
defaultExperience: this.container.defaultExperience(),
|
|
|
|
dataExplorerArea: Constants.Areas.ResourceTree
|
|
|
|
});
|
|
|
|
if (this.isCollectionExpanded()) {
|
|
|
|
this.collapseCollection();
|
|
|
|
} else {
|
|
|
|
this.expandCollection();
|
|
|
|
}
|
|
|
|
this.container.onUpdateTabsButtons([]);
|
2020-10-13 13:29:39 -07:00
|
|
|
this.container.tabsManager.refreshActiveTab(
|
|
|
|
tab => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
|
|
|
);
|
2020-05-25 21:30:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
public collapseCollection() {
|
|
|
|
if (!this.isCollectionExpanded()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.isCollectionExpanded(false);
|
|
|
|
TelemetryProcessor.trace(Action.CollapseTreeNode, ActionModifiers.Mark, {
|
|
|
|
description: "Collection node",
|
|
|
|
databaseAccountName: this.container.databaseAccount().name,
|
|
|
|
databaseName: this.databaseId,
|
|
|
|
collectionName: this.id(),
|
|
|
|
defaultExperience: this.container.defaultExperience(),
|
|
|
|
dataExplorerArea: Constants.Areas.ResourceTree
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
public expandCollection(): Q.Promise<any> {
|
|
|
|
if (this.isCollectionExpanded()) {
|
|
|
|
return Q();
|
|
|
|
}
|
|
|
|
|
|
|
|
this.isCollectionExpanded(true);
|
|
|
|
TelemetryProcessor.trace(Action.ExpandTreeNode, ActionModifiers.Mark, {
|
|
|
|
description: "Collection node",
|
|
|
|
databaseAccountName: this.container.databaseAccount().name,
|
|
|
|
databaseName: this.databaseId,
|
|
|
|
collectionName: this.id(),
|
|
|
|
defaultExperience: this.container.defaultExperience(),
|
|
|
|
dataExplorerArea: Constants.Areas.ResourceTree
|
|
|
|
});
|
|
|
|
|
|
|
|
return Q.resolve();
|
|
|
|
}
|
|
|
|
|
|
|
|
public onDocumentDBDocumentsClick() {
|
|
|
|
this.container.selectedNode(this);
|
|
|
|
this.selectedSubnodeKind(ViewModels.CollectionTabKind.Documents);
|
|
|
|
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
|
|
|
description: "Documents node",
|
|
|
|
databaseAccountName: this.container.databaseAccount().name,
|
|
|
|
databaseName: this.databaseId,
|
|
|
|
collectionName: this.id(),
|
|
|
|
defaultExperience: this.container.defaultExperience(),
|
|
|
|
dataExplorerArea: Constants.Areas.ResourceTree
|
|
|
|
});
|
|
|
|
|
2020-07-09 13:53:37 -07:00
|
|
|
const documentsTabs: DocumentsTab[] = this.container.tabsManager.getTabs(
|
|
|
|
ViewModels.CollectionTabKind.Documents,
|
2020-10-13 13:29:39 -07:00
|
|
|
tab => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
2020-07-09 13:53:37 -07:00
|
|
|
) as DocumentsTab[];
|
|
|
|
let documentsTab: DocumentsTab = documentsTabs && documentsTabs[0];
|
2020-05-25 21:30:55 -05:00
|
|
|
|
2020-07-09 13:53:37 -07:00
|
|
|
if (documentsTab) {
|
|
|
|
this.container.tabsManager.activateTab(documentsTab);
|
|
|
|
} else {
|
2020-05-25 21:30:55 -05:00
|
|
|
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
|
|
|
databaseAccountName: this.container.databaseAccount().name,
|
|
|
|
databaseName: this.databaseId,
|
|
|
|
collectionName: this.id(),
|
|
|
|
defaultExperience: this.container.defaultExperience(),
|
|
|
|
dataExplorerArea: Constants.Areas.Tab,
|
|
|
|
tabTitle: "Items"
|
|
|
|
});
|
|
|
|
this.documentIds([]);
|
2020-07-09 13:53:37 -07:00
|
|
|
|
2020-05-25 21:30:55 -05:00
|
|
|
documentsTab = new DocumentsTab({
|
|
|
|
partitionKey: this.partitionKey,
|
|
|
|
documentIds: ko.observableArray<DocumentId>([]),
|
|
|
|
tabKind: ViewModels.CollectionTabKind.Documents,
|
|
|
|
title: "Items",
|
|
|
|
isActive: ko.observable<boolean>(false),
|
|
|
|
collection: this,
|
|
|
|
node: this,
|
|
|
|
tabPath: `${this.databaseId}>${this.id()}>Documents`,
|
|
|
|
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/documents`,
|
|
|
|
onLoadStartKey: startKey,
|
2020-07-09 13:53:37 -07:00
|
|
|
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
2020-05-25 21:30:55 -05:00
|
|
|
});
|
|
|
|
|
2020-07-09 13:53:37 -07:00
|
|
|
this.container.tabsManager.activateNewTab(documentsTab);
|
|
|
|
}
|
2020-05-25 21:30:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
public onConflictsClick() {
|
|
|
|
this.container.selectedNode(this);
|
|
|
|
this.selectedSubnodeKind(ViewModels.CollectionTabKind.Conflicts);
|
|
|
|
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
|
|
|
description: "Conflicts node",
|
|
|
|
databaseAccountName: this.container.databaseAccount().name,
|
|
|
|
databaseName: this.databaseId,
|
|
|
|
collectionName: this.id(),
|
|
|
|
defaultExperience: this.container.defaultExperience(),
|
|
|
|
dataExplorerArea: Constants.Areas.ResourceTree
|
|
|
|
});
|
|
|
|
|
2020-07-09 13:53:37 -07:00
|
|
|
const conflictsTabs: ConflictsTab[] = this.container.tabsManager.getTabs(
|
|
|
|
ViewModels.CollectionTabKind.Conflicts,
|
2020-10-13 13:29:39 -07:00
|
|
|
tab => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
2020-07-09 13:53:37 -07:00
|
|
|
) as ConflictsTab[];
|
|
|
|
let conflictsTab: ConflictsTab = conflictsTabs && conflictsTabs[0];
|
2020-05-25 21:30:55 -05:00
|
|
|
|
2020-07-09 13:53:37 -07:00
|
|
|
if (conflictsTab) {
|
|
|
|
this.container.tabsManager.activateTab(conflictsTab);
|
|
|
|
} else {
|
2020-05-25 21:30:55 -05:00
|
|
|
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
|
|
|
databaseAccountName: this.container.databaseAccount().name,
|
|
|
|
databaseName: this.databaseId,
|
|
|
|
collectionName: this.id(),
|
|
|
|
defaultExperience: this.container.defaultExperience(),
|
|
|
|
dataExplorerArea: Constants.Areas.Tab,
|
|
|
|
tabTitle: "Conflicts"
|
|
|
|
});
|
|
|
|
this.documentIds([]);
|
2020-07-09 13:53:37 -07:00
|
|
|
|
|
|
|
const conflictsTab: ConflictsTab = new ConflictsTab({
|
2020-05-25 21:30:55 -05:00
|
|
|
partitionKey: this.partitionKey,
|
|
|
|
conflictIds: ko.observableArray<ConflictId>([]),
|
|
|
|
tabKind: ViewModels.CollectionTabKind.Conflicts,
|
|
|
|
title: "Conflicts",
|
|
|
|
isActive: ko.observable<boolean>(false),
|
|
|
|
collection: this,
|
|
|
|
node: this,
|
|
|
|
tabPath: `${this.databaseId}>${this.id()}>Conflicts`,
|
|
|
|
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/conflicts`,
|
|
|
|
onLoadStartKey: startKey,
|
2020-07-09 13:53:37 -07:00
|
|
|
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
2020-05-25 21:30:55 -05:00
|
|
|
});
|
|
|
|
|
2020-07-09 13:53:37 -07:00
|
|
|
this.container.tabsManager.activateNewTab(conflictsTab);
|
|
|
|
}
|
2020-05-25 21:30:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
public onTableEntitiesClick() {
|
|
|
|
this.container.selectedNode(this);
|
|
|
|
this.selectedSubnodeKind(ViewModels.CollectionTabKind.QueryTables);
|
|
|
|
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
|
|
|
description: "Entities node",
|
|
|
|
databaseAccountName: this.container.databaseAccount().name,
|
|
|
|
databaseName: this.databaseId,
|
|
|
|
collectionName: this.id(),
|
|
|
|
defaultExperience: this.container.defaultExperience(),
|
|
|
|
dataExplorerArea: Constants.Areas.ResourceTree
|
|
|
|
});
|
|
|
|
|
|
|
|
if (this.container.isPreferredApiCassandra() && !this.cassandraKeys) {
|
|
|
|
(<CassandraAPIDataClient>this.container.tableDataClient).getTableKeys(this).then((keys: CassandraTableKeys) => {
|
|
|
|
this.cassandraKeys = keys;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-07-09 13:53:37 -07:00
|
|
|
const queryTablesTabs: QueryTablesTab[] = this.container.tabsManager.getTabs(
|
|
|
|
ViewModels.CollectionTabKind.QueryTables,
|
2020-10-13 13:29:39 -07:00
|
|
|
tab => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
2020-07-09 13:53:37 -07:00
|
|
|
) as QueryTablesTab[];
|
|
|
|
let queryTablesTab: QueryTablesTab = queryTablesTabs && queryTablesTabs[0];
|
2020-05-25 21:30:55 -05:00
|
|
|
|
2020-07-09 13:53:37 -07:00
|
|
|
if (queryTablesTab) {
|
|
|
|
this.container.tabsManager.activateTab(queryTablesTab);
|
|
|
|
} else {
|
2020-05-25 21:30:55 -05:00
|
|
|
this.documentIds([]);
|
|
|
|
let title = `Entities`;
|
|
|
|
if (this.container.isPreferredApiCassandra()) {
|
|
|
|
title = `Rows`;
|
|
|
|
}
|
|
|
|
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
|
|
|
databaseAccountName: this.container.databaseAccount().name,
|
|
|
|
databaseName: this.databaseId,
|
|
|
|
collectionName: this.id(),
|
|
|
|
defaultExperience: this.container.defaultExperience(),
|
|
|
|
dataExplorerArea: Constants.Areas.Tab,
|
|
|
|
tabTitle: title
|
|
|
|
});
|
2020-07-09 13:53:37 -07:00
|
|
|
|
|
|
|
queryTablesTab = new QueryTablesTab({
|
2020-05-25 21:30:55 -05:00
|
|
|
tabKind: ViewModels.CollectionTabKind.QueryTables,
|
|
|
|
title: title,
|
|
|
|
tabPath: "",
|
2020-07-27 12:58:27 -05:00
|
|
|
|
2020-05-25 21:30:55 -05:00
|
|
|
collection: this,
|
|
|
|
|
|
|
|
node: this,
|
|
|
|
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/entities`,
|
|
|
|
isActive: ko.observable(false),
|
|
|
|
onLoadStartKey: startKey,
|
2020-07-09 13:53:37 -07:00
|
|
|
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
2020-05-25 21:30:55 -05:00
|
|
|
});
|
|
|
|
|
2020-07-09 13:53:37 -07:00
|
|
|
this.container.tabsManager.activateNewTab(queryTablesTab);
|
|
|
|
}
|
2020-05-25 21:30:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
public onGraphDocumentsClick() {
|
|
|
|
this.container.selectedNode(this);
|
|
|
|
this.selectedSubnodeKind(ViewModels.CollectionTabKind.Graph);
|
|
|
|
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
|
|
|
description: "Documents node",
|
|
|
|
databaseAccountName: this.container.databaseAccount().name,
|
|
|
|
databaseName: this.databaseId,
|
|
|
|
collectionName: this.id(),
|
|
|
|
defaultExperience: this.container.defaultExperience(),
|
|
|
|
dataExplorerArea: Constants.Areas.ResourceTree
|
|
|
|
});
|
|
|
|
|
2020-07-09 13:53:37 -07:00
|
|
|
const graphTabs: GraphTab[] = this.container.tabsManager.getTabs(
|
|
|
|
ViewModels.CollectionTabKind.Graph,
|
2020-10-13 13:29:39 -07:00
|
|
|
tab => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
2020-07-09 13:53:37 -07:00
|
|
|
) as GraphTab[];
|
|
|
|
let graphTab: GraphTab = graphTabs && graphTabs[0];
|
2020-05-25 21:30:55 -05:00
|
|
|
|
2020-07-09 13:53:37 -07:00
|
|
|
if (graphTab) {
|
|
|
|
this.container.tabsManager.activateTab(graphTab);
|
|
|
|
} else {
|
2020-05-25 21:30:55 -05:00
|
|
|
this.documentIds([]);
|
2020-07-09 13:53:37 -07:00
|
|
|
const title = "Graph";
|
|
|
|
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
|
|
|
databaseAccountName: this.container.databaseAccount().name,
|
|
|
|
databaseName: this.databaseId,
|
|
|
|
collectionName: this.id(),
|
|
|
|
defaultExperience: this.container.defaultExperience(),
|
|
|
|
dataExplorerArea: Constants.Areas.Tab,
|
|
|
|
tabTitle: title
|
|
|
|
});
|
2020-05-25 21:30:55 -05:00
|
|
|
|
2020-07-09 13:53:37 -07:00
|
|
|
graphTab = new GraphTab({
|
2020-08-06 14:03:46 -05:00
|
|
|
account: userContext.databaseAccount,
|
2020-07-09 13:53:37 -07:00
|
|
|
tabKind: ViewModels.CollectionTabKind.Graph,
|
|
|
|
node: this,
|
|
|
|
title: title,
|
|
|
|
tabPath: "",
|
2020-07-27 12:58:27 -05:00
|
|
|
|
2020-07-09 13:53:37 -07:00
|
|
|
collection: this,
|
2020-08-06 14:03:46 -05:00
|
|
|
masterKey: userContext.masterKey || "",
|
2020-07-09 13:53:37 -07:00
|
|
|
collectionPartitionKeyProperty: this.partitionKeyProperty,
|
|
|
|
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/graphs`,
|
|
|
|
collectionId: this.id(),
|
|
|
|
isActive: ko.observable(false),
|
|
|
|
databaseId: this.databaseId,
|
|
|
|
isTabsContentExpanded: this.container.isTabsContentExpanded,
|
|
|
|
onLoadStartKey: startKey,
|
|
|
|
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
|
|
|
});
|
2020-05-25 21:30:55 -05:00
|
|
|
|
2020-07-09 13:53:37 -07:00
|
|
|
this.container.tabsManager.activateNewTab(graphTab);
|
|
|
|
}
|
|
|
|
}
|
2020-05-25 21:30:55 -05:00
|
|
|
|
|
|
|
public onMongoDBDocumentsClick = () => {
|
|
|
|
this.container.selectedNode(this);
|
|
|
|
this.selectedSubnodeKind(ViewModels.CollectionTabKind.Documents);
|
|
|
|
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
|
|
|
description: "Documents node",
|
|
|
|
databaseAccountName: this.container.databaseAccount().name,
|
|
|
|
databaseName: this.databaseId,
|
|
|
|
collectionName: this.id(),
|
|
|
|
defaultExperience: this.container.defaultExperience(),
|
|
|
|
dataExplorerArea: Constants.Areas.ResourceTree
|
|
|
|
});
|
|
|
|
|
2020-07-09 13:53:37 -07:00
|
|
|
const mongoDocumentsTabs: MongoDocumentsTab[] = this.container.tabsManager.getTabs(
|
|
|
|
ViewModels.CollectionTabKind.Documents,
|
2020-10-13 13:29:39 -07:00
|
|
|
tab => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
2020-07-09 13:53:37 -07:00
|
|
|
) as MongoDocumentsTab[];
|
|
|
|
let mongoDocumentsTab: MongoDocumentsTab = mongoDocumentsTabs && mongoDocumentsTabs[0];
|
2020-05-25 21:30:55 -05:00
|
|
|
|
2020-07-09 13:53:37 -07:00
|
|
|
if (mongoDocumentsTab) {
|
|
|
|
this.container.tabsManager.activateTab(mongoDocumentsTab);
|
|
|
|
} else {
|
2020-05-25 21:30:55 -05:00
|
|
|
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
|
|
|
databaseAccountName: this.container.databaseAccount().name,
|
|
|
|
databaseName: this.databaseId,
|
|
|
|
collectionName: this.id(),
|
|
|
|
defaultExperience: this.container.defaultExperience(),
|
|
|
|
dataExplorerArea: Constants.Areas.Tab,
|
|
|
|
tabTitle: "Documents"
|
|
|
|
});
|
|
|
|
this.documentIds([]);
|
2020-07-09 13:53:37 -07:00
|
|
|
|
|
|
|
mongoDocumentsTab = new MongoDocumentsTab({
|
2020-05-25 21:30:55 -05:00
|
|
|
partitionKey: this.partitionKey,
|
|
|
|
documentIds: this.documentIds,
|
|
|
|
tabKind: ViewModels.CollectionTabKind.Documents,
|
|
|
|
title: "Documents",
|
|
|
|
tabPath: "",
|
2020-07-27 12:58:27 -05:00
|
|
|
|
2020-05-25 21:30:55 -05:00
|
|
|
collection: this,
|
|
|
|
|
|
|
|
node: this,
|
|
|
|
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/mongoDocuments`,
|
|
|
|
isActive: ko.observable(false),
|
|
|
|
onLoadStartKey: startKey,
|
2020-07-09 13:53:37 -07:00
|
|
|
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
2020-05-25 21:30:55 -05:00
|
|
|
});
|
2020-07-09 13:53:37 -07:00
|
|
|
this.container.tabsManager.activateNewTab(mongoDocumentsTab);
|
2020-05-25 21:30:55 -05:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-09-28 12:54:28 -07:00
|
|
|
public onSettingsClick = async (): Promise<void> => {
|
2020-05-25 21:30:55 -05:00
|
|
|
this.container.selectedNode(this);
|
|
|
|
this.selectedSubnodeKind(ViewModels.CollectionTabKind.Settings);
|
|
|
|
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
|
|
|
description: "Settings node",
|
|
|
|
databaseAccountName: this.container.databaseAccount().name,
|
|
|
|
databaseName: this.databaseId,
|
|
|
|
collectionName: this.id(),
|
|
|
|
defaultExperience: this.container.defaultExperience(),
|
|
|
|
dataExplorerArea: Constants.Areas.ResourceTree
|
|
|
|
});
|
|
|
|
|
2020-10-26 14:17:40 -07:00
|
|
|
const isSettingsV2Enabled = this.container.isSettingsV2Enabled();
|
|
|
|
if (!isSettingsV2Enabled) {
|
|
|
|
await this.loadOffer();
|
|
|
|
}
|
|
|
|
|
2020-07-24 13:13:54 -07:00
|
|
|
const tabTitle = !this.offer() ? "Settings" : "Scale & Settings";
|
2020-05-25 21:30:55 -05:00
|
|
|
const pendingNotificationsPromise: Q.Promise<DataModels.Notification> = this._getPendingThroughputSplitNotification();
|
2020-07-27 16:05:25 -05:00
|
|
|
const matchingTabs = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Settings, tab => {
|
2020-10-13 13:29:39 -07:00
|
|
|
return tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id();
|
2020-07-27 16:05:25 -05:00
|
|
|
});
|
2020-07-24 13:13:54 -07:00
|
|
|
|
2020-09-30 12:34:39 -07:00
|
|
|
const traceStartData = {
|
|
|
|
databaseAccountName: this.container.databaseAccount().name,
|
|
|
|
databaseName: this.databaseId,
|
|
|
|
collectionName: this.id(),
|
|
|
|
defaultExperience: this.container.defaultExperience(),
|
|
|
|
dataExplorerArea: Constants.Areas.Tab,
|
|
|
|
tabTitle: tabTitle
|
|
|
|
};
|
|
|
|
|
|
|
|
const settingsTabOptions: ViewModels.TabOptions = {
|
|
|
|
tabKind: undefined,
|
|
|
|
title: !this.offer() ? "Settings" : "Scale & Settings",
|
|
|
|
tabPath: "",
|
|
|
|
collection: this,
|
|
|
|
node: this,
|
|
|
|
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/settings`,
|
|
|
|
isActive: ko.observable(false),
|
|
|
|
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
|
|
|
};
|
|
|
|
|
|
|
|
if (isSettingsV2Enabled) {
|
2020-10-26 14:17:40 -07:00
|
|
|
let settingsTabV2 = matchingTabs && (matchingTabs[0] as SettingsTabV2);
|
|
|
|
this.launchSettingsTabV2(settingsTabV2, traceStartData, settingsTabOptions, pendingNotificationsPromise);
|
2020-09-30 12:34:39 -07:00
|
|
|
} else {
|
2020-10-26 14:17:40 -07:00
|
|
|
let settingsTab = matchingTabs && (matchingTabs[0] as SettingsTab);
|
|
|
|
this.launchSettingsTabV1(settingsTab, traceStartData, settingsTabOptions, pendingNotificationsPromise);
|
2020-09-30 12:34:39 -07:00
|
|
|
}
|
2020-10-26 14:17:40 -07:00
|
|
|
};
|
2020-09-30 12:34:39 -07:00
|
|
|
|
2020-10-26 14:17:40 -07:00
|
|
|
private launchSettingsTabV1 = (
|
|
|
|
settingsTab: SettingsTab,
|
|
|
|
traceStartData: any,
|
|
|
|
settingsTabOptions: ViewModels.TabOptions,
|
|
|
|
getPendingNotification: Q.Promise<DataModels.Notification>
|
|
|
|
): void => {
|
2020-05-25 21:30:55 -05:00
|
|
|
if (!settingsTab) {
|
2020-09-30 12:34:39 -07:00
|
|
|
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, traceStartData);
|
|
|
|
settingsTabOptions.onLoadStartKey = startKey;
|
2020-05-25 21:30:55 -05:00
|
|
|
|
2020-10-26 14:17:40 -07:00
|
|
|
getPendingNotification.then(
|
2020-05-25 21:30:55 -05:00
|
|
|
(data: any) => {
|
|
|
|
const pendingNotification: DataModels.Notification = data && data[0];
|
2020-10-26 14:17:40 -07:00
|
|
|
settingsTabOptions.tabKind = ViewModels.CollectionTabKind.Settings;
|
|
|
|
settingsTab = new SettingsTab(settingsTabOptions);
|
2020-07-09 13:53:37 -07:00
|
|
|
this.container.tabsManager.activateNewTab(settingsTab);
|
|
|
|
settingsTab.pendingNotification(pendingNotification);
|
2020-05-25 21:30:55 -05:00
|
|
|
},
|
|
|
|
(error: any) => {
|
|
|
|
TelemetryProcessor.traceFailure(
|
|
|
|
Action.Tab,
|
|
|
|
{
|
|
|
|
databaseAccountName: this.container.databaseAccount().name,
|
|
|
|
databaseName: this.databaseId,
|
|
|
|
collectionName: this.id(),
|
|
|
|
defaultExperience: this.container.defaultExperience(),
|
|
|
|
dataExplorerArea: Constants.Areas.Tab,
|
2020-10-26 14:17:40 -07:00
|
|
|
tabTitle: settingsTabOptions.title,
|
2020-05-25 21:30:55 -05:00
|
|
|
error: error
|
|
|
|
},
|
|
|
|
startKey
|
|
|
|
);
|
|
|
|
NotificationConsoleUtils.logConsoleMessage(
|
|
|
|
ConsoleDataType.Error,
|
2020-10-21 14:28:30 -07:00
|
|
|
`Error while fetching container settings for container ${this.id()}: ${error.message}`
|
2020-05-25 21:30:55 -05:00
|
|
|
);
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
} else {
|
2020-10-26 14:17:40 -07:00
|
|
|
getPendingNotification.then(
|
2020-05-25 21:30:55 -05:00
|
|
|
(pendingNotification: DataModels.Notification) => {
|
2020-07-09 13:53:37 -07:00
|
|
|
settingsTab.pendingNotification(pendingNotification);
|
|
|
|
this.container.tabsManager.activateTab(settingsTab);
|
2020-05-25 21:30:55 -05:00
|
|
|
},
|
|
|
|
(error: any) => {
|
2020-07-09 13:53:37 -07:00
|
|
|
settingsTab.pendingNotification(undefined);
|
|
|
|
this.container.tabsManager.activateTab(settingsTab);
|
2020-05-25 21:30:55 -05:00
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-10-26 14:17:40 -07:00
|
|
|
private launchSettingsTabV2 = (
|
|
|
|
settingsTabV2: SettingsTabV2,
|
|
|
|
traceStartData: any,
|
|
|
|
settingsTabOptions: ViewModels.TabOptions,
|
|
|
|
getPendingNotification: Q.Promise<DataModels.Notification>
|
|
|
|
): void => {
|
|
|
|
const settingsTabV2Options: ViewModels.SettingsTabV2Options = {
|
|
|
|
...settingsTabOptions,
|
|
|
|
getPendingNotification: getPendingNotification
|
|
|
|
};
|
|
|
|
|
|
|
|
if (!settingsTabV2) {
|
|
|
|
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, traceStartData);
|
|
|
|
settingsTabV2Options.onLoadStartKey = startKey;
|
|
|
|
settingsTabV2Options.tabKind = ViewModels.CollectionTabKind.SettingsV2;
|
|
|
|
settingsTabV2 = new SettingsTabV2(settingsTabV2Options);
|
|
|
|
this.container.tabsManager.activateNewTab(settingsTabV2);
|
|
|
|
} else {
|
|
|
|
this.container.tabsManager.activateTab(settingsTabV2);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-09-28 12:54:28 -07:00
|
|
|
private async loadCollectionQuotaInfo(): Promise<void> {
|
2020-05-25 21:30:55 -05:00
|
|
|
// TODO: Use the collection entity cache to get quota info
|
2020-09-28 12:54:28 -07:00
|
|
|
const quotaInfoWithUniqueKeyPolicy = await readCollectionQuotaInfo(this);
|
|
|
|
this.uniqueKeyPolicy = quotaInfoWithUniqueKeyPolicy.uniqueKeyPolicy;
|
|
|
|
const quotaInfo = _.omit(quotaInfoWithUniqueKeyPolicy, "uniqueKeyPolicy");
|
|
|
|
this.quotaInfo(quotaInfo);
|
2020-05-25 21:30:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
public onNewQueryClick(source: any, event: MouseEvent, queryText?: string) {
|
|
|
|
const collection: ViewModels.Collection = source.collection || source;
|
2020-07-09 13:53:37 -07:00
|
|
|
const id = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Query).length + 1;
|
2020-05-25 21:30:55 -05:00
|
|
|
const title = "Query " + id;
|
|
|
|
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
|
|
|
databaseAccountName: this.container.databaseAccount().name,
|
|
|
|
databaseName: this.databaseId,
|
|
|
|
collectionName: this.id(),
|
|
|
|
defaultExperience: this.container.defaultExperience(),
|
|
|
|
dataExplorerArea: Constants.Areas.Tab,
|
|
|
|
tabTitle: title
|
|
|
|
});
|
|
|
|
|
2020-07-09 13:53:37 -07:00
|
|
|
const queryTab: QueryTab = new QueryTab({
|
2020-05-25 21:30:55 -05:00
|
|
|
tabKind: ViewModels.CollectionTabKind.Query,
|
|
|
|
title: title,
|
|
|
|
tabPath: "",
|
|
|
|
collection: this,
|
|
|
|
node: this,
|
|
|
|
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/query`,
|
|
|
|
isActive: ko.observable(false),
|
|
|
|
queryText: queryText,
|
|
|
|
partitionKey: collection.partitionKey,
|
|
|
|
onLoadStartKey: startKey,
|
2020-07-09 13:53:37 -07:00
|
|
|
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
2020-05-25 21:30:55 -05:00
|
|
|
});
|
|
|
|
|
2020-07-09 13:53:37 -07:00
|
|
|
this.container.tabsManager.activateNewTab(queryTab);
|
2020-05-25 21:30:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
public onNewMongoQueryClick(source: any, event: MouseEvent, queryText?: string) {
|
|
|
|
const collection: ViewModels.Collection = source.collection || source;
|
2020-07-09 13:53:37 -07:00
|
|
|
const id = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Query).length + 1;
|
2020-05-25 21:30:55 -05:00
|
|
|
|
|
|
|
const title = "Query " + id;
|
|
|
|
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
|
|
|
databaseAccountName: this.container.databaseAccount().name,
|
|
|
|
databaseName: this.databaseId,
|
|
|
|
collectionName: this.id(),
|
|
|
|
defaultExperience: this.container.defaultExperience(),
|
|
|
|
dataExplorerArea: Constants.Areas.Tab,
|
|
|
|
tabTitle: title
|
|
|
|
});
|
|
|
|
|
2020-07-09 13:53:37 -07:00
|
|
|
const mongoQueryTab: MongoQueryTab = new MongoQueryTab({
|
2020-05-25 21:30:55 -05:00
|
|
|
tabKind: ViewModels.CollectionTabKind.Query,
|
|
|
|
title: title,
|
|
|
|
tabPath: "",
|
|
|
|
collection: this,
|
|
|
|
node: this,
|
|
|
|
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/mongoQuery`,
|
|
|
|
isActive: ko.observable(false),
|
|
|
|
partitionKey: collection.partitionKey,
|
|
|
|
onLoadStartKey: startKey,
|
2020-07-09 13:53:37 -07:00
|
|
|
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
2020-05-25 21:30:55 -05:00
|
|
|
});
|
|
|
|
|
2020-07-09 13:53:37 -07:00
|
|
|
this.container.tabsManager.activateNewTab(mongoQueryTab);
|
2020-05-25 21:30:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
public onNewGraphClick() {
|
2020-07-09 13:53:37 -07:00
|
|
|
const id: number = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Graph).length + 1;
|
|
|
|
const title: string = "Graph Query " + id;
|
|
|
|
|
|
|
|
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
|
|
|
|
databaseAccountName: this.container.databaseAccount().name,
|
|
|
|
databaseName: this.databaseId,
|
|
|
|
collectionName: this.id(),
|
|
|
|
defaultExperience: this.container.defaultExperience(),
|
|
|
|
dataExplorerArea: Constants.Areas.Tab,
|
|
|
|
tabTitle: title
|
|
|
|
});
|
|
|
|
|
|
|
|
const graphTab: GraphTab = new GraphTab({
|
2020-08-06 14:03:46 -05:00
|
|
|
account: userContext.databaseAccount,
|
2020-07-09 13:53:37 -07:00
|
|
|
tabKind: ViewModels.CollectionTabKind.Graph,
|
|
|
|
node: this,
|
|
|
|
title: title,
|
|
|
|
tabPath: "",
|
|
|
|
collection: this,
|
2020-08-06 14:03:46 -05:00
|
|
|
masterKey: userContext.masterKey || "",
|
2020-07-09 13:53:37 -07:00
|
|
|
collectionPartitionKeyProperty: this.partitionKeyProperty,
|
|
|
|
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/graphs`,
|
|
|
|
collectionId: this.id(),
|
|
|
|
isActive: ko.observable(false),
|
|
|
|
databaseId: this.databaseId,
|
|
|
|
isTabsContentExpanded: this.container.isTabsContentExpanded,
|
|
|
|
onLoadStartKey: startKey,
|
|
|
|
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
|
|
|
});
|
|
|
|
|
|
|
|
this.container.tabsManager.activateNewTab(graphTab);
|
2020-05-25 21:30:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
public onNewMongoShellClick() {
|
2020-07-09 13:53:37 -07:00
|
|
|
const id = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.MongoShell).length + 1;
|
|
|
|
const mongoShellTab: MongoShellTab = new MongoShellTab({
|
2020-05-25 21:30:55 -05:00
|
|
|
tabKind: ViewModels.CollectionTabKind.MongoShell,
|
|
|
|
title: "Shell " + id,
|
|
|
|
tabPath: "",
|
|
|
|
collection: this,
|
|
|
|
node: this,
|
|
|
|
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/mongoShell`,
|
|
|
|
isActive: ko.observable(false),
|
2020-07-09 13:53:37 -07:00
|
|
|
onUpdateTabsButtons: this.container.onUpdateTabsButtons
|
2020-05-25 21:30:55 -05:00
|
|
|
});
|
|
|
|
|
2020-07-09 13:53:37 -07:00
|
|
|
this.container.tabsManager.activateNewTab(mongoShellTab);
|
2020-05-25 21:30:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
public onNewStoredProcedureClick(source: ViewModels.Collection, event: MouseEvent) {
|
|
|
|
StoredProcedure.create(source, event);
|
|
|
|
}
|
|
|
|
|
|
|
|
public onNewUserDefinedFunctionClick(source: ViewModels.Collection, event: MouseEvent) {
|
|
|
|
UserDefinedFunction.create(source, event);
|
|
|
|
}
|
|
|
|
|
|
|
|
public onNewTriggerClick(source: ViewModels.Collection, event: MouseEvent) {
|
|
|
|
Trigger.create(source, event);
|
|
|
|
}
|
|
|
|
|
2020-09-15 13:31:30 -05:00
|
|
|
public createStoredProcedureNode(data: StoredProcedureDefinition & Resource): StoredProcedure {
|
2020-05-25 21:30:55 -05:00
|
|
|
const node = new StoredProcedure(this.container, this, data);
|
|
|
|
this.container.selectedNode(node);
|
|
|
|
this.children.push(node);
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
|
2020-09-15 13:31:30 -05:00
|
|
|
public createUserDefinedFunctionNode(data: UserDefinedFunctionDefinition & Resource): UserDefinedFunction {
|
2020-05-25 21:30:55 -05:00
|
|
|
const node = new UserDefinedFunction(this.container, this, data);
|
|
|
|
this.container.selectedNode(node);
|
|
|
|
this.children.push(node);
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
|
2020-09-15 13:31:30 -05:00
|
|
|
public createTriggerNode(data: TriggerDefinition & Resource): Trigger {
|
2020-05-25 21:30:55 -05:00
|
|
|
const node = new Trigger(this.container, this, data);
|
|
|
|
this.container.selectedNode(node);
|
|
|
|
this.children.push(node);
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
|
2020-07-21 13:50:51 -05:00
|
|
|
public findStoredProcedureWithId(sprocId: string): StoredProcedure {
|
|
|
|
return _.find(this.storedProcedures(), (storedProcedure: StoredProcedure) => storedProcedure.id() === sprocId);
|
2020-05-25 21:30:55 -05:00
|
|
|
}
|
|
|
|
|
2020-07-21 13:50:51 -05:00
|
|
|
public findTriggerWithId(triggerId: string): Trigger {
|
|
|
|
return _.find(this.triggers(), (trigger: Trigger) => trigger.id() === triggerId);
|
2020-05-25 21:30:55 -05:00
|
|
|
}
|
|
|
|
|
2020-07-21 13:50:51 -05:00
|
|
|
public findUserDefinedFunctionWithId(userDefinedFunctionId: string): UserDefinedFunction {
|
2020-05-25 21:30:55 -05:00
|
|
|
return _.find(
|
|
|
|
this.userDefinedFunctions(),
|
2020-07-21 13:50:51 -05:00
|
|
|
(userDefinedFunction: Trigger) => userDefinedFunction.id() === userDefinedFunctionId
|
2020-05-25 21:30:55 -05:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public expandCollapseStoredProcedures() {
|
|
|
|
this.selectedSubnodeKind(ViewModels.CollectionTabKind.StoredProcedures);
|
|
|
|
if (this.isStoredProceduresExpanded()) {
|
|
|
|
this.collapseStoredProcedures();
|
|
|
|
} else {
|
|
|
|
this.expandStoredProcedures();
|
|
|
|
}
|
2020-10-13 13:29:39 -07:00
|
|
|
this.container.tabsManager.refreshActiveTab(
|
|
|
|
tab => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
|
|
|
);
|
2020-05-25 21:30:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
public expandStoredProcedures() {
|
|
|
|
if (this.isStoredProceduresExpanded()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.loadStoredProcedures().then(
|
|
|
|
() => {
|
|
|
|
this.isStoredProceduresExpanded(true);
|
|
|
|
TelemetryProcessor.trace(Action.ExpandTreeNode, ActionModifiers.Mark, {
|
|
|
|
description: "Stored procedures node",
|
|
|
|
databaseAccountName: this.container.databaseAccount().name,
|
|
|
|
databaseName: this.databaseId,
|
|
|
|
collectionName: this.id(),
|
|
|
|
defaultExperience: this.container.defaultExperience(),
|
|
|
|
dataExplorerArea: Constants.Areas.ResourceTree
|
|
|
|
});
|
|
|
|
},
|
|
|
|
error => {
|
|
|
|
TelemetryProcessor.trace(Action.ExpandTreeNode, ActionModifiers.Failed, {
|
|
|
|
description: "Stored procedures node",
|
|
|
|
databaseAccountName: this.container.databaseAccount().name,
|
|
|
|
databaseName: this.databaseId,
|
|
|
|
collectionName: this.id(),
|
|
|
|
defaultExperience: this.container.defaultExperience(),
|
|
|
|
dataExplorerArea: Constants.Areas.ResourceTree,
|
2020-10-21 14:28:30 -07:00
|
|
|
error: typeof error === "string" ? error : error.message
|
2020-05-25 21:30:55 -05:00
|
|
|
});
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public collapseStoredProcedures() {
|
|
|
|
if (!this.isStoredProceduresExpanded()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.isStoredProceduresExpanded(false);
|
|
|
|
TelemetryProcessor.trace(Action.CollapseTreeNode, ActionModifiers.Mark, {
|
|
|
|
description: "Stored procedures node",
|
|
|
|
databaseAccountName: this.container.databaseAccount().name,
|
|
|
|
databaseName: this.databaseId,
|
|
|
|
collectionName: this.id(),
|
|
|
|
defaultExperience: this.container.defaultExperience(),
|
|
|
|
dataExplorerArea: Constants.Areas.ResourceTree
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
public expandCollapseUserDefinedFunctions() {
|
|
|
|
this.selectedSubnodeKind(ViewModels.CollectionTabKind.UserDefinedFunctions);
|
|
|
|
if (this.isUserDefinedFunctionsExpanded()) {
|
|
|
|
this.collapseUserDefinedFunctions();
|
|
|
|
} else {
|
|
|
|
this.expandUserDefinedFunctions();
|
|
|
|
}
|
2020-10-13 13:29:39 -07:00
|
|
|
this.container.tabsManager.refreshActiveTab(
|
|
|
|
tab => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
|
|
|
);
|
2020-05-25 21:30:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
public expandUserDefinedFunctions() {
|
|
|
|
if (this.isUserDefinedFunctionsExpanded()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.loadUserDefinedFunctions().then(
|
|
|
|
() => {
|
|
|
|
this.isUserDefinedFunctionsExpanded(true);
|
|
|
|
TelemetryProcessor.trace(Action.ExpandTreeNode, ActionModifiers.Mark, {
|
|
|
|
description: "UDF node",
|
|
|
|
databaseAccountName: this.container.databaseAccount().name,
|
|
|
|
databaseName: this.databaseId,
|
|
|
|
collectionName: this.id(),
|
|
|
|
defaultExperience: this.container.defaultExperience(),
|
|
|
|
dataExplorerArea: Constants.Areas.ResourceTree
|
|
|
|
});
|
|
|
|
},
|
|
|
|
error => {
|
|
|
|
TelemetryProcessor.trace(Action.ExpandTreeNode, ActionModifiers.Failed, {
|
|
|
|
description: "UDF node",
|
|
|
|
databaseAccountName: this.container.databaseAccount().name,
|
|
|
|
databaseName: this.databaseId,
|
|
|
|
collectionName: this.id(),
|
|
|
|
defaultExperience: this.container.defaultExperience(),
|
|
|
|
dataExplorerArea: Constants.Areas.ResourceTree,
|
2020-10-21 14:28:30 -07:00
|
|
|
error: typeof error === "string" ? error : error.message
|
2020-05-25 21:30:55 -05:00
|
|
|
});
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public collapseUserDefinedFunctions() {
|
|
|
|
if (!this.isUserDefinedFunctionsExpanded()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.isUserDefinedFunctionsExpanded(false);
|
|
|
|
TelemetryProcessor.trace(Action.ExpandTreeNode, ActionModifiers.Mark, {
|
|
|
|
description: "UDF node",
|
|
|
|
databaseAccountName: this.container.databaseAccount().name,
|
|
|
|
databaseName: this.databaseId,
|
|
|
|
collectionName: this.id(),
|
|
|
|
defaultExperience: this.container.defaultExperience(),
|
|
|
|
dataExplorerArea: Constants.Areas.ResourceTree
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
public expandCollapseTriggers() {
|
|
|
|
this.selectedSubnodeKind(ViewModels.CollectionTabKind.Triggers);
|
|
|
|
if (this.isTriggersExpanded()) {
|
|
|
|
this.collapseTriggers();
|
|
|
|
} else {
|
|
|
|
this.expandTriggers();
|
|
|
|
}
|
2020-10-13 13:29:39 -07:00
|
|
|
this.container.tabsManager.refreshActiveTab(
|
|
|
|
tab => tab.collection && tab.collection.databaseId === this.databaseId && tab.collection.id() === this.id()
|
|
|
|
);
|
2020-05-25 21:30:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
public expandTriggers() {
|
|
|
|
if (this.isTriggersExpanded()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.loadTriggers().then(
|
|
|
|
() => {
|
|
|
|
this.isTriggersExpanded(true);
|
|
|
|
TelemetryProcessor.trace(Action.ExpandTreeNode, ActionModifiers.Mark, {
|
|
|
|
description: "Triggers node",
|
|
|
|
databaseAccountName: this.container.databaseAccount().name,
|
|
|
|
databaseName: this.databaseId,
|
|
|
|
collectionName: this.id(),
|
|
|
|
defaultExperience: this.container.defaultExperience(),
|
|
|
|
dataExplorerArea: Constants.Areas.ResourceTree
|
|
|
|
});
|
|
|
|
},
|
|
|
|
error => {
|
|
|
|
this.isTriggersExpanded(true);
|
|
|
|
TelemetryProcessor.trace(Action.ExpandTreeNode, ActionModifiers.Mark, {
|
|
|
|
description: "Triggers node",
|
|
|
|
databaseAccountName: this.container.databaseAccount().name,
|
|
|
|
databaseName: this.databaseId,
|
|
|
|
collectionName: this.id(),
|
|
|
|
defaultExperience: this.container.defaultExperience(),
|
|
|
|
dataExplorerArea: Constants.Areas.ResourceTree,
|
2020-10-21 14:28:30 -07:00
|
|
|
error: typeof error === "string" ? error : error.message
|
2020-05-25 21:30:55 -05:00
|
|
|
});
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public collapseTriggers() {
|
|
|
|
if (!this.isTriggersExpanded()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.isTriggersExpanded(false);
|
|
|
|
TelemetryProcessor.trace(Action.CollapseTreeNode, ActionModifiers.Mark, {
|
|
|
|
description: "Triggers node",
|
|
|
|
databaseAccountName: this.container.databaseAccount().name,
|
|
|
|
databaseName: this.databaseId,
|
|
|
|
collectionName: this.id(),
|
|
|
|
defaultExperience: this.container.defaultExperience(),
|
|
|
|
dataExplorerArea: Constants.Areas.ResourceTree
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-09-15 13:31:30 -05:00
|
|
|
public loadStoredProcedures(): Promise<any> {
|
|
|
|
return readStoredProcedures(this.databaseId, this.id()).then(storedProcedures => {
|
2020-07-27 12:58:27 -05:00
|
|
|
const storedProceduresNodes: ViewModels.TreeNode[] = storedProcedures.map(
|
|
|
|
storedProcedure => new StoredProcedure(this.container, this, storedProcedure)
|
|
|
|
);
|
|
|
|
const otherNodes = this.children().filter(node => node.nodeKind !== "StoredProcedure");
|
|
|
|
const allNodes = otherNodes.concat(storedProceduresNodes);
|
|
|
|
this.children(allNodes);
|
|
|
|
});
|
2020-05-25 21:30:55 -05:00
|
|
|
}
|
|
|
|
|
2020-09-15 13:31:30 -05:00
|
|
|
public loadUserDefinedFunctions(): Promise<any> {
|
|
|
|
return readUserDefinedFunctions(this.databaseId, this.id()).then(userDefinedFunctions => {
|
2020-07-27 12:58:27 -05:00
|
|
|
const userDefinedFunctionsNodes: ViewModels.TreeNode[] = userDefinedFunctions.map(
|
|
|
|
udf => new UserDefinedFunction(this.container, this, udf)
|
|
|
|
);
|
|
|
|
const otherNodes = this.children().filter(node => node.nodeKind !== "UserDefinedFunction");
|
|
|
|
const allNodes = otherNodes.concat(userDefinedFunctionsNodes);
|
|
|
|
this.children(allNodes);
|
|
|
|
});
|
2020-05-25 21:30:55 -05:00
|
|
|
}
|
|
|
|
|
2020-09-15 13:31:30 -05:00
|
|
|
public loadTriggers(): Promise<any> {
|
|
|
|
return readTriggers(this.databaseId, this.id()).then(triggers => {
|
2020-07-27 12:58:27 -05:00
|
|
|
const triggerNodes: ViewModels.TreeNode[] = triggers.map(trigger => new Trigger(this.container, this, trigger));
|
|
|
|
const otherNodes = this.children().filter(node => node.nodeKind !== "Trigger");
|
|
|
|
const allNodes = otherNodes.concat(triggerNodes);
|
|
|
|
this.children(allNodes);
|
|
|
|
});
|
2020-05-25 21:30:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
public onDragOver(source: Collection, event: { originalEvent: DragEvent }) {
|
|
|
|
event.originalEvent.stopPropagation();
|
|
|
|
event.originalEvent.preventDefault();
|
|
|
|
}
|
|
|
|
|
|
|
|
public onDrop(source: Collection, event: { originalEvent: DragEvent }) {
|
|
|
|
event.originalEvent.stopPropagation();
|
|
|
|
event.originalEvent.preventDefault();
|
|
|
|
this.uploadFiles(event.originalEvent.dataTransfer.files);
|
|
|
|
}
|
|
|
|
|
|
|
|
public onDeleteCollectionContextMenuClick(source: ViewModels.Collection, event: MouseEvent | KeyboardEvent) {
|
|
|
|
this.container.deleteCollectionConfirmationPane.open();
|
|
|
|
}
|
|
|
|
|
|
|
|
public uploadFiles = (fileList: FileList): Q.Promise<UploadDetails> => {
|
|
|
|
// TODO: right now web worker is not working with AAD flow. Use main thread for upload for now until we have backend upload capability
|
2020-10-14 22:25:13 -05:00
|
|
|
if (configContext.platform === Platform.Hosted && window.authType === AuthType.AAD) {
|
2020-05-25 21:30:55 -05:00
|
|
|
return this._uploadFilesCors(fileList);
|
|
|
|
}
|
|
|
|
const documentUploader: Worker = new UploadWorker();
|
|
|
|
const deferred: Q.Deferred<UploadDetails> = Q.defer<UploadDetails>();
|
|
|
|
let inProgressNotificationId: string = "";
|
|
|
|
|
|
|
|
if (!fileList || fileList.length === 0) {
|
|
|
|
return Q.reject("No files specified");
|
|
|
|
}
|
|
|
|
documentUploader.onmessage = (event: MessageEvent) => {
|
|
|
|
const numSuccessful: number = event.data.numUploadsSuccessful;
|
|
|
|
const numFailed: number = event.data.numUploadsFailed;
|
|
|
|
const runtimeError: string = event.data.runtimeError;
|
|
|
|
const uploadDetails: UploadDetails = event.data.uploadDetails;
|
|
|
|
|
|
|
|
NotificationConsoleUtils.clearInProgressMessageWithId(inProgressNotificationId);
|
|
|
|
documentUploader.terminate();
|
|
|
|
if (!!runtimeError) {
|
|
|
|
deferred.reject(runtimeError);
|
|
|
|
} else if (numSuccessful === 0) {
|
|
|
|
// all uploads failed
|
|
|
|
NotificationConsoleUtils.logConsoleMessage(
|
|
|
|
ConsoleDataType.Error,
|
|
|
|
`Failed to upload all documents to container ${this.id()}`
|
|
|
|
);
|
|
|
|
} else if (numFailed > 0) {
|
|
|
|
NotificationConsoleUtils.logConsoleMessage(
|
|
|
|
ConsoleDataType.Error,
|
|
|
|
`Failed to upload ${numFailed} of ${numSuccessful + numFailed} documents to container ${this.id()}`
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
NotificationConsoleUtils.logConsoleMessage(
|
|
|
|
ConsoleDataType.Info,
|
|
|
|
`Successfully uploaded all ${numSuccessful} documents to container ${this.id()}`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
this._logUploadDetailsInConsole(uploadDetails);
|
|
|
|
deferred.resolve(uploadDetails);
|
|
|
|
};
|
|
|
|
documentUploader.onerror = (event: ErrorEvent): void => {
|
|
|
|
documentUploader.terminate();
|
|
|
|
deferred.reject(event.error);
|
|
|
|
};
|
|
|
|
|
|
|
|
const uploaderMessage: StartUploadMessageParams = {
|
|
|
|
files: fileList,
|
|
|
|
documentClientParams: {
|
|
|
|
databaseId: this.databaseId,
|
|
|
|
containerId: this.id(),
|
2020-08-06 14:03:46 -05:00
|
|
|
masterKey: userContext.masterKey,
|
|
|
|
endpoint: userContext.endpoint,
|
|
|
|
accessToken: userContext.accessToken,
|
|
|
|
platform: configContext.platform,
|
|
|
|
databaseAccount: userContext.databaseAccount
|
2020-05-25 21:30:55 -05:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
documentUploader.postMessage(uploaderMessage);
|
|
|
|
inProgressNotificationId = NotificationConsoleUtils.logConsoleMessage(
|
|
|
|
ConsoleDataType.InProgress,
|
|
|
|
`Uploading and creating documents in container ${this.id()}`
|
|
|
|
);
|
|
|
|
|
|
|
|
return deferred.promise;
|
|
|
|
};
|
|
|
|
|
|
|
|
private _uploadFilesCors(files: FileList): Q.Promise<UploadDetails> {
|
|
|
|
const deferred: Q.Deferred<UploadDetails> = Q.defer<UploadDetails>();
|
|
|
|
const promises: Array<Q.Promise<UploadDetailsRecord>> = [];
|
|
|
|
|
|
|
|
for (let i = 0; i < files.length; i++) {
|
|
|
|
promises.push(this._uploadFile(files[i]));
|
|
|
|
}
|
|
|
|
Q.all(promises).then((uploadDetails: Array<UploadDetailsRecord>) => {
|
|
|
|
deferred.resolve({ data: uploadDetails });
|
|
|
|
});
|
|
|
|
|
|
|
|
return deferred.promise;
|
|
|
|
}
|
|
|
|
|
|
|
|
private _uploadFile(file: File): Q.Promise<UploadDetailsRecord> {
|
|
|
|
const deferred: Q.Deferred<UploadDetailsRecord> = Q.defer();
|
|
|
|
|
|
|
|
const reader = new FileReader();
|
|
|
|
reader.onload = (evt: any): void => {
|
|
|
|
const fileData: string = evt.target.result;
|
|
|
|
this._createDocumentsFromFile(file.name, fileData).then(record => {
|
|
|
|
deferred.resolve(record);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
reader.onerror = (evt: ProgressEvent): void => {
|
|
|
|
deferred.resolve({
|
|
|
|
fileName: file.name,
|
|
|
|
numSucceeded: 0,
|
|
|
|
numFailed: 1,
|
|
|
|
errors: [(evt as any).error.message]
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
reader.readAsText(file);
|
|
|
|
|
|
|
|
return deferred.promise;
|
|
|
|
}
|
|
|
|
|
|
|
|
private _createDocumentsFromFile(fileName: string, documentContent: string): Q.Promise<UploadDetailsRecord> {
|
|
|
|
const deferred: Q.Deferred<UploadDetailsRecord> = Q.defer();
|
|
|
|
const record: UploadDetailsRecord = {
|
|
|
|
fileName: fileName,
|
|
|
|
numSucceeded: 0,
|
|
|
|
numFailed: 0,
|
|
|
|
errors: []
|
|
|
|
};
|
|
|
|
|
|
|
|
try {
|
|
|
|
const content = JSON.parse(documentContent);
|
|
|
|
const promises: Array<Q.Promise<any>> = [];
|
|
|
|
|
|
|
|
const triggerCreateDocument: (documentContent: any) => Q.Promise<any> = (documentContent: any) => {
|
2020-07-27 12:58:27 -05:00
|
|
|
return createDocument(this, documentContent).then(
|
2020-05-25 21:30:55 -05:00
|
|
|
doc => {
|
|
|
|
record.numSucceeded++;
|
|
|
|
return Q.resolve();
|
|
|
|
},
|
|
|
|
error => {
|
|
|
|
record.numFailed++;
|
2020-10-21 14:28:30 -07:00
|
|
|
record.errors = [...record.errors, error.message];
|
2020-05-25 21:30:55 -05:00
|
|
|
return Q.resolve();
|
|
|
|
}
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
if (Array.isArray(content)) {
|
|
|
|
for (let i = 0; i < content.length; i++) {
|
|
|
|
promises.push(triggerCreateDocument(content[i]));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
promises.push(triggerCreateDocument(content));
|
|
|
|
}
|
|
|
|
|
|
|
|
Q.all(promises).then(() => {
|
|
|
|
deferred.resolve(record);
|
|
|
|
});
|
|
|
|
} catch (e) {
|
|
|
|
record.numFailed++;
|
|
|
|
record.errors = [...record.errors, e.message];
|
|
|
|
deferred.resolve(record);
|
|
|
|
}
|
|
|
|
return deferred.promise;
|
|
|
|
}
|
|
|
|
|
|
|
|
private _getPendingThroughputSplitNotification(): Q.Promise<DataModels.Notification> {
|
|
|
|
if (!this.container) {
|
|
|
|
return Q.resolve(undefined);
|
|
|
|
}
|
|
|
|
|
|
|
|
const deferred: Q.Deferred<DataModels.Notification> = Q.defer<DataModels.Notification>();
|
2020-10-12 22:10:28 -05:00
|
|
|
fetchPortalNotifications().then(
|
2020-05-25 21:30:55 -05:00
|
|
|
(notifications: DataModels.Notification[]) => {
|
|
|
|
if (!notifications || notifications.length === 0) {
|
|
|
|
deferred.resolve(undefined);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const pendingNotification = _.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)
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
deferred.resolve(pendingNotification);
|
|
|
|
},
|
|
|
|
(error: any) => {
|
|
|
|
Logger.logError(
|
|
|
|
JSON.stringify({
|
2020-10-21 14:28:30 -07:00
|
|
|
error: error.message,
|
2020-05-25 21:30:55 -05:00
|
|
|
accountName: this.container && this.container.databaseAccount(),
|
|
|
|
databaseName: this.databaseId,
|
|
|
|
collectionName: this.id()
|
|
|
|
}),
|
|
|
|
"Settings tree node"
|
|
|
|
);
|
|
|
|
deferred.resolve(undefined);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
return deferred.promise;
|
|
|
|
}
|
|
|
|
|
|
|
|
private _logUploadDetailsInConsole(uploadDetails: UploadDetails): void {
|
|
|
|
const uploadDetailsRecords: UploadDetailsRecord[] = uploadDetails.data;
|
|
|
|
const numFiles: number = uploadDetailsRecords.length;
|
|
|
|
const stackTraceLimit: number = 100;
|
|
|
|
let stackTraceCount: number = 0;
|
|
|
|
let currentFileIndex = 0;
|
|
|
|
while (stackTraceCount < stackTraceLimit && currentFileIndex < numFiles) {
|
|
|
|
const errors: string[] = uploadDetailsRecords[currentFileIndex].errors;
|
|
|
|
for (let i = 0; i < errors.length; i++) {
|
|
|
|
if (stackTraceCount >= stackTraceLimit) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
NotificationConsoleUtils.logConsoleMessage(
|
|
|
|
ConsoleDataType.Error,
|
|
|
|
`Document creation error for container ${this.id()} - file ${
|
|
|
|
uploadDetailsRecords[currentFileIndex].fileName
|
|
|
|
}: ${errors[i]}`
|
|
|
|
);
|
|
|
|
stackTraceCount++;
|
|
|
|
}
|
|
|
|
currentFileIndex++;
|
|
|
|
}
|
|
|
|
|
|
|
|
uploadDetailsRecords.forEach((record: UploadDetailsRecord) => {
|
|
|
|
const consoleDataType: ConsoleDataType = record.numFailed > 0 ? ConsoleDataType.Error : ConsoleDataType.Info;
|
|
|
|
NotificationConsoleUtils.logConsoleMessage(
|
|
|
|
consoleDataType,
|
|
|
|
`Item creation summary for container ${this.id()} - file ${record.fileName}: ${
|
|
|
|
record.numSucceeded
|
|
|
|
} items created, ${record.numFailed} errors`
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Top-level method that will open the correct tab type depending on account API
|
|
|
|
*/
|
|
|
|
public openTab(): void {
|
|
|
|
if (this.container.isPreferredApiTable()) {
|
|
|
|
this.onTableEntitiesClick();
|
|
|
|
return;
|
|
|
|
} else if (this.container.isPreferredApiCassandra()) {
|
|
|
|
this.onTableEntitiesClick();
|
|
|
|
return;
|
|
|
|
} else if (this.container.isPreferredApiGraph()) {
|
|
|
|
this.onGraphDocumentsClick();
|
|
|
|
return;
|
|
|
|
} else if (this.container.isPreferredApiMongoDB()) {
|
|
|
|
this.onMongoDBDocumentsClick();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.onDocumentDBDocumentsClick();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get correct collection label depending on account API
|
|
|
|
*/
|
|
|
|
public getLabel(): string {
|
|
|
|
if (this.container.isPreferredApiTable()) {
|
2020-06-24 13:35:30 -05:00
|
|
|
return "Entities";
|
2020-05-25 21:30:55 -05:00
|
|
|
} else if (this.container.isPreferredApiCassandra()) {
|
|
|
|
return "Rows";
|
|
|
|
} else if (this.container.isPreferredApiGraph()) {
|
|
|
|
return "Graph";
|
|
|
|
} else if (this.container.isPreferredApiMongoDB()) {
|
|
|
|
return "Documents";
|
|
|
|
}
|
|
|
|
|
|
|
|
return "Items";
|
|
|
|
}
|
|
|
|
|
|
|
|
public getDatabase(): ViewModels.Database {
|
|
|
|
return this.container.findDatabaseWithId(this.databaseId);
|
|
|
|
}
|
2020-09-28 12:54:28 -07:00
|
|
|
|
|
|
|
public async loadOffer(): Promise<void> {
|
|
|
|
if (!this.container.isServerlessEnabled() && !this.offer()) {
|
|
|
|
this.container.isRefreshingExplorer(true);
|
|
|
|
const startKey: number = TelemetryProcessor.traceStart(Action.LoadOffers, {
|
|
|
|
databaseAccountName: this.container.databaseAccount().name,
|
|
|
|
databaseName: this.databaseId,
|
|
|
|
collectionName: this.id(),
|
|
|
|
defaultExperience: this.container.defaultExperience()
|
|
|
|
});
|
|
|
|
|
|
|
|
const params: DataModels.ReadCollectionOfferParams = {
|
|
|
|
collectionId: this.id(),
|
|
|
|
collectionResourceId: this.self,
|
|
|
|
databaseId: this.databaseId
|
|
|
|
};
|
|
|
|
|
|
|
|
try {
|
|
|
|
this.offer(await readCollectionOffer(params));
|
|
|
|
await this.loadCollectionQuotaInfo();
|
|
|
|
|
|
|
|
TelemetryProcessor.traceSuccess(
|
|
|
|
Action.LoadOffers,
|
|
|
|
{
|
|
|
|
databaseAccountName: this.container.databaseAccount().name,
|
|
|
|
databaseName: this.databaseId,
|
|
|
|
collectionName: this.id(),
|
|
|
|
defaultExperience: this.container.defaultExperience(),
|
|
|
|
offerVersion: this.offer()?.offerVersion
|
|
|
|
},
|
|
|
|
startKey
|
|
|
|
);
|
|
|
|
} catch (error) {
|
|
|
|
TelemetryProcessor.traceFailure(
|
|
|
|
Action.LoadOffers,
|
|
|
|
{
|
|
|
|
databaseAccountName: this.container.databaseAccount().name,
|
|
|
|
databaseName: this.databaseId,
|
|
|
|
collectionName: this.id(),
|
|
|
|
defaultExperience: this.container.defaultExperience()
|
|
|
|
},
|
|
|
|
startKey
|
|
|
|
);
|
|
|
|
throw error;
|
|
|
|
} finally {
|
|
|
|
this.container.isRefreshingExplorer(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-05-25 21:30:55 -05:00
|
|
|
}
|