From a264ea22756de1407c2a87b8b003bceeb2b6293c Mon Sep 17 00:00:00 2001 From: Steve Faulkner Date: Fri, 16 Apr 2021 13:23:03 -0500 Subject: [PATCH 01/27] Adds retry logic to Upload JSON (#684) --- src/Common/dataAccess/bulkCreateDocument.ts | 5 ++- src/Explorer/Tree/Collection.ts | 40 +++++++++++++++------ 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/Common/dataAccess/bulkCreateDocument.ts b/src/Common/dataAccess/bulkCreateDocument.ts index 0eb0d4475..ec7ae7b8f 100644 --- a/src/Common/dataAccess/bulkCreateDocument.ts +++ b/src/Common/dataAccess/bulkCreateDocument.ts @@ -22,9 +22,12 @@ export const bulkCreateDocument = async ( ); const successCount = response.filter((r) => r.statusCode === 201).length; + const throttledCount = response.filter((r) => r.statusCode === 429).length; logConsoleInfo( - `${documents.length} operations completed for container ${collection.id()}. ${successCount} operations succeeded` + `${ + documents.length + } operations completed for container ${collection.id()}. ${successCount} operations succeeded. ${throttledCount} operations throttled` ); return response; } catch (error) { diff --git a/src/Explorer/Tree/Collection.ts b/src/Explorer/Tree/Collection.ts index 62670cf54..11ae8e3b4 100644 --- a/src/Explorer/Tree/Collection.ts +++ b/src/Explorer/Tree/Collection.ts @@ -18,6 +18,7 @@ import { UploadDetailsRecord } from "../../Contracts/ViewModels"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import { userContext } from "../../UserContext"; +import { logConsoleInfo } from "../../Utils/NotificationConsoleUtils"; import Explorer from "../Explorer"; import { CassandraAPIDataClient, CassandraTableKey, CassandraTableKeys } from "../Tables/TableDataClient"; import ConflictsTab from "../Tabs/ConflictsTab"; @@ -1031,21 +1032,36 @@ export default class Collection implements ViewModels.Collection { try { const parsedContent = JSON.parse(documentContent); if (Array.isArray(parsedContent)) { - const chunkSize = 50; // 100 is the max # of bulk operations the SDK currently accepts but usually results in throttles on 400RU collections + const chunkSize = 100; // 100 is the max # of bulk operations the SDK currently accepts const chunkedContent = Array.from({ length: Math.ceil(parsedContent.length / chunkSize) }, (_, index) => parsedContent.slice(index * chunkSize, index * chunkSize + chunkSize) ); for (const chunk of chunkedContent) { - const responses = await bulkCreateDocument(this, chunk); - for (const response of responses) { - if (response.statusCode === 201) { - record.numSucceeded++; - } else if (response.statusCode === 429) { - record.numThrottled++; - } else { - record.numFailed++; - record.errors = [...record.errors, `${response.statusCode} ${response.resourceBody}`]; + let retryAttempts = 0; + let chunkComplete = false; + let documentsToAttempt = chunk; + while (retryAttempts < 10 && !chunkComplete) { + const responses = await bulkCreateDocument(this, documentsToAttempt); + const attemptedDocuments = [...documentsToAttempt]; + documentsToAttempt = []; + responses.forEach((response, index) => { + if (response.statusCode === 201) { + record.numSucceeded++; + } else if (response.statusCode === 429) { + documentsToAttempt.push(attemptedDocuments[index]); + } else { + record.numFailed++; + } + }); + if (documentsToAttempt.length === 0) { + chunkComplete = true; + break; } + logConsoleInfo( + `${documentsToAttempt.length} document creations were throttled. Waiting ${retryAttempts} seconds and retrying throttled documents` + ); + retryAttempts++; + await sleep(retryAttempts); } } } else { @@ -1145,3 +1161,7 @@ export default class Collection implements ViewModels.Collection { } } } + +function sleep(seconds: number) { + return new Promise((resolve) => setTimeout(resolve, seconds * 1000)); +} From 02ea26da71d973c10704743fdccc2b7323b076c7 Mon Sep 17 00:00:00 2001 From: Hardikkumar Nai <80053762+hardiknai-techm@users.noreply.github.com> Date: Sun, 18 Apr 2021 02:24:47 +0530 Subject: [PATCH 02/27] Remove Explorer.isPreferredCassandraAPI (#654) Co-authored-by: Steve Faulkner <471400+southpolesteve@users.noreply.github.com> --- src/Explorer/ContextMenuButtonFactory.ts | 6 +- .../Settings/SettingsComponent.test.tsx | 9 ++- .../Controls/Settings/SettingsComponent.tsx | 4 +- .../SubSettingsComponent.test.tsx | 12 +++- .../SubSettingsComponent.tsx | 2 +- .../SettingsComponent.test.tsx.snap | 4 -- .../ContainerSampleGenerator.test.ts | 4 +- src/Explorer/Explorer.tsx | 11 +--- .../CommandBar/CommandBarComponentAdapter.tsx | 1 - .../CommandBarComponentButtonFactory.test.ts | 27 ++++++--- .../CommandBarComponentButtonFactory.tsx | 6 +- src/Explorer/Panes/AddCollectionPane.ts | 4 +- src/Explorer/Panes/AddDatabasePane.ts | 8 +-- .../__snapshots__/index.test.tsx.snap | 2 - .../Panes/Tables/AddTableEntityPane.ts | 17 +++--- .../Panes/Tables/EditTableEntityPane.ts | 36 +++++------- src/Explorer/Panes/Tables/TableEntityPane.ts | 58 +++++++++---------- .../__snapshots__/index.test.tsx.snap | 1 - ...eteDatabaseConfirmationPanel.test.tsx.snap | 1 - src/Explorer/SplashScreen/SplashScreen.tsx | 2 +- .../Tables/DataTable/TableCommands.ts | 3 +- .../DataTable/TableEntityListViewModel.ts | 12 ++-- .../QueryBuilder/QueryBuilderViewModel.ts | 3 +- .../QueryBuilder/QueryClauseViewModel.ts | 11 ++-- .../Tables/QueryBuilder/QueryViewModel.ts | 7 ++- src/Explorer/Tabs/QueryTablesTab.ts | 34 +++++------ src/Explorer/Tree/Collection.test.ts | 4 +- src/Explorer/Tree/Collection.ts | 8 +-- src/Explorer/Tree/ResourceTreeAdapter.tsx | 2 +- src/RouteHandlers/TabRouteHandler.ts | 2 +- 30 files changed, 150 insertions(+), 151 deletions(-) diff --git a/src/Explorer/ContextMenuButtonFactory.ts b/src/Explorer/ContextMenuButtonFactory.ts index deb55ef5c..42e23de6c 100644 --- a/src/Explorer/ContextMenuButtonFactory.ts +++ b/src/Explorer/ContextMenuButtonFactory.ts @@ -123,7 +123,7 @@ export class ResourceTreeContextMenuButtonFactory { container: Explorer, storedProcedure: StoredProcedure ): TreeNodeMenuItem[] { - if (container.isPreferredApiCassandra()) { + if (userContext.apiType === "Cassandra") { return []; } @@ -137,7 +137,7 @@ export class ResourceTreeContextMenuButtonFactory { } public static createTriggerContextMenuItems(container: Explorer, trigger: Trigger): TreeNodeMenuItem[] { - if (container.isPreferredApiCassandra()) { + if (userContext.apiType === "Cassandra") { return []; } @@ -154,7 +154,7 @@ export class ResourceTreeContextMenuButtonFactory { container: Explorer, userDefinedFunction: UserDefinedFunction ): TreeNodeMenuItem[] { - if (container.isPreferredApiCassandra()) { + if (userContext.apiType === "Cassandra") { return []; } diff --git a/src/Explorer/Controls/Settings/SettingsComponent.test.tsx b/src/Explorer/Controls/Settings/SettingsComponent.test.tsx index 538a8249c..2db5adf5f 100644 --- a/src/Explorer/Controls/Settings/SettingsComponent.test.tsx +++ b/src/Explorer/Controls/Settings/SettingsComponent.test.tsx @@ -5,6 +5,7 @@ import { updateCollection } from "../../../Common/dataAccess/updateCollection"; import { updateOffer } from "../../../Common/dataAccess/updateOffer"; import * as DataModels from "../../../Contracts/DataModels"; import * as ViewModels from "../../../Contracts/ViewModels"; +import { updateUserContext } from "../../../UserContext"; import Explorer from "../../Explorer"; import { CollectionSettingsTabV2 } from "../../Tabs/SettingsTabV2"; import { SettingsComponent, SettingsComponentProps, SettingsComponentState } from "./SettingsComponent"; @@ -107,7 +108,13 @@ describe("SettingsComponent", () => { expect(settingsComponentInstance.shouldShowKeyspaceSharedThroughputMessage()).toEqual(false); const newContainer = new Explorer(); - newContainer.isPreferredApiCassandra = ko.computed(() => true); + updateUserContext({ + databaseAccount: { + properties: { + capabilities: [{ name: "EnableCassandra" }], + }, + } as DataModels.DatabaseAccount, + }); const newCollection = { ...collection }; newCollection.container = newContainer; diff --git a/src/Explorer/Controls/Settings/SettingsComponent.tsx b/src/Explorer/Controls/Settings/SettingsComponent.tsx index 8aa91f50e..4c151eb51 100644 --- a/src/Explorer/Controls/Settings/SettingsComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsComponent.tsx @@ -137,7 +137,7 @@ export class SettingsComponent extends React.Component - this.container && this.container.isPreferredApiCassandra() && hasDatabaseSharedThroughput(this.collection); + this.container && userContext.apiType === "Cassandra" && hasDatabaseSharedThroughput(this.collection); public hasConflictResolution = (): boolean => this.container?.databaseAccount && diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/SubSettingsComponent.test.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/SubSettingsComponent.test.tsx index ac36a4e60..2d25f9da7 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/SubSettingsComponent.test.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/SubSettingsComponent.test.tsx @@ -1,6 +1,7 @@ import { shallow } from "enzyme"; -import ko from "knockout"; import React from "react"; +import { DatabaseAccount } from "../../../../Contracts/DataModels"; +import { updateUserContext } from "../../../../UserContext"; import Explorer from "../../../Explorer"; import { ChangeFeedPolicyState, GeospatialConfigType, TtlOff, TtlOn, TtlOnNoDefault, TtlType } from "../SettingsUtils"; import { collection, container } from "../TestUtils"; @@ -104,8 +105,13 @@ describe("SubSettingsComponent", () => { it("partitionKey not visible", () => { const newContainer = new Explorer(); - - newContainer.isPreferredApiCassandra = ko.computed(() => true); + updateUserContext({ + databaseAccount: { + properties: { + capabilities: [{ name: "EnableCassandra" }], + }, + } as DatabaseAccount, + }); const props = { ...baseProps, container: newContainer }; const subSettingsComponent = new SubSettingsComponent(props); expect(subSettingsComponent.getPartitionKeyVisible()).toEqual(false); diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/SubSettingsComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/SubSettingsComponent.tsx index fd0cb538e..5f05edd75 100644 --- a/src/Explorer/Controls/Settings/SettingsSubComponents/SubSettingsComponent.tsx +++ b/src/Explorer/Controls/Settings/SettingsSubComponents/SubSettingsComponent.tsx @@ -320,7 +320,7 @@ export class SubSettingsComponent extends React.Component { if ( - this.props.container.isPreferredApiCassandra() || + userContext.apiType === "Cassandra" || this.props.container.isPreferredApiTable() || !this.props.collection.partitionKeyProperty || (this.props.container.isPreferredApiMongoDB() && this.props.collection.partitionKey.systemKey) diff --git a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap index d517bea32..1b9c96a0e 100644 --- a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap +++ b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap @@ -647,7 +647,6 @@ exports[`SettingsComponent renders 1`] = ` "isMongoIndexingEnabled": [Function], "isNotebookEnabled": [Function], "isNotebooksEnabledForAccount": [Function], - "isPreferredApiCassandra": [Function], "isPreferredApiGraph": [Function], "isPreferredApiMongoDB": [Function], "isPreferredApiTable": [Function], @@ -1411,7 +1410,6 @@ exports[`SettingsComponent renders 1`] = ` "isMongoIndexingEnabled": [Function], "isNotebookEnabled": [Function], "isNotebooksEnabledForAccount": [Function], - "isPreferredApiCassandra": [Function], "isPreferredApiGraph": [Function], "isPreferredApiMongoDB": [Function], "isPreferredApiTable": [Function], @@ -2188,7 +2186,6 @@ exports[`SettingsComponent renders 1`] = ` "isMongoIndexingEnabled": [Function], "isNotebookEnabled": [Function], "isNotebooksEnabledForAccount": [Function], - "isPreferredApiCassandra": [Function], "isPreferredApiGraph": [Function], "isPreferredApiMongoDB": [Function], "isPreferredApiTable": [Function], @@ -2952,7 +2949,6 @@ exports[`SettingsComponent renders 1`] = ` "isMongoIndexingEnabled": [Function], "isNotebookEnabled": [Function], "isNotebooksEnabledForAccount": [Function], - "isPreferredApiCassandra": [Function], "isPreferredApiGraph": [Function], "isPreferredApiMongoDB": [Function], "isPreferredApiTable": [Function], diff --git a/src/Explorer/DataSamples/ContainerSampleGenerator.test.ts b/src/Explorer/DataSamples/ContainerSampleGenerator.test.ts index 0d7f7cbbc..d59df695f 100644 --- a/src/Explorer/DataSamples/ContainerSampleGenerator.test.ts +++ b/src/Explorer/DataSamples/ContainerSampleGenerator.test.ts @@ -17,7 +17,6 @@ describe("ContainerSampleGenerator", () => { explorerStub.isPreferredApiGraph = ko.computed(() => false); explorerStub.isPreferredApiMongoDB = ko.computed(() => false); explorerStub.isPreferredApiTable = ko.computed(() => false); - explorerStub.isPreferredApiCassandra = ko.computed(() => false); explorerStub.canExceedMaximumValue = ko.computed(() => false); explorerStub.findDatabaseWithId = () => database; explorerStub.refreshAllDatabases = () => Q.resolve(); @@ -156,8 +155,6 @@ describe("ContainerSampleGenerator", () => { it("should not create any sample for Cassandra API account", async () => { const experience = "Sample generation not supported for this API Cassandra"; - const explorerStub = createExplorerStub(undefined); - updateUserContext({ databaseAccount: { properties: { @@ -165,6 +162,7 @@ describe("ContainerSampleGenerator", () => { }, } as DatabaseAccount, }); + const explorerStub = createExplorerStub(undefined); // Rejects with error that contains experience await expect(ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub)).rejects.toMatch(experience); }); diff --git a/src/Explorer/Explorer.tsx b/src/Explorer/Explorer.tsx index 6883ab29c..e2465b6ce 100644 --- a/src/Explorer/Explorer.tsx +++ b/src/Explorer/Explorer.tsx @@ -116,11 +116,6 @@ export default class Explorer { * Use userContext.apiType instead * */ public defaultExperience: ko.Observable; - /** - * @deprecated - * Compare a string with userContext.apiType instead: userContext.apiType === "Cassandra" - * */ - public isPreferredApiCassandra: ko.Computed; /** * @deprecated * Compare a string with userContext.apiType instead: userContext.apiType === "Mongo" @@ -414,10 +409,6 @@ export default class Explorer { }); }); - this.isPreferredApiCassandra = ko.computed(() => { - const defaultExperience = (this.defaultExperience && this.defaultExperience()) || ""; - return defaultExperience.toLowerCase() === Constants.DefaultAccountExperience.Cassandra.toLowerCase(); - }); this.isPreferredApiGraph = ko.computed(() => { const defaultExperience = (this.defaultExperience && this.defaultExperience()) || ""; return defaultExperience.toLowerCase() === Constants.DefaultAccountExperience.Graph.toLowerCase(); @@ -2097,7 +2088,7 @@ export default class Explorer { } public onNewCollectionClicked(): void { - if (this.isPreferredApiCassandra()) { + if (userContext.apiType === "Cassandra") { this.cassandraAddCollectionPane.open(); } else if (userContext.features.enableReactPane) { this.openAddCollectionPanel(); diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentAdapter.tsx b/src/Explorer/Menus/CommandBar/CommandBarComponentAdapter.tsx index c80e0bf70..95b693429 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentAdapter.tsx +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentAdapter.tsx @@ -31,7 +31,6 @@ export class CommandBarComponentAdapter implements ReactAdapter { const toWatch = [ container.isPreferredApiTable, container.isPreferredApiMongoDB, - container.isPreferredApiCassandra, container.isPreferredApiGraph, container.deleteCollectionText, container.deleteDatabaseText, diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts index a4874f0e3..12deb7569 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts @@ -1,5 +1,6 @@ import * as ko from "knockout"; import { AuthType } from "../../../AuthType"; +import { DatabaseAccount } from "../../../Contracts/DataModels"; import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService"; import { updateUserContext } from "../../../UserContext"; import Explorer from "../../Explorer"; @@ -17,7 +18,6 @@ describe("CommandBarComponentButtonFactory tests", () => { mockExplorer.addCollectionText = ko.observable("mockText"); mockExplorer.isPreferredApiTable = ko.computed(() => true); mockExplorer.isPreferredApiMongoDB = ko.computed(() => false); - mockExplorer.isPreferredApiCassandra = ko.computed(() => false); mockExplorer.isSparkEnabled = ko.observable(true); mockExplorer.isSynapseLinkUpdating = ko.observable(false); @@ -56,7 +56,6 @@ describe("CommandBarComponentButtonFactory tests", () => { mockExplorer.addCollectionText = ko.observable("mockText"); mockExplorer.isPreferredApiTable = ko.computed(() => true); mockExplorer.isPreferredApiMongoDB = ko.computed(() => false); - mockExplorer.isPreferredApiCassandra = ko.computed(() => false); mockExplorer.isSynapseLinkUpdating = ko.observable(false); mockExplorer.isSparkEnabled = ko.observable(true); mockExplorer.isSynapseLinkUpdating = ko.observable(false); @@ -119,7 +118,6 @@ describe("CommandBarComponentButtonFactory tests", () => { mockExplorer = {} as Explorer; mockExplorer.addCollectionText = ko.observable("mockText"); mockExplorer.isPreferredApiTable = ko.computed(() => true); - mockExplorer.isPreferredApiCassandra = ko.computed(() => false); mockExplorer.isSparkEnabled = ko.observable(true); mockExplorer.isSynapseLinkUpdating = ko.observable(false); @@ -208,15 +206,26 @@ describe("CommandBarComponentButtonFactory tests", () => { }); beforeEach(() => { - mockExplorer.isPreferredApiCassandra = ko.computed(() => true); + updateUserContext({ + databaseAccount: { + properties: { + capabilities: [{ name: "EnableCassandra" }], + }, + } as DatabaseAccount, + }); mockExplorer.isNotebookEnabled = ko.observable(false); mockExplorer.isNotebooksEnabledForAccount = ko.observable(false); mockExplorer.isRunningOnNationalCloud = ko.observable(false); }); it("Cassandra Api not available - button should be hidden", () => { - mockExplorer.isPreferredApiCassandra = ko.computed(() => false); - + updateUserContext({ + databaseAccount: { + properties: { + capabilities: [{ name: "EnableMongo" }], + }, + } as DatabaseAccount, + }); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); expect(openCassandraShellBtn).toBeUndefined(); @@ -281,7 +290,6 @@ describe("CommandBarComponentButtonFactory tests", () => { mockExplorer.addCollectionText = ko.observable("mockText"); mockExplorer.isPreferredApiTable = ko.computed(() => true); mockExplorer.isPreferredApiMongoDB = ko.computed(() => false); - mockExplorer.isPreferredApiCassandra = ko.computed(() => false); mockExplorer.isSynapseLinkUpdating = ko.observable(false); mockExplorer.isSparkEnabled = ko.observable(true); @@ -346,6 +354,11 @@ describe("CommandBarComponentButtonFactory tests", () => { }); it("should only show New SQL Query and Open Query buttons", () => { + updateUserContext({ + databaseAccount: { + kind: "DocumentDB", + } as DatabaseAccount, + }); const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer); expect(buttons.length).toBe(2); expect(buttons[0].commandButtonLabel).toBe("New SQL Query"); diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx index 7ba6f8645..b8eca3193 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx @@ -74,7 +74,7 @@ export function createStaticCommandBarButtons(container: Explorer): CommandButto buttons.push(createOpenMongoTerminalButton(container)); } - if (container.isPreferredApiCassandra()) { + if (userContext.apiType === "Cassandra") { buttons.push(createOpenCassandraTerminalButton(container)); } } @@ -289,7 +289,7 @@ function createNewDatabase(container: Explorer): CommandButtonComponentProps { } function createNewSQLQueryButton(container: Explorer): CommandButtonComponentProps { - if (userContext.apiType === "SQL" || container.isPreferredApiGraph()) { + if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") { const label = "New SQL Query"; return { iconSrc: AddSqlQueryIcon, @@ -303,7 +303,7 @@ function createNewSQLQueryButton(container: Explorer): CommandButtonComponentPro hasPopup: true, disabled: container.isDatabaseNodeOrNoneSelected(), }; - } else if (container.isPreferredApiMongoDB()) { + } else if (userContext.apiType === "Mongo") { const label = "New Query"; return { iconSrc: AddSqlQueryIcon, diff --git a/src/Explorer/Panes/AddCollectionPane.ts b/src/Explorer/Panes/AddCollectionPane.ts index 68303780d..383cecefa 100644 --- a/src/Explorer/Panes/AddCollectionPane.ts +++ b/src/Explorer/Panes/AddCollectionPane.ts @@ -388,7 +388,7 @@ export default class AddCollectionPane extends ContextualPaneBase { this.container == null || !!this.container.isPreferredApiMongoDB() || !!this.container.isPreferredApiTable() || - !!this.container.isPreferredApiCassandra() || + userContext.apiType === "Cassandra" || !!this.container.isPreferredApiGraph() ) { return false; @@ -599,7 +599,7 @@ export default class AddCollectionPane extends ContextualPaneBase { return true; } - if (this.container.isPreferredApiCassandra() && this.container.hasStorageAnalyticsAfecFeature()) { + if (userContext.apiType === "Cassandra" && this.container.hasStorageAnalyticsAfecFeature()) { return true; } diff --git a/src/Explorer/Panes/AddDatabasePane.ts b/src/Explorer/Panes/AddDatabasePane.ts index dff1f08ff..7e2eac94a 100644 --- a/src/Explorer/Panes/AddDatabasePane.ts +++ b/src/Explorer/Panes/AddDatabasePane.ts @@ -62,21 +62,21 @@ export default class AddDatabasePane extends ContextualPaneBase { this.databaseCreateNewShared = ko.observable(this.getSharedThroughputDefault()); this.databaseIdLabel = ko.computed(() => - this.container.isPreferredApiCassandra() ? "Keyspace id" : "Database id" + userContext.apiType === "Cassandra" ? "Keyspace id" : "Database id" ); this.databaseIdPlaceHolder = ko.computed(() => - this.container.isPreferredApiCassandra() ? "Type a new keyspace id" : "Type a new database id" + userContext.apiType === "Cassandra" ? "Type a new keyspace id" : "Type a new database id" ); this.databaseIdTooltipText = ko.computed(() => { - const isCassandraAccount: boolean = this.container.isPreferredApiCassandra(); + const isCassandraAccount: boolean = userContext.apiType === "Cassandra"; return `A ${isCassandraAccount ? "keyspace" : "database"} is a logical container of one or more ${ isCassandraAccount ? "tables" : "collections" }`; }); this.databaseLevelThroughputTooltipText = ko.computed(() => { - const isCassandraAccount: boolean = this.container.isPreferredApiCassandra(); + const isCassandraAccount: boolean = userContext.apiType === "Cassandra"; const databaseLabel: string = isCassandraAccount ? "keyspace" : "database"; const collectionsLabel: string = isCassandraAccount ? "tables" : "collections"; return `Provisioned throughput at the ${databaseLabel} level will be shared across all ${collectionsLabel} within the ${databaseLabel}.`; diff --git a/src/Explorer/Panes/SettingsPane/__snapshots__/index.test.tsx.snap b/src/Explorer/Panes/SettingsPane/__snapshots__/index.test.tsx.snap index 5ff1069fc..a2f6f62c3 100644 --- a/src/Explorer/Panes/SettingsPane/__snapshots__/index.test.tsx.snap +++ b/src/Explorer/Panes/SettingsPane/__snapshots__/index.test.tsx.snap @@ -623,7 +623,6 @@ exports[`Settings Pane should render Default properly 1`] = ` "isMongoIndexingEnabled": [Function], "isNotebookEnabled": [Function], "isNotebooksEnabledForAccount": [Function], - "isPreferredApiCassandra": [Function], "isPreferredApiGraph": [Function], "isPreferredApiMongoDB": [Function], "isPreferredApiTable": [Function], @@ -1510,7 +1509,6 @@ exports[`Settings Pane should render Gremlin properly 1`] = ` "isMongoIndexingEnabled": [Function], "isNotebookEnabled": [Function], "isNotebooksEnabledForAccount": [Function], - "isPreferredApiCassandra": [Function], "isPreferredApiGraph": [Function], "isPreferredApiMongoDB": [Function], "isPreferredApiTable": [Function], diff --git a/src/Explorer/Panes/Tables/AddTableEntityPane.ts b/src/Explorer/Panes/Tables/AddTableEntityPane.ts index 57e9f5b69..46eca3e36 100644 --- a/src/Explorer/Panes/Tables/AddTableEntityPane.ts +++ b/src/Explorer/Panes/Tables/AddTableEntityPane.ts @@ -1,10 +1,11 @@ import * as ko from "knockout"; import * as _ from "underscore"; import * as ViewModels from "../../../Contracts/ViewModels"; -import { CassandraTableKey, CassandraAPIDataClient } from "../../Tables/TableDataClient"; +import { userContext } from "../../../UserContext"; +import * as TableConstants from "../../Tables/Constants"; import * as DataTableUtilities from "../../Tables/DataTable/DataTableUtilities"; import * as Entities from "../../Tables/Entities"; -import * as TableConstants from "../../Tables/Constants"; +import { CassandraAPIDataClient, CassandraTableKey } from "../../Tables/TableDataClient"; import * as Utilities from "../../Tables/Utilities"; import EntityPropertyViewModel from "./EntityPropertyViewModel"; import TableEntityPane from "./TableEntityPane"; @@ -24,11 +25,9 @@ export default class AddTableEntityPane extends TableEntityPane { constructor(options: ViewModels.PaneOptions) { super(options); this.submitButtonText("Add Entity"); - this.container.isPreferredApiCassandra.subscribe((isCassandra) => { - if (isCassandra) { - this.submitButtonText("Add Row"); - } - }); + if (userContext.apiType === "Cassandra") { + this.submitButtonText("Add Row"); + } this.scrollId = ko.observable("addEntityScroll"); } @@ -57,7 +56,7 @@ export default class AddTableEntityPane extends TableEntityPane { headers = [TableConstants.EntityKeyNames.PartitionKey, TableConstants.EntityKeyNames.RowKey]; } } - if (this.container.isPreferredApiCassandra()) { + if (userContext.apiType === "Cassandra") { (this.container.tableDataClient) .getTableSchema(this.tableViewModel.queryTablesTab.collection) .then((columns: CassandraTableKey[]) => { @@ -94,7 +93,7 @@ export default class AddTableEntityPane extends TableEntityPane { headers && headers.forEach((key: string) => { if (!_.contains(AddTableEntityPane._excludedFields, key)) { - if (this.container.isPreferredApiCassandra()) { + if (userContext.apiType === "Cassandra") { const cassandraKeys = this.tableViewModel.queryTablesTab.collection.cassandraKeys.partitionKeys .concat(this.tableViewModel.queryTablesTab.collection.cassandraKeys.clusteringKeys) .map((key) => key.property); diff --git a/src/Explorer/Panes/Tables/EditTableEntityPane.ts b/src/Explorer/Panes/Tables/EditTableEntityPane.ts index a58b0864a..a4f178feb 100644 --- a/src/Explorer/Panes/Tables/EditTableEntityPane.ts +++ b/src/Explorer/Panes/Tables/EditTableEntityPane.ts @@ -1,14 +1,15 @@ import * as ko from "knockout"; import _ from "underscore"; import * as ViewModels from "../../../Contracts/ViewModels"; -import { CassandraTableKey, CassandraAPIDataClient } from "../../Tables/TableDataClient"; -import * as Entities from "../../Tables/Entities"; -import TableEntityPane from "./TableEntityPane"; -import * as Utilities from "../../Tables/Utilities"; -import * as TableConstants from "../../Tables/Constants"; -import EntityPropertyViewModel from "./EntityPropertyViewModel"; -import * as TableEntityProcessor from "../../Tables/TableEntityProcessor"; +import { userContext } from "../../../UserContext"; import Explorer from "../../Explorer"; +import * as TableConstants from "../../Tables/Constants"; +import * as Entities from "../../Tables/Entities"; +import { CassandraAPIDataClient, CassandraTableKey } from "../../Tables/TableDataClient"; +import * as TableEntityProcessor from "../../Tables/TableEntityProcessor"; +import * as Utilities from "../../Tables/Utilities"; +import EntityPropertyViewModel from "./EntityPropertyViewModel"; +import TableEntityPane from "./TableEntityPane"; export default class EditTableEntityPane extends TableEntityPane { container: Explorer; @@ -21,11 +22,9 @@ export default class EditTableEntityPane extends TableEntityPane { constructor(options: ViewModels.PaneOptions) { super(options); this.submitButtonText("Update Entity"); - this.container.isPreferredApiCassandra.subscribe((isCassandra) => { - if (isCassandra) { - this.submitButtonText("Update Row"); - } - }); + if (userContext.apiType === "Cassandra") { + this.submitButtonText("Update Row"); + } this.scrollId = ko.observable("editEntityScroll"); } @@ -44,7 +43,7 @@ export default class EditTableEntityPane extends TableEntityPane { property !== TableEntityProcessor.keyProperties.etag && property !== TableEntityProcessor.keyProperties.resourceId && property !== TableEntityProcessor.keyProperties.self && - (!this.container.isPreferredApiCassandra() || property !== TableConstants.EntityKeyNames.RowKey) + (userContext.apiType !== "Cassandra" || property !== TableConstants.EntityKeyNames.RowKey) ) { numberOfProperties++; } @@ -93,9 +92,9 @@ export default class EditTableEntityPane extends TableEntityPane { key !== TableEntityProcessor.keyProperties.etag && key !== TableEntityProcessor.keyProperties.resourceId && key !== TableEntityProcessor.keyProperties.self && - (!this.container.isPreferredApiCassandra() || key !== TableConstants.EntityKeyNames.RowKey) + (userContext.apiType !== "Cassandra" || key !== TableConstants.EntityKeyNames.RowKey) ) { - if (this.container.isPreferredApiCassandra()) { + if (userContext.apiType === "Cassandra") { const cassandraKeys = this.tableViewModel.queryTablesTab.collection.cassandraKeys.partitionKeys .concat(this.tableViewModel.queryTablesTab.collection.cassandraKeys.clusteringKeys) .map((key) => key.property); @@ -150,7 +149,7 @@ export default class EditTableEntityPane extends TableEntityPane { } } }); - if (this.container.isPreferredApiCassandra()) { + if (userContext.apiType === "Cassandra") { (this.container.tableDataClient) .getTableSchema(this.tableViewModel.queryTablesTab.collection) .then((properties: CassandraTableKey[]) => { @@ -169,10 +168,7 @@ export default class EditTableEntityPane extends TableEntityPane { var updatedEntity: any = {}; displayedAttributes && displayedAttributes.forEach((attribute: EntityPropertyViewModel) => { - if ( - attribute.name() && - (!this.tableViewModel.queryTablesTab.container.isPreferredApiCassandra() || attribute.value() !== "") - ) { + if (attribute.name() && (userContext.apiType !== "Cassandra" || attribute.value() !== "")) { var value = attribute.getPropertyValue(); var type = attribute.type(); if (type === TableConstants.TableType.Int64) { diff --git a/src/Explorer/Panes/Tables/TableEntityPane.ts b/src/Explorer/Panes/Tables/TableEntityPane.ts index f9f358aa7..23eafff11 100644 --- a/src/Explorer/Panes/Tables/TableEntityPane.ts +++ b/src/Explorer/Panes/Tables/TableEntityPane.ts @@ -1,15 +1,16 @@ import * as ko from "knockout"; import _ from "underscore"; -import * as DataTableUtilities from "../../Tables/DataTable/DataTableUtilities"; -import * as Entities from "../../Tables/Entities"; -import EntityPropertyViewModel from "./EntityPropertyViewModel"; +import { KeyCodes } from "../../../Common/Constants"; +import * as ViewModels from "../../../Contracts/ViewModels"; +import { userContext } from "../../../UserContext"; import * as TableConstants from "../../Tables/Constants"; +import * as DataTableUtilities from "../../Tables/DataTable/DataTableUtilities"; import TableEntityListViewModel from "../../Tables/DataTable/TableEntityListViewModel"; +import * as Entities from "../../Tables/Entities"; import * as TableEntityProcessor from "../../Tables/TableEntityProcessor"; import * as Utilities from "../../Tables/Utilities"; -import * as ViewModels from "../../../Contracts/ViewModels"; -import { KeyCodes } from "../../../Common/Constants"; import { ContextualPaneBase } from "../ContextualPaneBase"; +import EntityPropertyViewModel from "./EntityPropertyViewModel"; // Class with variables and functions that are common to both adding and editing entities export default abstract class TableEntityPane extends ContextualPaneBase { @@ -52,31 +53,29 @@ export default abstract class TableEntityPane extends ContextualPaneBase { constructor(options: ViewModels.PaneOptions) { super(options); - this.container.isPreferredApiCassandra.subscribe((isCassandra) => { - if (isCassandra) { - this.edmTypes([ - TableConstants.CassandraType.Text, - TableConstants.CassandraType.Ascii, - TableConstants.CassandraType.Bigint, - TableConstants.CassandraType.Blob, - TableConstants.CassandraType.Boolean, - TableConstants.CassandraType.Decimal, - TableConstants.CassandraType.Double, - TableConstants.CassandraType.Float, - TableConstants.CassandraType.Int, - TableConstants.CassandraType.Uuid, - TableConstants.CassandraType.Varchar, - TableConstants.CassandraType.Varint, - TableConstants.CassandraType.Inet, - TableConstants.CassandraType.Smallint, - TableConstants.CassandraType.Tinyint, - ]); - } - }); + if (userContext.apiType === "Cassandra") { + this.edmTypes([ + TableConstants.CassandraType.Text, + TableConstants.CassandraType.Ascii, + TableConstants.CassandraType.Bigint, + TableConstants.CassandraType.Blob, + TableConstants.CassandraType.Boolean, + TableConstants.CassandraType.Decimal, + TableConstants.CassandraType.Double, + TableConstants.CassandraType.Float, + TableConstants.CassandraType.Int, + TableConstants.CassandraType.Uuid, + TableConstants.CassandraType.Varchar, + TableConstants.CassandraType.Varint, + TableConstants.CassandraType.Inet, + TableConstants.CassandraType.Smallint, + TableConstants.CassandraType.Tinyint, + ]); + } this.canAdd = ko.computed(() => { // Cassandra can't add since the schema can't be changed once created - if (this.container.isPreferredApiCassandra()) { + if (userContext.apiType === "Cassandra") { return false; } // Adding '2' to the maximum to take into account PartitionKey and RowKey @@ -163,7 +162,7 @@ export default abstract class TableEntityPane extends ContextualPaneBase { public insertAttribute = (name?: string, type?: string): void => { let entityProperty: EntityPropertyViewModel; - if (!!name && !!type && this.container.isPreferredApiCassandra()) { + if (!!name && !!type && userContext.apiType === "Cassandra") { // TODO figure out validation story for blob and Inet so we can allow adding/editing them const nonEditableType: boolean = type === TableConstants.CassandraType.Blob || type === TableConstants.CassandraType.Inet; @@ -253,8 +252,7 @@ export default abstract class TableEntityPane extends ContextualPaneBase { key !== TableEntityProcessor.keyProperties.etag && key !== TableEntityProcessor.keyProperties.resourceId && key !== TableEntityProcessor.keyProperties.self && - (!viewModel.queryTablesTab.container.isPreferredApiCassandra() || - key !== TableConstants.EntityKeyNames.RowKey) + (userContext.apiType !== "Cassandra" || key !== TableConstants.EntityKeyNames.RowKey) ) { newHeaders.push(key); } diff --git a/src/Explorer/Panes/UploadItemsPane/__snapshots__/index.test.tsx.snap b/src/Explorer/Panes/UploadItemsPane/__snapshots__/index.test.tsx.snap index 730283b1e..c9f942b3f 100644 --- a/src/Explorer/Panes/UploadItemsPane/__snapshots__/index.test.tsx.snap +++ b/src/Explorer/Panes/UploadItemsPane/__snapshots__/index.test.tsx.snap @@ -623,7 +623,6 @@ exports[`Upload Items Pane should render Default properly 1`] = ` "isMongoIndexingEnabled": [Function], "isNotebookEnabled": [Function], "isNotebooksEnabledForAccount": [Function], - "isPreferredApiCassandra": [Function], "isPreferredApiGraph": [Function], "isPreferredApiMongoDB": [Function], "isPreferredApiTable": [Function], diff --git a/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap b/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap index 10241dd3c..49215da65 100644 --- a/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap +++ b/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap @@ -626,7 +626,6 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database "isMongoIndexingEnabled": [Function], "isNotebookEnabled": [Function], "isNotebooksEnabledForAccount": [Function], - "isPreferredApiCassandra": [Function], "isPreferredApiGraph": [Function], "isPreferredApiMongoDB": [Function], "isPreferredApiTable": [Function], diff --git a/src/Explorer/SplashScreen/SplashScreen.tsx b/src/Explorer/SplashScreen/SplashScreen.tsx index 541e6094f..43fec912f 100644 --- a/src/Explorer/SplashScreen/SplashScreen.tsx +++ b/src/Explorer/SplashScreen/SplashScreen.tsx @@ -256,7 +256,7 @@ export class SplashScreen extends React.Component { onClick: () => this.container.openBrowseQueriesPanel(), }); - if (!this.container.isPreferredApiCassandra()) { + if (userContext.apiType !== "Cassandra") { items.push({ iconSrc: NewStoredProcedureIcon, title: "New Stored Procedure", diff --git a/src/Explorer/Tables/DataTable/TableCommands.ts b/src/Explorer/Tables/DataTable/TableCommands.ts index a84dc9008..c3641fe44 100644 --- a/src/Explorer/Tables/DataTable/TableCommands.ts +++ b/src/Explorer/Tables/DataTable/TableCommands.ts @@ -1,4 +1,5 @@ import Q from "q"; +import { userContext } from "../../../UserContext"; import Explorer from "../../Explorer"; import * as Entities from "../Entities"; import * as DataTableUtilities from "./DataTableUtilities"; @@ -73,7 +74,7 @@ export default class TableCommands { } var entitiesToDelete: Entities.ITableEntity[] = viewModel.selected(); let deleteMessage: string = "Are you sure you want to delete the selected entities?"; - if (viewModel.queryTablesTab.container.isPreferredApiCassandra()) { + if (userContext.apiType === "Cassandra") { deleteMessage = "Are you sure you want to delete the selected rows?"; } if (window.confirm(deleteMessage)) { diff --git a/src/Explorer/Tables/DataTable/TableEntityListViewModel.ts b/src/Explorer/Tables/DataTable/TableEntityListViewModel.ts index b1f6e494f..f11730b2b 100644 --- a/src/Explorer/Tables/DataTable/TableEntityListViewModel.ts +++ b/src/Explorer/Tables/DataTable/TableEntityListViewModel.ts @@ -5,6 +5,7 @@ import { Areas } from "../../../Common/Constants"; import * as ViewModels from "../../../Contracts/ViewModels"; import { Action } from "../../../Shared/Telemetry/TelemetryConstants"; import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; +import { userContext } from "../../../UserContext"; import QueryTablesTab from "../../Tabs/QueryTablesTab"; import * as Constants from "../Constants"; import { getQuotedCqlIdentifier } from "../CqlUtilities"; @@ -412,10 +413,7 @@ export default class TableEntityListViewModel extends DataTableViewModel { } var entities = this.cache.data; - if ( - this.queryTablesTab.container.isPreferredApiCassandra() && - DataTableUtilities.checkForDefaultHeader(this.headers) - ) { + if (userContext.apiType === "Cassandra" && DataTableUtilities.checkForDefaultHeader(this.headers)) { (this.queryTablesTab.container.tableDataClient) .getTableSchema(this.queryTablesTab.collection) .then((headers: CassandraTableKey[]) => { @@ -427,7 +425,7 @@ export default class TableEntityListViewModel extends DataTableViewModel { } else { var selectedHeadersUnion: string[] = DataTableUtilities.getPropertyIntersectionFromTableEntities( entities, - this.queryTablesTab.container.isPreferredApiCassandra() + userContext.apiType === "Cassandra" ); var newHeaders: string[] = _.difference(selectedHeadersUnion, this.headers); if (newHeaders.length > 0) { @@ -512,7 +510,7 @@ export default class TableEntityListViewModel extends DataTableViewModel { return Q.resolve(finalEntities); } ); - } else if (this.continuationToken && this.queryTablesTab.container.isPreferredApiCassandra()) { + } else if (this.continuationToken && userContext.apiType === "Cassandra") { promise = Q( this.queryTablesTab.container.tableDataClient.queryDocuments( this.queryTablesTab.collection, @@ -523,7 +521,7 @@ export default class TableEntityListViewModel extends DataTableViewModel { ); } else { let query = this.sqlQuery(); - if (this.queryTablesTab.container.isPreferredApiCassandra()) { + if (userContext.apiType === "Cassandra") { query = this.cqlQuery(); } promise = Q( diff --git a/src/Explorer/Tables/QueryBuilder/QueryBuilderViewModel.ts b/src/Explorer/Tables/QueryBuilder/QueryBuilderViewModel.ts index a9c0d0dbe..59988c26e 100644 --- a/src/Explorer/Tables/QueryBuilder/QueryBuilderViewModel.ts +++ b/src/Explorer/Tables/QueryBuilder/QueryBuilderViewModel.ts @@ -1,5 +1,6 @@ import * as ko from "knockout"; import { KeyCodes } from "../../../Common/Constants"; +import { userContext } from "../../../UserContext"; import * as Constants from "../Constants"; import { getQuotedCqlIdentifier } from "../CqlUtilities"; import * as DataTableUtilities from "../DataTable/DataTableUtilities"; @@ -70,7 +71,7 @@ export default class QueryBuilderViewModel { private scrollEventListener: boolean; constructor(queryViewModel: QueryViewModel, tableEntityListViewModel: TableEntityListViewModel) { - if (tableEntityListViewModel.queryTablesTab.container.isPreferredApiCassandra()) { + if (userContext.apiType === "Cassandra") { this.edmTypes([ Constants.CassandraType.Text, Constants.CassandraType.Ascii, diff --git a/src/Explorer/Tables/QueryBuilder/QueryClauseViewModel.ts b/src/Explorer/Tables/QueryBuilder/QueryClauseViewModel.ts index fc1055b7d..2137f8405 100644 --- a/src/Explorer/Tables/QueryBuilder/QueryClauseViewModel.ts +++ b/src/Explorer/Tables/QueryBuilder/QueryClauseViewModel.ts @@ -1,9 +1,10 @@ import * as ko from "knockout"; import _ from "underscore"; +import { userContext } from "../../../UserContext"; import * as QueryBuilderConstants from "../Constants"; -import QueryBuilderViewModel from "./QueryBuilderViewModel"; -import ClauseGroup from "./ClauseGroup"; import * as Utilities from "../Utilities"; +import ClauseGroup from "./ClauseGroup"; +import QueryBuilderViewModel from "./QueryBuilderViewModel"; export default class QueryClauseViewModel { public checkedForGrouping: ko.Observable; @@ -68,7 +69,7 @@ export default class QueryClauseViewModel { this.getValueType(); this.isOperaterEditable = ko.pureComputed(() => { - const isPreferredApiCassandra = this._queryBuilderViewModel.tableEntityListViewModel.queryTablesTab.container.isPreferredApiCassandra(); + const isPreferredApiCassandra = userContext.apiType === "Cassandra"; const cassandraKeys = isPreferredApiCassandra ? this._queryBuilderViewModel.tableEntityListViewModel.queryTablesTab.collection.cassandraKeys.partitionKeys.map( (key) => key.property @@ -84,7 +85,7 @@ export default class QueryClauseViewModel { this.field() !== "Timestamp" && this.field() !== "PartitionKey" && this.field() !== "RowKey" && - !this._queryBuilderViewModel.tableEntityListViewModel.queryTablesTab.container.isPreferredApiCassandra() + userContext.apiType !== "Cassandra" ); this.and_or.subscribe((value) => { @@ -170,7 +171,7 @@ export default class QueryClauseViewModel { this.type(QueryBuilderConstants.TableType.String); } else { this.resetFromTimestamp(); - if (this._queryBuilderViewModel.tableEntityListViewModel.queryTablesTab.container.isPreferredApiCassandra()) { + if (userContext.apiType === "Cassandra") { const cassandraSchema = this._queryBuilderViewModel.tableEntityListViewModel.queryTablesTab.collection .cassandraSchema; for (let i = 0, len = cassandraSchema.length; i < len; i++) { diff --git a/src/Explorer/Tables/QueryBuilder/QueryViewModel.ts b/src/Explorer/Tables/QueryBuilder/QueryViewModel.ts index 41537c3d8..69ba3ae47 100644 --- a/src/Explorer/Tables/QueryBuilder/QueryViewModel.ts +++ b/src/Explorer/Tables/QueryBuilder/QueryViewModel.ts @@ -1,6 +1,7 @@ import * as ko from "knockout"; import * as _ from "underscore"; import { KeyCodes } from "../../../Common/Constants"; +import { userContext } from "../../../UserContext"; import QueryTablesTab from "../../Tabs/QueryTablesTab"; import { getQuotedCqlIdentifier } from "../CqlUtilities"; import * as DataTableUtilities from "../DataTable/DataTableUtilities"; @@ -46,7 +47,7 @@ export default class QueryViewModel { this._tableEntityListViewModel = queryTablesTab.tableEntityListViewModel(); this.queryTextIsReadOnly = ko.computed(() => { - return !this.queryTablesTab.container.isPreferredApiCassandra(); + return userContext.apiType !== "Cassandra"; }); let initialOptions = this._tableEntityListViewModel.headers; this.columnOptions = ko.observableArray(initialOptions); @@ -126,7 +127,7 @@ export default class QueryViewModel { private setFilter = (): string => { var queryString = this.isEditorActive() ? this.queryText() - : this.queryTablesTab.container.isPreferredApiCassandra() + : userContext.apiType === "Cassandra" ? this.queryBuilderViewModel().getCqlFilterFromClauses() : this.queryBuilderViewModel().getODataFilterFromClauses(); var filter = queryString; @@ -159,7 +160,7 @@ export default class QueryViewModel { public runQuery = (): DataTables.DataTable => { var filter = this.setFilter(); - if (filter && !this.queryTablesTab.container.isPreferredApiCassandra()) { + if (filter && userContext.apiType !== "Cassandra") { filter = filter.replace(/"/g, "'"); } var top = this.topValue(); diff --git a/src/Explorer/Tabs/QueryTablesTab.ts b/src/Explorer/Tabs/QueryTablesTab.ts index 651ad0937..70c563a8f 100644 --- a/src/Explorer/Tabs/QueryTablesTab.ts +++ b/src/Explorer/Tabs/QueryTablesTab.ts @@ -1,21 +1,21 @@ import * as ko from "knockout"; import Q from "q"; -import * as ViewModels from "../../Contracts/ViewModels"; -import TabsBase from "./TabsBase"; -import TableEntityListViewModel from "../Tables/DataTable/TableEntityListViewModel"; -import QueryViewModel from "../Tables/QueryBuilder/QueryViewModel"; -import TableCommands from "../Tables/DataTable/TableCommands"; -import { TableDataClient } from "../Tables/TableDataClient"; - +import AddEntityIcon from "../../../images/AddEntity.svg"; +import DeleteEntitiesIcon from "../../../images/DeleteEntities.svg"; +import EditEntityIcon from "../../../images/Edit-entity.svg"; +import ExecuteQueryIcon from "../../../images/ExecuteQuery.svg"; import QueryBuilderIcon from "../../../images/Query-Builder.svg"; import QueryTextIcon from "../../../images/Query-Text.svg"; -import ExecuteQueryIcon from "../../../images/ExecuteQuery.svg"; -import AddEntityIcon from "../../../images/AddEntity.svg"; -import EditEntityIcon from "../../../images/Edit-entity.svg"; -import DeleteEntitiesIcon from "../../../images/DeleteEntities.svg"; -import Explorer from "../Explorer"; +import * as ViewModels from "../../Contracts/ViewModels"; +import { userContext } from "../../UserContext"; import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent"; +import Explorer from "../Explorer"; +import TableCommands from "../Tables/DataTable/TableCommands"; +import TableEntityListViewModel from "../Tables/DataTable/TableEntityListViewModel"; +import QueryViewModel from "../Tables/QueryBuilder/QueryViewModel"; +import { TableDataClient } from "../Tables/TableDataClient"; import template from "./QueryTablesTab.html"; +import TabsBase from "./TabsBase"; // Will act as table explorer class export default class QueryTablesTab extends TabsBase { @@ -176,7 +176,7 @@ export default class QueryTablesTab extends TabsBase { protected getTabsButtons(): CommandButtonComponentProps[] { const buttons: CommandButtonComponentProps[] = []; if (this.queryBuilderButton.visible()) { - const label = this.container.isPreferredApiCassandra() ? "CQL Query Builder" : "Query Builder"; + const label = userContext.apiType === "Cassandra" ? "CQL Query Builder" : "Query Builder"; buttons.push({ iconSrc: QueryBuilderIcon, iconAlt: label, @@ -190,7 +190,7 @@ export default class QueryTablesTab extends TabsBase { } if (this.queryTextButton.visible()) { - const label = this.container.isPreferredApiCassandra() ? "CQL Query Text" : "Query Text"; + const label = userContext.apiType === "Cassandra" ? "CQL Query Text" : "Query Text"; buttons.push({ iconSrc: QueryTextIcon, iconAlt: label, @@ -217,7 +217,7 @@ export default class QueryTablesTab extends TabsBase { } if (this.addEntityButton.visible()) { - const label = this.container.isPreferredApiCassandra() ? "Add Row" : "Add Entity"; + const label = userContext.apiType === "Cassandra" ? "Add Row" : "Add Entity"; buttons.push({ iconSrc: AddEntityIcon, iconAlt: label, @@ -230,7 +230,7 @@ export default class QueryTablesTab extends TabsBase { } if (this.editEntityButton.visible()) { - const label = this.container.isPreferredApiCassandra() ? "Edit Row" : "Edit Entity"; + const label = userContext.apiType === "Cassandra" ? "Edit Row" : "Edit Entity"; buttons.push({ iconSrc: EditEntityIcon, iconAlt: label, @@ -243,7 +243,7 @@ export default class QueryTablesTab extends TabsBase { } if (this.deleteEntityButton.visible()) { - const label = this.container.isPreferredApiCassandra() ? "Delete Rows" : "Delete Entities"; + const label = userContext.apiType === "Cassandra" ? "Delete Rows" : "Delete Entities"; buttons.push({ iconSrc: DeleteEntitiesIcon, iconAlt: label, diff --git a/src/Explorer/Tree/Collection.test.ts b/src/Explorer/Tree/Collection.test.ts index 7ee9f890d..d55342a0d 100644 --- a/src/Explorer/Tree/Collection.test.ts +++ b/src/Explorer/Tree/Collection.test.ts @@ -34,9 +34,7 @@ describe("Collection", () => { mockContainer.isPreferredApiMongoDB = ko.computed(() => { return false; }); - mockContainer.isPreferredApiCassandra = ko.computed(() => { - return false; - }); + mockContainer.isDatabaseNodeOrNoneSelected = () => { return false; }; diff --git a/src/Explorer/Tree/Collection.ts b/src/Explorer/Tree/Collection.ts index 11ae8e3b4..58cffa72b 100644 --- a/src/Explorer/Tree/Collection.ts +++ b/src/Explorer/Tree/Collection.ts @@ -374,7 +374,7 @@ export default class Collection implements ViewModels.Collection { dataExplorerArea: Constants.Areas.ResourceTree, }); - if (this.container.isPreferredApiCassandra() && !this.cassandraKeys) { + if (userContext.apiType === "Cassandra" && !this.cassandraKeys) { (this.container.tableDataClient).getTableKeys(this).then((keys: CassandraTableKeys) => { this.cassandraKeys = keys; }); @@ -391,7 +391,7 @@ export default class Collection implements ViewModels.Collection { } else { this.documentIds([]); let title = `Entities`; - if (this.container.isPreferredApiCassandra()) { + if (userContext.apiType === "Cassandra") { title = `Rows`; } const startKey: number = TelemetryProcessor.traceStart(Action.Tab, { @@ -1084,7 +1084,7 @@ export default class Collection implements ViewModels.Collection { if (this.container.isPreferredApiTable()) { this.onTableEntitiesClick(); return; - } else if (this.container.isPreferredApiCassandra()) { + } else if (userContext.apiType === "Cassandra") { this.onTableEntitiesClick(); return; } else if (this.container.isPreferredApiGraph()) { @@ -1104,7 +1104,7 @@ export default class Collection implements ViewModels.Collection { public getLabel(): string { if (this.container.isPreferredApiTable()) { return "Entities"; - } else if (this.container.isPreferredApiCassandra()) { + } else if (userContext.apiType === "Cassandra") { return "Rows"; } else if (this.container.isPreferredApiGraph()) { return "Graph"; diff --git a/src/Explorer/Tree/ResourceTreeAdapter.tsx b/src/Explorer/Tree/ResourceTreeAdapter.tsx index 13ba398ce..9397675ad 100644 --- a/src/Explorer/Tree/ResourceTreeAdapter.tsx +++ b/src/Explorer/Tree/ResourceTreeAdapter.tsx @@ -273,7 +273,7 @@ export class ResourceTreeAdapter implements ReactAdapter { contextMenu: ResourceTreeContextMenuButtonFactory.createCollectionContextMenuButton(this.container, collection), }); - if (!this.container.isPreferredApiCassandra() || !this.container.isServerlessEnabled()) { + if (userContext.apiType !== "Cassandra" || !this.container.isServerlessEnabled()) { children.push({ label: database.isDatabaseShared() || this.container.isServerlessEnabled() ? "Settings" : "Scale & Settings", onClick: collection.onSettingsClick.bind(collection), diff --git a/src/RouteHandlers/TabRouteHandler.ts b/src/RouteHandlers/TabRouteHandler.ts index 784a84601..fd2e38f1c 100644 --- a/src/RouteHandlers/TabRouteHandler.ts +++ b/src/RouteHandlers/TabRouteHandler.ts @@ -147,7 +147,7 @@ export class TabRouteHandler { ); collection && collection.container && - (collection.container.isPreferredApiTable() || collection.container.isPreferredApiCassandra()) && + (collection.container.isPreferredApiTable() || userContext.apiType === "Cassandra") && collection.onTableEntitiesClick(); }); } From d74da34742915134192dcddd075f76296cc6fff7 Mon Sep 17 00:00:00 2001 From: Hardikkumar Nai <80053762+hardiknai-techm@users.noreply.github.com> Date: Sun, 18 Apr 2021 02:54:17 +0530 Subject: [PATCH 03/27] Remove explorer.is preferred api graph (#655) Co-authored-by: Steve Faulkner --- src/Explorer/ContextMenuButtonFactory.ts | 4 ++-- .../SettingsComponent.test.tsx.snap | 4 ---- .../ContainerSampleGenerator.test.ts | 9 ++++++-- .../DataSamples/ContainerSampleGenerator.ts | 2 +- src/Explorer/DataSamples/DataSamplesUtil.ts | 2 +- src/Explorer/Explorer.tsx | 15 ++++--------- .../CommandBar/CommandBarComponentAdapter.tsx | 1 - .../CommandBarComponentButtonFactory.tsx | 15 ++++++------- src/Explorer/Panes/AddCollectionPane.test.ts | 22 +++++++++++++++---- src/Explorer/Panes/AddCollectionPane.ts | 22 +++++++------------ .../__snapshots__/index.test.tsx.snap | 2 -- .../__snapshots__/index.test.tsx.snap | 1 - ...eteDatabaseConfirmationPanel.test.tsx.snap | 1 - src/Explorer/SplashScreen/SplashScreen.tsx | 2 +- src/Explorer/Tree/Collection.ts | 4 ++-- src/Explorer/Tree/ResourceTreeAdapter.tsx | 2 +- src/RouteHandlers/TabRouteHandler.ts | 5 +---- 17 files changed, 53 insertions(+), 60 deletions(-) diff --git a/src/Explorer/ContextMenuButtonFactory.ts b/src/Explorer/ContextMenuButtonFactory.ts index 42e23de6c..8337ec700 100644 --- a/src/Explorer/ContextMenuButtonFactory.ts +++ b/src/Explorer/ContextMenuButtonFactory.ts @@ -55,7 +55,7 @@ export class ResourceTreeContextMenuButtonFactory { selectedCollection: ViewModels.Collection ): TreeNodeMenuItem[] { const items: TreeNodeMenuItem[] = []; - if (userContext.apiType === "SQL" || container.isPreferredApiGraph()) { + if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") { items.push({ iconSrc: AddSqlQueryIcon, onClick: () => selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, null), @@ -80,7 +80,7 @@ export class ResourceTreeContextMenuButtonFactory { }); } - if (userContext.apiType === "SQL" || container.isPreferredApiGraph()) { + if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") { items.push({ iconSrc: AddStoredProcedureIcon, onClick: () => { diff --git a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap index 1b9c96a0e..db1ece0ab 100644 --- a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap +++ b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap @@ -647,7 +647,6 @@ exports[`SettingsComponent renders 1`] = ` "isMongoIndexingEnabled": [Function], "isNotebookEnabled": [Function], "isNotebooksEnabledForAccount": [Function], - "isPreferredApiGraph": [Function], "isPreferredApiMongoDB": [Function], "isPreferredApiTable": [Function], "isPublishNotebookPaneEnabled": [Function], @@ -1410,7 +1409,6 @@ exports[`SettingsComponent renders 1`] = ` "isMongoIndexingEnabled": [Function], "isNotebookEnabled": [Function], "isNotebooksEnabledForAccount": [Function], - "isPreferredApiGraph": [Function], "isPreferredApiMongoDB": [Function], "isPreferredApiTable": [Function], "isPublishNotebookPaneEnabled": [Function], @@ -2186,7 +2184,6 @@ exports[`SettingsComponent renders 1`] = ` "isMongoIndexingEnabled": [Function], "isNotebookEnabled": [Function], "isNotebooksEnabledForAccount": [Function], - "isPreferredApiGraph": [Function], "isPreferredApiMongoDB": [Function], "isPreferredApiTable": [Function], "isPublishNotebookPaneEnabled": [Function], @@ -2949,7 +2946,6 @@ exports[`SettingsComponent renders 1`] = ` "isMongoIndexingEnabled": [Function], "isNotebookEnabled": [Function], "isNotebooksEnabledForAccount": [Function], - "isPreferredApiGraph": [Function], "isPreferredApiMongoDB": [Function], "isPreferredApiTable": [Function], "isPublishNotebookPaneEnabled": [Function], diff --git a/src/Explorer/DataSamples/ContainerSampleGenerator.test.ts b/src/Explorer/DataSamples/ContainerSampleGenerator.test.ts index d59df695f..60308be0a 100644 --- a/src/Explorer/DataSamples/ContainerSampleGenerator.test.ts +++ b/src/Explorer/DataSamples/ContainerSampleGenerator.test.ts @@ -14,7 +14,6 @@ describe("ContainerSampleGenerator", () => { const createExplorerStub = (database: ViewModels.Database): Explorer => { const explorerStub = {} as Explorer; explorerStub.databases = ko.observableArray([database]); - explorerStub.isPreferredApiGraph = ko.computed(() => false); explorerStub.isPreferredApiMongoDB = ko.computed(() => false); explorerStub.isPreferredApiTable = ko.computed(() => false); explorerStub.canExceedMaximumValue = ko.computed(() => false); @@ -115,7 +114,13 @@ describe("ContainerSampleGenerator", () => { collection.databaseId = database.id(); const explorerStub = createExplorerStub(database); - explorerStub.isPreferredApiGraph = ko.computed(() => true); + updateUserContext({ + databaseAccount: { + properties: { + capabilities: [{ name: "EnableGremlin" }], + }, + } as DatabaseAccount, + }); const generator = await ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub); generator.setData(sampleData); diff --git a/src/Explorer/DataSamples/ContainerSampleGenerator.ts b/src/Explorer/DataSamples/ContainerSampleGenerator.ts index 9e7c83fed..7d6b42453 100644 --- a/src/Explorer/DataSamples/ContainerSampleGenerator.ts +++ b/src/Explorer/DataSamples/ContainerSampleGenerator.ts @@ -73,7 +73,7 @@ export class ContainerSampleGenerator { } const promises: Q.Promise[] = []; - if (this.container.isPreferredApiGraph()) { + if (userContext.apiType === "Gremlin") { // For Gremlin, all queries are executed sequentially, because some queries might be dependent on other queries // (e.g. adding edge requires vertices to be present) const queries: string[] = this.sampleDataFile.data; diff --git a/src/Explorer/DataSamples/DataSamplesUtil.ts b/src/Explorer/DataSamples/DataSamplesUtil.ts index 284192c0e..809964352 100644 --- a/src/Explorer/DataSamples/DataSamplesUtil.ts +++ b/src/Explorer/DataSamples/DataSamplesUtil.ts @@ -57,6 +57,6 @@ export class DataSamplesUtil { } public isSampleContainerCreationSupported(): boolean { - return userContext.apiType === "SQL" || this.container.isPreferredApiGraph(); + return userContext.apiType === "SQL" || userContext.apiType === "Gremlin"; } } diff --git a/src/Explorer/Explorer.tsx b/src/Explorer/Explorer.tsx index e2465b6ce..474efdff2 100644 --- a/src/Explorer/Explorer.tsx +++ b/src/Explorer/Explorer.tsx @@ -121,11 +121,7 @@ export default class Explorer { * Compare a string with userContext.apiType instead: userContext.apiType === "Mongo" * */ public isPreferredApiMongoDB: ko.Computed; - /** - * @deprecated - * Compare a string with userContext.apiType instead: userContext.apiType === "Gremlin" - * */ - public isPreferredApiGraph: ko.Computed; + /** * @deprecated * Compare a string with userContext.apiType instead: userContext.apiType === "Tables" @@ -409,11 +405,6 @@ export default class Explorer { }); }); - this.isPreferredApiGraph = ko.computed(() => { - const defaultExperience = (this.defaultExperience && this.defaultExperience()) || ""; - return defaultExperience.toLowerCase() === Constants.DefaultAccountExperience.Graph.toLowerCase(); - }); - this.isPreferredApiTable = ko.computed(() => { const defaultExperience = (this.defaultExperience && this.defaultExperience()) || ""; return defaultExperience.toLowerCase() === Constants.DefaultAccountExperience.Table.toLowerCase(); @@ -477,7 +468,9 @@ export default class Explorer { this.isHostedDataExplorerEnabled = ko.computed( () => - configContext.platform === Platform.Portal && !this.isRunningOnNationalCloud() && !this.isPreferredApiGraph() + configContext.platform === Platform.Portal && + !this.isRunningOnNationalCloud() && + userContext.apiType !== "Gremlin" ); this.isRightPanelV2Enabled = ko.computed(() => userContext.features.enableRightPanelV2); this.selectedDatabaseId = ko.computed(() => { diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentAdapter.tsx b/src/Explorer/Menus/CommandBar/CommandBarComponentAdapter.tsx index 95b693429..65ff5e176 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentAdapter.tsx +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentAdapter.tsx @@ -31,7 +31,6 @@ export class CommandBarComponentAdapter implements ReactAdapter { const toWatch = [ container.isPreferredApiTable, container.isPreferredApiMongoDB, - container.isPreferredApiGraph, container.deleteCollectionText, container.deleteDatabaseText, container.addCollectionText, diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx index b8eca3193..5bf8c5885 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx @@ -90,15 +90,15 @@ export function createStaticCommandBarButtons(container: Explorer): CommandButto buttons.push(createDivider()); } - const isSqlQuerySupported = userContext.apiType === "SQL" || container.isPreferredApiGraph(); + const isSqlQuerySupported = userContext.apiType === "SQL" || userContext.apiType === "Gremlin"; if (isSqlQuerySupported) { const newSqlQueryBtn = createNewSQLQueryButton(container); buttons.push(newSqlQueryBtn); } const isSupportedOpenQueryApi = - userContext.apiType === "SQL" || container.isPreferredApiMongoDB() || container.isPreferredApiGraph(); - const isSupportedOpenQueryFromDiskApi = userContext.apiType === "SQL" || container.isPreferredApiGraph(); + userContext.apiType === "SQL" || container.isPreferredApiMongoDB() || userContext.apiType === "Gremlin"; + const isSupportedOpenQueryFromDiskApi = userContext.apiType === "SQL" || userContext.apiType === "Gremlin"; if (isSupportedOpenQueryApi && container.selectedNode() && container.findSelectedCollection()) { const openQueryBtn = createOpenQueryButton(container); openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton(container)]; @@ -107,7 +107,7 @@ export function createStaticCommandBarButtons(container: Explorer): CommandButto buttons.push(createOpenQueryFromDiskButton(container)); } - if (areScriptsSupported(container)) { + if (areScriptsSupported()) { const label = "New Stored Procedure"; const newStoredProcedureBtn: CommandButtonComponentProps = { iconSrc: AddStoredProcedureIcon, @@ -216,8 +216,8 @@ export function createDivider(): CommandButtonComponentProps { }; } -function areScriptsSupported(container: Explorer): boolean { - return userContext.apiType === "SQL" || container.isPreferredApiGraph(); +function areScriptsSupported(): boolean { + return userContext.apiType === "SQL" || userContext.apiType === "Gremlin"; } function createNewCollectionGroup(container: Explorer): CommandButtonComponentProps { @@ -325,8 +325,7 @@ function createNewSQLQueryButton(container: Explorer): CommandButtonComponentPro export function createScriptCommandButtons(container: Explorer): CommandButtonComponentProps[] { const buttons: CommandButtonComponentProps[] = []; - const shouldEnableScriptsCommands: boolean = - !container.isDatabaseNodeOrNoneSelected() && areScriptsSupported(container); + const shouldEnableScriptsCommands: boolean = !container.isDatabaseNodeOrNoneSelected() && areScriptsSupported(); if (shouldEnableScriptsCommands) { const label = "New Stored Procedure"; diff --git a/src/Explorer/Panes/AddCollectionPane.test.ts b/src/Explorer/Panes/AddCollectionPane.test.ts index 98fc2890d..887f8c5bb 100644 --- a/src/Explorer/Panes/AddCollectionPane.test.ts +++ b/src/Explorer/Panes/AddCollectionPane.test.ts @@ -1,7 +1,8 @@ import * as Constants from "../../Common/Constants"; -import AddCollectionPane from "./AddCollectionPane"; -import Explorer from "../Explorer"; import { DatabaseAccount } from "../../Contracts/DataModels"; +import { updateUserContext } from "../../UserContext"; +import Explorer from "../Explorer"; +import AddCollectionPane from "./AddCollectionPane"; describe("Add Collection Pane", () => { describe("isValid()", () => { @@ -50,7 +51,14 @@ describe("Add Collection Pane", () => { }); it("should be false if graph API and partition key is /id or /label", () => { - explorer.defaultExperience(Constants.DefaultAccountExperience.Graph.toLowerCase()); + updateUserContext({ + databaseAccount: { + properties: { + capabilities: [{ name: "EnableGremlin" }], + }, + } as DatabaseAccount, + }); + const addCollectionPane = explorer.addCollectionPane as AddCollectionPane; addCollectionPane.partitionKey("/id"); expect(addCollectionPane.isValid()).toBe(false); @@ -60,7 +68,13 @@ describe("Add Collection Pane", () => { }); it("should be true for any non-graph API with /id or /label partition key", () => { - explorer.defaultExperience(Constants.DefaultAccountExperience.DocumentDB.toLowerCase()); + updateUserContext({ + databaseAccount: { + properties: { + capabilities: [{ name: "EnableCassandra" }], + }, + } as DatabaseAccount, + }); const addCollectionPane = explorer.addCollectionPane as AddCollectionPane; addCollectionPane.partitionKey("/id"); diff --git a/src/Explorer/Panes/AddCollectionPane.ts b/src/Explorer/Panes/AddCollectionPane.ts index 383cecefa..1cddb72b1 100644 --- a/src/Explorer/Panes/AddCollectionPane.ts +++ b/src/Explorer/Panes/AddCollectionPane.ts @@ -127,13 +127,13 @@ export default class AddCollectionPane extends ContextualPaneBase { }); this.partitionKey.extend({ rateLimit: 100 }); this.partitionKeyPattern = ko.pureComputed(() => { - if (this.container && this.container.isPreferredApiGraph()) { + if (userContext.apiType === "Gremlin") { return "^/[^/]*"; } return ".*"; }); this.partitionKeyTitle = ko.pureComputed(() => { - if (this.container && this.container.isPreferredApiGraph()) { + if (userContext.apiType === "Gremlin") { return "May not use composite partition key"; } return ""; @@ -331,7 +331,7 @@ export default class AddCollectionPane extends ContextualPaneBase { if (currentCollections >= maxCollections) { let typeOfContainer = "collection"; - if (this.container.isPreferredApiGraph() || this.container.isPreferredApiTable()) { + if (userContext.apiType === "Gremlin" || this.container.isPreferredApiTable()) { typeOfContainer = "container"; } @@ -368,7 +368,7 @@ export default class AddCollectionPane extends ContextualPaneBase { return "e.g., address.zipCode"; } - if (this.container && !!this.container.isPreferredApiGraph()) { + if (userContext.apiType === "Gremlin") { return "e.g., /address"; } @@ -384,17 +384,11 @@ export default class AddCollectionPane extends ContextualPaneBase { }); this.uniqueKeysVisible = ko.pureComputed(() => { - if ( - this.container == null || - !!this.container.isPreferredApiMongoDB() || - !!this.container.isPreferredApiTable() || - userContext.apiType === "Cassandra" || - !!this.container.isPreferredApiGraph() - ) { - return false; + if (userContext.apiType === "SQL") { + return true; } - return true; + return false; }); this.partitionKeyVisible = ko.computed(() => { @@ -1011,7 +1005,7 @@ export default class AddCollectionPane extends ContextualPaneBase { return false; } - if (this.container.isPreferredApiGraph() && (this.partitionKey() === "/id" || this.partitionKey() === "/label")) { + if (userContext.apiType === "Gremlin" && (this.partitionKey() === "/id" || this.partitionKey() === "/label")) { this.formErrors("/id and /label as partition keys are not allowed for graph."); return false; } diff --git a/src/Explorer/Panes/SettingsPane/__snapshots__/index.test.tsx.snap b/src/Explorer/Panes/SettingsPane/__snapshots__/index.test.tsx.snap index a2f6f62c3..0cf982b10 100644 --- a/src/Explorer/Panes/SettingsPane/__snapshots__/index.test.tsx.snap +++ b/src/Explorer/Panes/SettingsPane/__snapshots__/index.test.tsx.snap @@ -623,7 +623,6 @@ exports[`Settings Pane should render Default properly 1`] = ` "isMongoIndexingEnabled": [Function], "isNotebookEnabled": [Function], "isNotebooksEnabledForAccount": [Function], - "isPreferredApiGraph": [Function], "isPreferredApiMongoDB": [Function], "isPreferredApiTable": [Function], "isPublishNotebookPaneEnabled": [Function], @@ -1509,7 +1508,6 @@ exports[`Settings Pane should render Gremlin properly 1`] = ` "isMongoIndexingEnabled": [Function], "isNotebookEnabled": [Function], "isNotebooksEnabledForAccount": [Function], - "isPreferredApiGraph": [Function], "isPreferredApiMongoDB": [Function], "isPreferredApiTable": [Function], "isPublishNotebookPaneEnabled": [Function], diff --git a/src/Explorer/Panes/UploadItemsPane/__snapshots__/index.test.tsx.snap b/src/Explorer/Panes/UploadItemsPane/__snapshots__/index.test.tsx.snap index c9f942b3f..64fee8529 100644 --- a/src/Explorer/Panes/UploadItemsPane/__snapshots__/index.test.tsx.snap +++ b/src/Explorer/Panes/UploadItemsPane/__snapshots__/index.test.tsx.snap @@ -623,7 +623,6 @@ exports[`Upload Items Pane should render Default properly 1`] = ` "isMongoIndexingEnabled": [Function], "isNotebookEnabled": [Function], "isNotebooksEnabledForAccount": [Function], - "isPreferredApiGraph": [Function], "isPreferredApiMongoDB": [Function], "isPreferredApiTable": [Function], "isPublishNotebookPaneEnabled": [Function], diff --git a/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap b/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap index 49215da65..69fa8c792 100644 --- a/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap +++ b/src/Explorer/Panes/__snapshots__/DeleteDatabaseConfirmationPanel.test.tsx.snap @@ -626,7 +626,6 @@ exports[`Delete Database Confirmation Pane submit() Should call delete database "isMongoIndexingEnabled": [Function], "isNotebookEnabled": [Function], "isNotebooksEnabledForAccount": [Function], - "isPreferredApiGraph": [Function], "isPreferredApiMongoDB": [Function], "isPreferredApiTable": [Function], "isPublishNotebookPaneEnabled": [Function], diff --git a/src/Explorer/SplashScreen/SplashScreen.tsx b/src/Explorer/SplashScreen/SplashScreen.tsx index 43fec912f..85186f5c6 100644 --- a/src/Explorer/SplashScreen/SplashScreen.tsx +++ b/src/Explorer/SplashScreen/SplashScreen.tsx @@ -227,7 +227,7 @@ export class SplashScreen extends React.Component { } if (!this.container.isDatabaseNodeOrNoneSelected()) { - if (userContext.apiType === "SQL" || this.container.isPreferredApiGraph()) { + if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") { items.push({ iconSrc: NewQueryIcon, onClick: () => { diff --git a/src/Explorer/Tree/Collection.ts b/src/Explorer/Tree/Collection.ts index 58cffa72b..a876989e8 100644 --- a/src/Explorer/Tree/Collection.ts +++ b/src/Explorer/Tree/Collection.ts @@ -1087,7 +1087,7 @@ export default class Collection implements ViewModels.Collection { } else if (userContext.apiType === "Cassandra") { this.onTableEntitiesClick(); return; - } else if (this.container.isPreferredApiGraph()) { + } else if (userContext.apiType === "Gremlin") { this.onGraphDocumentsClick(); return; } else if (this.container.isPreferredApiMongoDB()) { @@ -1106,7 +1106,7 @@ export default class Collection implements ViewModels.Collection { return "Entities"; } else if (userContext.apiType === "Cassandra") { return "Rows"; - } else if (this.container.isPreferredApiGraph()) { + } else if (userContext.apiType === "Gremlin") { return "Graph"; } else if (this.container.isPreferredApiMongoDB()) { return "Documents"; diff --git a/src/Explorer/Tree/ResourceTreeAdapter.tsx b/src/Explorer/Tree/ResourceTreeAdapter.tsx index 9397675ad..b3f928497 100644 --- a/src/Explorer/Tree/ResourceTreeAdapter.tsx +++ b/src/Explorer/Tree/ResourceTreeAdapter.tsx @@ -253,7 +253,7 @@ export class ResourceTreeAdapter implements ReactAdapter { * @param container */ private static showScriptNodes(container: Explorer): boolean { - return userContext.apiType === "SQL" || container.isPreferredApiGraph(); + return userContext.apiType === "SQL" || userContext.apiType === "Gremlin"; } private buildCollectionNode(database: ViewModels.Database, collection: ViewModels.Collection): TreeNode { diff --git a/src/RouteHandlers/TabRouteHandler.ts b/src/RouteHandlers/TabRouteHandler.ts index fd2e38f1c..19a453c38 100644 --- a/src/RouteHandlers/TabRouteHandler.ts +++ b/src/RouteHandlers/TabRouteHandler.ts @@ -158,10 +158,7 @@ export class TabRouteHandler { databaseId, collectionId ); - collection && - collection.container && - collection.container.isPreferredApiGraph() && - collection.onGraphDocumentsClick(); + userContext.apiType === "Gremlin" && collection.onGraphDocumentsClick(); }); } From a9fd01f9b450744ebb9b865303abefe1df362f6f Mon Sep 17 00:00:00 2001 From: Jordi Bunster Date: Sun, 18 Apr 2021 09:22:27 -0700 Subject: [PATCH 04/27] Remove stfaul's subscription and account from URL (#694) --- preview/index.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/preview/index.js b/preview/index.js index 319d1079f..c17eec9ee 100644 --- a/preview/index.js +++ b/preview/index.js @@ -57,8 +57,6 @@ app.get("/pull/:pr(\\d+)", (req, res) => { const portal = new URL("https://ms.portal.azure.com/"); portal.searchParams.set("dataExplorerSource", explorer.href); - portal.hash = - "@microsoft.onmicrosoft.com/resource/subscriptions/b9c77f10-b438-4c32-9819-eef8a654e478/resourceGroups/stfaul/providers/Microsoft.DocumentDb/databaseAccounts/stfaul-sql/dataExplorer"; return res.redirect(portal.href); }) From e0060b12e5e96b9806d166f29ddef36a6ff0bb7d Mon Sep 17 00:00:00 2001 From: Jordi Bunster Date: Sun, 18 Apr 2021 17:48:39 -0700 Subject: [PATCH 05/27] Keep active tab state in one place (manager) (#683) With this change TabsBase objects will retain a reference to the TabsManager they belong to, so they can ask it if they're the active tab or not. This removes the possibility for bugs like activating an unmanaged tab, or having more than one active tab, etc. --- src/Contracts/ViewModels.ts | 1 - .../Settings/SettingsComponent.test.tsx | 1 - src/Explorer/Explorer.tsx | 8 +- .../CommandBar/CommandBarComponentAdapter.tsx | 4 +- src/Explorer/Tabs/DocumentsTab.test.ts | 12 --- src/Explorer/Tabs/NotebookV2Tab.ts | 1 - src/Explorer/Tabs/QueryTab.test.ts | 1 - src/Explorer/Tabs/TabsBase.ts | 41 ++++------ src/Explorer/Tabs/TabsManager.html | 27 ++++--- src/Explorer/Tabs/TabsManager.test.ts | 17 ++-- src/Explorer/Tabs/TabsManager.ts | 77 ++++++------------- src/Explorer/Tree/Collection.ts | 12 --- src/Explorer/Tree/Database.ts | 1 - src/Explorer/Tree/ResourceTokenCollection.ts | 2 - src/Explorer/Tree/StoredProcedure.ts | 4 +- src/Explorer/Tree/Trigger.ts | 4 +- src/Explorer/Tree/UserDefinedFunction.ts | 4 +- 17 files changed, 74 insertions(+), 143 deletions(-) diff --git a/src/Contracts/ViewModels.ts b/src/Contracts/ViewModels.ts index 71d76624b..dc14bb86c 100644 --- a/src/Contracts/ViewModels.ts +++ b/src/Contracts/ViewModels.ts @@ -276,7 +276,6 @@ export interface TabOptions { tabKind: CollectionTabKind; title: string; tabPath: string; - isActive: ko.Observable; hashLocation: string; onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]) => void; isTabsContentExpanded?: ko.Observable; diff --git a/src/Explorer/Controls/Settings/SettingsComponent.test.tsx b/src/Explorer/Controls/Settings/SettingsComponent.test.tsx index 2db5adf5f..69ee13439 100644 --- a/src/Explorer/Controls/Settings/SettingsComponent.test.tsx +++ b/src/Explorer/Controls/Settings/SettingsComponent.test.tsx @@ -39,7 +39,6 @@ describe("SettingsComponent", () => { tabPath: "", node: undefined, hashLocation: "settings", - isActive: ko.observable(false), onUpdateTabsButtons: undefined, }), }; diff --git a/src/Explorer/Explorer.tsx b/src/Explorer/Explorer.tsx index 474efdff2..19a559458 100644 --- a/src/Explorer/Explorer.tsx +++ b/src/Explorer/Explorer.tsx @@ -559,6 +559,12 @@ export default class Explorer { }); this.tabsManager = params?.tabsManager ?? new TabsManager(); + this.tabsManager.openedTabs.subscribe((tabs) => { + if (tabs.length === 0) { + this.selectedNode(undefined); + this.onUpdateTabsButtons([]); + } + }); this._panes = [ this.addDatabasePane, @@ -1570,7 +1576,6 @@ export default class Explorer { collection: null, masterKey: userContext.masterKey || "", hashLocation: "notebooks", - isActive: ko.observable(false), isTabsContentExpanded: ko.observable(true), onLoadStartKey: null, onUpdateTabsButtons: this.onUpdateTabsButtons, @@ -1976,7 +1981,6 @@ export default class Explorer { tabPath: title, collection: null, hashLocation: hashLocation, - isActive: ko.observable(false), isTabsContentExpanded: ko.observable(true), onLoadStartKey: null, onUpdateTabsButtons: this.onUpdateTabsButtons, diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentAdapter.tsx b/src/Explorer/Menus/CommandBar/CommandBarComponentAdapter.tsx index 65ff5e176..d93407c22 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentAdapter.tsx +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentAdapter.tsx @@ -23,8 +23,8 @@ export class CommandBarComponentAdapter implements ReactAdapter { constructor(container: Explorer) { this.container = container; this.tabsButtons = []; - this.isNotebookTabActive = ko.computed(() => - container.tabsManager.isTabActive(ViewModels.CollectionTabKind.NotebookV2) + this.isNotebookTabActive = ko.computed( + () => container.tabsManager.activeTab()?.tabKind === ViewModels.CollectionTabKind.NotebookV2 ); // These are the parameters watched by the react binding that will trigger a renderComponent() if one of the ko mutates diff --git a/src/Explorer/Tabs/DocumentsTab.test.ts b/src/Explorer/Tabs/DocumentsTab.test.ts index df46d171a..1d470f23a 100644 --- a/src/Explorer/Tabs/DocumentsTab.test.ts +++ b/src/Explorer/Tabs/DocumentsTab.test.ts @@ -16,8 +16,6 @@ describe("Documents tab", () => { title: "", tabPath: "", hashLocation: "", - isActive: ko.observable(false), - onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {}, }); @@ -89,8 +87,6 @@ describe("Documents tab", () => { title: "", tabPath: "", hashLocation: "", - isActive: ko.observable(false), - onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {}, }); @@ -106,8 +102,6 @@ describe("Documents tab", () => { title: "", tabPath: "", hashLocation: "", - isActive: ko.observable(false), - onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {}, }); @@ -123,8 +117,6 @@ describe("Documents tab", () => { title: "", tabPath: "", hashLocation: "", - isActive: ko.observable(false), - onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {}, }); @@ -140,8 +132,6 @@ describe("Documents tab", () => { title: "", tabPath: "", hashLocation: "", - isActive: ko.observable(false), - onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {}, }); @@ -157,8 +147,6 @@ describe("Documents tab", () => { title: "", tabPath: "", hashLocation: "", - isActive: ko.observable(false), - onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {}, }); diff --git a/src/Explorer/Tabs/NotebookV2Tab.ts b/src/Explorer/Tabs/NotebookV2Tab.ts index a7c2242c2..c3f3817be 100644 --- a/src/Explorer/Tabs/NotebookV2Tab.ts +++ b/src/Explorer/Tabs/NotebookV2Tab.ts @@ -88,7 +88,6 @@ export default class NotebookTabV2 extends TabsBase { public onCloseTabButtonClick(): Q.Promise { const cleanup = () => { this.notebookComponentAdapter.notebookShutdown(); - this.isActive(false); super.onCloseTabButtonClick(); }; diff --git a/src/Explorer/Tabs/QueryTab.test.ts b/src/Explorer/Tabs/QueryTab.test.ts index 2e588a99d..c146f6d6f 100644 --- a/src/Explorer/Tabs/QueryTab.test.ts +++ b/src/Explorer/Tabs/QueryTab.test.ts @@ -25,7 +25,6 @@ describe("Query Tab", () => { database: database, title: "", tabPath: "", - isActive: ko.observable(false), hashLocation: "", onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]): void => {}, }); diff --git a/src/Explorer/Tabs/TabsBase.ts b/src/Explorer/Tabs/TabsBase.ts index 23dbde0a4..3addaba4f 100644 --- a/src/Explorer/Tabs/TabsBase.ts +++ b/src/Explorer/Tabs/TabsBase.ts @@ -1,15 +1,16 @@ import * as ko from "knockout"; import Q from "q"; import * as Constants from "../../Common/Constants"; -import * as ViewModels from "../../Contracts/ViewModels"; -import * as DataModels from "../../Contracts/DataModels"; -import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; -import { RouteHandler } from "../../RouteHandlers/RouteHandler"; -import { WaitsForTemplateViewModel } from "../WaitsForTemplateViewModel"; -import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import * as ThemeUtility from "../../Common/ThemeUtility"; -import Explorer from "../Explorer"; +import * as DataModels from "../../Contracts/DataModels"; +import * as ViewModels from "../../Contracts/ViewModels"; +import { RouteHandler } from "../../RouteHandlers/RouteHandler"; +import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; +import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent"; +import Explorer from "../Explorer"; +import { WaitsForTemplateViewModel } from "../WaitsForTemplateViewModel"; +import { TabsManager } from "./TabsManager"; // TODO: Use specific actions for logging telemetry data export default class TabsBase extends WaitsForTemplateViewModel { @@ -20,18 +21,16 @@ export default class TabsBase extends WaitsForTemplateViewModel { public database: ViewModels.Database; public rid: string; public hasFocus: ko.Observable; - public isActive: ko.Observable; public isMouseOver: ko.Observable; public tabId: string; public tabKind: ViewModels.CollectionTabKind; public tabTitle: ko.Observable; public tabPath: ko.Observable; - public closeButtonTabIndex: ko.Computed; - public errorDetailsTabIndex: ko.Computed; public hashLocation: ko.Observable; public isExecutionError: ko.Observable; public isExecuting: ko.Observable; public pendingNotification?: ko.Observable; + public manager?: TabsManager; protected _theme: string; public onLoadStartKey: number; @@ -46,7 +45,6 @@ export default class TabsBase extends WaitsForTemplateViewModel { this.database = options.database; this.rid = options.rid || (this.collection && this.collection.rid) || ""; this.hasFocus = ko.observable(false); - this.isActive = options.isActive || ko.observable(false); this.isMouseOver = ko.observable(false); this.tabId = `tab${id}`; this.tabKind = options.tabKind; @@ -55,21 +53,12 @@ export default class TabsBase extends WaitsForTemplateViewModel { (options.tabPath && ko.observable(options.tabPath)) || (this.collection && ko.observable(`${this.collection.databaseId}>${this.collection.id()}>${this.tabTitle()}`)); - this.closeButtonTabIndex = ko.computed(() => (this.isActive() ? 0 : null)); - this.errorDetailsTabIndex = ko.computed(() => (this.isActive() ? 0 : null)); this.isExecutionError = ko.observable(false); this.isExecuting = ko.observable(false); this.pendingNotification = ko.observable(undefined); this.onLoadStartKey = options.onLoadStartKey; this.hashLocation = ko.observable(options.hashLocation || ""); this.hashLocation.subscribe((newLocation: string) => this.updateGlobalHash(newLocation)); - - this.isActive.subscribe((isActive: boolean) => { - if (isActive) { - this.onActivate(); - } - }); - this.closeTabButton = { enabled: ko.computed(() => { return true; @@ -82,12 +71,9 @@ export default class TabsBase extends WaitsForTemplateViewModel { } public onCloseTabButtonClick(): void { - const explorer = this.getContainer(); - explorer.tabsManager.closeTab(this.tabId, explorer); - + this.manager?.closeTab(this); TelemetryProcessor.trace(Action.Tab, ActionModifiers.Close, { tabName: this.constructor.name, - dataExplorerArea: Constants.Areas.Tab, tabTitle: this.tabTitle(), tabId: this.tabId, @@ -95,7 +81,7 @@ export default class TabsBase extends WaitsForTemplateViewModel { } public onTabClick(): void { - this.getContainer().tabsManager.activateTab(this); + this.manager?.activateTab(this); } protected updateSelectedNode(): void { @@ -127,6 +113,11 @@ export default class TabsBase extends WaitsForTemplateViewModel { return this.onSpaceOrEnterKeyPress(event, () => this.onCloseTabButtonClick()); }; + /** @deprecated this is no longer observable, bind to comparisons with manager.activeTab() instead */ + public isActive() { + return this === this.manager?.activeTab(); + } + public onActivate(): void { this.updateSelectedNode(); if (!!this.collection) { diff --git a/src/Explorer/Tabs/TabsManager.html b/src/Explorer/Tabs/TabsManager.html index d66a2ba33..a876cf38d 100644 --- a/src/Explorer/Tabs/TabsManager.html +++ b/src/Explorer/Tabs/TabsManager.html @@ -1,4 +1,8 @@ -
+