From 5948f69548f45b7a129db09b081ba9cf8df3813b Mon Sep 17 00:00:00 2001 From: frye-t Date: Fri, 26 Apr 2024 12:49:16 -0400 Subject: [PATCH 1/2] feat:add sidebar links to open dashboard for stacks and runs Added a globe icon that will open the ZenML Dashboard in the Browser for the corresponding Pipeline Run / Stack. Added test for the stack sidebar link command, but not for the pipeline as there is no pipeline test file --- package.json | 24 +- package.nls.json | 425 ++++++++++++++++-- src/commands/pipelines/cmds.ts | 23 + src/commands/pipelines/registry.ts | 3 + src/commands/pipelines/utils.ts | 36 ++ src/commands/stack/cmds.ts | 24 +- src/commands/stack/registry.ts | 4 + src/commands/stack/utils.ts | 19 + .../ts_tests/commands/stackCommands.test.ts | 21 + 9 files changed, 544 insertions(+), 35 deletions(-) create mode 100644 src/commands/pipelines/utils.ts diff --git a/package.json b/package.json index bcc32f6b..c96a06d1 100644 --- a/package.json +++ b/package.json @@ -199,6 +199,12 @@ "icon": "$(copy)", "category": "ZenML" }, + { + "command": "zenml.goToStackUrl", + "title": "Go to URL", + "icon": "$(globe)", + "category": "ZenML" + }, { "command": "zenml.setPipelineRunsPerPage", "title": "Set Runs Per Page", @@ -217,6 +223,12 @@ "icon": "$(trash)", "category": "ZenML Pipeline Runs" }, + { + "command": "zenml.goToPipelineUrl", + "title": "Go to URL", + "icon": "$(globe)", + "category": "ZenML Pipeline Runs" + }, { "command": "zenml.refreshEnvironmentView", "title": "Refresh Environment View", @@ -328,10 +340,20 @@ "command": "zenml.copyStack", "group": "inline@3" }, + { + "when": "stackCommandsRegistered && view == zenmlStackView && viewItem == stack", + "command": "zenml.goToStackUrl", + "group": "inline@4" + }, { "when": "pipelineCommandsRegistered && view == zenmlPipelineView && viewItem == pipelineRun", "command": "zenml.deletePipelineRun", - "group": "inline" + "group": "inline@1" + }, + { + "when": "pipelineCommandsRegistered && view == zenmlPipelineView && viewItem == pipelineRun", + "command": "zenml.goToPipelineUrl", + "group": "inline@2" }, { "when": "environmentCommandsRegistered && view == zenmlEnvironmentView && viewItem == interpreter", diff --git a/package.nls.json b/package.nls.json index 99e0ed37..c96a06d1 100644 --- a/package.nls.json +++ b/package.nls.json @@ -1,35 +1,394 @@ { - "extension.description": "Integrates ZenML directly into VS Code, enhancing machine learning workflow with support for pipelines, stacks, and server management.", - "command.promptForInterpreter": "Prompt to select a Python interpreter from the command palette in VSCode.", - "command.connectServer": "Establishes a connection to a specified ZenML server.", - "command.disconnectServer": "Disconnects from the currently connected ZenML server.", - "command.refreshServerStatus": "Refreshes the status of the ZenML server to reflect the current state.", - "command.refreshStackView": "Refreshes the stack view to display the latest information about ZenML stacks.", - "command.setStackItemsPerPage": "Sets the number of stacks to display per page in the stack view.", - "command.nextStackPage": "Navigates to the next page of stacks in the stack view.", - "command.previousStackPage": "Navigates to the previous page of stacks in the stack view.", - "command.getActiveStack": "Retrieves the currently active stack in ZenML.", - "command.renameStack": "Renames a specified ZenML stack.", - "command.setActiveStack": "Sets a specified stack as the active ZenML stack.", - "command.copyStack": "Creates a copy of a specified ZenML stack.", - "command.refreshPipelineView": "Refreshes the pipeline view to display the latest information on ZenML pipeline runs.", - "command.setPipelineRunsPerPage": "Sets the number of pipeline runs to display per page in the pipeline view.", - "command.nextPipelineRunsPage": "Navigates to the next page of pipeline runs in the pipeline view.", - "command.previousPipelineRunsPage": "Navigates to the previous page of pipeline runs in the pipeline view.", - "command.deletePipelineRun": "Deletes a specified ZenML pipeline run.", - "command.setPythonInterpreter": "Sets the Python interpreter for ZenML-related tasks within the VS Code environment.", - "command.refreshEnvironmentView": "Updates the Environment View with the latest ZenML configuration and system information.", - "command.restartLspServer": "Restarts the Language Server to ensure the latest configurations are used.", - "settings.args.description": "Specify arguments to pass to the ZenML CLI. Provide each argument as a separate item in the array.", - "settings.path.description": "Defines the path to the ZenML CLI. If left as an empty array, the default system path will be used.", - "settings.importStrategy.description": "Determines which ZenML CLI to use. Options include using a bundled version (`useBundled`) or attempting to use the CLI installed in the current Python environment (`fromEnvironment`).", - "settings.showNotifications.description": "Controls when notifications are shown by this extension.", - "settings.showNotifications.off.description": "All notifications are turned off, any errors or warnings when formatting Python files are still available in the logs.", - "settings.showNotifications.onError.description": "Notifications are shown only in the case of an error when formatting Python files.", - "settings.showNotifications.onWarning.description": "Notifications are shown for any errors and warnings when formatting Python files.", - "settings.showNotifications.always.description": "Notifications are show for anything that the server chooses to show when formatting Python files.", - "settings.interpreter.description": "Sets the path to the Python interpreter used by ZenML. This is used for launching the server and subprocesses. Leave empty to use the default interpreter.", - "settings.serverUrl.description": "URL to connect to the ZenML server.", - "settings.accessToken.description": "Access token required for authenticating with the ZenML server (not necessary currently).", - "settings.activeStackId.description": "Identifier for the currently active stack in ZenML. This is used to specify which stack is being used for operations." + "name": "zenml-vscode", + "publisher": "ZenML", + "displayName": "ZenML Studio", + "description": "Integrates ZenML directly into VS Code, enhancing machine learning workflow with support for pipelines, stacks, and server management.", + "version": "0.0.4", + "icon": "resources/extension-logo.png", + "preview": true, + "license": "Apache-2.0", + "categories": [ + "Machine Learning", + "Visualization" + ], + "repository": { + "type": "git", + "url": "https://github.com/zenml-io/vscode-zenml" + }, + "keywords": [ + "zenml", + "ml", + "machine learning", + "mlops", + "stack management", + "pipeline management", + "development tools" + ], + "engines": { + "vscode": "^1.86.0" + }, + "activationEvents": [ + "onStartupFinished", + "onLanguage:python", + "workspaceContains:*.py" + ], + "extensionDependencies": [ + "ms-python.python" + ], + "capabilities": { + "virtualWorkspaces": { + "supported": false, + "description": "Virtual Workspaces are not supported with ." + } + }, + "main": "./dist/extension.js", + "serverInfo": { + "name": "ZenML", + "module": "zenml-python" + }, + "scripts": { + "vscode:prepublish": "npm run package", + "compile": "webpack", + "watch": "webpack --watch", + "package": "webpack --mode production --devtool source-map --config ./webpack.config.js", + "compile-tests": "tsc -p . --outDir out", + "watch-tests": "tsc -p . -w --outDir out", + "pretest": "npm run compile-tests && npm run compile && npm run lint", + "format": "prettier --ignore-path .gitignore --write \"**/*.+(ts|json)\"", + "lint": "eslint src --ext ts", + "test": "vscode-test", + "vsce-package": "vsce package -o zenml.vsix" + }, + "contributes": { + "configuration": { + "title": "ZenML", + "properties": { + "zenml-python.args": { + "default": [], + "description": "Arguments passed in. Each argument is a separate item in the array.", + "items": { + "type": "string" + }, + "scope": "resource", + "type": "array" + }, + "zenml-python.path": { + "default": [], + "scope": "resource", + "items": { + "type": "string" + }, + "type": "array" + }, + "zenml-python.importStrategy": { + "default": "useBundled", + "enum": [ + "useBundled", + "fromEnvironment" + ], + "enumDescriptions": [ + "Always use the bundled version of ``.", + "Use `` from environment, fallback to bundled version only if `` not available in the environment." + ], + "scope": "window", + "type": "string" + }, + "zenml-python.interpreter": { + "default": [], + "description": "When set to a path to python executable, extension will use that to launch the server and any subprocess.", + "scope": "resource", + "items": { + "type": "string" + }, + "type": "array" + }, + "zenml-python.showNotifications": { + "default": "off", + "description": "Controls when notifications are shown by this extension.", + "enum": [ + "off", + "onError", + "onWarning", + "always" + ], + "enumDescriptions": [ + "All notifications are turned off, any errors or warning are still available in the logs.", + "Notifications are shown only in the case of an error.", + "Notifications are shown for errors and warnings.", + "Notifications are show for anything that the server chooses to show." + ], + "scope": "machine", + "type": "string" + }, + "zenml.serverUrl": { + "type": "string", + "default": "", + "description": "ZenML Server URL" + }, + "zenml.accessToken": { + "type": "string", + "default": "", + "description": "Access token for the ZenML server" + }, + "zenml.activeStackId": { + "type": "string", + "default": "", + "description": "Active stack id for the ZenML server" + } + } + }, + "commands": [ + { + "command": "zenml.promptForInterpreter", + "title": "Select Python Interpreter", + "category": "ZenML" + }, + { + "command": "zenml-python.restart", + "title": "Restart LSP Server", + "category": "ZenML" + }, + { + "command": "zenml.connectServer", + "title": "Connect", + "category": "ZenML Server" + }, + { + "command": "zenml.disconnectServer", + "title": "Disconnect", + "category": "ZenML Server" + }, + { + "command": "zenml.refreshServerStatus", + "title": "Refresh Server Status", + "icon": "$(refresh)", + "category": "ZenML Server" + }, + { + "command": "zenml.setStackItemsPerPage", + "title": "Set Stacks Per Page", + "icon": "$(layers)", + "category": "ZenML Stacks" + }, + { + "command": "zenml.refreshStackView", + "title": "Refresh Stack View", + "icon": "$(refresh)", + "category": "ZenML Stacks" + }, + { + "command": "zenml.getActiveStack", + "title": "Get Active Stack", + "category": "ZenML Stacks" + }, + { + "command": "zenml.renameStack", + "title": "Rename Stack", + "icon": "$(edit)", + "category": "ZenML Stacks" + }, + { + "command": "zenml.setActiveStack", + "title": "Set Active Stack", + "icon": "$(check)", + "category": "ZenML Stacks" + }, + { + "command": "zenml.copyStack", + "title": "Copy Stack", + "icon": "$(copy)", + "category": "ZenML" + }, + { + "command": "zenml.goToStackUrl", + "title": "Go to URL", + "icon": "$(globe)", + "category": "ZenML" + }, + { + "command": "zenml.setPipelineRunsPerPage", + "title": "Set Runs Per Page", + "icon": "$(layers)", + "category": "ZenML Pipeline Runs" + }, + { + "command": "zenml.refreshPipelineView", + "title": "Refresh Pipeline View", + "icon": "$(refresh)", + "category": "ZenML Pipeline Runs" + }, + { + "command": "zenml.deletePipelineRun", + "title": "Delete Pipeline Run", + "icon": "$(trash)", + "category": "ZenML Pipeline Runs" + }, + { + "command": "zenml.goToPipelineUrl", + "title": "Go to URL", + "icon": "$(globe)", + "category": "ZenML Pipeline Runs" + }, + { + "command": "zenml.refreshEnvironmentView", + "title": "Refresh Environment View", + "icon": "$(refresh)", + "category": "ZenML Environment" + }, + { + "command": "zenml.setPythonInterpreter", + "title": "Switch Python Interpreter", + "icon": "$(arrow-swap)", + "category": "ZenML Environment" + }, + { + "command": "zenml.restartLspServer", + "title": "Restart LSP Server", + "icon": "$(debug-restart)", + "category": "ZenML Environment" + } + ], + "viewsContainers": { + "activitybar": [ + { + "id": "zenml", + "title": "ZenML", + "icon": "resources/logo.png" + } + ] + }, + "views": { + "zenml": [ + { + "id": "zenmlServerView", + "name": "Server", + "icon": "$(vm)" + }, + { + "id": "zenmlStackView", + "name": "Stacks", + "icon": "$(layers)" + }, + { + "id": "zenmlPipelineView", + "name": "Pipeline Runs", + "icon": "$(beaker)" + }, + { + "id": "zenmlEnvironmentView", + "name": "Environment", + "icon": "$(server-environment)" + } + ] + }, + "menus": { + "view/title": [ + { + "when": "serverCommandsRegistered && view == zenmlServerView", + "command": "zenml.connectServer", + "group": "navigation" + }, + { + "when": "serverCommandsRegistered && view == zenmlServerView", + "command": "zenml.disconnectServer", + "group": "navigation" + }, + { + "when": "serverCommandsRegistered && view == zenmlServerView", + "command": "zenml.refreshServerStatus", + "group": "navigation" + }, + { + "when": "stackCommandsRegistered && view == zenmlStackView", + "command": "zenml.setStackItemsPerPage", + "group": "navigation@1" + }, + { + "when": "stackCommandsRegistered && view == zenmlStackView", + "command": "zenml.refreshStackView", + "group": "navigation@2" + }, + { + "when": "pipelineCommandsRegistered && view == zenmlPipelineView", + "command": "zenml.setPipelineRunsPerPage", + "group": "navigation@1" + }, + { + "when": "pipelineCommandsRegistered && view == zenmlPipelineView", + "command": "zenml.refreshPipelineView", + "group": "navigation@2" + }, + { + "when": "environmentCommandsRegistered && view == zenmlEnvironmentView", + "command": "zenml.restartLspServer", + "group": "navigation" + } + ], + "view/item/context": [ + { + "when": "stackCommandsRegistered && view == zenmlStackView && viewItem == stack", + "command": "zenml.setActiveStack", + "group": "inline@1" + }, + { + "when": "stackCommandsRegistered && view == zenmlStackView && viewItem == stack", + "command": "zenml.renameStack", + "group": "inline@2" + }, + { + "when": "stackCommandsRegistered && view == zenmlStackView && viewItem == stack", + "command": "zenml.copyStack", + "group": "inline@3" + }, + { + "when": "stackCommandsRegistered && view == zenmlStackView && viewItem == stack", + "command": "zenml.goToStackUrl", + "group": "inline@4" + }, + { + "when": "pipelineCommandsRegistered && view == zenmlPipelineView && viewItem == pipelineRun", + "command": "zenml.deletePipelineRun", + "group": "inline@1" + }, + { + "when": "pipelineCommandsRegistered && view == zenmlPipelineView && viewItem == pipelineRun", + "command": "zenml.goToPipelineUrl", + "group": "inline@2" + }, + { + "when": "environmentCommandsRegistered && view == zenmlEnvironmentView && viewItem == interpreter", + "command": "zenml.setPythonInterpreter", + "group": "inline" + } + ] + } + }, + "devDependencies": { + "@types/fs-extra": "^11.0.4", + "@types/mocha": "^10.0.6", + "@types/node": "^18.19.18", + "@types/sinon": "^17.0.3", + "@types/vscode": "^1.86.0", + "@types/webpack": "^5.28.5", + "@typescript-eslint/eslint-plugin": "^6.19.1", + "@typescript-eslint/parser": "^6.19.1", + "@vscode/test-cli": "^0.0.4", + "@vscode/test-electron": "^2.3.9", + "@vscode/vsce": "^2.24.0", + "eslint": "^8.56.0", + "eslint-config-prettier": "^9.1.0", + "prettier": "^3.2.5", + "sinon": "^17.0.1", + "ts-loader": "^9.5.1", + "ts-node": "^10.9.2", + "typescript": "^5.3.3", + "webpack": "^5.90.0", + "webpack-cli": "^5.1.4" + }, + "dependencies": { + "@vscode/python-extension": "^1.0.5", + "axios": "^1.6.7", + "fs-extra": "^11.2.0", + "vscode-languageclient": "^9.0.1" + } } diff --git a/src/commands/pipelines/cmds.ts b/src/commands/pipelines/cmds.ts index 92e3c4ba..843f7584 100644 --- a/src/commands/pipelines/cmds.ts +++ b/src/commands/pipelines/cmds.ts @@ -15,6 +15,7 @@ import { showErrorMessage, showInformationMessage } from '../../utils/notificati import { PipelineTreeItem } from '../../views/activityBar'; import { PipelineDataProvider } from '../../views/activityBar/pipelineView/PipelineDataProvider'; import * as vscode from 'vscode'; +import { getPipelineRunDashboardUrl } from './utils'; /** * Triggers a refresh of the pipeline view within the UI components. @@ -72,7 +73,29 @@ const deletePipelineRun = async (node: PipelineTreeItem): Promise => { } }; +/** + * Opens the selected pipieline run in the ZenML Dashboard in the browser + * + * @param {PipelineTreeItem} node The pipeline run to open. + */ +const goToPipelineUrl = (node: PipelineTreeItem): void => { + const url = getPipelineRunDashboardUrl(node.id); + + if (url) { + try { + const parsedUrl = vscode.Uri.parse(url); + + vscode.env.openExternal(parsedUrl); + vscode.window.showInformationMessage(`Opening: ${url}`); + } catch (error) { + console.log(error); + vscode.window.showErrorMessage(`Failed to open pipeline run URL: ${error}`); + } + } +}; + export const pipelineCommands = { refreshPipelineView, deletePipelineRun, + goToPipelineUrl, }; diff --git a/src/commands/pipelines/registry.ts b/src/commands/pipelines/registry.ts index fbbfb80d..48218aa9 100644 --- a/src/commands/pipelines/registry.ts +++ b/src/commands/pipelines/registry.ts @@ -34,6 +34,9 @@ export const registerPipelineCommands = (context: ExtensionContext) => { 'zenml.deletePipelineRun', async (node: PipelineTreeItem) => await pipelineCommands.deletePipelineRun(node) ), + registerCommand( + 'zenml.goToPipelineUrl', + async (node:PipelineTreeItem) => await pipelineCommands.goToPipelineUrl(node)), registerCommand('zenml.nextPipelineRunsPage', async () => pipelineDataProvider.goToNextPage() ), diff --git a/src/commands/pipelines/utils.ts b/src/commands/pipelines/utils.ts new file mode 100644 index 00000000..bf20a2e4 --- /dev/null +++ b/src/commands/pipelines/utils.ts @@ -0,0 +1,36 @@ +// Copyright(c) ZenML GmbH 2024. All Rights Reserved. +// Licensed under the Apache License, Version 2.0(the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +// or implied.See the License for the specific language governing +// permissions and limitations under the License. +import { getZenMLServerUrl } from '../../utils/global'; + +/** + * Gets the Dashboard URL for the corresponding ZenML pipeline run + * + * @param {string} id - The id of the ZenML pipeline run to be opened + * @returns {string} - The URL corresponding to the pipeline run in the ZenML Dashboard + */ +export const getPipelineRunDashboardUrl = (id: string): string => { + const PIPELINE_URL_STUB = "SERVER_URL/workspaces/default/all-runs/PIPELINE_ID/dag"; + const currentServerUrl = getZenMLServerUrl(); + + const pipelineUrl = PIPELINE_URL_STUB + .replace("SERVER_URL", currentServerUrl) + .replace("PIPELINE_ID", id); + + return pipelineUrl; +}; + +const pipelineUtils = { + getPipelineRunDashboardUrl, +}; + +export default pipelineUtils; \ No newline at end of file diff --git a/src/commands/stack/cmds.ts b/src/commands/stack/cmds.ts index b76b11dd..06277d05 100644 --- a/src/commands/stack/cmds.ts +++ b/src/commands/stack/cmds.ts @@ -13,7 +13,7 @@ import * as vscode from 'vscode'; import { StackDataProvider, StackTreeItem } from '../../views/activityBar'; import ZenMLStatusBar from '../../views/statusBar'; -import { switchActiveStack } from './utils'; +import { getStackDashboardUrl, switchActiveStack } from './utils'; import { LSClient } from '../../services/LSClient'; import { showInformationMessage } from '../../utils/notifications'; @@ -157,10 +157,32 @@ const setActiveStack = async (node: StackTreeItem): Promise => { ); }; +/** + * Opens the selected stack in the ZenML Dashboard in the browser + * + * @param {StackTreeItem} node The stack to open. + */ +const goToStackUrl = (node: StackTreeItem) => { + const url = getStackDashboardUrl(node.id); + + if (url) { + try { + const parsedUrl = vscode.Uri.parse(url); + + vscode.env.openExternal(parsedUrl); + vscode.window.showInformationMessage(`Opening: ${url}`); + } catch (error) { + console.log(error); + vscode.window.showErrorMessage(`Failed to open stack URL: ${error}`); + } + } +}; + export const stackCommands = { refreshStackView, refreshActiveStack, renameStack, copyStack, setActiveStack, + goToStackUrl }; diff --git a/src/commands/stack/registry.ts b/src/commands/stack/registry.ts index b7626a64..f22ec49d 100644 --- a/src/commands/stack/registry.ts +++ b/src/commands/stack/registry.ts @@ -15,6 +15,7 @@ import { stackCommands } from './cmds'; import { registerCommand } from '../../common/vscodeapi'; import { ZenExtension } from '../../services/ZenExtension'; import { ExtensionContext, commands, window } from 'vscode'; +import { node } from 'webpack'; /** * Registers stack-related commands for the extension. @@ -42,6 +43,9 @@ export const registerStackCommands = (context: ExtensionContext) => { 'zenml.setActiveStack', async (node: StackTreeItem) => await stackCommands.setActiveStack(node) ), + registerCommand( + 'zenml.goToStackUrl', + async (node: StackTreeItem) => await stackCommands.goToStackUrl(node)), registerCommand( 'zenml.copyStack', async (node: StackTreeItem) => await stackCommands.copyStack(node) diff --git a/src/commands/stack/utils.ts b/src/commands/stack/utils.ts index 95af943d..24612675 100644 --- a/src/commands/stack/utils.ts +++ b/src/commands/stack/utils.ts @@ -14,6 +14,7 @@ import * as vscode from 'vscode'; import { LSClient } from '../../services/LSClient'; import { GetActiveStackResponse, SetActiveStackResponse } from '../../types/LSClientResponseTypes'; import { showErrorMessage } from '../../utils/notifications'; +import { getZenMLServerUrl } from '../../utils/global'; /** * Switches the active ZenML stack to the specified stack name. @@ -81,10 +82,28 @@ export const getActiveStackIdFromConfig = (): string | undefined => { return config.get('activeStackId'); }; +/** + * Gets the Dashboard URL for the corresponding ZenML stack + * + * @param {string} id - The id of the ZenML stack to be opened + * @returns {string} - The URL corresponding to the pipeline in the ZenML Dashboard + */ +export const getStackDashboardUrl = (id: string): string => { + const STACK_URL_STUB = "SERVER_URL/workspaces/default/stacks/STACK_ID/configuration"; + const currentServerUrl = getZenMLServerUrl(); + + const stackUrl = STACK_URL_STUB + .replace("SERVER_URL", currentServerUrl) + .replace("STACK_ID", id); + + return stackUrl; +}; + const stackUtils = { switchActiveStack, getActiveStack, storeActiveStack, + getStackDashboardUrl, }; export default stackUtils; diff --git a/src/test/ts_tests/commands/stackCommands.test.ts b/src/test/ts_tests/commands/stackCommands.test.ts index 1da40c6b..815edc31 100644 --- a/src/test/ts_tests/commands/stackCommands.test.ts +++ b/src/test/ts_tests/commands/stackCommands.test.ts @@ -20,6 +20,7 @@ import { MockEventBus } from '../__mocks__/MockEventBus'; import { MockLSClient } from '../__mocks__/MockLSClient'; import { LSClient } from '../../../services/LSClient'; import { EventBus } from '../../../services/EventBus'; +import * as globalUtils from '../../../utils/global'; import stackUtils from '../../../commands/stack/utils'; import { MockStackDataProvider, MockZenMLStatusBar } from '../__mocks__/MockViewProviders'; @@ -40,6 +41,7 @@ suite('Stack Commands Test Suite', () => { mockLSClient = new MockLSClient(mockEventBus); mockStackDataProvider = new MockStackDataProvider(); mockStatusBar = new MockZenMLStatusBar(); + const stubbedServerUrl = 'http://mocked-server.com'; // Stub classes to return mock instances sandbox.stub(StackDataProvider, 'getInstance').returns(mockStackDataProvider); @@ -47,6 +49,7 @@ suite('Stack Commands Test Suite', () => { sandbox.stub(LSClient, 'getInstance').returns(mockLSClient); sandbox.stub(EventBus, 'getInstance').returns(mockEventBus); sandbox.stub(stackUtils, 'storeActiveStack').resolves(); + sandbox.stub(globalUtils, 'getZenMLServerUrl').returns(stubbedServerUrl); showInputBoxStub = sandbox.stub(vscode.window, 'showInputBox'); showInformationMessageStub = sandbox.stub(vscode.window, 'showInformationMessage'); @@ -106,6 +109,24 @@ suite('Stack Commands Test Suite', () => { ); }); + test('goToStackUrl opens the correct URL and shows an information message', () => { + const stackId = 'stack-id-123'; + const expectedUrl = stackUtils.getStackDashboardUrl(stackId); + + const openExternalStub = sandbox.stub(vscode.env, 'openExternal'); + + stackCommands.goToStackUrl({ label: 'Stack', id: stackId } as any); + + assert.strictEqual(openExternalStub.calledOnce, true, 'openExternal should be called once'); + assert.strictEqual(openExternalStub.args[0][0].toString(), expectedUrl, 'Correct URL should be passed to openExternal'); + assert.strictEqual(showInformationMessageStub.calledOnce, true, 'showInformationMessage should be called once'); + assert.strictEqual( + showInformationMessageStub.args[0][0], + `Opening: ${expectedUrl}`, + 'Correct information message should be shown' + ); + }); + test('stackDataProviderMock.refresh can be called directly', async () => { await mockStackDataProvider.refresh(); sinon.assert.calledOnce(mockStackDataProvider.refresh); From 4d3d986cfb7af803c212b0207fe8e49b8746a914 Mon Sep 17 00:00:00 2001 From: frye-t Date: Fri, 26 Apr 2024 12:56:31 -0400 Subject: [PATCH 2/2] Fixed package.nls.json error --- package.nls.json | 427 ++++------------------------------------------- 1 file changed, 35 insertions(+), 392 deletions(-) diff --git a/package.nls.json b/package.nls.json index c96a06d1..426b804f 100644 --- a/package.nls.json +++ b/package.nls.json @@ -1,394 +1,37 @@ { - "name": "zenml-vscode", - "publisher": "ZenML", - "displayName": "ZenML Studio", - "description": "Integrates ZenML directly into VS Code, enhancing machine learning workflow with support for pipelines, stacks, and server management.", - "version": "0.0.4", - "icon": "resources/extension-logo.png", - "preview": true, - "license": "Apache-2.0", - "categories": [ - "Machine Learning", - "Visualization" - ], - "repository": { - "type": "git", - "url": "https://github.com/zenml-io/vscode-zenml" - }, - "keywords": [ - "zenml", - "ml", - "machine learning", - "mlops", - "stack management", - "pipeline management", - "development tools" - ], - "engines": { - "vscode": "^1.86.0" - }, - "activationEvents": [ - "onStartupFinished", - "onLanguage:python", - "workspaceContains:*.py" - ], - "extensionDependencies": [ - "ms-python.python" - ], - "capabilities": { - "virtualWorkspaces": { - "supported": false, - "description": "Virtual Workspaces are not supported with ." - } - }, - "main": "./dist/extension.js", - "serverInfo": { - "name": "ZenML", - "module": "zenml-python" - }, - "scripts": { - "vscode:prepublish": "npm run package", - "compile": "webpack", - "watch": "webpack --watch", - "package": "webpack --mode production --devtool source-map --config ./webpack.config.js", - "compile-tests": "tsc -p . --outDir out", - "watch-tests": "tsc -p . -w --outDir out", - "pretest": "npm run compile-tests && npm run compile && npm run lint", - "format": "prettier --ignore-path .gitignore --write \"**/*.+(ts|json)\"", - "lint": "eslint src --ext ts", - "test": "vscode-test", - "vsce-package": "vsce package -o zenml.vsix" - }, - "contributes": { - "configuration": { - "title": "ZenML", - "properties": { - "zenml-python.args": { - "default": [], - "description": "Arguments passed in. Each argument is a separate item in the array.", - "items": { - "type": "string" - }, - "scope": "resource", - "type": "array" - }, - "zenml-python.path": { - "default": [], - "scope": "resource", - "items": { - "type": "string" - }, - "type": "array" - }, - "zenml-python.importStrategy": { - "default": "useBundled", - "enum": [ - "useBundled", - "fromEnvironment" - ], - "enumDescriptions": [ - "Always use the bundled version of ``.", - "Use `` from environment, fallback to bundled version only if `` not available in the environment." - ], - "scope": "window", - "type": "string" - }, - "zenml-python.interpreter": { - "default": [], - "description": "When set to a path to python executable, extension will use that to launch the server and any subprocess.", - "scope": "resource", - "items": { - "type": "string" - }, - "type": "array" - }, - "zenml-python.showNotifications": { - "default": "off", - "description": "Controls when notifications are shown by this extension.", - "enum": [ - "off", - "onError", - "onWarning", - "always" - ], - "enumDescriptions": [ - "All notifications are turned off, any errors or warning are still available in the logs.", - "Notifications are shown only in the case of an error.", - "Notifications are shown for errors and warnings.", - "Notifications are show for anything that the server chooses to show." - ], - "scope": "machine", - "type": "string" - }, - "zenml.serverUrl": { - "type": "string", - "default": "", - "description": "ZenML Server URL" - }, - "zenml.accessToken": { - "type": "string", - "default": "", - "description": "Access token for the ZenML server" - }, - "zenml.activeStackId": { - "type": "string", - "default": "", - "description": "Active stack id for the ZenML server" - } - } - }, - "commands": [ - { - "command": "zenml.promptForInterpreter", - "title": "Select Python Interpreter", - "category": "ZenML" - }, - { - "command": "zenml-python.restart", - "title": "Restart LSP Server", - "category": "ZenML" - }, - { - "command": "zenml.connectServer", - "title": "Connect", - "category": "ZenML Server" - }, - { - "command": "zenml.disconnectServer", - "title": "Disconnect", - "category": "ZenML Server" - }, - { - "command": "zenml.refreshServerStatus", - "title": "Refresh Server Status", - "icon": "$(refresh)", - "category": "ZenML Server" - }, - { - "command": "zenml.setStackItemsPerPage", - "title": "Set Stacks Per Page", - "icon": "$(layers)", - "category": "ZenML Stacks" - }, - { - "command": "zenml.refreshStackView", - "title": "Refresh Stack View", - "icon": "$(refresh)", - "category": "ZenML Stacks" - }, - { - "command": "zenml.getActiveStack", - "title": "Get Active Stack", - "category": "ZenML Stacks" - }, - { - "command": "zenml.renameStack", - "title": "Rename Stack", - "icon": "$(edit)", - "category": "ZenML Stacks" - }, - { - "command": "zenml.setActiveStack", - "title": "Set Active Stack", - "icon": "$(check)", - "category": "ZenML Stacks" - }, - { - "command": "zenml.copyStack", - "title": "Copy Stack", - "icon": "$(copy)", - "category": "ZenML" - }, - { - "command": "zenml.goToStackUrl", - "title": "Go to URL", - "icon": "$(globe)", - "category": "ZenML" - }, - { - "command": "zenml.setPipelineRunsPerPage", - "title": "Set Runs Per Page", - "icon": "$(layers)", - "category": "ZenML Pipeline Runs" - }, - { - "command": "zenml.refreshPipelineView", - "title": "Refresh Pipeline View", - "icon": "$(refresh)", - "category": "ZenML Pipeline Runs" - }, - { - "command": "zenml.deletePipelineRun", - "title": "Delete Pipeline Run", - "icon": "$(trash)", - "category": "ZenML Pipeline Runs" - }, - { - "command": "zenml.goToPipelineUrl", - "title": "Go to URL", - "icon": "$(globe)", - "category": "ZenML Pipeline Runs" - }, - { - "command": "zenml.refreshEnvironmentView", - "title": "Refresh Environment View", - "icon": "$(refresh)", - "category": "ZenML Environment" - }, - { - "command": "zenml.setPythonInterpreter", - "title": "Switch Python Interpreter", - "icon": "$(arrow-swap)", - "category": "ZenML Environment" - }, - { - "command": "zenml.restartLspServer", - "title": "Restart LSP Server", - "icon": "$(debug-restart)", - "category": "ZenML Environment" - } - ], - "viewsContainers": { - "activitybar": [ - { - "id": "zenml", - "title": "ZenML", - "icon": "resources/logo.png" - } - ] - }, - "views": { - "zenml": [ - { - "id": "zenmlServerView", - "name": "Server", - "icon": "$(vm)" - }, - { - "id": "zenmlStackView", - "name": "Stacks", - "icon": "$(layers)" - }, - { - "id": "zenmlPipelineView", - "name": "Pipeline Runs", - "icon": "$(beaker)" - }, - { - "id": "zenmlEnvironmentView", - "name": "Environment", - "icon": "$(server-environment)" - } - ] - }, - "menus": { - "view/title": [ - { - "when": "serverCommandsRegistered && view == zenmlServerView", - "command": "zenml.connectServer", - "group": "navigation" - }, - { - "when": "serverCommandsRegistered && view == zenmlServerView", - "command": "zenml.disconnectServer", - "group": "navigation" - }, - { - "when": "serverCommandsRegistered && view == zenmlServerView", - "command": "zenml.refreshServerStatus", - "group": "navigation" - }, - { - "when": "stackCommandsRegistered && view == zenmlStackView", - "command": "zenml.setStackItemsPerPage", - "group": "navigation@1" - }, - { - "when": "stackCommandsRegistered && view == zenmlStackView", - "command": "zenml.refreshStackView", - "group": "navigation@2" - }, - { - "when": "pipelineCommandsRegistered && view == zenmlPipelineView", - "command": "zenml.setPipelineRunsPerPage", - "group": "navigation@1" - }, - { - "when": "pipelineCommandsRegistered && view == zenmlPipelineView", - "command": "zenml.refreshPipelineView", - "group": "navigation@2" - }, - { - "when": "environmentCommandsRegistered && view == zenmlEnvironmentView", - "command": "zenml.restartLspServer", - "group": "navigation" - } - ], - "view/item/context": [ - { - "when": "stackCommandsRegistered && view == zenmlStackView && viewItem == stack", - "command": "zenml.setActiveStack", - "group": "inline@1" - }, - { - "when": "stackCommandsRegistered && view == zenmlStackView && viewItem == stack", - "command": "zenml.renameStack", - "group": "inline@2" - }, - { - "when": "stackCommandsRegistered && view == zenmlStackView && viewItem == stack", - "command": "zenml.copyStack", - "group": "inline@3" - }, - { - "when": "stackCommandsRegistered && view == zenmlStackView && viewItem == stack", - "command": "zenml.goToStackUrl", - "group": "inline@4" - }, - { - "when": "pipelineCommandsRegistered && view == zenmlPipelineView && viewItem == pipelineRun", - "command": "zenml.deletePipelineRun", - "group": "inline@1" - }, - { - "when": "pipelineCommandsRegistered && view == zenmlPipelineView && viewItem == pipelineRun", - "command": "zenml.goToPipelineUrl", - "group": "inline@2" - }, - { - "when": "environmentCommandsRegistered && view == zenmlEnvironmentView && viewItem == interpreter", - "command": "zenml.setPythonInterpreter", - "group": "inline" - } - ] - } - }, - "devDependencies": { - "@types/fs-extra": "^11.0.4", - "@types/mocha": "^10.0.6", - "@types/node": "^18.19.18", - "@types/sinon": "^17.0.3", - "@types/vscode": "^1.86.0", - "@types/webpack": "^5.28.5", - "@typescript-eslint/eslint-plugin": "^6.19.1", - "@typescript-eslint/parser": "^6.19.1", - "@vscode/test-cli": "^0.0.4", - "@vscode/test-electron": "^2.3.9", - "@vscode/vsce": "^2.24.0", - "eslint": "^8.56.0", - "eslint-config-prettier": "^9.1.0", - "prettier": "^3.2.5", - "sinon": "^17.0.1", - "ts-loader": "^9.5.1", - "ts-node": "^10.9.2", - "typescript": "^5.3.3", - "webpack": "^5.90.0", - "webpack-cli": "^5.1.4" - }, - "dependencies": { - "@vscode/python-extension": "^1.0.5", - "axios": "^1.6.7", - "fs-extra": "^11.2.0", - "vscode-languageclient": "^9.0.1" - } + "extension.description": "Integrates ZenML directly into VS Code, enhancing machine learning workflow with support for pipelines, stacks, and server management.", + "command.promptForInterpreter": "Prompt to select a Python interpreter from the command palette in VSCode.", + "command.connectServer": "Establishes a connection to a specified ZenML server.", + "command.disconnectServer": "Disconnects from the currently connected ZenML server.", + "command.refreshServerStatus": "Refreshes the status of the ZenML server to reflect the current state.", + "command.refreshStackView": "Refreshes the stack view to display the latest information about ZenML stacks.", + "command.setStackItemsPerPage": "Sets the number of stacks to display per page in the stack view.", + "command.nextStackPage": "Navigates to the next page of stacks in the stack view.", + "command.previousStackPage": "Navigates to the previous page of stacks in the stack view.", + "command.getActiveStack": "Retrieves the currently active stack in ZenML.", + "command.renameStack": "Renames a specified ZenML stack.", + "command.setActiveStack": "Sets a specified stack as the active ZenML stack.", + "command.copyStack": "Creates a copy of a specified ZenML stack.", + "command.goToStackUrl": "Opens the URL of the specific ZenML stack in the Dashboard", + "command.refreshPipelineView": "Refreshes the pipeline view to display the latest information on ZenML pipeline runs.", + "command.setPipelineRunsPerPage": "Sets the number of pipeline runs to display per page in the pipeline view.", + "command.nextPipelineRunsPage": "Navigates to the next page of pipeline runs in the pipeline view.", + "command.previousPipelineRunsPage": "Navigates to the previous page of pipeline runs in the pipeline view.", + "command.deletePipelineRun": "Deletes a specified ZenML pipeline run.", + "command.goToPipelineUrl": "Opens the URL of the specific ZenML pipeline in the Dashboard", + "command.setPythonInterpreter": "Sets the Python interpreter for ZenML-related tasks within the VS Code environment.", + "command.refreshEnvironmentView": "Updates the Environment View with the latest ZenML configuration and system information.", + "command.restartLspServer": "Restarts the Language Server to ensure the latest configurations are used.", + "settings.args.description": "Specify arguments to pass to the ZenML CLI. Provide each argument as a separate item in the array.", + "settings.path.description": "Defines the path to the ZenML CLI. If left as an empty array, the default system path will be used.", + "settings.importStrategy.description": "Determines which ZenML CLI to use. Options include using a bundled version (`useBundled`) or attempting to use the CLI installed in the current Python environment (`fromEnvironment`).", + "settings.showNotifications.description": "Controls when notifications are shown by this extension.", + "settings.showNotifications.off.description": "All notifications are turned off, any errors or warnings when formatting Python files are still available in the logs.", + "settings.showNotifications.onError.description": "Notifications are shown only in the case of an error when formatting Python files.", + "settings.showNotifications.onWarning.description": "Notifications are shown for any errors and warnings when formatting Python files.", + "settings.showNotifications.always.description": "Notifications are show for anything that the server chooses to show when formatting Python files.", + "settings.interpreter.description": "Sets the path to the Python interpreter used by ZenML. This is used for launching the server and subprocesses. Leave empty to use the default interpreter.", + "settings.serverUrl.description": "URL to connect to the ZenML server.", + "settings.accessToken.description": "Access token required for authenticating with the ZenML server (not necessary currently).", + "settings.activeStackId.description": "Identifier for the currently active stack in ZenML. This is used to specify which stack is being used for operations." }