Skip to content

Commit

Permalink
feat: open Zowe Explorer resources using VSCode URLs (#3271)
Browse files Browse the repository at this point in the history
* feat: open Zowe Explorer resources using VSCode URLs

Signed-off-by: Trae Yelovich <trae.yelovich@broadcom.com>

* fix failing tests, add new tests, fix Uri.parse stubs

Signed-off-by: Trae Yelovich <trae.yelovich@broadcom.com>

* chore: update ZE changelog

Signed-off-by: Trae Yelovich <trae.yelovich@broadcom.com>

* default value for Uri.path stub

Signed-off-by: Trae Yelovich <trae.yelovich@broadcom.com>

* feat: 'Copy External Link' for nodes in context menu

Signed-off-by: Trae Yelovich <trae.yelovich@broadcom.com>

* refactor: use context.extension.id to build URI

Signed-off-by: Trae Yelovich <trae.yelovich@broadcom.com>

* chore: incorporate changelog feedback

Signed-off-by: Trae Yelovich <trae.yelovich@broadcom.com>

* update command count after merge

Signed-off-by: Trae Yelovich <trae.yelovich@broadcom.com>

* refactor: Hide option from directories/PDS

We're not in a place to easily support directories and PDS right now.
We can add support for this in the future once its easy to
access the profile node and execute a search on it programatically.

Signed-off-by: Trae Yelovich <trae.yelovich@broadcom.com>

---------

Signed-off-by: Trae Yelovich <trae.yelovich@broadcom.com>
  • Loading branch information
traeok authored Oct 30, 2024
1 parent 95320f8 commit 1786fe3
Show file tree
Hide file tree
Showing 14 changed files with 230 additions and 47 deletions.
20 changes: 15 additions & 5 deletions packages/zowe-explorer-api/__mocks__/vscode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -611,14 +611,24 @@ export interface TreeDataProvider<T> {
}

export class Uri {
private static _regexp = /^(([^:/?#]+?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;

public static file(path: string): Uri {
return Uri.parse(path);
}
public static parse(value: string, _strict?: boolean): Uri {
const newUri = new Uri();
newUri.path = value;
const match = Uri._regexp.exec(value);
if (!match) {
return new Uri();
}

return newUri;
return Uri.from({
scheme: match[2] || "",
authority: match[4] || "",
path: match[5] || "",
query: match[7] || "",
fragment: match[9] || "",
});
}

public with(change: { scheme?: string; authority?: string; path?: string; query?: string; fragment?: string }): Uri {
Expand Down Expand Up @@ -688,7 +698,7 @@ export class Uri {
/**
* Path is the `/some/path` part of `http://www.example.com/some/path?query#fragment`.
*/
path: string;
path: string = "";

/**
* Query is the `query` part of `http://www.example.com/some/path?query#fragment`.
Expand Down Expand Up @@ -720,7 +730,7 @@ export class Uri {
* u.fsPath === '\\server\c$\folder\file.txt'
* ```
*/
fsPath: string;
fsPath: string = "";

public toString(): string {
let result = this.scheme ? `${this.scheme}://` : "";
Expand Down
1 change: 1 addition & 0 deletions packages/zowe-explorer/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen
- Update Zowe SDKs to `8.2.0` to get the latest enhancements from Imperative.
- Added expired JSON web token detection for profiles in each tree view (Data Sets, USS, Jobs). When a user performs a search on a profile, they are prompted to log in if their token expired. [#3175](https://github.com/zowe/zowe-explorer-vscode/issues/3175)
- Add a data set or USS resource to a virtual workspace with the new "Add to Workspace" context menu option. [#3265](https://github.com/zowe/zowe-explorer-vscode/issues/3265)
- Power users and developers can now build links to efficiently open mainframe resources in Zowe Explorer. Use the **Copy External Link** option in the context menu to get the URL for a data set or USS resource, or create a link in the format `vscode://Zowe.vscode-extension-for-zowe?<ZoweResourceUri>`. For more information on building resource URIs, see the [FileSystemProvider wiki article](https://github.com/zowe/zowe-explorer-vscode/wiki/FileSystemProvider#file-paths-vs-uris). [#3271](https://github.com/zowe/zowe-explorer-vscode/pull/3271)

### Bug fixes

Expand Down
59 changes: 54 additions & 5 deletions packages/zowe-explorer/__tests__/__mocks__/vscode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,47 @@ export interface WebviewViewProvider {
resolveWebviewView(webviewView: WebviewView, context: WebviewViewResolveContext, token: CancellationToken): Thenable<void> | void;
}

/**
* A uri handler is responsible for handling system-wide {@link Uri uris}.
*
* @see {@link window.registerUriHandler}.
*/
export interface UriHandler {
/**
* Handle the provided system-wide {@link Uri}.
*
* @see {@link window.registerUriHandler}.
*/
handleUri(uri: Uri): ProviderResult<void>;
}

export namespace window {
/**
* Registers a {@link UriHandler uri handler} capable of handling system-wide {@link Uri uris}.
* In case there are multiple windows open, the topmost window will handle the uri.
* A uri handler is scoped to the extension it is contributed from; it will only
* be able to handle uris which are directed to the extension itself. A uri must respect
* the following rules:
*
* - The uri-scheme must be `vscode.env.uriScheme`;
* - The uri-authority must be the extension id (e.g. `my.extension`);
* - The uri-path, -query and -fragment parts are arbitrary.
*
* For example, if the `my.extension` extension registers a uri handler, it will only
* be allowed to handle uris with the prefix `product-name://my.extension`.
*
* An extension can only register a single uri handler in its entire activation lifetime.
*
* * *Note:* There is an activation event `onUri` that fires when a uri directed for
* the current extension is about to be handled.
*
* @param handler The uri handler to register for this extension.
* @returns A {@link Disposable disposable} that unregisters the handler.
*/
export function registerUriHandler(handler: UriHandler): Disposable {
return () => {};
}

/**
* Register a new provider for webview views.
*
Expand Down Expand Up @@ -1529,14 +1569,23 @@ export interface TextDocument {
}

export class Uri {
private static _regexp = /^(([^:/?#]+?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;
public static file(path: string): Uri {
return Uri.parse(path);
}
public static parse(value: string, strict?: boolean): Uri {
const newUri = new Uri();
newUri.path = value;
public static parse(value: string, _strict?: boolean): Uri {
const match = Uri._regexp.exec(value);
if (!match) {
return new Uri();
}

return newUri;
return Uri.from({
scheme: match[2] || "",
authority: match[4] || "",
path: match[5] || "",
query: match[7] || "",
fragment: match[9] || "",
});
}

public with(change: { scheme?: string; authority?: string; path?: string; query?: string; fragment?: string }): Uri {
Expand Down Expand Up @@ -1606,7 +1655,7 @@ export class Uri {
/**
* Path is the `/some/path` part of `http://www.example.com/some/path?query#fragment`.
*/
path: string;
path: string = "";

/**
* Query is the `query` part of `http://www.example.com/some/path?query#fragment`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ async function createGlobalMocks() {
"zowe.compareWithSelected",
"zowe.compareWithSelectedReadOnly",
"zowe.compareFileStarted",
"zowe.copyExternalLink",
"zowe.placeholderCommand",
],
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import * as vscode from "vscode";
import { createIProfile, createISession, createInstanceOfProfile } from "../../../__mocks__/mockCreators/shared";
import { createDatasetSessionNode } from "../../../__mocks__/mockCreators/datasets";
import { createUSSNode } from "../../../__mocks__/mockCreators/uss";
import { UssFSProvider } from "../../../../src/trees/uss/UssFSProvider";
import { imperative, ProfilesCache, Gui, ZosEncoding, BaseProvider } from "@zowe/zowe-explorer-api";
import { Constants } from "../../../../src/configuration/Constants";
Expand Down Expand Up @@ -628,3 +629,20 @@ describe("Shared utils unit tests - function addToWorkspace", () => {
workspaceFolders[Symbol.dispose]();
});
});

describe("Shared utils unit tests - function copyExternalLink", () => {
it("does nothing for an invalid node or one without a resource URI", async () => {
const copyClipboardMock = jest.spyOn(vscode.env.clipboard, "writeText");
const ussNode = createUSSNode(createISession(), createIProfile());
ussNode.resourceUri = undefined;
await SharedUtils.copyExternalLink({ extension: { id: "Zowe.vscode-extension-for-zowe" } } as any, ussNode);
expect(copyClipboardMock).not.toHaveBeenCalled();
});

it("copies a link for a node with a resource URI", async () => {
const copyClipboardMock = jest.spyOn(vscode.env.clipboard, "writeText");
const ussNode = createUSSNode(createISession(), createIProfile());
await SharedUtils.copyExternalLink({ extension: { id: "Zowe.vscode-extension-for-zowe" } } as any, ussNode);
expect(copyClipboardMock).toHaveBeenCalledWith(`vscode://Zowe.vscode-extension-for-zowe?${ussNode.resourceUri?.toString()}`);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*
*/

import { commands, Uri } from "vscode";
import { ZoweUriHandler } from "../../../src/utils/UriHandler";

describe("ZoweUriHandler", () => {
function getBlockMocks() {
return {
executeCommand: jest.spyOn(commands, "executeCommand"),
};
}

it("does nothing if the parsed query does not start with a Zowe scheme", async () => {
const blockMocks = getBlockMocks();
await ZoweUriHandler.getInstance().handleUri(Uri.parse("vscode://Zowe.vscode-extension-for-zowe?blah-some-unknown-query"));
expect(blockMocks.executeCommand).not.toHaveBeenCalled();
});

it("calls vscode.open with the parsed URI if a Zowe resource URI was provided", async () => {
const blockMocks = getBlockMocks();
const uri = Uri.parse("vscode://Zowe.vscode-extension-for-zowe?zowe-ds:/lpar.zosmf/TEST.PS");
await ZoweUriHandler.getInstance().handleUri(uri);
const zoweUri = Uri.parse(uri.query);
expect(blockMocks.executeCommand).toHaveBeenCalledWith("vscode.open", zoweUri, { preview: false });
});
});
52 changes: 26 additions & 26 deletions packages/zowe-explorer/l10n/bundle.l10n.json
Original file line number Diff line number Diff line change
Expand Up @@ -208,32 +208,6 @@
"Profile auth error": "Profile auth error",
"Profile is not authenticated, please log in to continue": "Profile is not authenticated, please log in to continue",
"Retrieving response from USS list API": "Retrieving response from USS list API",
"The 'move' function is not implemented for this USS API.": "The 'move' function is not implemented for this USS API.",
"Could not list USS files: Empty path provided in URI": "Could not list USS files: Empty path provided in URI",
"Profile does not exist for this file.": "Profile does not exist for this file.",
"Saving USS file...": "Saving USS file...",
"Renaming {0} failed due to API error: {1}/File pathError message": {
"message": "Renaming {0} failed due to API error: {1}",
"comment": [
"File path",
"Error message"
]
},
"Deleting {0} failed due to API error: {1}/File nameError message": {
"message": "Deleting {0} failed due to API error: {1}",
"comment": [
"File name",
"Error message"
]
},
"No error details given": "No error details given",
"Error fetching destination {0} for paste action: {1}/USS pathError message": {
"message": "Error fetching destination {0} for paste action: {1}",
"comment": [
"USS path",
"Error message"
]
},
"Downloaded: {0}/Download time": {
"message": "Downloaded: {0}",
"comment": [
Expand Down Expand Up @@ -304,6 +278,32 @@
"initializeUSSFavorites.error.buttonRemove": "initializeUSSFavorites.error.buttonRemove",
"File does not exist. It may have been deleted.": "File does not exist. It may have been deleted.",
"Pulling from Mainframe...": "Pulling from Mainframe...",
"The 'move' function is not implemented for this USS API.": "The 'move' function is not implemented for this USS API.",
"Could not list USS files: Empty path provided in URI": "Could not list USS files: Empty path provided in URI",
"Profile does not exist for this file.": "Profile does not exist for this file.",
"Saving USS file...": "Saving USS file...",
"Renaming {0} failed due to API error: {1}/File pathError message": {
"message": "Renaming {0} failed due to API error: {1}",
"comment": [
"File path",
"Error message"
]
},
"Deleting {0} failed due to API error: {1}/File nameError message": {
"message": "Deleting {0} failed due to API error: {1}",
"comment": [
"File name",
"Error message"
]
},
"No error details given": "No error details given",
"Error fetching destination {0} for paste action: {1}/USS pathError message": {
"message": "Error fetching destination {0} for paste action: {1}",
"comment": [
"USS path",
"Error message"
]
},
"{0} location/Node type": {
"message": "{0} location",
"comment": [
Expand Down
19 changes: 11 additions & 8 deletions packages/zowe-explorer/l10n/poeditor.json
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,9 @@
"openWithEncoding": {
"Open with Encoding": ""
},
"copyExternalLink": {
"Copy External Link": ""
},
"zowe.history.deprecationMsg": {
"Changes made here will not be reflected in Zowe Explorer, use right-click Edit History option to access information from local storage.": ""
},
Expand Down Expand Up @@ -528,14 +531,6 @@
"Profile auth error": "",
"Profile is not authenticated, please log in to continue": "",
"Retrieving response from USS list API": "",
"The 'move' function is not implemented for this USS API.": "",
"Could not list USS files: Empty path provided in URI": "",
"Profile does not exist for this file.": "",
"Saving USS file...": "",
"Renaming {0} failed due to API error: {1}": "",
"Deleting {0} failed due to API error: {1}": "",
"No error details given": "",
"Error fetching destination {0} for paste action: {1}": "",
"Downloaded: {0}": "",
"Encoding: {0}": "",
"Binary": "",
Expand Down Expand Up @@ -564,6 +559,14 @@
"initializeUSSFavorites.error.buttonRemove": "",
"File does not exist. It may have been deleted.": "",
"Pulling from Mainframe...": "",
"The 'move' function is not implemented for this USS API.": "",
"Could not list USS files: Empty path provided in URI": "",
"Profile does not exist for this file.": "",
"Saving USS file...": "",
"Renaming {0} failed due to API error: {1}": "",
"Deleting {0} failed due to API error: {1}": "",
"No error details given": "",
"Error fetching destination {0} for paste action: {1}": "",
"{0} location": "",
"Choose a location to create the {0}": "",
"Name of file or directory": "",
Expand Down
24 changes: 22 additions & 2 deletions packages/zowe-explorer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,11 @@
"title": "%copyName%",
"category": "Zowe Explorer"
},
{
"command": "zowe.copyExternalLink",
"title": "%copyExternalLink%",
"category": "Zowe Explorer"
},
{
"command": "zowe.uss.addSession",
"title": "%uss.addSession%",
Expand Down Expand Up @@ -834,6 +839,11 @@
"command": "zowe.uss.copyRelativePath",
"group": "002_zowe_ussSystemSpecific@5"
},
{
"when": "view == zowe.uss.explorer && viewItem =~ /^(?!(directory|favorite|profile_fav|ussSession))/ && !listMultiSelection",
"command": "zowe.copyExternalLink",
"group": "002_zowe_ussSystemSpecific@6"
},
{
"when": "view == zowe.uss.explorer && viewItem =~ /^(?!.*_fav.*)(textFile.*|binaryFile.*|directory.*)/",
"command": "zowe.addFavorite",
Expand Down Expand Up @@ -1034,15 +1044,20 @@
"command": "zowe.ds.copyName",
"group": "098_zowe_dsMisc@0"
},
{
"when": "view == zowe.ds.explorer && viewItem =~ /^(ds|member).*/ && !listMultiSelection",
"command": "zowe.copyExternalLink",
"group": "098_zowe_dsMisc@1"
},
{
"when": "view == zowe.ds.explorer && viewItem =~ /^(pds|session).*/ && !listMultiSelection",
"command": "zowe.ds.filterBy",
"group": "098_zowe_dsMisc@1"
"group": "098_zowe_dsMisc@2"
},
{
"when": "view == zowe.ds.explorer && viewItem =~ /^(pds|session).*/ && !listMultiSelection",
"command": "zowe.ds.sortBy",
"group": "098_zowe_dsMisc@2"
"group": "098_zowe_dsMisc@3"
},
{
"when": "view == zowe.ds.explorer && viewItem =~ /^fileError.*/",
Expand Down Expand Up @@ -1219,6 +1234,11 @@
"command": "zowe.jobs.copyName",
"group": "003_zowe_jobsMisc@0"
},
{
"when": "view == zowe.jobs.explorer && viewItem =~ /^spool.*/",
"command": "zowe.copyExternalLink",
"group": "003_zowe_jobsMisc@1"
},
{
"when": "view == zowe.jobs.explorer && viewItem =~ /^spool.*/",
"command": "zowe.jobs.refreshSpool",
Expand Down
1 change: 1 addition & 0 deletions packages/zowe-explorer/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,5 +136,6 @@
"compareWithSelected": "Compare with Selected",
"compareWithSelectedReadOnly": "Compare with Selected (Read-Only)",
"openWithEncoding": "Open with Encoding",
"copyExternalLink": "Copy External Link",
"zowe.history.deprecationMsg": "Changes made here will not be reflected in Zowe Explorer, use right-click Edit History option to access information from local storage."
}
2 changes: 1 addition & 1 deletion packages/zowe-explorer/src/configuration/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { imperative, PersistenceSchemaEnum } from "@zowe/zowe-explorer-api";
import type { Profiles } from "./Profiles";

export class Constants {
public static readonly COMMAND_COUNT = 100;
public static readonly COMMAND_COUNT = 101;
public static readonly MAX_SEARCH_HISTORY = 5;
public static readonly MAX_FILE_HISTORY = 10;
public static readonly MS_PER_SEC = 1000;
Expand Down
Loading

0 comments on commit 1786fe3

Please sign in to comment.