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

fix: add extensions back to Data Set resources; resolve USS copy/paste bug #3127

Merged
merged 22 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
bacc920
wip(fix): add extensions back to data set resources
traeok Sep 19, 2024
c3f18cc
chore: update ZE changelog
traeok Sep 19, 2024
39af8cb
tests: Add patch coverage for extensionRemovedFromPath
traeok Sep 19, 2024
16a3bf7
tests: add case for deleting a pds
traeok Sep 19, 2024
4f416b1
chore: update ZE API changelog
traeok Sep 19, 2024
192e796
chore: update typedoc for DatasetUtils.getExtension
traeok Sep 19, 2024
f0a5baa
refactor: remove unneeded withExtension method and update ref
traeok Sep 19, 2024
0714f25
fix(uss): Copy/paste bugs
traeok Sep 19, 2024
77a4f21
tests(uss): comment copyTree tests for refactor
traeok Sep 19, 2024
21e729e
wip(tests): Same profile cases for copyTree
traeok Sep 19, 2024
5a7a669
wip(tests): copyTree w/ folder and diff profiles (collisions)
traeok Sep 19, 2024
674d4bb
tests: Remaining cases for copyTree, add success check & test case
traeok Sep 19, 2024
2ad426e
chore: run prepublish, update comment in code
traeok Sep 19, 2024
c66f196
refactor: make extensionRemovedFromPath public, add typedoc
traeok Sep 19, 2024
76e7366
chore: add USS bugfix to changelog
traeok Sep 19, 2024
d7a30e4
refactor: Move matches & extensions into map
traeok Sep 20, 2024
6c41742
tests: add 'ASSEMBLY' and 'SPFLOGS' to getExtension test case
traeok Sep 20, 2024
eb06448
refactor: Support RegExp in extension map
traeok Sep 21, 2024
1255657
Merge branch 'main' into fix/add-ds-extensions
traeok Sep 23, 2024
ef996c8
changes running pre-publish
JillieBeanSim Sep 23, 2024
8b32e87
Merge branch 'main' into fix/add-ds-extensions
traeok Sep 23, 2024
e250473
Merge branch 'main' into fix/add-ds-extensions
traeok Sep 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/zowe-explorer-api/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ All notable changes to the "zowe-explorer-api" extension will be documented in t
- Added the `BaseProfileAuthOptions` interface to define base profile authentication options for SSO login and logout. [#3076](https://github.com/zowe/zowe-explorer-vscode/pull/3076)
- Deprecated the methods `ZoweVsCodeExtension.loginWithBaseProfile` and `ZoweVsCodeExtension.logoutWithBaseProfile`. Use `ZoweVsCodeExtension.ssoLogin` and `ZoweVsCodeExtension.ssoLogout` instead, which use the `BaseProfileAuthOptions` interface and allow you to choose whether the token value in the base profile should have precedence in case there are conflicts. [#3076](https://github.com/zowe/zowe-explorer-vscode/pull/3076)
- Fixed bug in `ProfilesCache` class where old profiles were still accessible after deleting a Team configuration file. [#3124](https://github.com/zowe/zowe-explorer-vscode/issues/3124)
- Added `extensionRemovedFromPath` private function to the `DsEntry` class to allow removing the extension from a data set before making API calls. [#3121](https://github.com/zowe/zowe-explorer-vscode/issues/3121)

### Bug fixes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,23 @@ describe("DsEntryMetadata", () => {
});
expect(pdsEntryMeta.dsName).toBe("TEST.PDS(MEMBER)");
});

describe("extensionRemovedFromPath", () => {
it("returns a path without its extension", () => {
const fakeProfile: any = { name: "testProfile" };
const entryMeta = new DsEntryMetadata({
profile: fakeProfile,
path: "/testProfile/TEST.COBOL.DS.cbl",
});
expect((entryMeta as any).extensionRemovedFromPath()).toBe("/testProfile/TEST.COBOL.DS");
});
it("returns a path as-is if no extension is detected", () => {
const fakeProfile: any = { name: "testProfile" };
const entryMeta = new DsEntryMetadata({
profile: fakeProfile,
path: "/testProfile/TEST.SOME.DS",
});
expect((entryMeta as any).extensionRemovedFromPath()).toBe("/testProfile/TEST.SOME.DS");
});
});
});
29 changes: 28 additions & 1 deletion packages/zowe-explorer-api/src/fs/types/datasets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@ interface DsEntryProps {
stats: Types.DatasetStats;
}

