From 8629bcbe2dc71fb57ebc0f786a4c8fc61439882d Mon Sep 17 00:00:00 2001 From: Steve Faulkner Date: Wed, 24 Feb 2021 19:04:28 -0600 Subject: [PATCH] Move "Open Full Screen" to React Panel (#449) --- package-lock.json | 5 + package.json | 1 + src/Contracts/ViewModels.ts | 10 +- .../SettingsComponent.test.tsx.snap | 60 -- src/Explorer/Explorer.tsx | 236 +------ .../CommandBar/CommandBarComponentAdapter.tsx | 2 +- .../CommandBarComponentButtonFactory.test.ts | 2 +- .../CommandBarComponentButtonFactory.ts | 616 ------------------ .../CommandBarComponentButtonFactory.tsx | 581 +++++++++++++++++ src/Explorer/OpenActions.test.ts | 1 - src/Explorer/OpenActions.ts | 6 +- src/Explorer/OpenFullScreen.test.tsx | 17 + src/Explorer/OpenFullScreen.tsx | 61 ++ src/Explorer/Tabs/NotebookV2Tab.ts | 2 +- src/Main.tsx | 68 -- src/Platform/Hosted/Authorization.ts | 57 +- src/hooks/useFullScreenURLs.tsx | 18 + 17 files changed, 702 insertions(+), 1041 deletions(-) delete mode 100644 src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.ts create mode 100644 src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx create mode 100644 src/Explorer/OpenFullScreen.test.tsx create mode 100644 src/Explorer/OpenFullScreen.tsx create mode 100644 src/hooks/useFullScreenURLs.tsx diff --git a/package-lock.json b/package-lock.json index af44fe3f9..01f5a2c47 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7288,6 +7288,11 @@ "tiny-emitter": "^2.0.0" } }, + "clipboard-copy": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clipboard-copy/-/clipboard-copy-4.0.1.tgz", + "integrity": "sha512-wOlqdqziE/NNTUJsfSgXmBMIrYmfd5V0HCGsR8uAKHcg+h9NENWINcfRjtWGU77wDHC8B8ijV4hMTGYbrKovng==" + }, "cliui": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", diff --git a/package.json b/package.json index aa9b811c9..2b3dc63a1 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "bootstrap": "3.4.1", "canvas": "file:./canvas", "clean-webpack-plugin": "0.1.19", + "clipboard-copy": "4.0.1", "copy-webpack-plugin": "6.0.2", "crossroads": "0.12.2", "css-element-queries": "1.1.1", diff --git a/src/Contracts/ViewModels.ts b/src/Contracts/ViewModels.ts index d4f0a07a2..bdb5116fd 100644 --- a/src/Contracts/ViewModels.ts +++ b/src/Contracts/ViewModels.ts @@ -108,7 +108,7 @@ export interface CollectionBase extends TreeNode { isCollectionExpanded: ko.Observable; onDocumentDBDocumentsClick(): void; - onNewQueryClick(source: any, event: MouseEvent, queryText?: string): void; + onNewQueryClick(source: any, event?: MouseEvent, queryText?: string): void; expandCollection(): void; collapseCollection(): void; getDatabase(): Database; @@ -140,11 +140,11 @@ export interface Collection extends CollectionBase { onSettingsClick: () => Promise; onNewGraphClick(): void; - onNewMongoQueryClick(source: any, event: MouseEvent, queryText?: string): void; + onNewMongoQueryClick(source: any, event?: MouseEvent, queryText?: string): void; onNewMongoShellClick(): void; - onNewStoredProcedureClick(source: Collection, event: MouseEvent): void; - onNewUserDefinedFunctionClick(source: Collection, event: MouseEvent): void; - onNewTriggerClick(source: Collection, event: MouseEvent): void; + onNewStoredProcedureClick(source: Collection, event?: MouseEvent): void; + onNewUserDefinedFunctionClick(source: Collection, event?: MouseEvent): void; + onNewTriggerClick(source: Collection, event?: MouseEvent): void; storedProcedures: ko.Computed; userDefinedFunctions: ko.Computed; triggers: ko.Computed; diff --git a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap index 18471bb82..526fc74cc 100644 --- a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap +++ b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap @@ -33,7 +33,6 @@ exports[`SettingsComponent renders 1`] = ` "_isAfecFeatureRegistered": [Function], "_isInitializingNotebooks": false, "_isSystemDatabasePredicate": [Function], - "_openShareDialog": [Function], "_panes": Array [ AddDatabasePane { "autoPilotUsageCost": [Function], @@ -940,8 +939,6 @@ exports[`SettingsComponent renders 1`] = ` "isPreferredApiMongoDB": [Function], "isPreferredApiTable": [Function], "isPublishNotebookPaneEnabled": [Function], - "isReadToggled": [Function], - "isReadWriteToggled": [Function], "isRefreshingExplorer": [Function], "isResourceTokenCollectionNodeSelected": [Function], "isRightPanelV2Enabled": [Function], @@ -996,7 +993,6 @@ exports[`SettingsComponent renders 1`] = ` "onRefreshDatabasesKeyPress": [Function], "onRefreshResourcesClick": [Function], "onSwitchToConnectionString": [Function], - "onToggleKeyDown": [Function], "openDialog": undefined, "openSidePanel": undefined, "provideFeedbackEmail": [Function], @@ -1027,8 +1023,6 @@ exports[`SettingsComponent renders 1`] = ` "refreshDatabaseAccount": [Function], "refreshNotebookList": [Function], "refreshTreeTitle": [Function], - "renewToken": [Function], - "renewTokenError": [Function], "resourceTokenCollection": [Function], "resourceTokenCollectionId": [Function], "resourceTokenDatabaseId": [Function], @@ -1124,12 +1118,6 @@ exports[`SettingsComponent renders 1`] = ` "title": [Function], "visible": [Function], }, - "shareAccessData": [Function], - "shareAccessToggleState": [Function], - "shareAccessUrl": [Function], - "shareTokenCopyHelperText": [Function], - "shareUrlCopyHelperText": [Function], - "shouldShowShareDialogContents": [Function], "signInAad": [Function], "sparkClusterConnectionInfo": [Function], "splitter": Splitter { @@ -1189,9 +1177,6 @@ exports[`SettingsComponent renders 1`] = ` "openedTabs": [Function], }, "toggleLeftPaneExpandedKeyPress": [Function], - "toggleRead": [Function], - "toggleReadWrite": [Function], - "tokenForRenewal": [Function], "uploadFilePane": UploadFilePane { "container": [Circular], "extensions": [Function], @@ -1266,7 +1251,6 @@ exports[`SettingsComponent renders 1`] = ` "_isAfecFeatureRegistered": [Function], "_isInitializingNotebooks": false, "_isSystemDatabasePredicate": [Function], - "_openShareDialog": [Function], "_panes": Array [ AddDatabasePane { "autoPilotUsageCost": [Function], @@ -2173,8 +2157,6 @@ exports[`SettingsComponent renders 1`] = ` "isPreferredApiMongoDB": [Function], "isPreferredApiTable": [Function], "isPublishNotebookPaneEnabled": [Function], - "isReadToggled": [Function], - "isReadWriteToggled": [Function], "isRefreshingExplorer": [Function], "isResourceTokenCollectionNodeSelected": [Function], "isRightPanelV2Enabled": [Function], @@ -2229,7 +2211,6 @@ exports[`SettingsComponent renders 1`] = ` "onRefreshDatabasesKeyPress": [Function], "onRefreshResourcesClick": [Function], "onSwitchToConnectionString": [Function], - "onToggleKeyDown": [Function], "openDialog": undefined, "openSidePanel": undefined, "provideFeedbackEmail": [Function], @@ -2260,8 +2241,6 @@ exports[`SettingsComponent renders 1`] = ` "refreshDatabaseAccount": [Function], "refreshNotebookList": [Function], "refreshTreeTitle": [Function], - "renewToken": [Function], - "renewTokenError": [Function], "resourceTokenCollection": [Function], "resourceTokenCollectionId": [Function], "resourceTokenDatabaseId": [Function], @@ -2357,12 +2336,6 @@ exports[`SettingsComponent renders 1`] = ` "title": [Function], "visible": [Function], }, - "shareAccessData": [Function], - "shareAccessToggleState": [Function], - "shareAccessUrl": [Function], - "shareTokenCopyHelperText": [Function], - "shareUrlCopyHelperText": [Function], - "shouldShowShareDialogContents": [Function], "signInAad": [Function], "sparkClusterConnectionInfo": [Function], "splitter": Splitter { @@ -2422,9 +2395,6 @@ exports[`SettingsComponent renders 1`] = ` "openedTabs": [Function], }, "toggleLeftPaneExpandedKeyPress": [Function], - "toggleRead": [Function], - "toggleReadWrite": [Function], - "tokenForRenewal": [Function], "uploadFilePane": UploadFilePane { "container": [Circular], "extensions": [Function], @@ -2512,7 +2482,6 @@ exports[`SettingsComponent renders 1`] = ` "_isAfecFeatureRegistered": [Function], "_isInitializingNotebooks": false, "_isSystemDatabasePredicate": [Function], - "_openShareDialog": [Function], "_panes": Array [ AddDatabasePane { "autoPilotUsageCost": [Function], @@ -3419,8 +3388,6 @@ exports[`SettingsComponent renders 1`] = ` "isPreferredApiMongoDB": [Function], "isPreferredApiTable": [Function], "isPublishNotebookPaneEnabled": [Function], - "isReadToggled": [Function], - "isReadWriteToggled": [Function], "isRefreshingExplorer": [Function], "isResourceTokenCollectionNodeSelected": [Function], "isRightPanelV2Enabled": [Function], @@ -3475,7 +3442,6 @@ exports[`SettingsComponent renders 1`] = ` "onRefreshDatabasesKeyPress": [Function], "onRefreshResourcesClick": [Function], "onSwitchToConnectionString": [Function], - "onToggleKeyDown": [Function], "openDialog": undefined, "openSidePanel": undefined, "provideFeedbackEmail": [Function], @@ -3506,8 +3472,6 @@ exports[`SettingsComponent renders 1`] = ` "refreshDatabaseAccount": [Function], "refreshNotebookList": [Function], "refreshTreeTitle": [Function], - "renewToken": [Function], - "renewTokenError": [Function], "resourceTokenCollection": [Function], "resourceTokenCollectionId": [Function], "resourceTokenDatabaseId": [Function], @@ -3603,12 +3567,6 @@ exports[`SettingsComponent renders 1`] = ` "title": [Function], "visible": [Function], }, - "shareAccessData": [Function], - "shareAccessToggleState": [Function], - "shareAccessUrl": [Function], - "shareTokenCopyHelperText": [Function], - "shareUrlCopyHelperText": [Function], - "shouldShowShareDialogContents": [Function], "signInAad": [Function], "sparkClusterConnectionInfo": [Function], "splitter": Splitter { @@ -3668,9 +3626,6 @@ exports[`SettingsComponent renders 1`] = ` "openedTabs": [Function], }, "toggleLeftPaneExpandedKeyPress": [Function], - "toggleRead": [Function], - "toggleReadWrite": [Function], - "tokenForRenewal": [Function], "uploadFilePane": UploadFilePane { "container": [Circular], "extensions": [Function], @@ -3745,7 +3700,6 @@ exports[`SettingsComponent renders 1`] = ` "_isAfecFeatureRegistered": [Function], "_isInitializingNotebooks": false, "_isSystemDatabasePredicate": [Function], - "_openShareDialog": [Function], "_panes": Array [ AddDatabasePane { "autoPilotUsageCost": [Function], @@ -4652,8 +4606,6 @@ exports[`SettingsComponent renders 1`] = ` "isPreferredApiMongoDB": [Function], "isPreferredApiTable": [Function], "isPublishNotebookPaneEnabled": [Function], - "isReadToggled": [Function], - "isReadWriteToggled": [Function], "isRefreshingExplorer": [Function], "isResourceTokenCollectionNodeSelected": [Function], "isRightPanelV2Enabled": [Function], @@ -4708,7 +4660,6 @@ exports[`SettingsComponent renders 1`] = ` "onRefreshDatabasesKeyPress": [Function], "onRefreshResourcesClick": [Function], "onSwitchToConnectionString": [Function], - "onToggleKeyDown": [Function], "openDialog": undefined, "openSidePanel": undefined, "provideFeedbackEmail": [Function], @@ -4739,8 +4690,6 @@ exports[`SettingsComponent renders 1`] = ` "refreshDatabaseAccount": [Function], "refreshNotebookList": [Function], "refreshTreeTitle": [Function], - "renewToken": [Function], - "renewTokenError": [Function], "resourceTokenCollection": [Function], "resourceTokenCollectionId": [Function], "resourceTokenDatabaseId": [Function], @@ -4836,12 +4785,6 @@ exports[`SettingsComponent renders 1`] = ` "title": [Function], "visible": [Function], }, - "shareAccessData": [Function], - "shareAccessToggleState": [Function], - "shareAccessUrl": [Function], - "shareTokenCopyHelperText": [Function], - "shareUrlCopyHelperText": [Function], - "shouldShowShareDialogContents": [Function], "signInAad": [Function], "sparkClusterConnectionInfo": [Function], "splitter": Splitter { @@ -4901,9 +4844,6 @@ exports[`SettingsComponent renders 1`] = ` "openedTabs": [Function], }, "toggleLeftPaneExpandedKeyPress": [Function], - "toggleRead": [Function], - "toggleReadWrite": [Function], - "tokenForRenewal": [Function], "uploadFilePane": UploadFilePane { "container": [Circular], "extensions": [Function], diff --git a/src/Explorer/Explorer.tsx b/src/Explorer/Explorer.tsx index 371f21307..60c72cfc7 100644 --- a/src/Explorer/Explorer.tsx +++ b/src/Explorer/Explorer.tsx @@ -94,16 +94,6 @@ BindingHandlersRegisterer.registerBindingHandlers(); // Hold a reference to ComponentRegisterer to prevent transpiler to ignore import var tmp = ComponentRegisterer; -enum ShareAccessToggleState { - ReadWrite, - Read, -} - -interface AdHocAccessData { - readWriteUrl: string; - readUrl: string; -} - export interface ExplorerParams { setIsNotificationConsoleExpanded: (isExpanded: boolean) => void; setNotificationConsoleData: (consoleData: ConsoleData) => void; @@ -159,8 +149,8 @@ export default class Explorer { // Panes public contextPanes: ContextualPaneBase[]; - private openSidePanel: (headerText: string, panelContent: JSX.Element) => void; - private closeSidePanel: () => void; + public openSidePanel: (headerText: string, panelContent: JSX.Element) => void; + public closeSidePanel: () => void; // Resource Tree public databases: ko.ObservableArray; @@ -226,15 +216,6 @@ export default class Explorer { public canExceedMaximumValue: ko.Computed; public isAutoscaleDefaultEnabled: ko.Observable; - public shouldShowShareDialogContents: ko.Observable; - public shareAccessData: ko.Observable; - public renewExplorerShareAccess: (explorer: Explorer, token: string) => Q.Promise; - public renewTokenError: ko.Observable; - public tokenForRenewal: ko.Observable; - public shareAccessToggleState: ko.Observable; - public shareAccessUrl: ko.Observable; - public shareUrlCopyHelperText: ko.Observable; - public shareTokenCopyHelperText: ko.Observable; public isSchemaEnabled: ko.Computed; // Notebooks @@ -401,25 +382,6 @@ export default class Explorer { this.resourceTokenCollection = ko.observable(); this.resourceTokenPartitionKey = ko.observable(); this.isAuthWithResourceToken = ko.observable(false); - - this.shareAccessData = ko.observable({ - readWriteUrl: undefined, - readUrl: undefined, - }); - this.tokenForRenewal = ko.observable(""); - this.renewTokenError = ko.observable(""); - this.shareAccessUrl = ko.observable(); - this.shareUrlCopyHelperText = ko.observable("Click to copy"); - this.shareTokenCopyHelperText = ko.observable("Click to copy"); - this.shareAccessToggleState = ko.observable(ShareAccessToggleState.ReadWrite); - this.shareAccessToggleState.subscribe((toggleState: ShareAccessToggleState) => { - if (toggleState === ShareAccessToggleState.ReadWrite) { - this.shareAccessUrl(this.shareAccessData && this.shareAccessData().readWriteUrl); - } else { - this.shareAccessUrl(this.shareAccessData && this.shareAccessData().readUrl); - } - }); - this.shouldShowShareDialogContents = ko.observable(false); this.isGalleryPublishEnabled = ko.computed( () => configContext.ENABLE_GALLERY_PUBLISH || this.isFeatureEnabled(Constants.Features.enableGalleryPublish) ); @@ -1041,123 +1003,6 @@ export default class Explorer { // TODO: return result } - public copyUrlLink(src: any, event: MouseEvent): void { - const urlLinkInput: HTMLInputElement = document.getElementById("shareUrlLink") as HTMLInputElement; - urlLinkInput && urlLinkInput.select(); - document.execCommand("copy"); - this.shareUrlCopyHelperText("Copied"); - setTimeout(() => this.shareUrlCopyHelperText("Click to copy"), Constants.ClientDefaults.copyHelperTimeoutMs); - - TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, { - description: "Copy full screen URL", - databaseAccountName: this.databaseAccount() && this.databaseAccount().name, - defaultExperience: this.defaultExperience && this.defaultExperience(), - dataExplorerArea: Constants.Areas.ShareDialog, - }); - } - - public onCopyUrlLinkKeyPress(src: any, event: KeyboardEvent): boolean { - if (event.keyCode === Constants.KeyCodes.Enter || event.keyCode === Constants.KeyCodes.Space) { - this.copyUrlLink(src, null); - return false; - } - - return true; - } - - public copyToken(src: any, event: MouseEvent): void { - const tokenInput: HTMLInputElement = document.getElementById("shareToken") as HTMLInputElement; - tokenInput && tokenInput.select(); - document.execCommand("copy"); - this.shareTokenCopyHelperText("Copied"); - setTimeout(() => this.shareTokenCopyHelperText("Click to copy"), Constants.ClientDefaults.copyHelperTimeoutMs); - } - - public onCopyTokenKeyPress(src: any, event: KeyboardEvent): boolean { - if (event.keyCode === Constants.KeyCodes.Enter || event.keyCode === Constants.KeyCodes.Space) { - this.copyToken(src, null); - return false; - } - - return true; - } - - public renewToken = (): void => { - TelemetryProcessor.trace(Action.ConnectEncryptionToken); - this.renewTokenError(""); - const id: string = NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.InProgress, - "Initiating connection to account" - ); - this.renewExplorerShareAccess(this, this.tokenForRenewal()) - .fail((error: any) => { - const stringifiedError: string = getErrorMessage(error); - this.renewTokenError("Invalid connection string specified"); - NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.Error, - `Failed to initiate connection to account: ${stringifiedError}` - ); - }) - .finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id)); - }; - - public generateSharedAccessData(): void { - const id: string = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, "Generating share url"); - AuthHeadersUtil.generateEncryptedToken().then( - (tokenResponse: DataModels.GenerateTokenResponse) => { - NotificationConsoleUtils.clearInProgressMessageWithId(id); - NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, "Successfully generated share url"); - this.shareAccessData({ - readWriteUrl: this._getShareAccessUrlForToken(tokenResponse.readWrite), - readUrl: this._getShareAccessUrlForToken(tokenResponse.read), - }); - !this.shareAccessData().readWriteUrl && this.shareAccessToggleState(ShareAccessToggleState.Read); // select read toggle by default for readers - this.shareAccessToggleState.valueHasMutated(); // to set initial url and token state - this.shareAccessData.valueHasMutated(); - this._openShareDialog(); - }, - (error: any) => { - NotificationConsoleUtils.clearInProgressMessageWithId(id); - NotificationConsoleUtils.logConsoleMessage( - ConsoleDataType.Error, - `Failed to generate share url: ${getErrorMessage(error)}` - ); - console.error(error); - } - ); - } - - public isConnectExplorerVisible(): boolean { - return $("#connectExplorer").is(":visible") || false; - } - - public isReadWriteToggled: () => boolean = (): boolean => { - return this.shareAccessToggleState() === ShareAccessToggleState.ReadWrite; - }; - - public isReadToggled: () => boolean = (): boolean => { - return this.shareAccessToggleState() === ShareAccessToggleState.Read; - }; - - public toggleReadWrite: (src: any, event: MouseEvent) => void = (src: any, event: MouseEvent) => { - this.shareAccessToggleState(ShareAccessToggleState.ReadWrite); - }; - - public toggleRead: (src: any, event: MouseEvent) => void = (src: any, event: MouseEvent) => { - this.shareAccessToggleState(ShareAccessToggleState.Read); - }; - - public onToggleKeyDown: (src: any, event: KeyboardEvent) => boolean = (src: any, event: KeyboardEvent) => { - if (event.keyCode === Constants.KeyCodes.LeftArrow) { - this.toggleReadWrite(src, null); - return false; - } else if (event.keyCode === Constants.KeyCodes.RightArrow) { - this.toggleRead(src, null); - return false; - } - return true; - }; - public isDatabaseNodeOrNoneSelected(): boolean { return this.isNoneSelected() || this.isDatabaseNodeSelected(); } @@ -1824,83 +1669,6 @@ export default class Explorer { return deferred.promise; } - // TODO: Abstract this elsewhere - private _openShareDialog: () => void = (): void => { - if (!$("#shareDataAccessFlyout").dialog("instance")) { - const accountMetadataInfo = { - databaseAccountName: this.databaseAccount() && this.databaseAccount().name, - defaultExperience: this.defaultExperience && this.defaultExperience(), - dataExplorerArea: Constants.Areas.ShareDialog, - }; - const openFullscreenButton = { - text: "Open", - class: "openFullScreenBtn openFullScreenCancelBtn", - click: () => { - TelemetryProcessor.trace( - Action.SelectItem, - ActionModifiers.Mark, - _.extend({}, { description: "Open full screen" }, accountMetadataInfo) - ); - - const hiddenAnchorElement: HTMLAnchorElement = document.createElement("a"); - hiddenAnchorElement.href = this.shareAccessUrl(); - hiddenAnchorElement.target = "_blank"; - $("#shareDataAccessFlyout").dialog("close"); - hiddenAnchorElement.click(); - }, - }; - const cancelButton = { - text: "Cancel", - class: "shareCancelButton openFullScreenCancelBtn", - click: () => { - TelemetryProcessor.trace( - Action.SelectItem, - ActionModifiers.Mark, - _.extend({}, { description: "Cancel open full screen" }, accountMetadataInfo) - ); - $("#shareDataAccessFlyout").dialog("close"); - }, - }; - $("#shareDataAccessFlyout").dialog({ - autoOpen: false, - buttons: [openFullscreenButton, cancelButton], - closeOnEscape: true, - draggable: false, - dialogClass: "no-close", - position: { my: "right top", at: "right bottom", of: $(".OpenFullScreen") }, - resizable: false, - title: "Open Full Screen", - width: 400, - close: (event: Event, ui: JQueryUI.DialogUIParams) => this.shouldShowShareDialogContents(false), - }); - $("#shareDataAccessFlyout").dialog("option", "classes", { - "ui-widget-content": "shareUrlDialog", - "ui-widget-header": "shareUrlTitle", - "ui-dialog-titlebar-close": "shareClose", - "ui-button": "shareCloseIcon", - "ui-button-icon": "cancelIcon", - "ui-icon": "", - }); - $("#shareDataAccessFlyout").dialog("option", "open", (event: Event, ui: JQueryUI.DialogUIParams) => - $(".openFullScreenBtn").focus() - ); - } - $("#shareDataAccessFlyout").dialog("close"); - this.shouldShowShareDialogContents(true); - $("#shareDataAccessFlyout").dialog("open"); - }; - - private _getShareAccessUrlForToken(token: string): string { - if (!token) { - return undefined; - } - - const urlPrefixWithKeyParam: string = `${configContext.hostedExplorerURL}?key=`; - const currentActiveTab = this.tabsManager.activeTab(); - - return `${urlPrefixWithKeyParam}${token}#/${(currentActiveTab && currentActiveTab.hashLocation()) || ""}`; - } - private _initSettings() { if (!ExplorerSettings.hasSettingsDefined()) { ExplorerSettings.createDefaultSettings(); diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentAdapter.tsx b/src/Explorer/Menus/CommandBar/CommandBarComponentAdapter.tsx index 36dbf6d2a..1afce01fb 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentAdapter.tsx +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentAdapter.tsx @@ -7,7 +7,7 @@ import * as ko from "knockout"; import * as React from "react"; import { ReactAdapter } from "../../../Bindings/ReactBindingHandler"; import * as ViewModels from "../../../Contracts/ViewModels"; -import { CommandBarComponentButtonFactory } from "./CommandBarComponentButtonFactory"; +import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory"; import { CommandBar, ICommandBarItemProps } from "office-ui-fabric-react/lib/CommandBar"; import { StyleConstants } from "../../../Common/Constants"; import * as CommandBarUtil from "./CommandBarUtil"; diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts index 86ad19a21..fe13a1d70 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts @@ -1,5 +1,5 @@ import * as ko from "knockout"; -import { CommandBarComponentButtonFactory } from "./CommandBarComponentButtonFactory"; +import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory"; import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService"; import NotebookManager from "../../Notebook/NotebookManager"; import Explorer from "../../Explorer"; diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.ts b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.ts deleted file mode 100644 index 6e69368e0..000000000 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.ts +++ /dev/null @@ -1,616 +0,0 @@ -import * as ViewModels from "../../../Contracts/ViewModels"; -import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants"; -import { Areas } from "../../../Common/Constants"; -import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; - -import AddDatabaseIcon from "../../../../images/AddDatabase.svg"; -import AddCollectionIcon from "../../../../images/AddCollection.svg"; -import AddSqlQueryIcon from "../../../../images/AddSqlQuery_16x16.svg"; -import BrowseQueriesIcon from "../../../../images/BrowseQuery.svg"; -import * as Constants from "../../../Common/Constants"; -import OpenInTabIcon from "../../../../images/open-in-tab.svg"; -import OpenQueryFromDiskIcon from "../../../../images/OpenQueryFromDisk.svg"; -import CosmosTerminalIcon from "../../../../images/Cosmos-Terminal.svg"; -import HostedTerminalIcon from "../../../../images/Hosted-Terminal.svg"; -import AddStoredProcedureIcon from "../../../../images/AddStoredProcedure.svg"; -import SettingsIcon from "../../../../images/settings_15x15.svg"; -import AddUdfIcon from "../../../../images/AddUdf.svg"; -import AddTriggerIcon from "../../../../images/AddTrigger.svg"; -import ScaleIcon from "../../../../images/Scale_15x15.svg"; -import FeedbackIcon from "../../../../images/Feedback-Command.svg"; -import EnableNotebooksIcon from "../../../../images/notebook/Notebook-enable.svg"; -import NewNotebookIcon from "../../../../images/notebook/Notebook-new.svg"; -import ResetWorkspaceIcon from "../../../../images/notebook/Notebook-reset-workspace.svg"; -import GitHubIcon from "../../../../images/github.svg"; -import SynapseIcon from "../../../../images/synapse-link.svg"; -import { configContext, Platform } from "../../../ConfigContext"; -import Explorer from "../../Explorer"; -import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; - -export class CommandBarComponentButtonFactory { - private static counter: number = 0; - - public static createStaticCommandBarButtons(container: Explorer): CommandButtonComponentProps[] { - if (container.isAuthWithResourceToken()) { - return CommandBarComponentButtonFactory.createStaticCommandBarButtonsForResourceToken(container); - } - - const newCollectionBtn = CommandBarComponentButtonFactory.createNewCollectionGroup(container); - const buttons: CommandButtonComponentProps[] = []; - - if (container.isFeatureEnabled && container.isFeatureEnabled("regionselectbutton")) { - const regions = [{ name: "West US" }, { name: "East US" }, { name: "North Europe" }]; - buttons.push({ - iconSrc: null, - onCommandClick: () => {}, - commandButtonLabel: null, - hasPopup: false, - isDropdown: true, - dropdownPlaceholder: "West US", - dropdownSelectedKey: "West US", - dropdownWidth: 100, - children: regions.map( - (region) => - ({ - iconSrc: null, - onCommandClick: () => {}, - commandButtonLabel: region.name, - dropdownItemKey: region.name, - hasPopup: false, - disabled: false, - ariaLabel: "", - } as CommandButtonComponentProps) - ), - ariaLabel: "", - }); - } - - buttons.push(newCollectionBtn); - - const addSynapseLink = CommandBarComponentButtonFactory.createOpenSynapseLinkDialogButton(container); - if (addSynapseLink) { - buttons.push(CommandBarComponentButtonFactory.createDivider()); - buttons.push(addSynapseLink); - } - - if (!container.isPreferredApiTable()) { - newCollectionBtn.children = [CommandBarComponentButtonFactory.createNewCollectionGroup(container)]; - const newDatabaseBtn = CommandBarComponentButtonFactory.createNewDatabase(container); - newCollectionBtn.children.push(newDatabaseBtn); - } - - buttons.push(CommandBarComponentButtonFactory.createDivider()); - - if (container.isNotebookEnabled()) { - const newNotebookButton = CommandBarComponentButtonFactory.createNewNotebookButton(container); - newNotebookButton.children = [ - CommandBarComponentButtonFactory.createNewNotebookButton(container), - CommandBarComponentButtonFactory.createuploadNotebookButton(container), - ]; - buttons.push(newNotebookButton); - - if (container.notebookManager?.gitHubOAuthService) { - buttons.push(CommandBarComponentButtonFactory.createManageGitHubAccountButton(container)); - } - } - - if (!container.isRunningOnNationalCloud()) { - if (!container.isNotebookEnabled()) { - buttons.push(CommandBarComponentButtonFactory.createEnableNotebooksButton(container)); - } - - if (container.isPreferredApiMongoDB()) { - buttons.push(CommandBarComponentButtonFactory.createOpenMongoTerminalButton(container)); - } - - if (container.isPreferredApiCassandra()) { - buttons.push(CommandBarComponentButtonFactory.createOpenCassandraTerminalButton(container)); - } - } - - if (container.isNotebookEnabled()) { - buttons.push(CommandBarComponentButtonFactory.createOpenTerminalButton(container)); - - buttons.push(CommandBarComponentButtonFactory.createNotebookWorkspaceResetButton(container)); - } - - if (!container.isDatabaseNodeOrNoneSelected()) { - if (container.isNotebookEnabled()) { - buttons.push(CommandBarComponentButtonFactory.createDivider()); - } - - const isSqlQuerySupported = container.isPreferredApiDocumentDB() || container.isPreferredApiGraph(); - if (isSqlQuerySupported) { - const newSqlQueryBtn = CommandBarComponentButtonFactory.createNewSQLQueryButton(container); - buttons.push(newSqlQueryBtn); - } - - const isSupportedOpenQueryApi = - container.isPreferredApiDocumentDB() || container.isPreferredApiMongoDB() || container.isPreferredApiGraph(); - const isSupportedOpenQueryFromDiskApi = container.isPreferredApiDocumentDB() || container.isPreferredApiGraph(); - if (isSupportedOpenQueryApi && container.selectedNode() && container.findSelectedCollection()) { - const openQueryBtn = CommandBarComponentButtonFactory.createOpenQueryButton(container); - openQueryBtn.children = [ - CommandBarComponentButtonFactory.createOpenQueryButton(container), - CommandBarComponentButtonFactory.createOpenQueryFromDiskButton(container), - ]; - buttons.push(openQueryBtn); - } else if (isSupportedOpenQueryFromDiskApi && container.selectedNode() && container.findSelectedCollection()) { - buttons.push(CommandBarComponentButtonFactory.createOpenQueryFromDiskButton(container)); - } - - if (CommandBarComponentButtonFactory.areScriptsSupported(container)) { - const label = "New Stored Procedure"; - const newStoredProcedureBtn: CommandButtonComponentProps = { - iconSrc: AddStoredProcedureIcon, - iconAlt: label, - onCommandClick: () => { - const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); - selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection, null); - }, - commandButtonLabel: label, - ariaLabel: label, - hasPopup: true, - disabled: container.isDatabaseNodeOrNoneSelected(), - }; - - newStoredProcedureBtn.children = CommandBarComponentButtonFactory.createScriptCommandButtons(container); - buttons.push(newStoredProcedureBtn); - } - } - - return buttons; - } - - public static createContextCommandBarButtons(container: Explorer): CommandButtonComponentProps[] { - const buttons: CommandButtonComponentProps[] = []; - - if (!container.isDatabaseNodeOrNoneSelected() && container.isPreferredApiMongoDB()) { - const label = "New Shell"; - const newMongoShellBtn: CommandButtonComponentProps = { - iconSrc: HostedTerminalIcon, - iconAlt: label, - onCommandClick: () => { - const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); - selectedCollection && (selectedCollection).onNewMongoShellClick(); - }, - commandButtonLabel: label, - ariaLabel: label, - hasPopup: true, - disabled: container.isDatabaseNodeOrNoneSelected() && container.isPreferredApiMongoDB(), - }; - buttons.push(newMongoShellBtn); - } - - return buttons; - } - - public static createControlCommandBarButtons(container: Explorer): CommandButtonComponentProps[] { - const buttons: CommandButtonComponentProps[] = []; - if (configContext.platform === Platform.Hosted) { - return buttons; - } - - if (!container.isPreferredApiCassandra()) { - const label = "Settings"; - const settingsPaneButton: CommandButtonComponentProps = { - iconSrc: SettingsIcon, - iconAlt: label, - onCommandClick: () => container.settingsPane.open(), - commandButtonLabel: null, - ariaLabel: label, - tooltipText: label, - hasPopup: true, - disabled: false, - }; - buttons.push(settingsPaneButton); - } - - if (container.isHostedDataExplorerEnabled()) { - const label = "Open Full Screen"; - const fullScreenButton: CommandButtonComponentProps = { - iconSrc: OpenInTabIcon, - iconAlt: label, - onCommandClick: () => container.generateSharedAccessData(), - commandButtonLabel: null, - ariaLabel: label, - tooltipText: label, - hasPopup: false, - disabled: !container.isHostedDataExplorerEnabled(), - className: "OpenFullScreen", - }; - buttons.push(fullScreenButton); - } - - if (configContext.platform !== Platform.Emulator) { - const label = "Feedback"; - const feedbackButtonOptions: CommandButtonComponentProps = { - iconSrc: FeedbackIcon, - iconAlt: label, - onCommandClick: () => container.provideFeedbackEmail(), - commandButtonLabel: null, - ariaLabel: label, - tooltipText: label, - hasPopup: false, - disabled: false, - }; - buttons.push(feedbackButtonOptions); - } - - return buttons; - } - - public static createDivider(): CommandButtonComponentProps { - const label = `divider${CommandBarComponentButtonFactory.counter++}`; - return { - isDivider: true, - commandButtonLabel: label, - hasPopup: false, - iconSrc: null, - iconAlt: null, - onCommandClick: null, - ariaLabel: label, - }; - } - - private static areScriptsSupported(container: Explorer): boolean { - return container.isPreferredApiDocumentDB() || container.isPreferredApiGraph(); - } - - private static createNewCollectionGroup(container: Explorer): CommandButtonComponentProps { - const label = container.addCollectionText(); - return { - iconSrc: AddCollectionIcon, - iconAlt: label, - onCommandClick: () => container.onNewCollectionClicked(), - commandButtonLabel: label, - ariaLabel: label, - hasPopup: true, - id: "createNewContainerCommandButton", - }; - } - - private static createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonComponentProps { - if (configContext.platform === Platform.Emulator) { - return null; - } - - if (container.isServerlessEnabled()) { - return null; - } - - if ( - container.databaseAccount && - container.databaseAccount() && - container.databaseAccount().properties && - container.databaseAccount().properties.enableAnalyticalStorage - ) { - return null; - } - - const capabilities = - (container.databaseAccount && - container.databaseAccount() && - container.databaseAccount().properties && - container.databaseAccount().properties.capabilities) || - []; - if (capabilities.some((capability) => capability.name === Constants.CapabilityNames.EnableStorageAnalytics)) { - return null; - } - - const label = "Enable Azure Synapse Link"; - return { - iconSrc: SynapseIcon, - iconAlt: label, - onCommandClick: () => container.openEnableSynapseLinkDialog(), - commandButtonLabel: label, - hasPopup: false, - disabled: container.isSynapseLinkUpdating(), - ariaLabel: label, - }; - } - - private static createNewDatabase(container: Explorer): CommandButtonComponentProps { - const label = container.addDatabaseText(); - return { - iconSrc: AddDatabaseIcon, - iconAlt: label, - onCommandClick: () => { - container.addDatabasePane.open(); - document.getElementById("linkAddDatabase").focus(); - }, - commandButtonLabel: label, - ariaLabel: label, - hasPopup: true, - }; - } - - private static createNewSQLQueryButton(container: Explorer): CommandButtonComponentProps { - if (container.isPreferredApiDocumentDB() || container.isPreferredApiGraph()) { - const label = "New SQL Query"; - return { - iconSrc: AddSqlQueryIcon, - iconAlt: label, - onCommandClick: () => { - const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); - selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, null); - }, - commandButtonLabel: label, - ariaLabel: label, - hasPopup: true, - disabled: container.isDatabaseNodeOrNoneSelected(), - }; - } else if (container.isPreferredApiMongoDB()) { - const label = "New Query"; - return { - iconSrc: AddSqlQueryIcon, - iconAlt: label, - onCommandClick: () => { - const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); - selectedCollection && (selectedCollection).onNewMongoQueryClick(selectedCollection, null); - }, - commandButtonLabel: label, - ariaLabel: label, - hasPopup: true, - disabled: container.isDatabaseNodeOrNoneSelected(), - }; - } - - return null; - } - - public static createScriptCommandButtons(container: Explorer): CommandButtonComponentProps[] { - const buttons: CommandButtonComponentProps[] = []; - - const shouldEnableScriptsCommands: boolean = - !container.isDatabaseNodeOrNoneSelected() && CommandBarComponentButtonFactory.areScriptsSupported(container); - - if (shouldEnableScriptsCommands) { - const label = "New Stored Procedure"; - const newStoredProcedureBtn: CommandButtonComponentProps = { - iconSrc: AddStoredProcedureIcon, - iconAlt: label, - onCommandClick: () => { - const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); - selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection, null); - }, - commandButtonLabel: label, - ariaLabel: label, - hasPopup: true, - disabled: container.isDatabaseNodeOrNoneSelected(), - }; - buttons.push(newStoredProcedureBtn); - } - - if (shouldEnableScriptsCommands) { - const label = "New UDF"; - const newUserDefinedFunctionBtn: CommandButtonComponentProps = { - iconSrc: AddUdfIcon, - iconAlt: label, - onCommandClick: () => { - const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); - selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection, null); - }, - commandButtonLabel: label, - ariaLabel: label, - hasPopup: true, - disabled: container.isDatabaseNodeOrNoneSelected(), - }; - buttons.push(newUserDefinedFunctionBtn); - } - - if (shouldEnableScriptsCommands) { - const label = "New Trigger"; - const newTriggerBtn: CommandButtonComponentProps = { - iconSrc: AddTriggerIcon, - iconAlt: label, - onCommandClick: () => { - const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); - selectedCollection && selectedCollection.onNewTriggerClick(selectedCollection, null); - }, - commandButtonLabel: label, - ariaLabel: label, - hasPopup: true, - disabled: container.isDatabaseNodeOrNoneSelected(), - }; - buttons.push(newTriggerBtn); - } - - return buttons; - } - - private static createNewNotebookButton(container: Explorer): CommandButtonComponentProps { - const label = "New Notebook"; - return { - iconSrc: NewNotebookIcon, - iconAlt: label, - onCommandClick: () => container.onNewNotebookClicked(), - commandButtonLabel: label, - hasPopup: false, - disabled: false, - ariaLabel: label, - }; - } - - private static createuploadNotebookButton(container: Explorer): CommandButtonComponentProps { - const label = "Upload to Notebook Server"; - return { - iconSrc: NewNotebookIcon, - iconAlt: label, - onCommandClick: () => container.onUploadToNotebookServerClicked(), - commandButtonLabel: label, - hasPopup: false, - disabled: false, - ariaLabel: label, - }; - } - - private static createOpenQueryButton(container: Explorer): CommandButtonComponentProps { - const label = "Open Query"; - return { - iconSrc: BrowseQueriesIcon, - iconAlt: label, - onCommandClick: () => container.browseQueriesPane.open(), - commandButtonLabel: label, - ariaLabel: label, - hasPopup: true, - disabled: false, - }; - } - - private static createOpenQueryFromDiskButton(container: Explorer): CommandButtonComponentProps { - const label = "Open Query From Disk"; - return { - iconSrc: OpenQueryFromDiskIcon, - iconAlt: label, - onCommandClick: () => container.loadQueryPane.open(), - commandButtonLabel: label, - ariaLabel: label, - hasPopup: true, - disabled: false, - }; - } - - private static createEnableNotebooksButton(container: Explorer): CommandButtonComponentProps { - if (configContext.platform === Platform.Emulator) { - return null; - } - const label = "Enable Notebooks (Preview)"; - const tooltip = - "Notebooks are not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks."; - const description = - "Looks like you have not yet created a notebooks workspace for this account. To proceed and start using notebooks, we'll need to create a default notebooks workspace in this account."; - return { - iconSrc: EnableNotebooksIcon, - iconAlt: label, - onCommandClick: () => container.setupNotebooksPane.openWithTitleAndDescription(label, description), - commandButtonLabel: label, - hasPopup: false, - disabled: !container.isNotebooksEnabledForAccount(), - ariaLabel: label, - tooltipText: container.isNotebooksEnabledForAccount() ? "" : tooltip, - }; - } - - private static createOpenTerminalButton(container: Explorer): CommandButtonComponentProps { - const label = "Open Terminal"; - return { - iconSrc: CosmosTerminalIcon, - iconAlt: label, - onCommandClick: () => container.openNotebookTerminal(ViewModels.TerminalKind.Default), - commandButtonLabel: label, - hasPopup: false, - disabled: false, - ariaLabel: label, - }; - } - - private static createOpenMongoTerminalButton(container: Explorer): CommandButtonComponentProps { - const label = "Open Mongo Shell"; - const tooltip = - "This feature is not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks."; - const title = "Set up workspace"; - const description = - "Looks like you have not created a workspace for this account. To proceed and start using features including mongo shell and notebook, we will need to create a default workspace in this account."; - const disableButton = !container.isNotebooksEnabledForAccount() && !container.isNotebookEnabled(); - return { - iconSrc: HostedTerminalIcon, - iconAlt: label, - onCommandClick: () => { - if (container.isNotebookEnabled()) { - container.openNotebookTerminal(ViewModels.TerminalKind.Mongo); - } else { - container.setupNotebooksPane.openWithTitleAndDescription(title, description); - } - }, - commandButtonLabel: label, - hasPopup: false, - disabled: disableButton, - ariaLabel: label, - tooltipText: !disableButton ? "" : tooltip, - }; - } - - private static createOpenCassandraTerminalButton(container: Explorer): CommandButtonComponentProps { - const label = "Open Cassandra Shell"; - const tooltip = - "This feature is not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks."; - const title = "Set up workspace"; - const description = - "Looks like you have not created a workspace for this account. To proceed and start using features including cassandra shell and notebook, we will need to create a default workspace in this account."; - const disableButton = !container.isNotebooksEnabledForAccount() && !container.isNotebookEnabled(); - return { - iconSrc: HostedTerminalIcon, - iconAlt: label, - onCommandClick: () => { - if (container.isNotebookEnabled()) { - container.openNotebookTerminal(ViewModels.TerminalKind.Cassandra); - } else { - container.setupNotebooksPane.openWithTitleAndDescription(title, description); - } - }, - commandButtonLabel: label, - hasPopup: false, - disabled: disableButton, - ariaLabel: label, - tooltipText: !disableButton ? "" : tooltip, - }; - } - - private static createNotebookWorkspaceResetButton(container: Explorer): CommandButtonComponentProps { - const label = "Reset Workspace"; - return { - iconSrc: ResetWorkspaceIcon, - iconAlt: label, - onCommandClick: () => container.resetNotebookWorkspace(), - commandButtonLabel: label, - hasPopup: false, - disabled: false, - ariaLabel: label, - }; - } - - private static createManageGitHubAccountButton(container: Explorer): CommandButtonComponentProps { - let connectedToGitHub: boolean = container.notebookManager?.gitHubOAuthService.isLoggedIn(); - const label = connectedToGitHub ? "Manage GitHub settings" : "Connect to GitHub"; - return { - iconSrc: GitHubIcon, - iconAlt: label, - onCommandClick: () => { - if (!connectedToGitHub) { - TelemetryProcessor.trace(Action.NotebooksGitHubConnect, ActionModifiers.Mark, { - databaseAccountName: container.databaseAccount() && container.databaseAccount().name, - defaultExperience: container.defaultExperience && container.defaultExperience(), - dataExplorerArea: Areas.Notebook, - }); - } - container.gitHubReposPane.open(); - }, - commandButtonLabel: label, - hasPopup: false, - disabled: false, - ariaLabel: label, - }; - } - - private static createStaticCommandBarButtonsForResourceToken(container: Explorer): CommandButtonComponentProps[] { - const newSqlQueryBtn = CommandBarComponentButtonFactory.createNewSQLQueryButton(container); - const openQueryBtn = CommandBarComponentButtonFactory.createOpenQueryButton(container); - - newSqlQueryBtn.disabled = !container.isResourceTokenCollectionNodeSelected(); - newSqlQueryBtn.onCommandClick = () => { - const resourceTokenCollection: ViewModels.CollectionBase = container.resourceTokenCollection(); - resourceTokenCollection && resourceTokenCollection.onNewQueryClick(resourceTokenCollection, undefined); - }; - - openQueryBtn.disabled = !container.isResourceTokenCollectionNodeSelected(); - if (!openQueryBtn.disabled) { - openQueryBtn.children = [ - CommandBarComponentButtonFactory.createOpenQueryButton(container), - CommandBarComponentButtonFactory.createOpenQueryFromDiskButton(container), - ]; - } - - return [newSqlQueryBtn, openQueryBtn]; - } -} diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx new file mode 100644 index 000000000..d9e7de658 --- /dev/null +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx @@ -0,0 +1,581 @@ +import * as ViewModels from "../../../Contracts/ViewModels"; +import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants"; +import { Areas } from "../../../Common/Constants"; +import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; + +import AddDatabaseIcon from "../../../../images/AddDatabase.svg"; +import AddCollectionIcon from "../../../../images/AddCollection.svg"; +import AddSqlQueryIcon from "../../../../images/AddSqlQuery_16x16.svg"; +import BrowseQueriesIcon from "../../../../images/BrowseQuery.svg"; +import * as Constants from "../../../Common/Constants"; +import OpenInTabIcon from "../../../../images/open-in-tab.svg"; +import OpenQueryFromDiskIcon from "../../../../images/OpenQueryFromDisk.svg"; +import CosmosTerminalIcon from "../../../../images/Cosmos-Terminal.svg"; +import HostedTerminalIcon from "../../../../images/Hosted-Terminal.svg"; +import AddStoredProcedureIcon from "../../../../images/AddStoredProcedure.svg"; +import SettingsIcon from "../../../../images/settings_15x15.svg"; +import AddUdfIcon from "../../../../images/AddUdf.svg"; +import AddTriggerIcon from "../../../../images/AddTrigger.svg"; +import FeedbackIcon from "../../../../images/Feedback-Command.svg"; +import EnableNotebooksIcon from "../../../../images/notebook/Notebook-enable.svg"; +import NewNotebookIcon from "../../../../images/notebook/Notebook-new.svg"; +import ResetWorkspaceIcon from "../../../../images/notebook/Notebook-reset-workspace.svg"; +import GitHubIcon from "../../../../images/github.svg"; +import SynapseIcon from "../../../../images/synapse-link.svg"; +import { configContext, Platform } from "../../../ConfigContext"; +import Explorer from "../../Explorer"; +import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent"; +import * as React from "react"; +import { OpenFullScreen } from "../../OpenFullScreen"; + +let counter = 0; + +export function createStaticCommandBarButtons(container: Explorer): CommandButtonComponentProps[] { + if (container.isAuthWithResourceToken()) { + return createStaticCommandBarButtonsForResourceToken(container); + } + + const newCollectionBtn = createNewCollectionGroup(container); + const buttons: CommandButtonComponentProps[] = []; + + buttons.push(newCollectionBtn); + + const addSynapseLink = createOpenSynapseLinkDialogButton(container); + if (addSynapseLink) { + buttons.push(createDivider()); + buttons.push(addSynapseLink); + } + + if (!container.isPreferredApiTable()) { + newCollectionBtn.children = [createNewCollectionGroup(container)]; + const newDatabaseBtn = createNewDatabase(container); + newCollectionBtn.children.push(newDatabaseBtn); + } + + buttons.push(createDivider()); + + if (container.isNotebookEnabled()) { + const newNotebookButton = createNewNotebookButton(container); + newNotebookButton.children = [createNewNotebookButton(container), createuploadNotebookButton(container)]; + buttons.push(newNotebookButton); + + if (container.notebookManager?.gitHubOAuthService) { + buttons.push(createManageGitHubAccountButton(container)); + } + } + + if (!container.isRunningOnNationalCloud()) { + if (!container.isNotebookEnabled()) { + buttons.push(createEnableNotebooksButton(container)); + } + + if (container.isPreferredApiMongoDB()) { + buttons.push(createOpenMongoTerminalButton(container)); + } + + if (container.isPreferredApiCassandra()) { + buttons.push(createOpenCassandraTerminalButton(container)); + } + } + + if (container.isNotebookEnabled()) { + buttons.push(createOpenTerminalButton(container)); + + buttons.push(createNotebookWorkspaceResetButton(container)); + } + + if (!container.isDatabaseNodeOrNoneSelected()) { + if (container.isNotebookEnabled()) { + buttons.push(createDivider()); + } + + const isSqlQuerySupported = container.isPreferredApiDocumentDB() || container.isPreferredApiGraph(); + if (isSqlQuerySupported) { + const newSqlQueryBtn = createNewSQLQueryButton(container); + buttons.push(newSqlQueryBtn); + } + + const isSupportedOpenQueryApi = + container.isPreferredApiDocumentDB() || container.isPreferredApiMongoDB() || container.isPreferredApiGraph(); + const isSupportedOpenQueryFromDiskApi = container.isPreferredApiDocumentDB() || container.isPreferredApiGraph(); + if (isSupportedOpenQueryApi && container.selectedNode() && container.findSelectedCollection()) { + const openQueryBtn = createOpenQueryButton(container); + openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton(container)]; + buttons.push(openQueryBtn); + } else if (isSupportedOpenQueryFromDiskApi && container.selectedNode() && container.findSelectedCollection()) { + buttons.push(createOpenQueryFromDiskButton(container)); + } + + if (areScriptsSupported(container)) { + const label = "New Stored Procedure"; + const newStoredProcedureBtn: CommandButtonComponentProps = { + iconSrc: AddStoredProcedureIcon, + iconAlt: label, + onCommandClick: () => { + const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); + selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection); + }, + commandButtonLabel: label, + ariaLabel: label, + hasPopup: true, + disabled: container.isDatabaseNodeOrNoneSelected(), + }; + + newStoredProcedureBtn.children = createScriptCommandButtons(container); + buttons.push(newStoredProcedureBtn); + } + } + + return buttons; +} + +export function createContextCommandBarButtons(container: Explorer): CommandButtonComponentProps[] { + const buttons: CommandButtonComponentProps[] = []; + + if (!container.isDatabaseNodeOrNoneSelected() && container.isPreferredApiMongoDB()) { + const label = "New Shell"; + const newMongoShellBtn: CommandButtonComponentProps = { + iconSrc: HostedTerminalIcon, + iconAlt: label, + onCommandClick: () => { + const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); + selectedCollection && selectedCollection.onNewMongoShellClick(); + }, + commandButtonLabel: label, + ariaLabel: label, + hasPopup: true, + disabled: container.isDatabaseNodeOrNoneSelected() && container.isPreferredApiMongoDB(), + }; + buttons.push(newMongoShellBtn); + } + + return buttons; +} + +export function createControlCommandBarButtons(container: Explorer): CommandButtonComponentProps[] { + const buttons: CommandButtonComponentProps[] = []; + if (configContext.platform === Platform.Hosted) { + return buttons; + } + + if (!container.isPreferredApiCassandra()) { + const label = "Settings"; + const settingsPaneButton: CommandButtonComponentProps = { + iconSrc: SettingsIcon, + iconAlt: label, + onCommandClick: () => container.settingsPane.open(), + commandButtonLabel: undefined, + ariaLabel: label, + tooltipText: label, + hasPopup: true, + disabled: false, + }; + buttons.push(settingsPaneButton); + } + + if (container.isHostedDataExplorerEnabled()) { + const label = "Open Full Screen"; + const fullScreenButton: CommandButtonComponentProps = { + iconSrc: OpenInTabIcon, + iconAlt: label, + onCommandClick: () => { + container.openSidePanel("Open Full Screen", ); + }, + commandButtonLabel: undefined, + ariaLabel: label, + tooltipText: label, + hasPopup: false, + disabled: !container.isHostedDataExplorerEnabled(), + className: "OpenFullScreen", + }; + buttons.push(fullScreenButton); + } + + if (configContext.platform !== Platform.Emulator) { + const label = "Feedback"; + const feedbackButtonOptions: CommandButtonComponentProps = { + iconSrc: FeedbackIcon, + iconAlt: label, + onCommandClick: () => container.provideFeedbackEmail(), + commandButtonLabel: undefined, + ariaLabel: label, + tooltipText: label, + hasPopup: false, + disabled: false, + }; + buttons.push(feedbackButtonOptions); + } + + return buttons; +} + +export function createDivider(): CommandButtonComponentProps { + const label = `divider${counter++}`; + return { + isDivider: true, + commandButtonLabel: label, + hasPopup: false, + iconSrc: undefined, + iconAlt: undefined, + onCommandClick: undefined, + ariaLabel: label, + }; +} + +function areScriptsSupported(container: Explorer): boolean { + return container.isPreferredApiDocumentDB() || container.isPreferredApiGraph(); +} + +function createNewCollectionGroup(container: Explorer): CommandButtonComponentProps { + const label = container.addCollectionText(); + return { + iconSrc: AddCollectionIcon, + iconAlt: label, + onCommandClick: () => container.onNewCollectionClicked(), + commandButtonLabel: label, + ariaLabel: label, + hasPopup: true, + id: "createNewContainerCommandButton", + }; +} + +function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonComponentProps { + if (configContext.platform === Platform.Emulator) { + return undefined; + } + + if (container.isServerlessEnabled()) { + return undefined; + } + + if ( + container.databaseAccount && + container.databaseAccount() && + container.databaseAccount().properties && + container.databaseAccount().properties.enableAnalyticalStorage + ) { + return undefined; + } + + const capabilities = + (container.databaseAccount && + container.databaseAccount() && + container.databaseAccount().properties && + container.databaseAccount().properties.capabilities) || + []; + if (capabilities.some((capability) => capability.name === Constants.CapabilityNames.EnableStorageAnalytics)) { + return undefined; + } + + const label = "Enable Azure Synapse Link"; + return { + iconSrc: SynapseIcon, + iconAlt: label, + onCommandClick: () => container.openEnableSynapseLinkDialog(), + commandButtonLabel: label, + hasPopup: false, + disabled: container.isSynapseLinkUpdating(), + ariaLabel: label, + }; +} + +function createNewDatabase(container: Explorer): CommandButtonComponentProps { + const label = container.addDatabaseText(); + return { + iconSrc: AddDatabaseIcon, + iconAlt: label, + onCommandClick: () => { + container.addDatabasePane.open(); + document.getElementById("linkAddDatabase").focus(); + }, + commandButtonLabel: label, + ariaLabel: label, + hasPopup: true, + }; +} + +function createNewSQLQueryButton(container: Explorer): CommandButtonComponentProps { + if (container.isPreferredApiDocumentDB() || container.isPreferredApiGraph()) { + const label = "New SQL Query"; + return { + iconSrc: AddSqlQueryIcon, + iconAlt: label, + onCommandClick: () => { + const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); + selectedCollection && selectedCollection.onNewQueryClick(selectedCollection); + }, + commandButtonLabel: label, + ariaLabel: label, + hasPopup: true, + disabled: container.isDatabaseNodeOrNoneSelected(), + }; + } else if (container.isPreferredApiMongoDB()) { + const label = "New Query"; + return { + iconSrc: AddSqlQueryIcon, + iconAlt: label, + onCommandClick: () => { + const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); + selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection); + }, + commandButtonLabel: label, + ariaLabel: label, + hasPopup: true, + disabled: container.isDatabaseNodeOrNoneSelected(), + }; + } + + return undefined; +} + +export function createScriptCommandButtons(container: Explorer): CommandButtonComponentProps[] { + const buttons: CommandButtonComponentProps[] = []; + + const shouldEnableScriptsCommands: boolean = + !container.isDatabaseNodeOrNoneSelected() && areScriptsSupported(container); + + if (shouldEnableScriptsCommands) { + const label = "New Stored Procedure"; + const newStoredProcedureBtn: CommandButtonComponentProps = { + iconSrc: AddStoredProcedureIcon, + iconAlt: label, + onCommandClick: () => { + const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); + selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection); + }, + commandButtonLabel: label, + ariaLabel: label, + hasPopup: true, + disabled: container.isDatabaseNodeOrNoneSelected(), + }; + buttons.push(newStoredProcedureBtn); + } + + if (shouldEnableScriptsCommands) { + const label = "New UDF"; + const newUserDefinedFunctionBtn: CommandButtonComponentProps = { + iconSrc: AddUdfIcon, + iconAlt: label, + onCommandClick: () => { + const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); + selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection); + }, + commandButtonLabel: label, + ariaLabel: label, + hasPopup: true, + disabled: container.isDatabaseNodeOrNoneSelected(), + }; + buttons.push(newUserDefinedFunctionBtn); + } + + if (shouldEnableScriptsCommands) { + const label = "New Trigger"; + const newTriggerBtn: CommandButtonComponentProps = { + iconSrc: AddTriggerIcon, + iconAlt: label, + onCommandClick: () => { + const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); + selectedCollection && selectedCollection.onNewTriggerClick(selectedCollection); + }, + commandButtonLabel: label, + ariaLabel: label, + hasPopup: true, + disabled: container.isDatabaseNodeOrNoneSelected(), + }; + buttons.push(newTriggerBtn); + } + + return buttons; +} + +function createNewNotebookButton(container: Explorer): CommandButtonComponentProps { + const label = "New Notebook"; + return { + iconSrc: NewNotebookIcon, + iconAlt: label, + onCommandClick: () => container.onNewNotebookClicked(), + commandButtonLabel: label, + hasPopup: false, + disabled: false, + ariaLabel: label, + }; +} + +function createuploadNotebookButton(container: Explorer): CommandButtonComponentProps { + const label = "Upload to Notebook Server"; + return { + iconSrc: NewNotebookIcon, + iconAlt: label, + onCommandClick: () => container.onUploadToNotebookServerClicked(), + commandButtonLabel: label, + hasPopup: false, + disabled: false, + ariaLabel: label, + }; +} + +function createOpenQueryButton(container: Explorer): CommandButtonComponentProps { + const label = "Open Query"; + return { + iconSrc: BrowseQueriesIcon, + iconAlt: label, + onCommandClick: () => container.browseQueriesPane.open(), + commandButtonLabel: label, + ariaLabel: label, + hasPopup: true, + disabled: false, + }; +} + +function createOpenQueryFromDiskButton(container: Explorer): CommandButtonComponentProps { + const label = "Open Query From Disk"; + return { + iconSrc: OpenQueryFromDiskIcon, + iconAlt: label, + onCommandClick: () => container.loadQueryPane.open(), + commandButtonLabel: label, + ariaLabel: label, + hasPopup: true, + disabled: false, + }; +} + +function createEnableNotebooksButton(container: Explorer): CommandButtonComponentProps { + if (configContext.platform === Platform.Emulator) { + return undefined; + } + const label = "Enable Notebooks (Preview)"; + const tooltip = + "Notebooks are not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks."; + const description = + "Looks like you have not yet created a notebooks workspace for this account. To proceed and start using notebooks, we'll need to create a default notebooks workspace in this account."; + return { + iconSrc: EnableNotebooksIcon, + iconAlt: label, + onCommandClick: () => container.setupNotebooksPane.openWithTitleAndDescription(label, description), + commandButtonLabel: label, + hasPopup: false, + disabled: !container.isNotebooksEnabledForAccount(), + ariaLabel: label, + tooltipText: container.isNotebooksEnabledForAccount() ? "" : tooltip, + }; +} + +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: false, + ariaLabel: label, + }; +} + +function createOpenMongoTerminalButton(container: Explorer): CommandButtonComponentProps { + const label = "Open Mongo Shell"; + const tooltip = + "This feature is not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks."; + const title = "Set up workspace"; + const description = + "Looks like you have not created a workspace for this account. To proceed and start using features including mongo shell and notebook, we will need to create a default workspace in this account."; + const disableButton = !container.isNotebooksEnabledForAccount() && !container.isNotebookEnabled(); + return { + iconSrc: HostedTerminalIcon, + iconAlt: label, + onCommandClick: () => { + if (container.isNotebookEnabled()) { + container.openNotebookTerminal(ViewModels.TerminalKind.Mongo); + } else { + container.setupNotebooksPane.openWithTitleAndDescription(title, description); + } + }, + commandButtonLabel: label, + hasPopup: false, + disabled: disableButton, + ariaLabel: label, + tooltipText: !disableButton ? "" : tooltip, + }; +} + +function createOpenCassandraTerminalButton(container: Explorer): CommandButtonComponentProps { + const label = "Open Cassandra Shell"; + const tooltip = + "This feature is not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks."; + const title = "Set up workspace"; + const description = + "Looks like you have not created a workspace for this account. To proceed and start using features including cassandra shell and notebook, we will need to create a default workspace in this account."; + const disableButton = !container.isNotebooksEnabledForAccount() && !container.isNotebookEnabled(); + return { + iconSrc: HostedTerminalIcon, + iconAlt: label, + onCommandClick: () => { + if (container.isNotebookEnabled()) { + container.openNotebookTerminal(ViewModels.TerminalKind.Cassandra); + } else { + container.setupNotebooksPane.openWithTitleAndDescription(title, description); + } + }, + commandButtonLabel: label, + hasPopup: false, + disabled: disableButton, + ariaLabel: label, + tooltipText: !disableButton ? "" : tooltip, + }; +} + +function createNotebookWorkspaceResetButton(container: Explorer): CommandButtonComponentProps { + const label = "Reset Workspace"; + return { + iconSrc: ResetWorkspaceIcon, + iconAlt: label, + onCommandClick: () => container.resetNotebookWorkspace(), + commandButtonLabel: label, + hasPopup: false, + disabled: false, + ariaLabel: label, + }; +} + +function createManageGitHubAccountButton(container: Explorer): CommandButtonComponentProps { + const connectedToGitHub: boolean = container.notebookManager?.gitHubOAuthService.isLoggedIn(); + const label = connectedToGitHub ? "Manage GitHub settings" : "Connect to GitHub"; + return { + iconSrc: GitHubIcon, + iconAlt: label, + onCommandClick: () => { + if (!connectedToGitHub) { + TelemetryProcessor.trace(Action.NotebooksGitHubConnect, ActionModifiers.Mark, { + databaseAccountName: container.databaseAccount() && container.databaseAccount().name, + defaultExperience: container.defaultExperience && container.defaultExperience(), + dataExplorerArea: Areas.Notebook, + }); + } + container.gitHubReposPane.open(); + }, + commandButtonLabel: label, + hasPopup: false, + disabled: false, + ariaLabel: label, + }; +} + +function createStaticCommandBarButtonsForResourceToken(container: Explorer): CommandButtonComponentProps[] { + const newSqlQueryBtn = createNewSQLQueryButton(container); + const openQueryBtn = createOpenQueryButton(container); + + newSqlQueryBtn.disabled = !container.isResourceTokenCollectionNodeSelected(); + newSqlQueryBtn.onCommandClick = () => { + const resourceTokenCollection: ViewModels.CollectionBase = container.resourceTokenCollection(); + resourceTokenCollection && resourceTokenCollection.onNewQueryClick(resourceTokenCollection, undefined); + }; + + openQueryBtn.disabled = !container.isResourceTokenCollectionNodeSelected(); + if (!openQueryBtn.disabled) { + openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton(container)]; + } + + return [newSqlQueryBtn, openQueryBtn]; +} diff --git a/src/Explorer/OpenActions.test.ts b/src/Explorer/OpenActions.test.ts index ce6d4375a..ab9e3314a 100644 --- a/src/Explorer/OpenActions.test.ts +++ b/src/Explorer/OpenActions.test.ts @@ -20,7 +20,6 @@ describe("OpenActions", () => { explorer.cassandraAddCollectionPane = {} as CassandraAddCollectionPane; explorer.cassandraAddCollectionPane.open = jest.fn(); explorer.closeAllPanes = () => {}; - explorer.isConnectExplorerVisible = () => false; database = { id: ko.observable("db"), diff --git a/src/Explorer/OpenActions.ts b/src/Explorer/OpenActions.ts index 4997a7266..aa3fc21ec 100644 --- a/src/Explorer/OpenActions.ts +++ b/src/Explorer/OpenActions.ts @@ -133,19 +133,19 @@ function openPane(action: ActionContracts.OpenPane, explorer: Explorer) { (action).paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.AddCollection] ) { explorer.closeAllPanes(); - !explorer.isConnectExplorerVisible() && explorer.addCollectionPane.open(); + explorer.addCollectionPane.open(); } else if ( action.paneKind === ActionContracts.PaneKind.CassandraAddCollection || (action).paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.CassandraAddCollection] ) { explorer.closeAllPanes(); - !explorer.isConnectExplorerVisible() && explorer.cassandraAddCollectionPane.open(); + explorer.cassandraAddCollectionPane.open(); } else if ( action.paneKind === ActionContracts.PaneKind.GlobalSettings || (action).paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.GlobalSettings] ) { explorer.closeAllPanes(); - !explorer.isConnectExplorerVisible() && explorer.settingsPane.open(); + explorer.settingsPane.open(); } } diff --git a/src/Explorer/OpenFullScreen.test.tsx b/src/Explorer/OpenFullScreen.test.tsx new file mode 100644 index 000000000..f2473cf2b --- /dev/null +++ b/src/Explorer/OpenFullScreen.test.tsx @@ -0,0 +1,17 @@ +jest.mock("../hooks/useFullScreenURLs"); +import "@testing-library/jest-dom"; +import { render, screen } from "@testing-library/react"; +import React from "react"; +import { useFullScreenURLs } from "../hooks/useFullScreenURLs"; +import { OpenFullScreen } from "./OpenFullScreen"; + +it("renders the correct URLs", () => { + (useFullScreenURLs as jest.Mock).mockReturnValue({ + readWrite: "read and write url", + read: "read only url", + }); + + render(); + expect(screen.getByLabelText("Read and Write")).toHaveValue("https://cosmos.azure.com/?key=read and write url"); + expect(screen.getByLabelText("Read Only")).toHaveValue("https://cosmos.azure.com/?key=read only url"); +}); diff --git a/src/Explorer/OpenFullScreen.tsx b/src/Explorer/OpenFullScreen.tsx new file mode 100644 index 000000000..711706cf7 --- /dev/null +++ b/src/Explorer/OpenFullScreen.tsx @@ -0,0 +1,61 @@ +import { Spinner, Stack, Text, TextField } from "office-ui-fabric-react"; +import { DefaultButton, PrimaryButton } from "office-ui-fabric-react/lib/Button"; +import * as React from "react"; +import { useFullScreenURLs } from "../hooks/useFullScreenURLs"; +import copyToClipboard from "clipboard-copy"; + +export const OpenFullScreen: React.FunctionComponent = () => { + const result = useFullScreenURLs(); + if (!result) { + return ; + } + + const readWriteUrl = `https://cosmos.azure.com/?key=${result.readWrite}`; + const readUrl = `https://cosmos.azure.com/?key=${result.read}`; + + return ( + <> + + + Open this database account in a new browser tab with Cosmos DB Explorer. Or copy the read-write or read only + access urls below to share with others. For security purposes, the URLs grant time-bound access to the + account. When access expires, you can reconnect, using a valid connection string for the account. + + + + { + copyToClipboard(readWriteUrl); + }} + text="Copy" + iconProps={{ iconName: "Copy" }} + /> + { + window.open(readWriteUrl, "_blank"); + }} + text="Open" + iconProps={{ iconName: "OpenInNewWindow" }} + /> + + + + { + copyToClipboard(readUrl); + }} + text="Copy" + iconProps={{ iconName: "Copy" }} + /> + { + window.open(readUrl, "_blank"); + }} + text="Open" + iconProps={{ iconName: "OpenInNewWindow" }} + /> + + + + ); +}; diff --git a/src/Explorer/Tabs/NotebookV2Tab.ts b/src/Explorer/Tabs/NotebookV2Tab.ts index d4859c304..690302def 100644 --- a/src/Explorer/Tabs/NotebookV2Tab.ts +++ b/src/Explorer/Tabs/NotebookV2Tab.ts @@ -20,7 +20,7 @@ import KillKernelIcon from "../../../images/notebook/Notebook-stop.svg"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import { Action, ActionModifiers, Source } from "../../Shared/Telemetry/TelemetryConstants"; import { Areas, ArmApiVersions } from "../../Common/Constants"; -import { CommandBarComponentButtonFactory } from "../Menus/CommandBar/CommandBarComponentButtonFactory"; +import * as CommandBarComponentButtonFactory from "../Menus/CommandBar/CommandBarComponentButtonFactory"; import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent"; import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils"; import { NotebookComponentAdapter } from "../Notebook/NotebookComponent/NotebookComponentAdapter"; diff --git a/src/Main.tsx b/src/Main.tsx index 316dcf3a8..1a00f2a09 100644 --- a/src/Main.tsx +++ b/src/Main.tsx @@ -58,7 +58,6 @@ import { initializeIcons } from "office-ui-fabric-react/lib/Icons"; import { ExplorerParams } from "./Explorer/Explorer"; import React, { useState } from "react"; import ReactDOM from "react-dom"; -import copyImage from "../images/Copy.svg"; import hdeConnectImage from "../images/HdeConnectCosmosDB.svg"; import refreshImg from "../images/refresh-cosmos.svg"; import arrowLeftImg from "../images/imgarrowlefticon.svg"; @@ -117,74 +116,7 @@ const App: React.FunctionComponent = () => { className="flexContainer hideOverflows" style={{ display: "none" }} > - {/* Main Command Bar - Start */}
- {/* Main Command Bar - End */} - {/* Share url flyout - Start */} -
-
-
- - Open this database account in a new browser tab with Cosmos DB Explorer. Or copy the read-write or read - only access urls below to share with others. For security purposes, the URLs grant time-bound access to - the account. When access expires, you can reconnect, using a valid connection string for the account. - -
-
-
- - - Read-Write - -
-
- - - Read - -
-
-
- - - Copy link - - -
-
-
-
- {/* Share url flyout - End */} {/* Collections Tree and Tabs - Begin */}
{/* Collections Tree - Start */} diff --git a/src/Platform/Hosted/Authorization.ts b/src/Platform/Hosted/Authorization.ts index 2aaac31ad..64cd00a72 100644 --- a/src/Platform/Hosted/Authorization.ts +++ b/src/Platform/Hosted/Authorization.ts @@ -6,39 +6,15 @@ import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility" import { userContext } from "../../UserContext"; export default class AuthHeadersUtil { - public static generateEncryptedToken(): Q.Promise { + public static async generateEncryptedToken(readOnly: boolean = false): Promise { const url = configContext.BACKEND_ENDPOINT + "/api/tokens/generateToken" + AuthHeadersUtil._generateResourceUrl(); - const explorer = window.dataExplorer; const headers: any = { authorization: userContext.authorizationToken }; - headers[Constants.HttpHeaders.getReadOnlyKey] = !explorer.hasWriteAccess(); + headers[Constants.HttpHeaders.getReadOnlyKey] = readOnly; - return AuthHeadersUtil._initiateGenerateTokenRequest({ - url: url, - type: "POST", - headers: headers, - contentType: "application/json", - cache: false, - }); - } - - public static generateUnauthenticatedEncryptedTokenForConnectionString( - connectionString: string - ): Q.Promise { - if (!connectionString) { - return Q.reject("None or empty connection string specified"); - } - - const url = configContext.BACKEND_ENDPOINT + "/api/guest/tokens/generateToken"; - const headers: any = {}; - headers[Constants.HttpHeaders.connectionString] = connectionString; - - return AuthHeadersUtil._initiateGenerateTokenRequest({ - url: url, - type: "POST", - headers: headers, - contentType: "application/json", - cache: false, - }); + const response = await fetch(url, { method: "POST", headers }); + const result = await response.json(); + // This API has a quirk where the response must be parsed to JSON twice + return JSON.parse(result); } private static _generateResourceUrl(): string { @@ -56,25 +32,4 @@ export default class AuthHeadersUtil { const rtype = ""; return `?resourceUrl=${resourceUrl}&rid=${rid}&rtype=${rtype}&sid=${sid}&rg=${rg}&dba=${dba}&api=${apiKind}`; } - - private static _initiateGenerateTokenRequest( - requestSettings: JQueryAjaxSettings - ): Q.Promise { - const deferred: Q.Deferred = Q.defer(); - - $.ajax(requestSettings).then( - (data: string, textStatus: string, xhr: JQueryXHR) => { - if (!data) { - deferred.reject("No token generated"); - } - - deferred.resolve(JSON.parse(data)); - }, - (xhr: JQueryXHR, textStatus: string, error: any) => { - deferred.reject(xhr.responseText); - } - ); - - return deferred.promise.timeout(Constants.ClientDefaults.requestTimeoutMs); - } } diff --git a/src/hooks/useFullScreenURLs.tsx b/src/hooks/useFullScreenURLs.tsx new file mode 100644 index 000000000..a83cd859a --- /dev/null +++ b/src/hooks/useFullScreenURLs.tsx @@ -0,0 +1,18 @@ +import { useEffect, useState } from "react"; +import { GenerateTokenResponse } from "../Contracts/DataModels"; +import AuthHeadersUtil from "../Platform/Hosted/Authorization"; + +export function useFullScreenURLs(): GenerateTokenResponse | undefined { + const [state, setState] = useState(); + + useEffect(() => { + Promise.all([AuthHeadersUtil.generateEncryptedToken(), AuthHeadersUtil.generateEncryptedToken(true)]).then( + ([readWriteResponse, readOnlyResponse]) => + setState({ + readWrite: readWriteResponse.readWrite, + read: readOnlyResponse.read, + }) + ); + }, []); + return state; +}