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 @@
-
+