diff --git a/packages/zowe-explorer/CHANGELOG.md b/packages/zowe-explorer/CHANGELOG.md index e9e87e8e00..f740058a41 100644 --- a/packages/zowe-explorer/CHANGELOG.md +++ b/packages/zowe-explorer/CHANGELOG.md @@ -8,6 +8,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 Data Set or USS resources to a virtual workspace with the new "Add to Workspace" context menu option. [#3265](https://github.com/zowe/zowe-explorer-vscode/issues/3265) ### Bug fixes diff --git a/packages/zowe-explorer/__tests__/__mocks__/vscode.ts b/packages/zowe-explorer/__tests__/__mocks__/vscode.ts index 5c32ad9803..0f9e3ad87a 100644 --- a/packages/zowe-explorer/__tests__/__mocks__/vscode.ts +++ b/packages/zowe-explorer/__tests__/__mocks__/vscode.ts @@ -1320,6 +1320,23 @@ export namespace workspace { export function onDidOpenTextDocument(listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]) {} export function onDidSaveTextDocument(listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]) {} + export function updateWorkspaceFolders( + start: number, + deleteCount: number | undefined | null, + ...workspaceFoldersToAdd: { + /** + * The uri of a workspace folder that's to be added. + */ + readonly uri: Uri; + /** + * The name of a workspace folder that's to be added. + */ + readonly name?: string; + }[] + ): boolean { + return false; + } + export function getConfiguration(configuration: string) { return { update: () => { diff --git a/packages/zowe-explorer/__tests__/__unit__/trees/shared/SharedUtils.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/trees/shared/SharedUtils.unit.test.ts index 181ee8c6a9..c874c19c3b 100644 --- a/packages/zowe-explorer/__tests__/__unit__/trees/shared/SharedUtils.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/trees/shared/SharedUtils.unit.test.ts @@ -25,6 +25,7 @@ import { SharedUtils } from "../../../../src/trees/shared/SharedUtils"; import { ZoweUSSNode } from "../../../../src/trees/uss/ZoweUSSNode"; import { AuthUtils } from "../../../../src/utils/AuthUtils"; import { SharedTreeProviders } from "../../../../src/trees/shared/SharedTreeProviders"; +import { MockedProperty } from "../../../__mocks__/mockUtils"; function createGlobalMocks() { const newMocks = { @@ -551,3 +552,44 @@ describe("Shared utils unit tests - function parseFavorites", () => { expect(warnSpy).toHaveBeenCalledWith("Failed to parse a saved favorite. Attempted to parse: [testProfile]: "); }); }); + +describe("Shared utils unit tests - function addToWorkspace", () => { + it("adds a Data Set resource to the workspace", () => { + const datasetNode = new ZoweDatasetNode({ + label: "EXAMPLE.DS", + collapsibleState: vscode.TreeItemCollapsibleState.None, + contextOverride: Constants.DS_DS_CONTEXT, + profile: createIProfile(), + }); + const updateWorkspaceFoldersMock = jest.spyOn(vscode.workspace, "updateWorkspaceFolders").mockImplementation(); + SharedUtils.addToWorkspace(datasetNode, null as any); + expect(updateWorkspaceFoldersMock).toHaveBeenCalledWith(0, null, { uri: datasetNode.resourceUri, name: datasetNode.label as string }); + }); + it("adds a USS resource to the workspace", () => { + const ussNode = new ZoweUSSNode({ + label: "textFile.txt", + collapsibleState: vscode.TreeItemCollapsibleState.None, + contextOverride: Constants.USS_TEXT_FILE_CONTEXT, + profile: createIProfile(), + }); + const updateWorkspaceFoldersMock = jest.spyOn(vscode.workspace, "updateWorkspaceFolders").mockImplementation(); + SharedUtils.addToWorkspace(ussNode, null as any); + expect(updateWorkspaceFoldersMock).toHaveBeenCalledWith(0, null, { uri: ussNode.resourceUri, name: ussNode.label as string }); + }); + it("skips adding a resource that's already in the workspace", () => { + const ussNode = new ZoweUSSNode({ + label: "textFile.txt", + collapsibleState: vscode.TreeItemCollapsibleState.None, + contextOverride: Constants.USS_TEXT_FILE_CONTEXT, + profile: createIProfile(), + }); + const workspaceFolders = new MockedProperty(vscode.workspace, "workspaceFolders", { + value: [{ uri: ussNode.resourceUri, name: ussNode.label }], + }); + const updateWorkspaceFoldersMock = jest.spyOn(vscode.workspace, "updateWorkspaceFolders").mockImplementation(); + updateWorkspaceFoldersMock.mockClear(); + SharedUtils.addToWorkspace(ussNode, null as any); + expect(updateWorkspaceFoldersMock).not.toHaveBeenCalledWith(0, null, { uri: ussNode.resourceUri, name: ussNode.label as string }); + workspaceFolders[Symbol.dispose](); + }); +}); diff --git a/packages/zowe-explorer/package.json b/packages/zowe-explorer/package.json index 14ea967bbc..d9a4acd568 100644 --- a/packages/zowe-explorer/package.json +++ b/packages/zowe-explorer/package.json @@ -211,6 +211,11 @@ "light": "./resources/light/favorites-open-light.svg" } }, + { + "command": "zowe.addToWorkspace", + "title": "%addToWorkspace%", + "category": "Zowe Explorer" + }, { "command": "zowe.ds.addSession", "title": "%addSession%", @@ -839,6 +844,11 @@ "command": "zowe.removeFavorite", "group": "003_zowe_ussWorkspace@1" }, + { + "when": "view == zowe.uss.explorer && viewItem =~ /^(textFile.*|binaryFile.*|directory.*)/", + "command": "zowe.addToWorkspace", + "group": "003_zowe_ussWorkspace@2" + }, { "when": "view == zowe.uss.explorer && viewItem =~ /^(?!.*_fav.*)ussSession.*/ && !listMultiSelection", "command": "zowe.addFavorite", @@ -1009,6 +1019,11 @@ "command": "zowe.removeFavorite", "group": "002_zowe_dsWorkspace@1" }, + { + "when": "view == zowe.ds.explorer && viewItem =~ /^(pds|ds|migr|member).*/", + "command": "zowe.addToWorkspace", + "group": "003_zowe_dsWorkspace@2" + }, { "when": "view == zowe.ds.explorer && viewItem == profile_fav && !listMultiSelection", "command": "zowe.removeFavProfile", diff --git a/packages/zowe-explorer/package.nls.json b/packages/zowe-explorer/package.nls.json index b21aa2e228..690853c3a1 100644 --- a/packages/zowe-explorer/package.nls.json +++ b/packages/zowe-explorer/package.nls.json @@ -20,6 +20,7 @@ "diff.overwrite": "Overwrite", "diff.useRemote": "Use Remote", "addFavorite": "Add to Favorites", + "addToWorkspace": "Add to Workspace", "removeFavProfile": "Remove profile from Favorites", "addSession": "Add Profile to Data Sets View", "createDataset": "Create New Data Set", diff --git a/packages/zowe-explorer/src/trees/shared/SharedInit.ts b/packages/zowe-explorer/src/trees/shared/SharedInit.ts index daefdba139..6ba16e4e19 100644 --- a/packages/zowe-explorer/src/trees/shared/SharedInit.ts +++ b/packages/zowe-explorer/src/trees/shared/SharedInit.ts @@ -214,6 +214,7 @@ export class SharedInit { } }) ); + context.subscriptions.push(vscode.commands.registerCommand("zowe.addToWorkspace", SharedUtils.addToWorkspace)); context.subscriptions.push( vscode.commands.registerCommand("zowe.removeFavProfile", (node: IZoweTreeNode) => SharedTreeProviders.getProviderForNode(node).removeFavProfile(node.label as string, true) diff --git a/packages/zowe-explorer/src/trees/shared/SharedUtils.ts b/packages/zowe-explorer/src/trees/shared/SharedUtils.ts index 0288f84d91..2f9a6a6bea 100644 --- a/packages/zowe-explorer/src/trees/shared/SharedUtils.ts +++ b/packages/zowe-explorer/src/trees/shared/SharedUtils.ts @@ -287,4 +287,28 @@ export class SharedUtils { public static getSessionLabel(node: IZoweTreeNode): string { return (SharedContext.isSession(node) ? node : node.getSessionNode()).label as string; } + + /** + * Adds one or more Data Sets/USS nodes to a workspace. + * @param node Single node selection + * @param nodeList List of selected nodes + */ + public static addToWorkspace( + this: void, + node: IZoweUSSTreeNode | IZoweDatasetTreeNode, + nodeList: IZoweUSSTreeNode[] | IZoweDatasetTreeNode[] + ): void { + const workspaceFolders = vscode.workspace.workspaceFolders; + const selectedNodes = SharedUtils.getSelectedNodeList(node, nodeList); + for (const item of selectedNodes) { + if (workspaceFolders?.some((folder) => folder.uri === item.resourceUri)) { + continue; + } + + vscode.workspace.updateWorkspaceFolders(workspaceFolders?.length ?? 0, null, { + uri: item.resourceUri, + name: item.label as string, + }); + } + } }