From 1dea6486fe772bd0165b954e7be8264f866f1b3d Mon Sep 17 00:00:00 2001 From: Rudy Flores <68666202+rudyflores@users.noreply.github.com> Date: Tue, 19 Sep 2023 16:06:40 -0400 Subject: [PATCH 1/7] add option to change tag of uss file Signed-off-by: Rudy Flores <68666202+rudyflores@users.noreply.github.com> --- .../src/profiles/ZoweExplorerApi.ts | 8 +++++++ .../src/profiles/ZoweExplorerZosmfApi.ts | 21 ++++++++++++++++++- packages/zowe-explorer-api/src/utils/files.ts | 1 + .../i18n/sample/package.i18n.json | 5 ++++- .../zowe-explorer/src/uss/AttributeView.ts | 13 ++++++++++++ .../webviews/edit-attributes/src/App.tsx | 13 ++++++++++++ .../webviews/edit-attributes/src/types.ts | 1 + 7 files changed, 60 insertions(+), 2 deletions(-) diff --git a/packages/zowe-explorer-api/src/profiles/ZoweExplorerApi.ts b/packages/zowe-explorer-api/src/profiles/ZoweExplorerApi.ts index 905722b894..d79c99ca37 100644 --- a/packages/zowe-explorer-api/src/profiles/ZoweExplorerApi.ts +++ b/packages/zowe-explorer-api/src/profiles/ZoweExplorerApi.ts @@ -205,6 +205,14 @@ export namespace ZoweExplorerApi { * @returns {Promise} */ rename(currentUssPath: string, newUssPath: string): Promise; + + /** + * Get the tag of a USS file + * + * @param {string} currentUssPath + * @returns {Promise} + */ + getTag?(ussPath: string): Promise; } /** diff --git a/packages/zowe-explorer-api/src/profiles/ZoweExplorerZosmfApi.ts b/packages/zowe-explorer-api/src/profiles/ZoweExplorerZosmfApi.ts index c20cda5208..554515929a 100644 --- a/packages/zowe-explorer-api/src/profiles/ZoweExplorerZosmfApi.ts +++ b/packages/zowe-explorer-api/src/profiles/ZoweExplorerZosmfApi.ts @@ -147,6 +147,14 @@ export class ZosmfUssApi extends ZosmfApiCommon implements ZoweExplorerApi.IUss public async updateAttributes(ussPath: string, attributes: Partial): Promise { try { + if (attributes.tag) { + await zowe.Utilities.putUSSPayload(this.getSession(), ussPath, { + request: "chtag", + action: "set", + type: "text", + codeset: attributes.tag !== null ? attributes.tag.toString() : attributes.tag, + }); + } if ((attributes.group || attributes.gid) && (attributes.owner || attributes.uid)) { await zowe.Utilities.putUSSPayload(this.getSession(), ussPath, { request: "chown", @@ -161,7 +169,6 @@ export class ZosmfUssApi extends ZosmfApiCommon implements ZoweExplorerApi.IUss recursive: true, }); } - if (attributes.perms) { await zowe.Utilities.putUSSPayload(this.getSession(), ussPath, { request: "chmod", @@ -205,6 +212,18 @@ export class ZosmfUssApi extends ZosmfApiCommon implements ZoweExplorerApi.IUss apiResponse: result, }; } + + public async getTag(ussPath: string): Promise { + const result = JSON.parse( + Buffer.from( + await zowe.Utilities.putUSSPayload(this.getSession(), ussPath, { + request: "chtag", + action: "list", + }) + ).toString() + ); + return result.stdout[0].split(" ")[1] as string; + } } /** diff --git a/packages/zowe-explorer-api/src/utils/files.ts b/packages/zowe-explorer-api/src/utils/files.ts index 76b1026ff2..cb95337463 100644 --- a/packages/zowe-explorer-api/src/utils/files.ts +++ b/packages/zowe-explorer-api/src/utils/files.ts @@ -21,6 +21,7 @@ export type FileAttributes = { owner: string; uid: number; perms: string; + tag?: string; }; export function permStringToOctal(perms: string): number { diff --git a/packages/zowe-explorer/i18n/sample/package.i18n.json b/packages/zowe-explorer/i18n/sample/package.i18n.json index 5930969b05..8bc5d8bf6c 100644 --- a/packages/zowe-explorer/i18n/sample/package.i18n.json +++ b/packages/zowe-explorer/i18n/sample/package.i18n.json @@ -146,5 +146,8 @@ "createZoweSchema.reload.button": "Reload Window", "createZoweSchema.reload.infoMessage": "Team Configuration file created. Location: {0}. \n Please reload your window.", "copyFile": "Copy", - "pasteFile": "Paste" + "pasteFile": "Paste", + "jobs.sortbyreturncode": "Sort by ReturnCode", + "jobs.sortbyname": "Sort by Name", + "jobs.sortbyid": "Sort by ID" } diff --git a/packages/zowe-explorer/src/uss/AttributeView.ts b/packages/zowe-explorer/src/uss/AttributeView.ts index baaac36f3a..5a880dbccc 100644 --- a/packages/zowe-explorer/src/uss/AttributeView.ts +++ b/packages/zowe-explorer/src/uss/AttributeView.ts @@ -12,6 +12,7 @@ import { FileAttributes, Gui, IZoweTree, IZoweUSSTreeNode, WebView, ZoweExplorerApi } from "@zowe/zowe-explorer-api"; import { Disposable, ExtensionContext } from "vscode"; import { ZoweExplorerApiRegister } from "../ZoweExplorerApiRegister"; +import * as contextually from "../shared/context"; export class AttributeView extends WebView { private treeProvider: IZoweTree; @@ -30,11 +31,18 @@ export class AttributeView extends WebView { this.ussApi = ZoweExplorerApiRegister.getUssApi(this.ussNode.getProfile()); } + private async attachTag(node: IZoweUSSTreeNode): Promise { + if (this.ussApi.getTag && !contextually.isUssDirectory(node)) { + node.attributes.tag = await this.ussApi.getTag(node.fullPath); + } + } + protected async onDidReceiveMessage(message: any): Promise { switch (message.command) { case "refresh": if (this.canUpdate) { this.onUpdateDisposable = this.ussNode.onUpdate(async (node) => { + await this.attachTag(node); await this.panel.webview.postMessage({ attributes: node.attributes, name: node.fullPath, @@ -51,6 +59,7 @@ export class AttributeView extends WebView { } break; case "ready": + await this.attachTag(this.ussNode); await this.panel.webview.postMessage({ attributes: this.ussNode.attributes, name: this.ussNode.fullPath, @@ -104,6 +113,10 @@ export class AttributeView extends WebView { newAttrs.perms = attrs.perms; } + if (this.ussNode.attributes.tag !== attrs.tag && this.ussApi.getTag) { + newAttrs.tag = attrs.tag; + } + await this.ussApi.updateAttributes(this.ussNode.fullPath, newAttrs); this.ussNode.attributes = { ...(this.ussNode.attributes ?? {}), ...newAttrs } as FileAttributes; diff --git a/packages/zowe-explorer/webviews/edit-attributes/src/App.tsx b/packages/zowe-explorer/webviews/edit-attributes/src/App.tsx index b15dcfd43e..0d44c55652 100644 --- a/packages/zowe-explorer/webviews/edit-attributes/src/App.tsx +++ b/packages/zowe-explorer/webviews/edit-attributes/src/App.tsx @@ -15,6 +15,7 @@ import isEqual from "lodash.isequal"; const vscodeApi = acquireVsCodeApi(); export function App() { + const notSupported = "NOT SUPPORTED"; const [readonly, setReadonly] = useState(false); const [allowUpdate, setAllowUpdate] = useState(false); const [attributes, setAttributes] = useState>({ @@ -111,6 +112,7 @@ export function App() { }; return all; }, {}), + tag: attributes.tag ?? notSupported, }; setAttributes({ @@ -155,6 +157,17 @@ export function App() {
{attributes.current.name}
+ {attributes.initial?.directory ?? false ? null : ( +
+ updateFileAttributes("tag", e.target.value)} + > + Tag + +
+ )}
diff --git a/packages/zowe-explorer/webviews/edit-attributes/src/types.ts b/packages/zowe-explorer/webviews/edit-attributes/src/types.ts index f1dba099f4..72efca076a 100644 --- a/packages/zowe-explorer/webviews/edit-attributes/src/types.ts +++ b/packages/zowe-explorer/webviews/edit-attributes/src/types.ts @@ -15,4 +15,5 @@ export type FileAttributes = { directory: boolean; group: string; perms: FilePermissions; + tag?: string; }; From 54f2cc6686b11def8cae19a049dd2977d20ccd0a Mon Sep 17 00:00:00 2001 From: Rudy Flores <68666202+rudyflores@users.noreply.github.com> Date: Wed, 20 Sep 2023 11:27:11 -0400 Subject: [PATCH 2/7] add changelog Signed-off-by: Rudy Flores <68666202+rudyflores@users.noreply.github.com> --- packages/zowe-explorer-api/CHANGELOG.md | 2 ++ packages/zowe-explorer/CHANGELOG.md | 1 + 2 files changed, 3 insertions(+) diff --git a/packages/zowe-explorer-api/CHANGELOG.md b/packages/zowe-explorer-api/CHANGELOG.md index f3d025e88f..5e1d59f8c7 100644 --- a/packages/zowe-explorer-api/CHANGELOG.md +++ b/packages/zowe-explorer-api/CHANGELOG.md @@ -6,6 +6,8 @@ All notable changes to the "zowe-explorer-api" extension will be documented in t ### New features and enhancements +- Added optional `getTag` function to `ZoweExplorerAPI.IUss` for getting the tag of a file on USS. + ### Bug fixes ## `2.11.0` diff --git a/packages/zowe-explorer/CHANGELOG.md b/packages/zowe-explorer/CHANGELOG.md index d6550315ec..6a0f73f34d 100644 --- a/packages/zowe-explorer/CHANGELOG.md +++ b/packages/zowe-explorer/CHANGELOG.md @@ -7,6 +7,7 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen ### New features and enhancements - Added "Sort Jobs" feature for job nodes in Jobs tree view. [#2257](https://github.com/zowe/vscode-extension-for-zowe/issues/2251) +- Added new edit feature on `Edit Attributes` view for changing file tags on USS [#2113](https://github.com/zowe/vscode-extension-for-zowe/issues/2113) ### Bug fixes From 8bc51c8410e1a527b761285c90aead8e386a4f25 Mon Sep 17 00:00:00 2001 From: Rudy Flores <68666202+rudyflores@users.noreply.github.com> Date: Wed, 20 Sep 2023 11:31:51 -0400 Subject: [PATCH 3/7] update codeql config to check coverage on webviews code Signed-off-by: Rudy Flores <68666202+rudyflores@users.noreply.github.com> --- .github/resources/codeql-config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/resources/codeql-config.yml b/.github/resources/codeql-config.yml index c6087ff119..49da1ed460 100644 --- a/.github/resources/codeql-config.yml +++ b/.github/resources/codeql-config.yml @@ -5,5 +5,6 @@ queries: paths: - packages + - packages/zowe-explorer/webviews paths-ignore: - node_modules From d5754baa69c4f0df28e3a8a2f1013120abd30f39 Mon Sep 17 00:00:00 2001 From: Rudy Flores <68666202+rudyflores@users.noreply.github.com> Date: Wed, 20 Sep 2023 11:39:42 -0400 Subject: [PATCH 4/7] Revert "update codeql config to check coverage on webviews code" This reverts commit 8bc51c8410e1a527b761285c90aead8e386a4f25. Signed-off-by: Rudy Flores <68666202+rudyflores@users.noreply.github.com> --- .github/resources/codeql-config.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/resources/codeql-config.yml b/.github/resources/codeql-config.yml index 49da1ed460..c6087ff119 100644 --- a/.github/resources/codeql-config.yml +++ b/.github/resources/codeql-config.yml @@ -5,6 +5,5 @@ queries: paths: - packages - - packages/zowe-explorer/webviews paths-ignore: - node_modules From 9478fa219f6d7ccf38d608c7257048b4cecea705 Mon Sep 17 00:00:00 2001 From: Rudy Flores <68666202+rudyflores@users.noreply.github.com> Date: Wed, 20 Sep 2023 12:14:22 -0400 Subject: [PATCH 5/7] add unit test coverage Signed-off-by: Rudy Flores <68666202+rudyflores@users.noreply.github.com> --- .../api/ZoweExplorerZosmfApi.unit.test.ts | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/packages/zowe-explorer/__tests__/__unit__/api/ZoweExplorerZosmfApi.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/api/ZoweExplorerZosmfApi.unit.test.ts index 21fdff381b..40449b9978 100644 --- a/packages/zowe-explorer/__tests__/__unit__/api/ZoweExplorerZosmfApi.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/api/ZoweExplorerZosmfApi.unit.test.ts @@ -120,4 +120,31 @@ describe("Zosmf API tests", () => { Promise.resolve(zowe.Download.ussFile(api.getSession(), "/some/input/path", {})) ); }); + + it("should update the tag attribute of a USS file if a new change is made", async () => { + const api = new ZosmfUssApi(); + const changeTagSpy = jest.fn(); + Object.defineProperty(zowe, "Utilities", { + value: { + putUSSPayload: changeTagSpy, + }, + configurable: true, + }); + await expect(api.updateAttributes("/test/path", { tag: "utf-8" })).resolves.not.toThrow(); + expect(changeTagSpy).toBeCalledTimes(1); + }); + + it("should get the tag of a file successfully", async () => { + const api = new ZosmfUssApi(); + jest.spyOn(JSON, "parse").mockReturnValue({ + stdout: ["-t UTF-8 tesfile.txt"], + }); + Object.defineProperty(zowe, "Utilities", { + value: { + putUSSPayload: () => Buffer.from(""), + }, + configurable: true, + }); + await expect(api.getTag("testfile.txt")).resolves.toEqual("UTF-8"); + }); }); From a3297aa9df5b3bd9a4b184d09664c33ad80c9125 Mon Sep 17 00:00:00 2001 From: Rudy Flores <68666202+rudyflores@users.noreply.github.com> Date: Wed, 20 Sep 2023 14:57:18 -0400 Subject: [PATCH 6/7] fix broken test and clean up code Signed-off-by: Rudy Flores <68666202+rudyflores@users.noreply.github.com> --- .../src/profiles/ZoweExplorerZosmfApi.ts | 14 +++++--------- .../__unit__/uss/AttributeView.unit.test.ts | 16 ++++++++++------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/zowe-explorer-api/src/profiles/ZoweExplorerZosmfApi.ts b/packages/zowe-explorer-api/src/profiles/ZoweExplorerZosmfApi.ts index 554515929a..1075472f49 100644 --- a/packages/zowe-explorer-api/src/profiles/ZoweExplorerZosmfApi.ts +++ b/packages/zowe-explorer-api/src/profiles/ZoweExplorerZosmfApi.ts @@ -214,15 +214,11 @@ export class ZosmfUssApi extends ZosmfApiCommon implements ZoweExplorerApi.IUss } public async getTag(ussPath: string): Promise { - const result = JSON.parse( - Buffer.from( - await zowe.Utilities.putUSSPayload(this.getSession(), ussPath, { - request: "chtag", - action: "list", - }) - ).toString() - ); - return result.stdout[0].split(" ")[1] as string; + const response = await zowe.Utilities.putUSSPayload(this.getSession(), ussPath, { + request: "chtag", + action: "list", + }); + return JSON.parse(response.toString()).stdout[0].split(" ")[1] as string; } } diff --git a/packages/zowe-explorer/__tests__/__unit__/uss/AttributeView.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/uss/AttributeView.unit.test.ts index ce554ede95..d26119d6d1 100644 --- a/packages/zowe-explorer/__tests__/__unit__/uss/AttributeView.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/uss/AttributeView.unit.test.ts @@ -13,6 +13,7 @@ import { ExtensionContext } from "vscode"; import { AttributeView } from "../../../src/uss/AttributeView"; import { IZoweTree, IZoweUSSTreeNode, ZoweExplorerApi } from "@zowe/zowe-explorer-api"; import { ZoweExplorerApiRegister } from "../../../src/ZoweExplorerApiRegister"; +import * as contextually from "../../../src/shared/context"; describe("AttributeView unit tests", () => { let view: AttributeView; @@ -21,6 +22,7 @@ describe("AttributeView unit tests", () => { const node = { attributes: { perms: "----------", + tag: undefined, }, label: "example node", fullPath: "/z/some/path", @@ -33,7 +35,9 @@ describe("AttributeView unit tests", () => { beforeAll(() => { jest.spyOn(ZoweExplorerApiRegister, "getUssApi").mockReturnValue({ updateAttributes: updateAttributesMock, + getTag: () => Promise.resolve("UTF-8"), } as unknown as ZoweExplorerApi.IUss); + jest.spyOn(contextually, "isUssDirectory").mockReturnValue(false); view = new AttributeView(context, treeProvider, node); }); @@ -41,21 +45,21 @@ describe("AttributeView unit tests", () => { node.onUpdate = jest.fn(); }); - it("refreshes properly when webview sends 'refresh' command", () => { + it("refreshes properly when webview sends 'refresh' command", async () => { // case 1: node is a root node - (view as any).onDidReceiveMessage({ command: "refresh" }); + await (view as any).onDidReceiveMessage({ command: "refresh" }); expect(treeProvider.refresh).toHaveBeenCalled(); // case 2: node is a child node node.getParent = jest.fn().mockReturnValueOnce({ label: "parent node" } as IZoweUSSTreeNode); - (view as any).onDidReceiveMessage({ command: "refresh" }); + await (view as any).onDidReceiveMessage({ command: "refresh" }); expect(treeProvider.refreshElement).toHaveBeenCalled(); expect(node.onUpdate).toHaveBeenCalledTimes(2); }); - it("dispatches node data to webview when 'ready' command is received", () => { - (view as any).onDidReceiveMessage({ command: "ready" }); + it("dispatches node data to webview when 'ready' command is received", async () => { + await (view as any).onDidReceiveMessage({ command: "ready" }); expect(view.panel.webview.postMessage).toHaveBeenCalledWith({ attributes: node.attributes, name: node.fullPath, @@ -65,7 +69,7 @@ describe("AttributeView unit tests", () => { it("updates attributes when 'update-attributes' command is received", async () => { // case 1: no attributes provided from webview (sanity check) - (view as any).onDidReceiveMessage({ command: "update-attributes" }); + await (view as any).onDidReceiveMessage({ command: "update-attributes" }); expect(updateAttributesMock).not.toHaveBeenCalled(); const attributes = { From aa68168969551065b801c5f66a234c59f68c654a Mon Sep 17 00:00:00 2001 From: Rudy Flores <68666202+rudyflores@users.noreply.github.com> Date: Wed, 20 Sep 2023 15:26:49 -0400 Subject: [PATCH 7/7] add coverage on API package side Signed-off-by: Rudy Flores <68666202+rudyflores@users.noreply.github.com> --- .../ZoweExplorerZosmfApi.unit.test.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/packages/zowe-explorer-api/__tests__/__unit__/profiles/ZoweExplorerZosmfApi.unit.test.ts b/packages/zowe-explorer-api/__tests__/__unit__/profiles/ZoweExplorerZosmfApi.unit.test.ts index ab4e76d44d..f700d6df52 100644 --- a/packages/zowe-explorer-api/__tests__/__unit__/profiles/ZoweExplorerZosmfApi.unit.test.ts +++ b/packages/zowe-explorer-api/__tests__/__unit__/profiles/ZoweExplorerZosmfApi.unit.test.ts @@ -260,6 +260,30 @@ describe("ZosmfUssApi", () => { expect(logoutSpy).toHaveBeenCalledWith(fakeSession); }); + it("should retrieve the tag of a file", async () => { + const zosmfApi = new ZosmfUssApi(); + jest.spyOn(JSON, "parse").mockReturnValue({ + stdout: ["-t UTF-8 tesfile.txt"], + }); + + Object.defineProperty(zowe.Utilities, "putUSSPayload", { + value: () => Buffer.from(""), + configurable: true, + }); + await expect(zosmfApi.getTag("testfile.txt")).resolves.toEqual("UTF-8"); + }); + + it("should update the tag attribute when passed in", async () => { + const zosmfApi = new ZosmfUssApi(); + const changeTagSpy = jest.fn(); + Object.defineProperty(zowe.Utilities, "putUSSPayload", { + value: changeTagSpy, + configurable: true, + }); + await expect(zosmfApi.updateAttributes("/test/path", { tag: "utf-8" })).resolves.not.toThrow(); + expect(changeTagSpy).toBeCalledTimes(1); + }); + const ussApis: ITestApi[] = [ { name: "isFileTagBinOrAscii",