Skip to content

Commit

Permalink
fix(v3): Migrate/recall updates in tree and filesystem (#3335)
Browse files Browse the repository at this point in the history
* fix: update nodes for recalled and migrated data sets

Signed-off-by: Trae Yelovich <[email protected]>

* tests: datasetNodeMigrated, datasetNodeRecalled

Signed-off-by: Trae Yelovich <[email protected]>

* chore: add changelog

Signed-off-by: Trae Yelovich <[email protected]>

* refactor: remove static from dataset{Migrated,Recalled}

Signed-off-by: Trae Yelovich <[email protected]>

* chore: reword changelog items

Signed-off-by: Trae Yelovich <[email protected]>

* refactor: add command to PS nodes after recall; update test

Signed-off-by: Trae Yelovich <[email protected]>

* remove unused comment, update migrate node refresh

Signed-off-by: Trae Yelovich <[email protected]>

* chore: update typedoc

Signed-off-by: Trae Yelovich <[email protected]>

* refactor: make datasetMigrated public to reuse logic

Signed-off-by: Trae Yelovich <[email protected]>

* fix: do not mark ds as migrated right away

Signed-off-by: Trae Yelovich <[email protected]>

* do not call writeFile for recalled ds URI if it exists

Signed-off-by: Trae Yelovich <[email protected]>

* fix: handle favorited DS for migrate/recall

Signed-off-by: Trae Yelovich <[email protected]>

---------

Signed-off-by: Trae Yelovich <[email protected]>
Co-authored-by: Billie Simmons <[email protected]>
  • Loading branch information
traeok and JillieBeanSim authored Nov 25, 2024
1 parent 3e75cfe commit d68621f
Show file tree
Hide file tree
Showing 4 changed files with 232 additions and 30 deletions.
3 changes: 3 additions & 0 deletions packages/zowe-explorer/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen
- Fixed an issue where opening a PDS member after renaming an expanded PDS resulted in an error. [#3314](https://github.com/zowe/zowe-explorer-vscode/issues/3314)
- Fixed issue where persistent settings defined at the workspace level were migrated into global storage rather than workspace-specific storage. [#3180](https://github.com/zowe/zowe-explorer-vscode/issues/3180)
- Fixed an issue where renaming a data set with unsaved changes did not cancel the rename operation. Now, when renaming a data set with unsaved changes, you are prompted to resolve them before continuing. [#3326](https://github.com/zowe/zowe-explorer-vscode/pull/3326)
- Fixed an issue where a migrated data set is unusable after it is recalled through Zowe Explorer. [#3294](https://github.com/zowe/zowe-explorer-vscode/issues/3294)
- Fixed an issue where a recalled PDS is expandable after it is migrated through Zowe Explorer. [#3294](https://github.com/zowe/zowe-explorer-vscode/issues/3294)
- Fixed an issue where data set nodes did not update if migrated or recalled outside of Zowe Explorer. [#3294](https://github.com/zowe/zowe-explorer-vscode/issues/3294)

## `3.0.3`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*/

import * as vscode from "vscode";
import { DsEntry, Gui, imperative, PdsEntry, Validation } from "@zowe/zowe-explorer-api";
import { DsEntry, Gui, imperative, PdsEntry, Validation, ZoweScheme } from "@zowe/zowe-explorer-api";
import * as zosfiles from "@zowe/zos-files-for-zowe-sdk";
import {
createSessCfgFromArgs,
Expand All @@ -28,6 +28,8 @@ import { Profiles } from "../../../../src/configuration/Profiles";
import { ZoweLogger } from "../../../../src/tools/ZoweLogger";
import { DatasetFSProvider } from "../../../../src/trees/dataset/DatasetFSProvider";
import { ZoweDatasetNode } from "../../../../src/trees/dataset/ZoweDatasetNode";
import { IconUtils } from "../../../../src/icons/IconUtils";
import { IconGenerator } from "../../../../src/icons/IconGenerator";

// Missing the definition of path module, because I need the original logic for tests
jest.mock("fs");
Expand Down Expand Up @@ -751,3 +753,148 @@ describe("ZoweDatasetNode Unit Tests - Function node.setStats", () => {
createDirMock.mockRestore();
});
});

describe("ZoweDatasetNode Unit Tests - function datasetRecalled", () => {
it("changes the collapsible state", async () => {
const dsNode = new ZoweDatasetNode({
label: "MIGRATED.PDS",
collapsibleState: vscode.TreeItemCollapsibleState.None,
contextOverride: Constants.DS_MIGRATED_FILE_CONTEXT,
profile: createIProfile(),
});
await (dsNode as any).datasetRecalled(true);
expect(dsNode.collapsibleState).toBe(vscode.TreeItemCollapsibleState.Collapsed);
});

it("adds a resource URI", async () => {
const dsNode = new ZoweDatasetNode({
label: "MIGRATED.PDS",
collapsibleState: vscode.TreeItemCollapsibleState.None,
contextOverride: Constants.DS_MIGRATED_FILE_CONTEXT,
parentNode: createDatasetSessionNode(createISession(), createIProfile()),
profile: createIProfile(),
});
await (dsNode as any).datasetRecalled(true);
expect(dsNode.resourceUri).toStrictEqual(
vscode.Uri.from({
scheme: ZoweScheme.DS,
path: "/sestest/MIGRATED.PDS",
})
);
});

it("adds a command to the node - PS", async () => {
const dsNode = new ZoweDatasetNode({
label: "MIGRATED.PS",
collapsibleState: vscode.TreeItemCollapsibleState.None,
contextOverride: Constants.DS_MIGRATED_FILE_CONTEXT,
parentNode: createDatasetSessionNode(createISession(), createIProfile()),
profile: createIProfile(),
});
await (dsNode as any).datasetRecalled(false);
expect(dsNode.resourceUri).toStrictEqual(
vscode.Uri.from({
scheme: ZoweScheme.DS,
path: "/sestest/MIGRATED.PS",
})
);
expect(dsNode.command).toStrictEqual({ command: "vscode.open", title: "", arguments: [dsNode.resourceUri] });
});

it("creates a file system entry - PDS", async () => {
const dsNode = new ZoweDatasetNode({
label: "MIGRATED.PDS",
collapsibleState: vscode.TreeItemCollapsibleState.None,
contextOverride: Constants.DS_MIGRATED_FILE_CONTEXT,
profile: createIProfile(),
});
const createDirMock = jest.spyOn(vscode.workspace.fs, "createDirectory").mockImplementation();
await (dsNode as any).datasetRecalled(true);
expect(createDirMock).toHaveBeenCalledWith(dsNode.resourceUri);
});

it("creates a file system entry - PS", async () => {
const dsNode = new ZoweDatasetNode({
label: "MIGRATED.PS",
collapsibleState: vscode.TreeItemCollapsibleState.None,
contextOverride: Constants.DS_MIGRATED_FILE_CONTEXT,
parentNode: createDatasetSessionNode(createISession(), createIProfile()),
profile: createIProfile(),
});
const writeFileMock = jest.spyOn(vscode.workspace.fs, "writeFile").mockImplementation();
await (dsNode as any).datasetRecalled(false);
expect(writeFileMock).toHaveBeenCalledWith(dsNode.resourceUri, new Uint8Array());
});

it("updates the icon to folder - PDS", async () => {
const dsNode = new ZoweDatasetNode({
label: "MIGRATED.PDS",
collapsibleState: vscode.TreeItemCollapsibleState.None,
contextOverride: Constants.DS_MIGRATED_FILE_CONTEXT,
profile: createIProfile(),
});
await (dsNode as any).datasetRecalled(true);
expect(dsNode.iconPath).toBe(IconGenerator.getIconById(IconUtils.IconId.folder).path);
});

it("updates the icon to file - PS", async () => {
const dsNode = new ZoweDatasetNode({
label: "MIGRATED.PS",
collapsibleState: vscode.TreeItemCollapsibleState.None,
contextOverride: Constants.DS_MIGRATED_FILE_CONTEXT,
profile: createIProfile(),
});
await (dsNode as any).datasetRecalled(false);
expect(dsNode.iconPath).toBe(IconGenerator.getIconById(IconUtils.IconId.document).path);
});
});

describe("ZoweDatasetNode Unit Tests - function datasetMigrated", () => {
it("changes the collapsible state", () => {
const dsNode = new ZoweDatasetNode({
label: "SOME.PDS",
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
contextOverride: Constants.DS_PDS_CONTEXT,
profile: createIProfile(),
});
dsNode.datasetMigrated();
expect(dsNode.collapsibleState).toBe(vscode.TreeItemCollapsibleState.None);
});

it("removes the resource URI and command", () => {
const dsNode = new ZoweDatasetNode({
label: "SOME.PDS",
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
contextOverride: Constants.DS_PDS_CONTEXT,
parentNode: createDatasetSessionNode(createISession(), createIProfile()),
profile: createIProfile(),
});
dsNode.datasetMigrated();
expect(dsNode.resourceUri).toBeUndefined();
expect(dsNode.command).toBeUndefined();
});

it("removes the file system entry", () => {
const dsNode = new ZoweDatasetNode({
label: "MIGRATED.PDS",
collapsibleState: vscode.TreeItemCollapsibleState.None,
contextOverride: Constants.DS_MIGRATED_FILE_CONTEXT,
profile: createIProfile(),
});
const uri = dsNode.resourceUri;
const removeEntryMock = jest.spyOn(DatasetFSProvider.instance, "removeEntry").mockImplementation();
dsNode.datasetMigrated();
expect(removeEntryMock).toHaveBeenCalledWith(uri);
});

it("changes the icon to the migrated icon", () => {
const dsNode = new ZoweDatasetNode({
label: "MIGRATED.PDS",
collapsibleState: vscode.TreeItemCollapsibleState.None,
contextOverride: Constants.DS_MIGRATED_FILE_CONTEXT,
profile: createIProfile(),
});
dsNode.datasetMigrated();
expect(dsNode.iconPath).toBe(IconGenerator.getIconById(IconUtils.IconId.migrated).path);
});
});
16 changes: 4 additions & 12 deletions packages/zowe-explorer/src/trees/dataset/DatasetActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1371,17 +1371,15 @@ export class DatasetActions {
if (Profiles.getInstance().validProfile !== Validation.ValidationType.INVALID) {
const { dataSetName } = DatasetUtils.getNodeLabels(node);
try {
const response = await ZoweExplorerApiRegister.getMvsApi(node.getProfile()).hMigrateDataSet(dataSetName);
Gui.showMessage(
vscode.l10n.t({
message: "Migration of data set {0} requested.",
args: [dataSetName],
comment: ["Data Set name"],
})
);
node.contextValue = Constants.DS_MIGRATED_FILE_CONTEXT;
node.setIcon(IconGenerator.getIconByNode(node).path);
datasetProvider.refresh();
const response = await ZoweExplorerApiRegister.getMvsApi(node.getProfile()).hMigrateDataSet(dataSetName);
datasetProvider.refreshElement(node.getParent());
return response;
} catch (err) {
ZoweLogger.error(err);
Expand All @@ -1405,21 +1403,15 @@ export class DatasetActions {
if (Profiles.getInstance().validProfile !== Validation.ValidationType.INVALID) {
const { dataSetName } = DatasetUtils.getNodeLabels(node);
try {
const response = await ZoweExplorerApiRegister.getMvsApi(node.getProfile()).hRecallDataSet(dataSetName);
Gui.showMessage(
vscode.l10n.t({
message: "Recall of data set {0} requested.",
args: [dataSetName],
comment: ["Data Set name"],
})
);
if (node.collapsibleState !== vscode.TreeItemCollapsibleState.None) {
node.contextValue = Constants.DS_PDS_CONTEXT;
} else {
node.contextValue = (await node.getEncoding())?.kind === "binary" ? Constants.DS_DS_BINARY_CONTEXT : Constants.DS_DS_CONTEXT;
}
node.setIcon(IconGenerator.getIconByNode(node).path);
datasetProvider.refresh();
const response = await ZoweExplorerApiRegister.getMvsApi(node.getProfile()).hRecallDataSet(dataSetName);
datasetProvider.refreshElement(node.getParent());
return response;
} catch (err) {
ZoweLogger.error(err);
Expand Down
94 changes: 77 additions & 17 deletions packages/zowe-explorer/src/trees/dataset/ZoweDatasetNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export class ZoweDatasetNode extends ZoweTreeNode implements IZoweDatasetTreeNod
*
* @param {IZoweTreeOpts} opts
*/

public constructor(opts: Definitions.IZoweDatasetTreeOpts) {
super(opts.label, opts.collapsibleState, opts.parentNode, opts.session, opts.profile);
if (opts.encoding != null) {
Expand Down Expand Up @@ -101,13 +102,9 @@ export class ZoweDatasetNode extends ZoweTreeNode implements IZoweDatasetTreeNod
this.id = this.label as string;
}

if (this.label !== vscode.l10n.t("Favorites")) {
if (this.label !== vscode.l10n.t("Favorites") && this.contextValue !== Constants.DS_MIGRATED_FILE_CONTEXT) {
const sessionLabel = opts.profile?.name ?? SharedUtils.getSessionLabel(this);
if (
this.contextValue === Constants.DS_DS_CONTEXT ||
this.contextValue === Constants.DS_PDS_CONTEXT ||
this.contextValue === Constants.DS_MIGRATED_FILE_CONTEXT
) {
if (this.contextValue === Constants.DS_DS_CONTEXT || this.contextValue === Constants.DS_PDS_CONTEXT) {
this.resourceUri = vscode.Uri.from({
scheme: ZoweScheme.DS,
path: `/${sessionLabel}/${this.label as string}`,
Expand Down Expand Up @@ -141,7 +138,6 @@ export class ZoweDatasetNode extends ZoweTreeNode implements IZoweDatasetTreeNod
}
}
}

public updateStats(item: any): void {
if ("c4date" in item && "m4date" in item) {
const { m4date, mtime, msec }: { m4date: string; mtime: string; msec: string } = item;
Expand Down Expand Up @@ -194,6 +190,65 @@ export class ZoweDatasetNode extends ZoweTreeNode implements IZoweDatasetTreeNod
return dsEntry.stats;
}

/**
* Updates this node so the recalled data set can be interacted with.
* @param isPds Whether the data set is a PDS
*/
private async datasetRecalled(isPds: boolean): Promise<void> {
// Change context value to match dsorg, update collapsible state and assign resource URI
// Preserve favorite context and any additional context values
this.contextValue = this.contextValue.replace(Constants.DS_MIGRATED_FILE_CONTEXT, isPds ? Constants.DS_PDS_CONTEXT : Constants.DS_DS_CONTEXT);
this.collapsibleState = isPds ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None;
this.resourceUri = vscode.Uri.from({
scheme: ZoweScheme.DS,
path: `/${SharedUtils.getSessionLabel(this)}/${this.label as string}`,
});

// Replace icon on existing node with new one
const icon = IconGenerator.getIconByNode(this);
if (icon) {
this.setIcon(icon.path);
}

// Create entry in the filesystem to represent the recalled data set
if (isPds) {
await vscode.workspace.fs.createDirectory(this.resourceUri);
} else {
this.command = { command: "vscode.open", title: "", arguments: [this.resourceUri] };
if (!DatasetFSProvider.instance.exists(this.resourceUri)) {
await vscode.workspace.fs.writeFile(this.resourceUri, new Uint8Array());
}
}
}

/**
* Updates this data set node so it is marked as migrated.
*/
public datasetMigrated(): void {
// Change the context value and collapsible state to represent a migrated data set
// Preserve favorite context and any additional context values
const isBinary = SharedContext.isBinary(this);
const isPds = this.collapsibleState !== vscode.TreeItemCollapsibleState.None;
let previousContext = isBinary ? Constants.DS_DS_BINARY_CONTEXT : Constants.DS_DS_CONTEXT;
if (isPds) {
previousContext = Constants.DS_PDS_CONTEXT;
}
this.contextValue = this.contextValue.replace(previousContext, Constants.DS_MIGRATED_FILE_CONTEXT);
this.collapsibleState = vscode.TreeItemCollapsibleState.None;

// Remove the entry from the file system
DatasetFSProvider.instance.removeEntry(this.resourceUri);

// Remove the node's resource URI and command
this.resourceUri = this.command = undefined;

// Assign migrated icon to the data set node
const icon = IconGenerator.getIconByNode(this);
if (icon) {
this.setIcon(icon.path);
}
}

/**
* Retrieves child nodes of this ZoweDatasetNode
*
Expand Down Expand Up @@ -253,7 +308,22 @@ export class ZoweDatasetNode extends ZoweTreeNode implements IZoweDatasetTreeNod
let dsNode = existingItems[item.dsname ?? item.member];
if (dsNode != null) {
elementChildren[dsNode.label.toString()] = dsNode;
if (SharedContext.isMigrated(dsNode) && item.migr?.toUpperCase() !== "YES") {
await dsNode.datasetRecalled(item.dsorg === "PO" || item.dsorg === "PO-E");
} else if (!SharedContext.isMigrated(dsNode) && item.migr?.toUpperCase() === "YES") {
dsNode.datasetMigrated();
}
// Creates a ZoweDatasetNode for a PDS
} else if (item.migr && item.migr.toUpperCase() === "YES") {
dsNode = new ZoweDatasetNode({
label: item.dsname,
collapsibleState: vscode.TreeItemCollapsibleState.None,
parentNode: this,
contextOverride: Constants.DS_MIGRATED_FILE_CONTEXT,
profile: cachedProfile,
});
elementChildren[dsNode.label.toString()] = dsNode;
// Creates a ZoweDatasetNode for a VSAM file
} else if (item.dsorg === "PO" || item.dsorg === "PO-E") {
dsNode = new ZoweDatasetNode({
label: item.dsname,
Expand All @@ -275,16 +345,6 @@ export class ZoweDatasetNode extends ZoweTreeNode implements IZoweDatasetTreeNod
dsNode.errorDetails = item.error; // Save imperative error to avoid extra z/OS requests
elementChildren[dsNode.label.toString()] = dsNode;
// Creates a ZoweDatasetNode for a migrated dataset
} else if (item.migr && item.migr.toUpperCase() === "YES") {
dsNode = new ZoweDatasetNode({
label: item.dsname,
collapsibleState: vscode.TreeItemCollapsibleState.None,
parentNode: this,
contextOverride: Constants.DS_MIGRATED_FILE_CONTEXT,
profile: cachedProfile,
});
elementChildren[dsNode.label.toString()] = dsNode;
// Creates a ZoweDatasetNode for a VSAM file
} else if (item.dsorg === "VS") {
let altLabel = item.dsname;
let endPoint = altLabel.indexOf(".DATA");
Expand Down

0 comments on commit d68621f

Please sign in to comment.