export const DS_EXTENSION_MAP: Map<string, (string | RegExp)[]> = new Map([
[".c", ["C"]],
[".jcl", ["JCL", "JCLLIB", "CNTL", "PROC", "PROCLIB"]],
[".cbl", ["COBOL", "CBL", "COB", "SCBL"]],
[".cpy", ["COPYBOOK", "COPY", "CPY", "COBCOPY"]],
[".inc", ["INC", "INCLUDE", "PLINC"]],
[".pli", ["PLI", "PL1", "PLX", "PCX"]],
[".sh", ["SH", "SHELL"]],
[".rexx", ["REXX", "REXEC", "EXEC"]],
[".xml", ["XML"]],
[".asm", ["ASM", /ASSEMBL/]],
[".log", ["LOG", /SPFLOG/]],
]);

export class DsEntry extends FileEntry implements DsEntryProps {
public metadata: DsEntryMetadata;

Expand Down Expand Up @@ -47,8 +61,21 @@ export class DsEntryMetadata implements EntryMetadata {
this.path = metadata.path;
}

/**
* @returns the data set's file system path without the extension
*/
public extensionRemovedFromPath(): string {
for (const ext of DS_EXTENSION_MAP.keys()) {
if (this.path.endsWith(ext)) {
return this.path.replace(ext, "");
}
}

return this.path;
}

public get dsName(): string {
const segments = this.path.split("/").filter(Boolean);
const segments = this.extensionRemovedFromPath().split("/").filter(Boolean);
return segments[1] ? `${segments[0]}(${segments[1]})` : segments[0];
}
}
2 changes: 2 additions & 0 deletions packages/zowe-explorer/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen
- Fixed behavior of logout action when token is defined in both base profile and parent profile. [#3076](https://github.com/zowe/zowe-explorer-vscode/issues/3076)
- Fixed bug that displayed obsolete profiles in the Zowe Explorer tree views after the associated team configuration file was deleted. [#3124](https://github.com/zowe/zowe-explorer-vscode/issues/3124)
- Fix issue with extender profiles not being included in fresh team configuration file. [#3122](https://github.com/zowe/zowe-explorer-vscode/issues/3122)
- Fixed issue where file extensions were removed from data sets, causing language detection to sometimes fail for Zowe Explorer extenders. [#3121](https://github.com/zowe/zowe-explorer-vscode/issues/3121)
- Fixed an issue where copying and pasting a file/folder in the USS tree would fail abruptly, displaying an error. [#3128](https://github.com/zowe/zowe-explorer-vscode/issues/3128)

## `3.0.0-next.202409132122`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,38 +9,50 @@
*
*/

import { Disposable, FilePermission, FileSystemError, FileType, languages, TextDocument, TextEditor, Uri } from "vscode";
import { Disposable, FilePermission, FileSystemError, FileType, TextEditor, Uri } from "vscode";
import { createIProfile } from "../../../__mocks__/mockCreators/shared";
import { DirEntry, DsEntry, FileEntry, FilterEntry, FsAbstractUtils, Gui, PdsEntry, ZoweScheme } from "@zowe/zowe-explorer-api";
import {
DirEntry,
DsEntry,
DsEntryMetadata,
FileEntry,
FilterEntry,
FsAbstractUtils,
FsDatasetsUtils,
Gui,
PdsEntry,
ZoweScheme,
} from "@zowe/zowe-explorer-api";
import { MockedProperty } from "../../../__mocks__/mockUtils";
import { DatasetFSProvider } from "../../../../src/trees/dataset/DatasetFSProvider";
import { ZoweExplorerApiRegister } from "../../../../src/extending/ZoweExplorerApiRegister";
import { ZoweLogger } from "../../../../src/tools/ZoweLogger";
const dayjs = require("dayjs");

const testProfile = createIProfile();
const testEntries = {
ps: {
...new DsEntry("USER.DATA.PS", false),
metadata: {
metadata: new DsEntryMetadata({
profile: testProfile,
path: "/USER.DATA.PS",
},
}),
etag: "OLDETAG",
isMember: false,
} as DsEntry,
pds: {
...new PdsEntry("USER.DATA.PDS"),
metadata: {
metadata: new DsEntryMetadata({
profile: testProfile,
path: "/USER.DATA.PDS",
},
}),
} as PdsEntry,
pdsMember: {
...new DsEntry("MEMBER1", true),
metadata: {
metadata: new DsEntryMetadata({
profile: testProfile,
path: "/USER.DATA.PDS/MEMBER1",
},
}),
isMember: true,
} as DsEntry,
session: {
...new FilterEntry("sestest"),
Expand Down Expand Up @@ -743,7 +755,7 @@ describe("fetchDataset", () => {
mvsApiMock.mockRestore();
});

it("existing URI - PS", async () => {
it("existing URI", async () => {
const fakePs = Object.assign(Object.create(Object.getPrototypeOf(testEntries.ps)), testEntries.ps);
const lookupMock = jest.spyOn(DatasetFSProvider.instance, "lookup").mockReturnValue(fakePs);
const writeFileSpy = jest.spyOn(DatasetFSProvider.instance as any, "writeFile");
Expand Down Expand Up @@ -841,6 +853,26 @@ describe("delete", () => {
mvsApiMock.mockRestore();
});

it("successfully deletes a PDS", async () => {
const fakePds = { ...testEntries.pds };
const mockMvsApi = {
deleteDataSet: jest.fn(),
};
const mvsApiMock = jest.spyOn(ZoweExplorerApiRegister, "getMvsApi").mockReturnValueOnce(mockMvsApi as any);
const _lookupMock = jest.spyOn(DatasetFSProvider.instance as any, "lookup").mockReturnValueOnce(fakePds);
const _fireSoonMock = jest.spyOn(DatasetFSProvider.instance as any, "_fireSoon").mockImplementation();
const isPdsEntry = jest.spyOn(FsDatasetsUtils, "isPdsEntry").mockReturnValueOnce(true);
jest.spyOn(DatasetFSProvider.instance as any, "_lookupParentDirectory").mockReturnValueOnce({ ...testEntries.session });

await DatasetFSProvider.instance.delete(testUris.pds, { recursive: false });
expect(mockMvsApi.deleteDataSet).toHaveBeenCalledWith(fakePds.name, { responseTimeout: undefined });
expect(_lookupMock).toHaveBeenCalledWith(testUris.pds, false);
expect(_fireSoonMock).toHaveBeenCalled();

mvsApiMock.mockRestore();
isPdsEntry.mockRestore();
});

it("throws an error if it could not delete an entry", async () => {
const fakePs = { ...testEntries.ps };
const fakeSession = { ...testEntries.session, entries: new Map() };
Expand Down Expand Up @@ -879,7 +911,7 @@ describe("rename", () => {
it("renames a PS", async () => {
const oldPs = { ...testEntries.ps };
const mockMvsApi = {
renameDataSetMember: jest.fn(),
renameDataSet: jest.fn(),
};
const mvsApiMock = jest.spyOn(ZoweExplorerApiRegister, "getMvsApi").mockReturnValueOnce(mockMvsApi as any);
const _lookupMock = jest
Expand All @@ -889,7 +921,7 @@ describe("rename", () => {
.spyOn(DatasetFSProvider.instance as any, "_lookupParentDirectory")
.mockReturnValueOnce({ ...testEntries.session });
await DatasetFSProvider.instance.rename(testUris.ps, testUris.ps.with({ path: "/USER.DATA.PS2" }), { overwrite: true });
expect(mockMvsApi.renameDataSetMember).toHaveBeenCalledWith("", "USER.DATA.PS", "USER.DATA.PS2");
expect(mockMvsApi.renameDataSet).toHaveBeenCalledWith("USER.DATA.PS", "USER.DATA.PS2");
_lookupMock.mockRestore();
mvsApiMock.mockRestore();
_lookupParentDirectoryMock.mockRestore();
Expand Down Expand Up @@ -946,72 +978,3 @@ describe("rename", () => {
_lookupParentDirectoryMock.mockRestore();
});
});

describe("onDidOpenTextDocument", () => {
const setTextDocLanguage = jest.spyOn(languages, "setTextDocumentLanguage");

afterEach(() => {
setTextDocLanguage.mockClear();
});

it("handles ZoweScheme.DS documents", async () => {
const dsUri = Uri.from({
path: "/profile/USER.WONDROUS.C/AMAZING",
scheme: ZoweScheme.DS,
});
const doc = {
uri: dsUri,
languageId: undefined,
} as unknown as TextDocument;
await DatasetFSProvider.onDidOpenTextDocument(doc);
expect(setTextDocLanguage).toHaveBeenCalledWith(doc, "c");
});

it("returns early if the language ID could not be identified", async () => {
const dsUri = Uri.from({
path: "/profile/TEST.DS/AMAZING",
scheme: ZoweScheme.DS,
});
const doc = {
uri: dsUri,
languageId: undefined,
} as unknown as TextDocument;
await DatasetFSProvider.onDidOpenTextDocument(doc);
expect(setTextDocLanguage).not.toHaveBeenCalled();
});

it("returns early if the scheme is not ZoweScheme.DS", async () => {
const fileUri = Uri.from({
path: "/var/www/AMAZING.txt",
scheme: "file",
});
const doc = {
uri: fileUri,
languageId: "plaintext",
} as unknown as TextDocument;
await DatasetFSProvider.onDidOpenTextDocument(doc);
expect(setTextDocLanguage).not.toHaveBeenCalled();
expect(doc.languageId).toBe("plaintext");
});

it("handles an error when setting the language ID", async () => {
setTextDocLanguage.mockImplementationOnce(() => {
throw new Error("Not available");
});
const dsUri = Uri.from({
path: "/profile/TEST.C.DS/MISSING",
scheme: ZoweScheme.DS,
});
const doc = {
fileName: "MISSING",
uri: dsUri,
languageId: "rust",
} as unknown as TextDocument;

const warnSpy = jest.spyOn(ZoweLogger, "warn");
await DatasetFSProvider.onDidOpenTextDocument(doc);
expect(setTextDocLanguage).toHaveBeenCalled();
expect(warnSpy).toHaveBeenCalledWith("Could not set document language for MISSING - tried languageId 'c'");
expect(doc.languageId).toBe("rust");
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,29 @@

import { DatasetUtils } from "../../../../src/trees/dataset/DatasetUtils";

describe("Dataset utils unit tests - function getLanguageId", () => {
it("returns the proper language ID", () => {
describe("Dataset utils unit tests - function getExtension", () => {
it("returns the proper file extension", () => {
const pairs = [
{ name: "TEST.DS.C", languageId: "c" },
{ name: "TEST.PDS.C(MEMBER)", languageId: "c" },
{ name: "TEST.DS.JCL", languageId: "jcl" },
{ name: "TEST.DS.CBL", languageId: "cobol" },
{ name: "TEST.PDS.CPY(M1)", languageId: "copybook" },
{ name: "TEST.DS.INCLUDE", languageId: "inc" },
{ name: "TEST.DS.PLX", languageId: "pli" },
{ name: "TEST.DS.SHELL", languageId: "shellscript" },
{ name: "TEST.DS.EXEC", languageId: "rexx" },
{ name: "TEST.DS.XML", languageId: "xml" },
{ name: "TEST.DS.ASM", languageId: "asm" },
{ name: "TEST.DS.LOG", languageId: "log" },
{ name: "TEST.DS.C", extension: ".c" },
{ name: "TEST.PDS.C(MEMBER)", extension: ".c" },
{ name: "TEST.DS.JCL", extension: ".jcl" },
{ name: "TEST.DS.CBL", extension: ".cbl" },
{ name: "TEST.PDS.CPY(M1)", extension: ".cpy" },
{ name: "TEST.DS.INCLUDE", extension: ".inc" },
{ name: "TEST.DS.PLX", extension: ".pli" },
{ name: "TEST.DS.SHELL", extension: ".sh" },
{ name: "TEST.DS.EXEC", extension: ".rexx" },
{ name: "TEST.DS.XML", extension: ".xml" },
{ name: "TEST.DS.ASM", extension: ".asm" },
{ name: "TEST.DS.ASSEMBLY", extension: ".asm" },
{ name: "TEST.DS.LOG", extension: ".log" },
{ name: "TEST.DS.SPFLOGS", extension: ".log" },
];
for (const pair of pairs) {
expect(DatasetUtils.getLanguageId(pair.name)).toBe(pair.languageId);
expect(DatasetUtils.getExtension(pair.name)).toBe(pair.extension);
}
});
it("returns null if no language ID was found", () => {
expect(DatasetUtils.getLanguageId("TEST.DS")).toBe(null);
it("returns null if no language was detected", () => {
expect(DatasetUtils.getExtension("TEST.DS")).toBe(null);
});
});
Loading
Loading