Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to change tag of uss file #2470

Merged
merged 9 commits into from
Sep 27, 2023
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;
rudyflores marked this conversation as resolved.
Show resolved Hide resolved
}
}

/**
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;
};