From 7c0aae6ffa171e44980c27023a85aeab684b16dc Mon Sep 17 00:00:00 2001 From: JustinKol <144163838+JustinKol@users.noreply.github.com> Date: Mon, 12 May 2025 10:55:06 -0400 Subject: [PATCH] Open VS Code via Data Explorer button (#2116) * master pull * Reverting .npmrc file * Removed logging userContext * Prettier run * Added support for opening CosmosDB Account without clicking database tab * Reverting change in settings.json * Prettier run * Added check if the link closed * Added check if the link didn't closed * Check if VS Code was opened, if not popup with download button link * Prettier run * Redirect to Download VS Code if not opened * Added error message to VS Code timeout and redirect * Fixing baseUrl from testing * Increased timeout for when user is asked to open VS Code * switched to iframe for redirects * Fixed VS Code url * Removed insider url * Added log messages * Added link to vCore data explorer dashboard * Increased timeout to 2.5 secs to see if that helps with VS Code open popup * Changed to dialog box * Changed param name * Increase startTime for extra popup * Changed to dialog box only when no VS Code detected * Fixed vscode url * Changed title back to Open CosmosDB in VS Code * Added text on required extensions * Removed text on required extensions as it will prompt by default * Fixed wording and Primary Button timeout * Spelled out VS Code * Removed console log of timeout * Updated snapshots and lowered timeout * Remove VS Code button from Gremlin * Prettier run on CommandBarComponentButtonFactory * Changed from referencing location to a link * Prettier run * Reverting back to popup for opening * Updated unit test snapshots * Added vscode: to Content Security Policy * Reverting back to popup only if opening times out --- images/vscode.svg | 1 + src/Explorer/Explorer.tsx | 63 +++++++++++++++++++ .../CommandBarComponentButtonFactory.tsx | 21 ++++++- src/Shared/Telemetry/TelemetryConstants.ts | 1 + web.config | 2 +- 5 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 images/vscode.svg diff --git a/images/vscode.svg b/images/vscode.svg new file mode 100644 index 000000000..137be57f0 --- /dev/null +++ b/images/vscode.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Explorer/Explorer.tsx b/src/Explorer/Explorer.tsx index a07a7dad3..5c619663f 100644 --- a/src/Explorer/Explorer.tsx +++ b/src/Explorer/Explorer.tsx @@ -282,6 +282,69 @@ export default class Explorer { } } + public openInVsCode(): void { + TelemetryProcessor.traceStart(Action.OpenVSCode); + this.openVsCodeButtonClick(); + } + + private openVsCodeButtonClick(): void { + const activeTab = useTabs.getState().activeTab; + const resourceId = encodeURIComponent(userContext.databaseAccount.id); + const database = encodeURIComponent(activeTab?.collection?.databaseId); + const container = encodeURIComponent(activeTab?.collection?.id()); + const baseUrl = `vscod://ms-azuretools.vscode-cosmosdb?resourceId=${resourceId}`; + const vscodeUrl = activeTab ? `${baseUrl}&database=${database}&container=${container}` : baseUrl; + const startTime = Date.now(); + let vsCodeNotOpened = false; + + setTimeout(() => { + const timeOutTime = Date.now() - startTime; + if (!vsCodeNotOpened && timeOutTime < 1050) { + vsCodeNotOpened = true; + useDialog.getState().openDialog(openVSCodeDialogProps); + } + }, 1000); + + const link = document.createElement("a"); + link.href = vscodeUrl; + link.rel = "noopener noreferrer"; + document.body.appendChild(link); + + try { + link.click(); + document.body.removeChild(link); + TelemetryProcessor.traceStart(Action.OpenVSCode); + } catch (error) { + if (!vsCodeNotOpened) { + vsCodeNotOpened = true; + logConsoleError(`Failed to open VS Code: ${getErrorMessage(error)}`); + } + } + + const openVSCodeDialogProps: DialogProps = { + linkProps: { + linkText: "Download Visual Studio Code", + linkUrl: "https://code.visualstudio.com/download", + }, + isModal: true, + title: `Open your Azure Cosmos DB account in Visual Studio Code`, + subText: `Please ensure Visual Studio Code is installed on your device. + If you don't have it installed, please download it from the link below.`, + primaryButtonText: "Open in VS Code", + secondaryButtonText: "Cancel", + + onPrimaryButtonClick: () => { + vsCodeNotOpened = false; + this.openVsCodeButtonClick(); + useDialog.getState().closeDialog(); + }, + onSecondaryButtonClick: () => { + useDialog.getState().closeDialog(); + TelemetryProcessor.traceCancel(Action.OpenVSCode); + }, + }; + } + public async openCESCVAFeedbackBlade(): Promise { sendMessage({ type: MessageTypes.OpenCESCVAFeedbackBlade }); Logger.logInfo( diff --git a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx index f9aa5e54f..fbf1b4434 100644 --- a/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx +++ b/src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.tsx @@ -14,6 +14,7 @@ import OpenQueryFromDiskIcon from "../../../../images/OpenQueryFromDisk.svg"; import OpenInTabIcon from "../../../../images/open-in-tab.svg"; import SettingsIcon from "../../../../images/settings_15x15.svg"; import SynapseIcon from "../../../../images/synapse-link.svg"; +import VSCodeIcon from "../../../../images/vscode.svg"; import { AuthType } from "../../../AuthType"; import * as Constants from "../../../Common/Constants"; import { Platform, configContext } from "../../../ConfigContext"; @@ -60,6 +61,10 @@ export function createStaticCommandBarButtons( addDivider(); buttons.push(addSynapseLink); } + if (userContext.apiType !== "Gremlin") { + const addVsCode = createOpenVsCodeDialogButton(container); + buttons.push(addVsCode); + } } if (isDataplaneRbacSupported(userContext.apiType)) { @@ -268,6 +273,18 @@ function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonCo }; } +function createOpenVsCodeDialogButton(container: Explorer): CommandButtonComponentProps { + const label = "Visual Studio Code"; + return { + iconSrc: VSCodeIcon, + iconAlt: label, + onCommandClick: () => container.openInVsCode(), + commandButtonLabel: label, + hasPopup: false, + ariaLabel: label, + }; +} + function createLoginForEntraIDButton(container: Explorer): CommandButtonComponentProps { if (configContext.platform !== Platform.Portal) { return undefined; @@ -500,6 +517,6 @@ export function createPostgreButtons(container: Explorer): CommandButtonComponen export function createVCoreMongoButtons(container: Explorer): CommandButtonComponentProps[] { const openVCoreMongoTerminalButton = createOpenTerminalButtonByKind(container, ViewModels.TerminalKind.VCoreMongo); - - return [openVCoreMongoTerminalButton]; + const addVsCode = createOpenVsCodeDialogButton(container); + return [openVCoreMongoTerminalButton, addVsCode]; } diff --git a/src/Shared/Telemetry/TelemetryConstants.ts b/src/Shared/Telemetry/TelemetryConstants.ts index c20a49f93..031d061cc 100644 --- a/src/Shared/Telemetry/TelemetryConstants.ts +++ b/src/Shared/Telemetry/TelemetryConstants.ts @@ -149,6 +149,7 @@ export enum Action { UploadDocuments, // Used in Fabric. Please do not rename. CloudShellUserConsent, CloudShellTerminalSession, + OpenVSCode, } export const ActionModifiers = { diff --git a/web.config b/web.config index 8efe7ce6d..f29c8b878 100644 --- a/web.config +++ b/web.config @@ -30,7 +30,7 @@ - +