Skip to content

Commit

Permalink
Merge pull request #2470 from zowe/change-file-tags-ui
Browse files Browse the repository at this point in the history
Add option to change tag of uss file
  • Loading branch information
rudyflores authored Sep 27, 2023
2 parents a449d97 + aa68168 commit fd0649f
Show file tree
Hide file tree
Showing 12 changed files with 120 additions and 8 deletions.
2 changes: 2 additions & 0 deletions packages/zowe-explorer-api/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<ZosmfUssApi>[] = [
{
name: "isFileTagBinOrAscii",
Expand Down
8 changes: 8 additions & 0 deletions packages/zowe-explorer-api/src/profiles/ZoweExplorerApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,14 @@ export namespace ZoweExplorerApi {
* @returns {Promise<zowe.IZosFilesResponse>}
*/
rename(currentUssPath: string, newUssPath: string): Promise<zowe.IZosFilesResponse>;

/**
* Get the tag of a USS file
*
* @param {string} currentUssPath
* @returns {Promise<zowe.IZosFilesResponse>}
*/
getTag?(ussPath: string): Promise<string>;
}

/**
Expand Down
17 changes: 16 additions & 1 deletion packages/zowe-explorer-api/src/profiles/ZoweExplorerZosmfApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,14 @@ export class ZosmfUssApi extends ZosmfApiCommon implements ZoweExplorerApi.IUss

public async updateAttributes(ussPath: string, attributes: Partial<FileAttributes>): Promise<zowe.IZosFilesResponse> {
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",
Expand All @@ -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",
Expand Down Expand Up @@ -205,6 +212,14 @@ export class ZosmfUssApi extends ZosmfApiCommon implements ZoweExplorerApi.IUss
apiResponse: result,
};
}

public async getTag(ussPath: string): Promise<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;
}
}

/**
Expand Down
1 change: 1 addition & 0 deletions packages/zowe-explorer-api/src/utils/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export type FileAttributes = {
owner: string;
uid: number;
perms: string;
tag?: string;
};

export function permStringToOctal(perms: string): number {
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 @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -21,6 +22,7 @@ describe("AttributeView unit tests", () => {
const node = {
attributes: {
perms: "----------",
tag: undefined,
},
label: "example node",
fullPath: "/z/some/path",
Expand All @@ -33,29 +35,31 @@ 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);
});

afterEach(() => {
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,
Expand All @@ -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 = {
Expand Down
5 changes: 4 additions & 1 deletion packages/zowe-explorer/i18n/sample/package.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
13 changes: 13 additions & 0 deletions packages/zowe-explorer/src/uss/AttributeView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<IZoweUSSTreeNode>;
Expand All @@ -30,11 +31,18 @@ export class AttributeView extends WebView {
this.ussApi = ZoweExplorerApiRegister.getUssApi(this.ussNode.getProfile());
}

private async attachTag(node: IZoweUSSTreeNode): Promise<void> {
if (this.ussApi.getTag && !contextually.isUssDirectory(node)) {
node.attributes.tag = await this.ussApi.getTag(node.fullPath);
}
}

protected async onDidReceiveMessage(message: any): Promise<void> {
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,
Expand All @@ -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,
Expand Down Expand Up @@ -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;

Expand Down
13 changes: 13 additions & 0 deletions packages/zowe-explorer/webviews/edit-attributes/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<Record<"current" | "initial", FileAttributes | null>>({
Expand Down Expand Up @@ -111,6 +112,7 @@ export function App() {
};
return all;
}, {}),
tag: attributes.tag ?? notSupported,
};

setAttributes({
Expand Down Expand Up @@ -155,6 +157,17 @@ export function App() {
<pre style={{ fontSize: "1.25em" }}>{attributes.current.name}</pre>
</strong>
<VSCodeDivider />
{attributes.initial?.directory ?? false ? null : (
<div style={{ marginTop: "1em", display: "flex", marginLeft: "1em" }}>
<VSCodeTextField
readonly={attributes.current.tag === notSupported}
value={attributes.current.tag}
onInput={(e: any) => updateFileAttributes("tag", e.target.value)}
>
Tag
</VSCodeTextField>
</div>
)}
<div style={{ marginTop: "1em" }}>
<div style={{ maxWidth: "fit-content" }}>
<div style={{ display: "flex", marginLeft: "1em" }}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ export type FileAttributes = {
directory: boolean;
group: string;
perms: FilePermissions;
tag?: string;
};

0 comments on commit fd0649f

Please sign in to comment.