Skip to content

Commit

Permalink
Merge pull request #3127 from zowe/fix/add-ds-extensions
Browse files Browse the repository at this point in the history
fix: add extensions back to Data Set resources; resolve USS copy/paste bug
  • Loading branch information
JillieBeanSim authored Sep 23, 2024
2 parents 45fb36f + e250473 commit 3c524b8
Show file tree
Hide file tree
Showing 15 changed files with 469 additions and 360 deletions.
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

0 comments on commit 3c524b8

Please sign in to comment.