From 0df68c4967710baf485c6b53ecf1e00380c2746d Mon Sep 17 00:00:00 2001 From: Vsevolod Kukol Date: Tue, 26 Mar 2024 17:22:15 +0100 Subject: [PATCH 01/13] Fix initial container loading in Fabric (#1771) * Fix initial container loading in Fabric There is a rendering issue where the documents table doesn't resize properly if explorer is loaded in the beackground (invisible). To workaround this, track DE visibility from Fabric RPC and open the first container only once DE becomes visible. If DE has been already shown, open the container right away. * Preserve glitchy behavior if Fabric UX doesn't send isVisible * Preserve Fabric visibility in global status and fix a race condition where visibility might change during initialization. --- src/Contracts/FabricMessagesContract.ts | 3 ++- src/UserContext.ts | 1 + src/hooks/useKnockoutExplorer.ts | 22 +++++++++++++++++----- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/Contracts/FabricMessagesContract.ts b/src/Contracts/FabricMessagesContract.ts index d0b36f1c6..b381fd845 100644 --- a/src/Contracts/FabricMessagesContract.ts +++ b/src/Contracts/FabricMessagesContract.ts @@ -53,6 +53,7 @@ export type FabricMessageV2 = id: string; message: { connectionId: string; + isVisible: boolean; }; } | { @@ -72,7 +73,7 @@ export type FabricMessageV2 = }; } | { - type: "setToolbarStatus"; + type: "explorerVisible"; message: { visible: boolean; }; diff --git a/src/UserContext.ts b/src/UserContext.ts index daae0f052..2fa1bb946 100644 --- a/src/UserContext.ts +++ b/src/UserContext.ts @@ -51,6 +51,7 @@ interface FabricContext { connectionId: string; databaseConnectionInfo: FabricDatabaseConnectionInfo | undefined; isReadOnly: boolean; + isVisible: boolean; } export type AdminFeedbackControlPolicy = diff --git a/src/hooks/useKnockoutExplorer.ts b/src/hooks/useKnockoutExplorer.ts index 22b5078c9..b04b16c72 100644 --- a/src/hooks/useKnockoutExplorer.ts +++ b/src/hooks/useKnockoutExplorer.ts @@ -2,7 +2,6 @@ import { createUri } from "Common/UrlUtility"; import { DATA_EXPLORER_RPC_VERSION } from "Contracts/DataExplorerMessagesContract"; import { FABRIC_RPC_VERSION, FabricMessageV2 } from "Contracts/FabricMessagesContract"; import Explorer from "Explorer/Explorer"; -import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter"; import { useSelectedNode } from "Explorer/useSelectedNode"; import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil"; import { getNetworkSettingsWarningMessage } from "Utils/NetworkUtility"; @@ -90,6 +89,7 @@ async function configureFabric(): Promise { // These are the versions of Fabric that Data Explorer supports. const SUPPORTED_FABRIC_VERSIONS = [FABRIC_RPC_VERSION]; + let firstContainerOpened = false; let explorer: Explorer; return new Promise((resolve) => { window.addEventListener( @@ -121,7 +121,10 @@ async function configureFabric(): Promise { await scheduleRefreshDatabaseResourceToken(true); resolve(explorer); await explorer.refreshAllDatabases(); - openFirstContainer(explorer, userContext.fabricContext.databaseConnectionInfo.databaseId); + if (userContext.fabricContext.isVisible && !firstContainerOpened) { + firstContainerOpened = true; + openFirstContainer(explorer, userContext.fabricContext.databaseConnectionInfo.databaseId); + } break; } case "newContainer": @@ -132,8 +135,16 @@ async function configureFabric(): Promise { handleCachedDataMessage(data); break; } - case "setToolbarStatus": { - useCommandBar.getState().setIsHidden(data.message.visible === false); + case "explorerVisible": { + userContext.fabricContext.isVisible = data.message.visible; + if ( + userContext.fabricContext.isVisible && + !firstContainerOpened && + userContext?.fabricContext?.databaseConnectionInfo?.databaseId !== undefined + ) { + firstContainerOpened = true; + openFirstContainer(explorer, userContext.fabricContext.databaseConnectionInfo.databaseId); + } break; } default: @@ -327,12 +338,13 @@ function configureHostedWithResourceToken(config: ResourceToken): Explorer { return explorer; } -function createExplorerFabric(params: { connectionId: string }): Explorer { +function createExplorerFabric(params: { connectionId: string; isVisible: boolean }): Explorer { updateUserContext({ fabricContext: { connectionId: params.connectionId, databaseConnectionInfo: undefined, isReadOnly: true, + isVisible: params.isVisible ?? true, }, authType: AuthType.ConnectionString, databaseAccount: { From 56408a97d7ff9354d2abb4b328f75314cf3ef436 Mon Sep 17 00:00:00 2001 From: JustinKol <144163838+JustinKol@users.noreply.github.com> Date: Tue, 26 Mar 2024 12:36:04 -0400 Subject: [PATCH 02/13] Add container ids to tabs (#1772) * Added container ids to tabs * prettier run * Updated for undefined scenarios * prettier * added ellipsis to long container names * added slice * prettier * Added ellipsis to long DB names in tabs * Added undefined DB case * Replaced dots with ellipsis character * corrected undefined return value --- src/Explorer/Tabs/TabsBase.ts | 27 +++++++++++++++++++++++---- src/Explorer/Tree/Collection.ts | 4 ++-- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/Explorer/Tabs/TabsBase.ts b/src/Explorer/Tabs/TabsBase.ts index a6f3a45dd..0425eac91 100644 --- a/src/Explorer/Tabs/TabsBase.ts +++ b/src/Explorer/Tabs/TabsBase.ts @@ -40,11 +40,10 @@ export default class TabsBase extends WaitsForTemplateViewModel { this.database = options.database; this.rid = options.rid || (this.collection && this.collection.rid) || ""; this.tabKind = options.tabKind; - this.tabTitle = ko.observable(options.title); + this.tabTitle = ko.observable(this.getTitle(options)); this.tabPath = - ko.observable(options.tabPath ?? "") || - (this.collection && - ko.observable(`${this.collection.databaseId}>${this.collection.id()}>${this.tabTitle()}`)); + this.collection && + ko.observable(`${this.collection.databaseId}>${this.collection.id()}>${options.title}`); this.pendingNotification = ko.observable(undefined); this.onLoadStartKey = options.onLoadStartKey; this.closeTabButton = { @@ -143,6 +142,26 @@ export default class TabsBase extends WaitsForTemplateViewModel { return (this.collection && this.collection.container) || (this.database && this.database.container); } + public getTitle(options: ViewModels.TabOptions): string { + const coll = this.collection?.id(); + const db = this.database?.id(); + if (coll) { + if (coll.length > 8) { + return coll.slice(0, 5) + "…" + options.title; + } else { + return coll + "." + options.title; + } + } else if (db) { + if (db.length > 8) { + return db.slice(0, 5) + "…" + options.title; + } else { + return db + "." + options.title; + } + } else { + return options.title; + } + } + /** Renders a Javascript object to be displayed inside Monaco Editor */ public renderObjectForEditor(value: any, replacer: any, space: string | number): string { return JSON.stringify(value, replacer, space); diff --git a/src/Explorer/Tree/Collection.ts b/src/Explorer/Tree/Collection.ts index 649c560a9..d7c673620 100644 --- a/src/Explorer/Tree/Collection.ts +++ b/src/Explorer/Tree/Collection.ts @@ -308,7 +308,7 @@ export default class Collection implements ViewModels.Collection { collectionName: this.id(), dataExplorerArea: Constants.Areas.Tab, - tabTitle: this.rawDataModel.id + " - Items", + tabTitle: "Items", }); this.documentIds([]); @@ -316,7 +316,7 @@ export default class Collection implements ViewModels.Collection { partitionKey: this.partitionKey, documentIds: ko.observableArray([]), tabKind: ViewModels.CollectionTabKind.Documents, - title: this.rawDataModel.id + " - Items", + title: "Items", collection: this, node: this, tabPath: `${this.databaseId}>${this.id()}>Documents`, From f24b0bcf1b0116df79d72a0af08515060e8b8f25 Mon Sep 17 00:00:00 2001 From: Vsevolod Kukol Date: Thu, 28 Mar 2024 16:05:38 +0100 Subject: [PATCH 03/13] Show the Feedback button in Portal only (#1775) This feature is not supported on any other platform incl. Hosted. --- .../Menus/CommandBar/CommandBarComponentButtonFactory.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx index c37ae41fb..869dd2383 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx @@ -240,7 +240,7 @@ export function createControlCommandBarButtons(container: Explorer): CommandButt buttons.push(fullScreenButton); } - if (configContext.platform !== Platform.Emulator) { + if (configContext.platform === Platform.Portal) { const label = "Feedback"; const feedbackButtonOptions: CommandButtonComponentProps = { iconSrc: FeedbackIcon, From 5aa6b0abe17163bf5f6bdd6cb6e3a4b30b2ba3eb Mon Sep 17 00:00:00 2001 From: sunghyunkang1111 <114709653+sunghyunkang1111@users.noreply.github.com> Date: Thu, 28 Mar 2024 12:10:32 -0500 Subject: [PATCH 04/13] fix opening collections (#1780) * fix opening collections * fix opening collections * fix opening collections * fix opening collections --- src/Explorer/OpenActions/OpenActions.tsx | 192 ++++++++++++----------- 1 file changed, 104 insertions(+), 88 deletions(-) diff --git a/src/Explorer/OpenActions/OpenActions.tsx b/src/Explorer/OpenActions/OpenActions.tsx index 876f8e391..f3ef288c8 100644 --- a/src/Explorer/OpenActions/OpenActions.tsx +++ b/src/Explorer/OpenActions/OpenActions.tsx @@ -1,4 +1,5 @@ // TODO convert this file to an action registry in order to have actions and their handlers be more tightly coupled. +import { useDatabases } from "Explorer/useDatabases"; import React from "react"; import { ActionContracts } from "../../Contracts/ExplorerContracts"; import * as ViewModels from "../../Contracts/ViewModels"; @@ -40,97 +41,112 @@ function openCollectionTab( databases: ViewModels.Database[], initialDatabaseIndex = 0, ) { - for (let i = initialDatabaseIndex; i < databases.length; i++) { - const database: ViewModels.Database = databases[i]; - if (!!action.databaseResourceId && database.id() !== action.databaseResourceId) { - continue; - } - - const collectionActionHandler = (collections: ViewModels.Collection[]) => { - if (!action.collectionResourceId && collections.length === 0) { - subscription.dispose(); - openCollectionTab(action, databases, ++i); - return; - } - - for (let j = 0; j < collections.length; j++) { - const collection: ViewModels.Collection = collections[j]; - if (!!action.collectionResourceId && collection.id() !== action.collectionResourceId) { - continue; - } - - // select the collection - collection.expandCollection(); - - if ( - action.tabKind === ActionContracts.TabKind.SQLDocuments || - action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SQLDocuments] - ) { - collection.onDocumentDBDocumentsClick(); - break; - } - - if ( - action.tabKind === ActionContracts.TabKind.MongoDocuments || - action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.MongoDocuments] - ) { - collection.onMongoDBDocumentsClick(); - break; - } - - if ( - action.tabKind === ActionContracts.TabKind.SchemaAnalyzer || - action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SchemaAnalyzer] - ) { - collection.onSchemaAnalyzerClick(); - break; - } - - if ( - action.tabKind === ActionContracts.TabKind.TableEntities || - action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.TableEntities] - ) { - collection.onTableEntitiesClick(); - break; - } - - if ( - action.tabKind === ActionContracts.TabKind.Graph || - action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.Graph] - ) { - collection.onGraphDocumentsClick(); - break; - } - - if ( - action.tabKind === ActionContracts.TabKind.SQLQuery || - action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SQLQuery] - ) { - collection.onNewQueryClick( - collection, - undefined, - generateQueryText(action as ActionContracts.OpenQueryTab, collection.partitionKeyProperties), - ); - break; - } - - if ( - action.tabKind === ActionContracts.TabKind.ScaleSettings || - action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.ScaleSettings] - ) { - collection.onSettingsClick(); - break; - } - } - subscription.dispose(); + //if databases are not yet loaded, wait until loaded + if (!databases || databases.length === 0) { + const databaseActionHandler = (databases: ViewModels.Database[]) => { + databasesUnsubscription(); + openCollectionTab(action, databases, 0); + return; }; + const databasesUnsubscription = useDatabases.subscribe(databaseActionHandler, (state) => state.databases); + } else { + for (let i = initialDatabaseIndex; i < databases.length; i++) { + const database: ViewModels.Database = databases[i]; + if (!!action.databaseResourceId && database.id() !== action.databaseResourceId) { + continue; + } - const subscription = database.collections.subscribe((collections) => collectionActionHandler(collections)); - if (database.collections && database.collections() && database.collections().length) { - collectionActionHandler(database.collections()); + //expand database first if not expanded to load the collections + if (!database.isDatabaseExpanded?.()) { + database.expandDatabase?.(); + } + + const collectionActionHandler = (collections: ViewModels.Collection[]) => { + if (!action.collectionResourceId && collections.length === 0) { + subscription.dispose(); + openCollectionTab(action, databases, ++i); + return; + } + + for (let j = 0; j < collections.length; j++) { + const collection: ViewModels.Collection = collections[j]; + if (!!action.collectionResourceId && collection.id() !== action.collectionResourceId) { + continue; + } + + // select the collection + collection.expandCollection(); + + if ( + action.tabKind === ActionContracts.TabKind.SQLDocuments || + action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SQLDocuments] + ) { + collection.onDocumentDBDocumentsClick(); + break; + } + + if ( + action.tabKind === ActionContracts.TabKind.MongoDocuments || + action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.MongoDocuments] + ) { + collection.onMongoDBDocumentsClick(); + break; + } + + if ( + action.tabKind === ActionContracts.TabKind.SchemaAnalyzer || + action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SchemaAnalyzer] + ) { + collection.onSchemaAnalyzerClick(); + break; + } + + if ( + action.tabKind === ActionContracts.TabKind.TableEntities || + action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.TableEntities] + ) { + collection.onTableEntitiesClick(); + break; + } + + if ( + action.tabKind === ActionContracts.TabKind.Graph || + action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.Graph] + ) { + collection.onGraphDocumentsClick(); + break; + } + + if ( + action.tabKind === ActionContracts.TabKind.SQLQuery || + action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SQLQuery] + ) { + collection.onNewQueryClick( + collection, + undefined, + generateQueryText(action as ActionContracts.OpenQueryTab, collection.partitionKeyProperties), + ); + break; + } + + if ( + action.tabKind === ActionContracts.TabKind.ScaleSettings || + action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.ScaleSettings] + ) { + collection.onSettingsClick(); + break; + } + } + subscription.dispose(); + }; + + const subscription = database.collections.subscribe((collections) => collectionActionHandler(collections)); + if (database.collections && database.collections() && database.collections().length) { + collectionActionHandler(database.collections()); + } + + break; } - - break; } } From cabedf4a290ca480796ac931a68d23fed998eefe Mon Sep 17 00:00:00 2001 From: jawelton74 <103591340+jawelton74@users.noreply.github.com> Date: Mon, 1 Apr 2024 07:54:04 -0700 Subject: [PATCH 05/13] Enable new backend endpoint to be passed to Data Explorer via message. (#1782) --- src/Contracts/ViewModels.ts | 1 + src/hooks/useKnockoutExplorer.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Contracts/ViewModels.ts b/src/Contracts/ViewModels.ts index b79ddce4e..66fd54ec4 100644 --- a/src/Contracts/ViewModels.ts +++ b/src/Contracts/ViewModels.ts @@ -387,6 +387,7 @@ export interface DataExplorerInputsFrame { dnsSuffix?: string; serverId?: string; extensionEndpoint?: string; + portalBackendEndpoint?: string; mongoProxyEndpoint?: string; cassandraProxyEndpoint?: string; subscriptionType?: SubscriptionType; diff --git a/src/hooks/useKnockoutExplorer.ts b/src/hooks/useKnockoutExplorer.ts index b04b16c72..4e80b3476 100644 --- a/src/hooks/useKnockoutExplorer.ts +++ b/src/hooks/useKnockoutExplorer.ts @@ -497,6 +497,7 @@ function updateContextsFromPortalMessage(inputs: DataExplorerInputsFrame) { ARM_ENDPOINT: normalizeArmEndpoint(inputs.csmEndpoint || configContext.ARM_ENDPOINT), MONGO_PROXY_ENDPOINT: inputs.mongoProxyEndpoint, CASSANDRA_PROXY_ENDPOINT: inputs.cassandraProxyEndpoint, + PORTAL_BACKEND_ENDPOINT: inputs.portalBackendEndpoint, }); updateUserContext({ From 86f2bc171f63c516ff37fbcb04c26e4eac377592 Mon Sep 17 00:00:00 2001 From: sindhuba <122321535+sindhuba@users.noreply.github.com> Date: Mon, 1 Apr 2024 08:44:42 -0700 Subject: [PATCH 06/13] Remove notebooks UI (#1779) * Fix API endpoint for CassandraProxy query API * Remove notebooks UI components * Fix tests * Address comments * Fix unit tests * Remove commented code --- .../CommandBarComponentButtonFactory.test.ts | 269 ------------------ .../CommandBarComponentButtonFactory.tsx | 143 ---------- src/Explorer/Tree/ResourceTree.tsx | 8 - src/Explorer/Tree/ResourceTreeAdapter.tsx | 5 - 4 files changed, 425 deletions(-) diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts index 677df81de..0a4a805d2 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts @@ -2,10 +2,8 @@ import * as ko from "knockout"; import { AuthType } from "../../../AuthType"; import { DatabaseAccount } from "../../../Contracts/DataModels"; import { CollectionBase } from "../../../Contracts/ViewModels"; -import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService"; import { updateUserContext } from "../../../UserContext"; import Explorer from "../../Explorer"; -import NotebookManager from "../../Notebook/NotebookManager"; import { useNotebook } from "../../Notebook/useNotebook"; import { useDatabases } from "../../useDatabases"; import { useSelectedNode } from "../../useSelectedNode"; @@ -72,181 +70,6 @@ describe("CommandBarComponentButtonFactory tests", () => { }); }); - describe("Enable notebook button", () => { - const enableNotebookBtnLabel = "Enable Notebooks (Preview)"; - const selectedNodeState = useSelectedNode.getState(); - - beforeAll(() => { - mockExplorer = {} as Explorer; - updateUserContext({ - portalEnv: "prod", - databaseAccount: { - properties: { - capabilities: [{ name: "EnableTable" }], - }, - } as DatabaseAccount, - }); - }); - - afterEach(() => { - updateUserContext({ - portalEnv: "prod", - }); - useNotebook.getState().setIsNotebookEnabled(false); - useNotebook.getState().setIsNotebooksEnabledForAccount(false); - }); - - it("Notebooks is already enabled - button should be hidden", () => { - useNotebook.getState().setIsNotebookEnabled(true); - useNotebook.getState().setIsNotebooksEnabledForAccount(true); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel); - expect(enableNotebookBtn).toBeUndefined(); - }); - - it("Account is running on one of the national clouds - button should be hidden", () => { - updateUserContext({ - portalEnv: "mooncake", - }); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel); - expect(enableNotebookBtn).toBeUndefined(); - }); - - it("Notebooks is not enabled but is available - button should be shown and enabled", () => { - useNotebook.getState().setIsNotebooksEnabledForAccount(true); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel); - - //TODO: modify once notebooks are available - expect(enableNotebookBtn).toBeUndefined(); - //expect(enableNotebookBtn).toBeDefined(); - //expect(enableNotebookBtn.disabled).toBe(false); - //expect(enableNotebookBtn.tooltipText).toBe(""); - }); - - it("Notebooks is not enabled and is unavailable - button should be shown and disabled", () => { - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const enableNotebookBtn = buttons.find((button) => button.commandButtonLabel === enableNotebookBtnLabel); - - //TODO: modify once notebooks are available - expect(enableNotebookBtn).toBeUndefined(); - //expect(enableNotebookBtn).toBeDefined(); - //expect(enableNotebookBtn.disabled).toBe(true); - //expect(enableNotebookBtn.tooltipText).toBe( - // "Notebooks are not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks." - //); - }); - }); - - describe("Open Mongo shell button", () => { - const openMongoShellBtnLabel = "Open Mongo shell"; - const selectedNodeState = useSelectedNode.getState(); - - beforeAll(() => { - mockExplorer = {} as Explorer; - updateUserContext({ - databaseAccount: { - properties: { - capabilities: [{ name: "EnableTable" }], - }, - } as DatabaseAccount, - }); - }); - - afterAll(() => { - updateUserContext({ - apiType: "SQL", - }); - useNotebook.getState().setIsShellEnabled(false); - }); - - beforeEach(() => { - updateUserContext({ - apiType: "Mongo", - }); - useNotebook.getState().setIsShellEnabled(true); - }); - - afterEach(() => { - useNotebook.getState().setIsNotebookEnabled(false); - useNotebook.getState().setIsNotebooksEnabledForAccount(false); - }); - - it("Mongo Api not available - button should be hidden", () => { - updateUserContext({ - apiType: "SQL", - }); - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); - expect(openMongoShellBtn).toBeUndefined(); - }); - - it("Running on a national cloud - button should be hidden", () => { - updateUserContext({ - portalEnv: "mooncake", - }); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); - expect(openMongoShellBtn).toBeUndefined(); - }); - - it("Notebooks is not enabled and is unavailable - button should be hidden", () => { - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); - expect(openMongoShellBtn).toBeUndefined(); - }); - - it("Notebooks is not enabled and is available - button should be hidden", () => { - useNotebook.getState().setIsNotebooksEnabledForAccount(true); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); - expect(openMongoShellBtn).toBeUndefined(); - }); - - it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => { - useNotebook.getState().setIsNotebookEnabled(true); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); - expect(openMongoShellBtn).toBeDefined(); - - //TODO: modify once notebooks are available - expect(openMongoShellBtn.disabled).toBe(true); - //expect(openMongoShellBtn.disabled).toBe(false); - //expect(openMongoShellBtn.tooltipText).toBe(""); - }); - - it("Notebooks is enabled and is available - button should be shown and enabled", () => { - useNotebook.getState().setIsNotebookEnabled(true); - useNotebook.getState().setIsNotebooksEnabledForAccount(true); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); - expect(openMongoShellBtn).toBeDefined(); - - //TODO: modify once notebooks are available - expect(openMongoShellBtn.disabled).toBe(true); - //expect(openMongoShellBtn.disabled).toBe(false); - //expect(openMongoShellBtn.tooltipText).toBe(""); - }); - - it("Notebooks is enabled and is available, terminal is unavailable due to ipRules - button should be hidden", () => { - useNotebook.getState().setIsNotebookEnabled(true); - useNotebook.getState().setIsNotebooksEnabledForAccount(true); - useNotebook.getState().setIsShellEnabled(false); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const openMongoShellBtn = buttons.find((button) => button.commandButtonLabel === openMongoShellBtnLabel); - expect(openMongoShellBtn).toBeUndefined(); - }); - }); - describe("Open Cassandra shell button", () => { const openCassandraShellBtnLabel = "Open Cassandra shell"; const selectedNodeState = useSelectedNode.getState(); @@ -305,42 +128,6 @@ describe("CommandBarComponentButtonFactory tests", () => { const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); expect(openCassandraShellBtn).toBeUndefined(); }); - - it("Notebooks is not enabled and is available - button should be shown and enabled", () => { - useNotebook.getState().setIsNotebooksEnabledForAccount(true); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); - expect(openCassandraShellBtn).toBeUndefined(); - }); - - it("Notebooks is enabled and is unavailable - button should be shown and enabled", () => { - useNotebook.getState().setIsNotebookEnabled(true); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); - - expect(openCassandraShellBtn).toBeDefined(); - - //TODO: modify once notebooks are available - expect(openCassandraShellBtn.disabled).toBe(true); - //expect(openCassandraShellBtn.disabled).toBe(false); - //expect(openCassandraShellBtn.tooltipText).toBe(""); - }); - - it("Notebooks is enabled and is available - button should be shown and enabled", () => { - useNotebook.getState().setIsNotebookEnabled(true); - useNotebook.getState().setIsNotebooksEnabledForAccount(true); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel); - expect(openCassandraShellBtn).toBeDefined(); - - //TODO: modify once notebooks are available - expect(openCassandraShellBtn.disabled).toBe(true); - //expect(openCassandraShellBtn.disabled).toBe(false); - //expect(openCassandraShellBtn.tooltipText).toBe(""); - }); }); describe("Open Postgres and vCore Mongo buttons", () => { @@ -368,62 +155,6 @@ describe("CommandBarComponentButtonFactory tests", () => { }); }); - describe("GitHub buttons", () => { - const connectToGitHubBtnLabel = "Connect to GitHub"; - const manageGitHubSettingsBtnLabel = "Manage GitHub settings"; - const selectedNodeState = useSelectedNode.getState(); - - beforeAll(() => { - mockExplorer = {} as Explorer; - updateUserContext({ - databaseAccount: { - properties: { - capabilities: [{ name: "EnableTable" }], - }, - } as DatabaseAccount, - }); - - mockExplorer.notebookManager = new NotebookManager(); - mockExplorer.notebookManager.gitHubOAuthService = new GitHubOAuthService(undefined); - }); - - afterEach(() => { - jest.resetAllMocks(); - useNotebook.getState().setIsNotebookEnabled(false); - }); - - it("Notebooks is enabled and GitHubOAuthService is not logged in - connect to github button should be visible", () => { - useNotebook.getState().setIsNotebookEnabled(true); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const connectToGitHubBtn = buttons.find((button) => button.commandButtonLabel === connectToGitHubBtnLabel); - expect(connectToGitHubBtn).toBeDefined(); - }); - - it("Notebooks is enabled and GitHubOAuthService is logged in - manage github settings button should be visible", () => { - useNotebook.getState().setIsNotebookEnabled(true); - mockExplorer.notebookManager.gitHubOAuthService.isLoggedIn = jest.fn().mockReturnValue(true); - - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - const manageGitHubSettingsBtn = buttons.find( - (button) => button.commandButtonLabel === manageGitHubSettingsBtnLabel, - ); - expect(manageGitHubSettingsBtn).toBeDefined(); - }); - - it("Notebooks is not enabled - connect to github and manage github settings buttons should be hidden", () => { - const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState); - - const connectToGitHubBtn = buttons.find((button) => button.commandButtonLabel === connectToGitHubBtnLabel); - expect(connectToGitHubBtn).toBeUndefined(); - - const manageGitHubSettingsBtn = buttons.find( - (button) => button.commandButtonLabel === manageGitHubSettingsBtnLabel, - ); - expect(manageGitHubSettingsBtn).toBeUndefined(); - }); - }); - describe("Resource token", () => { const mockCollection = { id: ko.observable("test") } as CollectionBase; useSelectedNode.getState().setSelectedNode(mockCollection); diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx index 869dd2383..9851dd668 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx @@ -7,14 +7,10 @@ import AddStoredProcedureIcon from "../../../../images/AddStoredProcedure.svg"; import AddTriggerIcon from "../../../../images/AddTrigger.svg"; import AddUdfIcon from "../../../../images/AddUdf.svg"; import BrowseQueriesIcon from "../../../../images/BrowseQuery.svg"; -import CosmosTerminalIcon from "../../../../images/Cosmos-Terminal.svg"; import FeedbackIcon from "../../../../images/Feedback-Command.svg"; import HomeIcon from "../../../../images/Home_16.svg"; import HostedTerminalIcon from "../../../../images/Hosted-Terminal.svg"; import OpenQueryFromDiskIcon from "../../../../images/OpenQueryFromDisk.svg"; -import GitHubIcon from "../../../../images/github.svg"; -import NewNotebookIcon from "../../../../images/notebook/Notebook-new.svg"; -import ResetWorkspaceIcon from "../../../../images/notebook/Notebook-reset-workspace.svg"; import OpenInTabIcon from "../../../../images/open-in-tab.svg"; import SettingsIcon from "../../../../images/settings_15x15.svg"; import SynapseIcon from "../../../../images/synapse-link.svg"; @@ -22,7 +18,6 @@ import { AuthType } from "../../../AuthType"; import * as Constants from "../../../Common/Constants"; import { Platform, configContext } from "../../../ConfigContext"; import * as ViewModels from "../../../Contracts/ViewModels"; -import { JunoClient } from "../../../Juno/JunoClient"; import { userContext } from "../../../UserContext"; import { getCollectionName, getDatabaseName } from "../../../Utils/APITypeUtils"; import { isRunningOnNationalCloud } from "../../../Utils/CloudUtils"; @@ -33,7 +28,6 @@ import { useNotebook } from "../../Notebook/useNotebook"; import { OpenFullScreen } from "../../OpenFullScreen"; import { AddDatabasePanel } from "../../Panes/AddDatabasePanel/AddDatabasePanel"; import { BrowseQueriesPane } from "../../Panes/BrowseQueriesPane/BrowseQueriesPane"; -import { GitHubReposPanel } from "../../Panes/GitHubReposPanel/GitHubReposPanel"; import { LoadQueryPane } from "../../Panes/LoadQueryPane/LoadQueryPane"; import { SettingsPane } from "../../Panes/SettingsPane/SettingsPane"; import { useDatabases } from "../../useDatabases"; @@ -80,57 +74,6 @@ export function createStaticCommandBarButtons( } } - if (useNotebook.getState().isNotebookEnabled) { - addDivider(); - const notebookButtons: CommandButtonComponentProps[] = []; - - const newNotebookButton = createNewNotebookButton(container); - newNotebookButton.children = [createNewNotebookButton(container), createuploadNotebookButton(container)]; - notebookButtons.push(newNotebookButton); - - if (container.notebookManager?.gitHubOAuthService) { - notebookButtons.push(createManageGitHubAccountButton(container)); - } - if (useNotebook.getState().isPhoenixFeatures && configContext.isTerminalEnabled) { - notebookButtons.push(createOpenTerminalButton(container)); - } - if (useNotebook.getState().isPhoenixNotebooks && selectedNodeState.isConnectedToContainer()) { - notebookButtons.push(createNotebookWorkspaceResetButton(container)); - } - if ( - (userContext.apiType === "Mongo" && - useNotebook.getState().isShellEnabled && - selectedNodeState.isDatabaseNodeOrNoneSelected()) || - userContext.apiType === "Cassandra" - ) { - notebookButtons.push(createDivider()); - if (userContext.apiType === "Cassandra") { - notebookButtons.push(createOpenTerminalButtonByKind(container, ViewModels.TerminalKind.Cassandra)); - } else { - notebookButtons.push(createOpenTerminalButtonByKind(container, ViewModels.TerminalKind.Mongo)); - } - } - - notebookButtons.forEach((btn) => { - if (btn.commandButtonLabel.indexOf("Cassandra") !== -1) { - if (!useNotebook.getState().isPhoenixFeatures) { - applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.cassandraShellTemporarilyDownMsg); - } - } else if (btn.commandButtonLabel.indexOf("Mongo") !== -1) { - if (!useNotebook.getState().isPhoenixFeatures) { - applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.mongoShellTemporarilyDownMsg); - } - } else if (btn.commandButtonLabel.indexOf("Open Terminal") !== -1) { - if (!useNotebook.getState().isPhoenixFeatures) { - applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.temporarilyDownMsg); - } - } else if (!useNotebook.getState().isPhoenixNotebooks) { - applyNotebooksTemporarilyDownStyle(btn, Constants.Notebook.temporarilyDownMsg); - } - buttons.push(btn); - }); - } - if (!selectedNodeState.isDatabaseNodeOrNoneSelected()) { const isQuerySupported = userContext.apiType === "SQL" || userContext.apiType === "Gremlin"; @@ -449,40 +392,6 @@ export function createScriptCommandButtons(selectedNodeState: SelectedNodeState) return buttons; } -function applyNotebooksTemporarilyDownStyle(buttonProps: CommandButtonComponentProps, tooltip: string): void { - if (!buttonProps.isDivider) { - buttonProps.disabled = true; - buttonProps.tooltipText = tooltip; - } -} - -function createNewNotebookButton(container: Explorer): CommandButtonComponentProps { - const label = "New Notebook"; - return { - id: "newNotebookBtn", - iconSrc: NewNotebookIcon, - iconAlt: label, - onCommandClick: () => container.onNewNotebookClicked(), - commandButtonLabel: label, - hasPopup: false, - disabled: useSelectedNode.getState().isQueryCopilotCollectionSelected(), - ariaLabel: label, - }; -} - -function createuploadNotebookButton(container: Explorer): CommandButtonComponentProps { - const label = "Upload to Notebook Server"; - return { - iconSrc: NewNotebookIcon, - iconAlt: label, - onCommandClick: () => container.openUploadFilePanel(), - commandButtonLabel: label, - hasPopup: false, - disabled: useSelectedNode.getState().isQueryCopilotCollectionSelected(), - ariaLabel: label, - }; -} - function createOpenQueryButton(container: Explorer): CommandButtonComponentProps { const label = "Open Query"; return { @@ -510,19 +419,6 @@ function createOpenQueryFromDiskButton(): CommandButtonComponentProps { }; } -function createOpenTerminalButton(container: Explorer): CommandButtonComponentProps { - const label = "Open Terminal"; - return { - iconSrc: CosmosTerminalIcon, - iconAlt: label, - onCommandClick: () => container.openNotebookTerminal(ViewModels.TerminalKind.Default), - commandButtonLabel: label, - hasPopup: false, - disabled: useSelectedNode.getState().isQueryCopilotCollectionSelected(), - ariaLabel: label, - }; -} - function createOpenTerminalButtonByKind( container: Explorer, terminalKind: ViewModels.TerminalKind, @@ -562,45 +458,6 @@ function createOpenTerminalButtonByKind( }; } -function createNotebookWorkspaceResetButton(container: Explorer): CommandButtonComponentProps { - const label = "Reset Workspace"; - return { - iconSrc: ResetWorkspaceIcon, - iconAlt: label, - onCommandClick: () => container.resetNotebookWorkspace(), - commandButtonLabel: label, - hasPopup: false, - disabled: useSelectedNode.getState().isQueryCopilotCollectionSelected(), - ariaLabel: label, - }; -} - -function createManageGitHubAccountButton(container: Explorer): CommandButtonComponentProps { - const connectedToGitHub: boolean = container.notebookManager?.gitHubOAuthService.isLoggedIn(); - const label = connectedToGitHub ? "Manage GitHub settings" : "Connect to GitHub"; - const junoClient = new JunoClient(); - return { - iconSrc: GitHubIcon, - iconAlt: label, - onCommandClick: () => { - useSidePanel - .getState() - .openSidePanel( - label, - , - ); - }, - commandButtonLabel: label, - hasPopup: false, - disabled: useSelectedNode.getState().isQueryCopilotCollectionSelected(), - ariaLabel: label, - }; -} - function createStaticCommandBarButtonsForResourceToken( container: Explorer, selectedNodeState: SelectedNodeState, diff --git a/src/Explorer/Tree/ResourceTree.tsx b/src/Explorer/Tree/ResourceTree.tsx index b245f327f..b5f759534 100644 --- a/src/Explorer/Tree/ResourceTree.tsx +++ b/src/Explorer/Tree/ResourceTree.tsx @@ -373,11 +373,6 @@ export const ResourceTree: React.FC = ({ container }: Resourc iconSrc: NewNotebookIcon, onClick: () => container.onCreateDirectory(item, isGithubTree), }, - { - label: "New Notebook", - iconSrc: NewNotebookIcon, - onClick: () => container.onNewNotebookClicked(item, isGithubTree), - }, { label: "Upload File", iconSrc: NewNotebookIcon, @@ -786,9 +781,6 @@ export const ResourceTree: React.FC = ({ container }: Resourc - - - {/* {buildGalleryCallout()} */} diff --git a/src/Explorer/Tree/ResourceTreeAdapter.tsx b/src/Explorer/Tree/ResourceTreeAdapter.tsx index abe05e85f..b22f0f916 100644 --- a/src/Explorer/Tree/ResourceTreeAdapter.tsx +++ b/src/Explorer/Tree/ResourceTreeAdapter.tsx @@ -800,11 +800,6 @@ export class ResourceTreeAdapter implements ReactAdapter { iconSrc: NewNotebookIcon, onClick: () => this.container.onCreateDirectory(item), }, - { - label: "New Notebook", - iconSrc: NewNotebookIcon, - onClick: () => this.container.onNewNotebookClicked(item), - }, { label: "Upload File", iconSrc: NewNotebookIcon, From 18cc2a419523760a9d437edc79f6c65452799ebd Mon Sep 17 00:00:00 2001 From: Asier Isayas Date: Tue, 2 Apr 2024 12:34:58 -0400 Subject: [PATCH 07/13] Activate Mongo and Cassandra Proxies in MPAC (#1776) * Fix API endpoint for CassandraProxy query API * activated mongo proxy * added mpac * Activate CassandraProxy API endpoints for MPAC * Run npm format * Set CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED when we detect new Cassandra Proxy endpoints in IP rules. * query documents API fix * simplify ip check --------- Co-authored-by: Senthamil Sindhu Co-authored-by: Asier Isayas Co-authored-by: Jade Welton --- src/Common/MongoProxyClient.ts | 6 ++--- src/ConfigContext.ts | 20 ++++++--------- src/Explorer/Tables/TableDataClient.ts | 9 +++---- .../Tabs/QueryTab/QueryTabComponent.tsx | 2 +- src/Explorer/Tabs/Tabs.tsx | 25 +++++++++++-------- 5 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/Common/MongoProxyClient.ts b/src/Common/MongoProxyClient.ts index e37b0eff5..749248919 100644 --- a/src/Common/MongoProxyClient.ts +++ b/src/Common/MongoProxyClient.ts @@ -67,7 +67,7 @@ export function queryDocuments( query: string, continuationToken?: string, ): Promise { - if (!useMongoProxyEndpoint("resourcelist")) { + if (!useMongoProxyEndpoint("resourcelist") || !useMongoProxyEndpoint("queryDocuments")) { return queryDocuments_ToBeDeprecated(databaseId, collection, isResourceList, query, continuationToken); } @@ -106,7 +106,7 @@ export function queryDocuments( headers[CosmosSDKConstants.HttpHeaders.Continuation] = continuationToken; } - const path = isResourceList ? "/resourcelist" : ""; + const path = isResourceList ? "/resourcelist" : "/queryDocuments"; return window .fetch(`${endpoint}${path}`, { @@ -690,7 +690,7 @@ export function getARMCreateCollectionEndpoint(params: DataModels.MongoParameter } function useMongoProxyEndpoint(api: string): boolean { - const activeMongoProxyEndpoints: string[] = [MongoProxyEndpoints.Development]; + const activeMongoProxyEndpoints: string[] = [MongoProxyEndpoints.Development, MongoProxyEndpoints.Mpac]; let canAccessMongoProxy: boolean = userContext.databaseAccount.properties.publicNetworkAccess === "Enabled"; if (userContext.databaseAccount.properties.ipRules?.length > 0) { canAccessMongoProxy = canAccessMongoProxy && configContext.MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED; diff --git a/src/ConfigContext.ts b/src/ConfigContext.ts index e3350c7f6..3a8ef1cb5 100644 --- a/src/ConfigContext.ts +++ b/src/ConfigContext.ts @@ -102,21 +102,17 @@ let configContext: Readonly = { NEW_BACKEND_APIS: [BackendApi.GenerateToken], MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod, NEW_MONGO_APIS: [ - // "resourcelist", - // "createDocument", - // "readDocument", - // "updateDocument", - // "deleteDocument", - // "createCollectionWithProxy", + "resourcelist", + "queryDocuments", + "createDocument", + "readDocument", + "updateDocument", + "deleteDocument", + "createCollectionWithProxy", ], MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED: false, CASSANDRA_PROXY_ENDPOINT: CassandraProxyEndpoints.Prod, - NEW_CASSANDRA_APIS: [ - // "postQuery", - // "createOrDelete", - // "getKeys", - // "getSchema", - ], + NEW_CASSANDRA_APIS: ["postQuery", "createOrDelete", "getKeys", "getSchema"], CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED: false, isTerminalEnabled: false, isPhoenixEnabled: false, diff --git a/src/Explorer/Tables/TableDataClient.ts b/src/Explorer/Tables/TableDataClient.ts index 3d5f05c72..097ade395 100644 --- a/src/Explorer/Tables/TableDataClient.ts +++ b/src/Explorer/Tables/TableDataClient.ts @@ -19,7 +19,7 @@ import Explorer from "../Explorer"; import * as TableConstants from "./Constants"; import * as Entities from "./Entities"; import * as TableEntityProcessor from "./TableEntityProcessor"; -import { CassandraProxyAPIs } from "../../Common/Constants"; +import { CassandraProxyAPIs, CassandraProxyEndpoints } from "../../Common/Constants"; export interface CassandraTableKeys { partitionKeys: CassandraTableKey[]; @@ -458,7 +458,7 @@ export class CassandraAPIDataClient extends TableDataClient { } public getTableKeys(collection: ViewModels.Collection): Q.Promise { - if (!this.useCassandraProxyEndpoint("getTableKeys")) { + if (!this.useCassandraProxyEndpoint("getKeys")) { return this.getTableKeys_ToBeDeprecated(collection); } @@ -732,6 +732,7 @@ export class CassandraAPIDataClient extends TableDataClient { } private useCassandraProxyEndpoint(api: string): boolean { + const activeCassandraProxyEndpoints: string[] = [CassandraProxyEndpoints.Development, CassandraProxyEndpoints.Mpac]; let canAccessCassandraProxy: boolean = userContext.databaseAccount.properties.publicNetworkAccess === "Enabled"; if (userContext.databaseAccount.properties.ipRules?.length > 0) { canAccessCassandraProxy = canAccessCassandraProxy && configContext.CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED; @@ -740,9 +741,7 @@ export class CassandraAPIDataClient extends TableDataClient { return ( canAccessCassandraProxy && configContext.NEW_CASSANDRA_APIS?.includes(api) && - [Constants.CassandraProxyEndpoints.Development, Constants.CassandraProxyEndpoints.Mpac].includes( - configContext.CASSANDRA_PROXY_ENDPOINT, - ) + activeCassandraProxyEndpoints.includes(configContext.CASSANDRA_PROXY_ENDPOINT) ); } } diff --git a/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx b/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx index ff44a4be3..c439cfc83 100644 --- a/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx +++ b/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx @@ -134,7 +134,7 @@ export default class QueryTabComponent extends React.Component { if ((userContext.apiType === "Mongo" || userContext.apiType === "Cassandra") && ipRules?.length) { const legacyPortalBackendIPs: string[] = PortalBackendIPs[configContext.BACKEND_ENDPOINT]; const ipAddressesFromIPRules: string[] = ipRules.map((ipRule) => ipRule.ipAddressOrRange); - const ipRulesIncludeLegacyPortalBackend: boolean = - ipAddressesFromIPRules.filter((ipAddressFromIPRule) => legacyPortalBackendIPs.includes(ipAddressFromIPRule)) - ?.length === legacyPortalBackendIPs.length; - + const ipRulesIncludeLegacyPortalBackend: boolean = legacyPortalBackendIPs.every((legacyPortalBackendIP: string) => + ipAddressesFromIPRules.includes(legacyPortalBackendIP), + ); if (!ipRulesIncludeLegacyPortalBackend) { return false; } @@ -344,9 +343,9 @@ const showMongoAndCassandraProxiesNetworkSettingsWarning = (): boolean => { ? [...MongoProxyOutboundIPs[MongoProxyEndpoints.Mpac], ...MongoProxyOutboundIPs[MongoProxyEndpoints.Prod]] : MongoProxyOutboundIPs[configContext.MONGO_PROXY_ENDPOINT]; - const ipRulesIncludeMongoProxy: boolean = - ipAddressesFromIPRules.filter((ipAddressFromIPRule) => mongoProxyOutboundIPs.includes(ipAddressFromIPRule)) - ?.length === mongoProxyOutboundIPs.length; + const ipRulesIncludeMongoProxy: boolean = mongoProxyOutboundIPs.every((mongoProxyOutboundIP: string) => + ipAddressesFromIPRules.includes(mongoProxyOutboundIP), + ); if (ipRulesIncludeMongoProxy) { updateConfigContext({ @@ -368,9 +367,15 @@ const showMongoAndCassandraProxiesNetworkSettingsWarning = (): boolean => { ] : CassandraProxyOutboundIPs[configContext.CASSANDRA_PROXY_ENDPOINT]; - const ipRulesIncludeCassandraProxy: boolean = - ipAddressesFromIPRules.filter((ipAddressFromIPRule) => cassandraProxyOutboundIPs.includes(ipAddressFromIPRule)) - ?.length === cassandraProxyOutboundIPs.length; + const ipRulesIncludeCassandraProxy: boolean = cassandraProxyOutboundIPs.every( + (cassandraProxyOutboundIP: string) => ipAddressesFromIPRules.includes(cassandraProxyOutboundIP), + ); + + if (ipRulesIncludeCassandraProxy) { + updateConfigContext({ + CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED: true, + }); + } return !ipRulesIncludeCassandraProxy; } From 1464745659419580831f1e080122a9569ea1a576 Mon Sep 17 00:00:00 2001 From: sunghyunkang1111 <114709653+sunghyunkang1111@users.noreply.github.com> Date: Tue, 2 Apr 2024 11:49:33 -0500 Subject: [PATCH 08/13] Add activate/close tab contracts and add to queryTab (#1783) --- src/Contracts/MessageTypes.ts | 1 + src/Explorer/Tabs/QueryTab/QueryTab.tsx | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/Contracts/MessageTypes.ts b/src/Contracts/MessageTypes.ts index 1cbd86bb8..afe13a6a8 100644 --- a/src/Contracts/MessageTypes.ts +++ b/src/Contracts/MessageTypes.ts @@ -35,6 +35,7 @@ export enum MessageTypes { CreateWorkspace, CreateSparkPool, RefreshDatabaseAccount, + ActivateTab, CloseTab, OpenQuickstartBlade, OpenPostgreSQLPasswordReset, diff --git a/src/Explorer/Tabs/QueryTab/QueryTab.tsx b/src/Explorer/Tabs/QueryTab/QueryTab.tsx index 47c2ce2ba..f297f91f4 100644 --- a/src/Explorer/Tabs/QueryTab/QueryTab.tsx +++ b/src/Explorer/Tabs/QueryTab/QueryTab.tsx @@ -1,3 +1,5 @@ +import { sendMessage } from "Common/MessageHandler"; +import { MessageTypes } from "Contracts/MessageTypes"; import { CopilotProvider } from "Explorer/QueryCopilot/QueryCopilotContext"; import { userContext } from "UserContext"; import React from "react"; @@ -54,6 +56,11 @@ export class NewQueryTab extends TabsBase { ); } + public onActivate(): void { + this.propagateTabInformation(MessageTypes.ActivateTab); + super.onActivate(); + } + public onTabClick(): void { useTabs.getState().activateTab(this); this.iTabAccessor.onTabClickEvent(); @@ -61,6 +68,7 @@ export class NewQueryTab extends TabsBase { public onCloseTabButtonClick(): void { useTabs.getState().closeTab(this); + this.propagateTabInformation(MessageTypes.CloseTab); if (this.iTabAccessor) { this.iTabAccessor.onCloseClickEvent(true); } @@ -69,4 +77,15 @@ export class NewQueryTab extends TabsBase { public getContainer(): Explorer { return this.props.container; } + + private propagateTabInformation(type: MessageTypes): void { + sendMessage({ + type, + data: { + kind: this.tabKind, + databaseId: this.collection?.databaseId, + collectionId: this.collection?.id?.(), + }, + }); + } } From b44778b00ac47b7e35f24b72278e0ba67a5ea4ca Mon Sep 17 00:00:00 2001 From: Ashley Stanton-Nurse Date: Tue, 2 Apr 2024 10:51:19 -0700 Subject: [PATCH 09/13] fix #3061738 by unclobbering some Monaco styles we clobber (#1784) --- less/documentDB.less | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/less/documentDB.less b/less/documentDB.less index d8ae159db..357d159b3 100644 --- a/less/documentDB.less +++ b/less/documentDB.less @@ -2296,6 +2296,17 @@ a:link { display: none !important; } +.monaco-editor .quick-input-list-label { + /* Restore some of Monaco's default styles that are clobbered by our global styles */ + padding: 0; + line-height: 22px; +} + +.monaco-editor .quick-input-list .highlight { + /* Padding in highlighted text within the quick input list breaks the flow of the text */ + padding: 0; +} + td a { color: #393939; } From 3c5d899e47d621163944e343c72057af7045ae76 Mon Sep 17 00:00:00 2001 From: sunghyunkang1111 <114709653+sunghyunkang1111@users.noreply.github.com> Date: Tue, 2 Apr 2024 17:54:53 -0500 Subject: [PATCH 10/13] add the new message to the bottom to avoid contract breaking (#1786) --- src/Contracts/MessageTypes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Contracts/MessageTypes.ts b/src/Contracts/MessageTypes.ts index afe13a6a8..a19b69e5f 100644 --- a/src/Contracts/MessageTypes.ts +++ b/src/Contracts/MessageTypes.ts @@ -35,7 +35,6 @@ export enum MessageTypes { CreateWorkspace, CreateSparkPool, RefreshDatabaseAccount, - ActivateTab, CloseTab, OpenQuickstartBlade, OpenPostgreSQLPasswordReset, @@ -48,6 +47,7 @@ export enum MessageTypes { GetAllResourceTokens, // Data Explorer -> Fabric Ready, // Data Explorer -> Fabric OpenCESCVAFeedbackBlade, + ActivateTab, } export interface AuthorizationToken { From f533eeb0fc541b7450eac75d8a210ebc6077d272 Mon Sep 17 00:00:00 2001 From: Ashley Stanton-Nurse Date: Thu, 4 Apr 2024 09:16:23 -0700 Subject: [PATCH 11/13] add support for react dev tools in the cosmos explorer (#1788) --- src/Main.tsx | 3 +++ src/ReactDevTools.ts | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Main.tsx b/src/Main.tsx index d62f9b45e..c6b79b139 100644 --- a/src/Main.tsx +++ b/src/Main.tsx @@ -1,3 +1,6 @@ +// Import this first, to ensure that the dev tools hook is copied before React is loaded. +import "./ReactDevTools"; + // CSS Dependencies import { initializeIcons, loadTheme } from "@fluentui/react"; import { QuickstartCarousel } from "Explorer/Quickstart/QuickstartCarousel"; diff --git a/src/ReactDevTools.ts b/src/ReactDevTools.ts index 09947f934..2a12d81d5 100644 --- a/src/ReactDevTools.ts +++ b/src/ReactDevTools.ts @@ -1,3 +1,7 @@ if (window.parent !== window) { - (window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__ = (window.parent as any).__REACT_DEVTOOLS_GLOBAL_HOOK__; + try { + (window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__ = (window.parent as any).__REACT_DEVTOOLS_GLOBAL_HOOK__; + } catch { + // No-op. We can throw here if the parent is not the same origin (such as in the Azure portal). + } } From db50f428328cf36c6d7e41915f02797caa0986f7 Mon Sep 17 00:00:00 2001 From: Ashley Stanton-Nurse Date: Thu, 4 Apr 2024 09:17:09 -0700 Subject: [PATCH 12/13] [Task #3061771] Correct render order issues on undo (#1785) * fix #3061771 by correcting render order issues on undo * clarifying comment * fix lints * push an undo stop before executing edits * tidy up some unnecessary comments --- src/Explorer/Controls/Editor/EditorReact.tsx | 27 +++++++++++++++---- .../Tabs/QueryTab/QueryTabComponent.tsx | 13 +++++---- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/Explorer/Controls/Editor/EditorReact.tsx b/src/Explorer/Controls/Editor/EditorReact.tsx index 956253a05..1d7a7381b 100644 --- a/src/Explorer/Controls/Editor/EditorReact.tsx +++ b/src/Explorer/Controls/Editor/EditorReact.tsx @@ -46,9 +46,21 @@ export class EditorReact extends React.Component { + // Hooking the model's onDidChangeContent event because of some event ordering issues. + // If a single user input causes BOTH the editor content to change AND the cursor selection to change (which is likely), + // then there are some inconsistencies as to which event fires first. + // But the editor.onDidChangeModelContent event seems to always fire before the cursor selection event. + // (This is NOT true for the model's onDidChangeContent event, which sometimes fires after the cursor selection event.) + // If the cursor selection event fires first, then the calling component may re-render the component with old content, so we want to ensure the model content changed event always fires first. + this.editor.onDidChangeModelContent(() => { const queryEditorModel = this.editor.getModel(); this.props.onContentChanged(queryEditorModel.getValue()); }); diff --git a/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx b/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx index c439cfc83..fa849c212 100644 --- a/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx +++ b/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx @@ -496,13 +496,16 @@ export default class QueryTabComponent extends React.Component 0) { this.executeQueryButton = { @@ -544,7 +547,7 @@ export default class QueryTabComponent extends React.Component Date: Thu, 4 Apr 2024 10:18:50 -0700 Subject: [PATCH 13/13] Change copilot settings call to use new backend endpoint. (#1781) * Change copilot settings call to use new backend endpoint. * Refactor EndpointUtils function for new backend enablement. --- src/Common/Constants.ts | 5 +++-- src/ConfigContext.ts | 1 - .../QueryCopilot/Shared/QueryCopilotClient.ts | 8 ++++++- .../Hosted/Components/ConnectExplorer.tsx | 4 ++-- src/Utils/EndpointUtils.ts | 22 +++++++++++++++---- 5 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/Common/Constants.ts b/src/Common/Constants.ts index fe9c2672d..1eed03a4f 100644 --- a/src/Common/Constants.ts +++ b/src/Common/Constants.ts @@ -124,8 +124,9 @@ export enum MongoBackendEndpointType { remote, } -export enum BackendApi { - GenerateToken, +export class BackendApi { + public static readonly GenerateToken: string = "GenerateToken"; + public static readonly PortalSettings: string = "PortalSettings"; } export class PortalBackendEndpoints { diff --git a/src/ConfigContext.ts b/src/ConfigContext.ts index 3a8ef1cb5..435166b90 100644 --- a/src/ConfigContext.ts +++ b/src/ConfigContext.ts @@ -99,7 +99,6 @@ let configContext: Readonly = { JUNO_ENDPOINT: JunoEndpoints.Prod, BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com", PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Prod, - NEW_BACKEND_APIS: [BackendApi.GenerateToken], MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod, NEW_MONGO_APIS: [ "resourcelist", diff --git a/src/Explorer/QueryCopilot/Shared/QueryCopilotClient.ts b/src/Explorer/QueryCopilot/Shared/QueryCopilotClient.ts index 6d2cafdc5..59101cbb3 100644 --- a/src/Explorer/QueryCopilot/Shared/QueryCopilotClient.ts +++ b/src/Explorer/QueryCopilot/Shared/QueryCopilotClient.ts @@ -1,6 +1,7 @@ import { FeedOptions } from "@azure/cosmos"; import { Areas, + BackendApi, ConnectionStatusType, ContainerStatusType, HttpStatusCodes, @@ -30,6 +31,7 @@ import { Action } from "Shared/Telemetry/TelemetryConstants"; import { traceFailure, traceStart, traceSuccess } from "Shared/Telemetry/TelemetryProcessor"; import { userContext } from "UserContext"; import { getAuthorizationHeader } from "Utils/AuthorizationUtils"; +import { useNewPortalBackendEndpoint } from "Utils/EndpointUtils"; import { queryPagesUntilContentPresent } from "Utils/QueryUtils"; import { QueryCopilotState, useQueryCopilot } from "hooks/useQueryCopilot"; import { useTabs } from "hooks/useTabs"; @@ -80,7 +82,11 @@ export const isCopilotFeatureRegistered = async (subscriptionId: string): Promis }; export const getCopilotEnabled = async (): Promise => { - const url = `${configContext.BACKEND_ENDPOINT}/api/portalsettings/querycopilot`; + const backendEndpoint: string = useNewPortalBackendEndpoint(BackendApi.PortalSettings) + ? configContext.PORTAL_BACKEND_ENDPOINT + : configContext.BACKEND_ENDPOINT; + + const url = `${backendEndpoint}/api/portalsettings/querycopilot`; const authorizationHeader: AuthorizationTokenHeaderMetadata = getAuthorizationHeader(); const headers = { [authorizationHeader.header]: authorizationHeader.token }; diff --git a/src/Platform/Hosted/Components/ConnectExplorer.tsx b/src/Platform/Hosted/Components/ConnectExplorer.tsx index 6b33d021b..749f248ba 100644 --- a/src/Platform/Hosted/Components/ConnectExplorer.tsx +++ b/src/Platform/Hosted/Components/ConnectExplorer.tsx @@ -1,6 +1,6 @@ import { useBoolean } from "@fluentui/react-hooks"; import { userContext } from "UserContext"; -import { usePortalBackendEndpoint } from "Utils/EndpointUtils"; +import { useNewPortalBackendEndpoint } from "Utils/EndpointUtils"; import * as React from "react"; import ConnectImage from "../../../../images/HdeConnectCosmosDB.svg"; import ErrorImage from "../../../../images/error.svg"; @@ -19,7 +19,7 @@ interface Props { } export const fetchEncryptedToken = async (connectionString: string): Promise => { - if (!usePortalBackendEndpoint(BackendApi.GenerateToken)) { + if (!useNewPortalBackendEndpoint(BackendApi.GenerateToken)) { return await fetchEncryptedToken_ToBeDeprecated(connectionString); } diff --git a/src/Utils/EndpointUtils.ts b/src/Utils/EndpointUtils.ts index 4962c285b..c59db4205 100644 --- a/src/Utils/EndpointUtils.ts +++ b/src/Utils/EndpointUtils.ts @@ -145,8 +145,22 @@ export const allowedJunoOrigins: ReadonlyArray = [ export const allowedNotebookServerUrls: ReadonlyArray = []; -export function usePortalBackendEndpoint(backendApi: BackendApi): boolean { - const activePortalBackendEndpoints: string[] = [PortalBackendEndpoints.Development]; - const activeBackendApi: boolean = configContext.NEW_BACKEND_APIS?.includes(backendApi) || false; - return activeBackendApi && activePortalBackendEndpoints.includes(configContext.PORTAL_BACKEND_ENDPOINT as string); +// +// Temporary function to determine if a portal backend API is supported by the +// new backend in this environment. +// +// TODO: Remove this function once new backend migration is completed for all environments. +// +export function useNewPortalBackendEndpoint(backendApi: string): boolean { + // This maps backend APIs to the environments supported by the new backend. + const newBackendApiEnvironmentMap: { [key: string]: string[] } = { + [BackendApi.GenerateToken]: [PortalBackendEndpoints.Development], + [BackendApi.PortalSettings]: [PortalBackendEndpoints.Development, PortalBackendEndpoints.Mpac], + }; + + if (!newBackendApiEnvironmentMap[backendApi] || !configContext.PORTAL_BACKEND_ENDPOINT) { + return false; + } + + return newBackendApiEnvironmentMap[backendApi].includes(configContext.PORTAL_BACKEND_ENDPOINT); }