From bacc9202f161c2443759cc3c3f5b4bcd5a57927b Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Thu, 19 Sep 2024 11:52:51 -0400 Subject: [PATCH 01/19] wip(fix): add extensions back to data set resources Signed-off-by: Trae Yelovich --- .../src/fs/types/datasets.ts | 12 ++- .../dataset/DatasetFSProvider.unit.test.ts | 94 +++---------------- .../trees/dataset/DatasetUtils.unit.test.ts | 34 +++---- .../src/trees/dataset/DatasetFSProvider.ts | 83 +++++++--------- .../src/trees/dataset/DatasetInit.ts | 1 - .../src/trees/dataset/DatasetUtils.ts | 29 +++--- .../src/trees/dataset/ZoweDatasetNode.ts | 6 +- 7 files changed, 99 insertions(+), 160 deletions(-) diff --git a/packages/zowe-explorer-api/src/fs/types/datasets.ts b/packages/zowe-explorer-api/src/fs/types/datasets.ts index 32d8291804..fd1fd073a4 100644 --- a/packages/zowe-explorer-api/src/fs/types/datasets.ts +++ b/packages/zowe-explorer-api/src/fs/types/datasets.ts @@ -47,8 +47,18 @@ export class DsEntryMetadata implements EntryMetadata { this.path = metadata.path; } + private extensionRemovedFromPath(): string { + for (const ext of [".c", ".jcl", ".cobol", ".cpy", ".inc", ".pli", ".sh", ".rexx", ".xml", ".asm", ".log"]) { + 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]; } } diff --git a/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetFSProvider.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetFSProvider.unit.test.ts index cf5c22d0e7..e3f80ddae7 100644 --- a/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetFSProvider.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetFSProvider.unit.test.ts @@ -9,38 +9,39 @@ * */ -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, 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"), @@ -757,7 +758,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"); @@ -893,7 +894,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 @@ -903,7 +904,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(); @@ -960,72 +961,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"); - }); -}); diff --git a/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetUtils.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetUtils.unit.test.ts index f92e532578..950e4e4cd2 100644 --- a/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetUtils.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetUtils.unit.test.ts @@ -11,27 +11,27 @@ 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: ".cobol" }, + { 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.LOG", 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); }); }); diff --git a/packages/zowe-explorer/src/trees/dataset/DatasetFSProvider.ts b/packages/zowe-explorer/src/trees/dataset/DatasetFSProvider.ts index 0846cf4809..eed601fe87 100644 --- a/packages/zowe-explorer/src/trees/dataset/DatasetFSProvider.ts +++ b/packages/zowe-explorer/src/trees/dataset/DatasetFSProvider.ts @@ -28,11 +28,11 @@ import { FileEntry, } from "@zowe/zowe-explorer-api"; import { IZosFilesResponse } from "@zowe/zos-files-for-zowe-sdk"; -import { DatasetUtils } from "./DatasetUtils"; import { Profiles } from "../../configuration/Profiles"; import { ZoweExplorerApiRegister } from "../../extending/ZoweExplorerApiRegister"; import { ZoweLogger } from "../../tools/ZoweLogger"; import * as dayjs from "dayjs"; +import { DatasetUtils } from "./DatasetUtils"; export class DatasetFSProvider extends BaseProvider implements vscode.FileSystemProvider { private static _instance: DatasetFSProvider; @@ -55,29 +55,6 @@ export class DatasetFSProvider extends BaseProvider implements vscode.FileSystem return DatasetFSProvider._instance; } - /** - * onDidOpenTextDocument event listener for the dataset provider. - * Updates the opened document with the correct language ID. - * @param doc The document received from the onDidOpenTextDocument event. - */ - public static async onDidOpenTextDocument(this: void, doc: vscode.TextDocument): Promise { - if (doc.uri.scheme !== ZoweScheme.DS) { - return; - } - - const parentPath = path.posix.basename(path.posix.join(doc.uri.path, "..")); - const languageId = DatasetUtils.getLanguageId(parentPath); - if (languageId == null) { - return; - } - - try { - await vscode.languages.setTextDocumentLanguage(doc, languageId); - } catch (err) { - ZoweLogger.warn(`Could not set document language for ${doc.fileName} - tried languageId '${languageId}'`); - } - } - public watch(_uri: vscode.Uri, _options: { readonly recursive: boolean; readonly excludes: readonly string[] }): vscode.Disposable { // ignore, fires for all changes... return new vscode.Disposable(() => {}); @@ -118,8 +95,8 @@ export class DatasetFSProvider extends BaseProvider implements vscode.FileSystem const pds = this._lookupParentDirectory(uri); resp = await ZoweExplorerApiRegister.getMvsApi(uriInfo.profile).allMembers(pds.name, { attributes: true }); } else { - // PDS or Data Set - resp = await ZoweExplorerApiRegister.getMvsApi(uriInfo.profile).dataSet(path.posix.basename(uri.path), { attributes: true }); + // Data Set + resp = await ZoweExplorerApiRegister.getMvsApi(uriInfo.profile).dataSet(path.parse(uri.path).name, { attributes: true }); } // Attempt to parse a successful API response and update the data set's cached stats. @@ -166,24 +143,23 @@ export class DatasetFSProvider extends BaseProvider implements vscode.FileSystem for (const ds of resp.apiResponse?.items ?? resp.apiResponse ?? []) { let tempEntry = profileEntry.entries.get(ds.dsname); if (tempEntry == null) { + let name = ds.dsname; if (ds.dsorg === "PO" || ds.dsorg === "PO-E") { // Entry is a PDS tempEntry = new PdsEntry(ds.dsname); } else if (ds.dsorg === "VS") { // TODO: Add VSAM and ZFS support in Zowe Explorer continue; - } else if (ds.migr?.toUpperCase() === "YES") { - // migrated - tempEntry = new DsEntry(ds.dsname, false); } else { - // PS - tempEntry = new DsEntry(ds.dsname, false); + // PS or migrated + name = DatasetUtils.withExtension(ds.dsname as string); + tempEntry = new DsEntry(name, false); } tempEntry.metadata = new DsEntryMetadata({ ...profileEntry.metadata, - path: path.posix.join(profileEntry.metadata.path, ds.dsname), + path: path.posix.join(profileEntry.metadata.path, name), }); - profileEntry.entries.set(ds.dsname, tempEntry); + profileEntry.entries.set(name, tempEntry); } } } @@ -193,13 +169,15 @@ export class DatasetFSProvider extends BaseProvider implements vscode.FileSystem private async fetchEntriesForDataset(entry: PdsEntry, uri: vscode.Uri, uriInfo: UriFsInfo): Promise { const members = await ZoweExplorerApiRegister.getMvsApi(uriInfo.profile).allMembers(path.posix.basename(uri.path)); + const pdsExtension = DatasetUtils.getExtension(entry.name); for (const ds of members.apiResponse?.items || []) { - let tempEntry = entry.entries.get(ds.member); + const fullMemberName = `${ds.member as string}${pdsExtension ?? ""}`; + let tempEntry = entry.entries.get(fullMemberName); if (tempEntry == null) { - tempEntry = new DsEntry(ds.member, true); - tempEntry.metadata = new DsEntryMetadata({ ...entry.metadata, path: path.posix.join(entry.metadata.path, ds.member) }); - entry.entries.set(ds.member, tempEntry); + tempEntry = new DsEntry(fullMemberName, true); + tempEntry.metadata = new DsEntryMetadata({ ...entry.metadata, path: path.posix.join(entry.metadata.path, fullMemberName) }); + entry.entries.set(fullMemberName, tempEntry); } } } @@ -220,10 +198,13 @@ export class DatasetFSProvider extends BaseProvider implements vscode.FileSystem const entryExists = entry != null; let entryIsDir = entry != null ? entry.type === vscode.FileType.Directory : false; + // /DATA.SET/MEMBER const uriPath = uri.path.substring(uriInfo.slashAfterProfilePos + 1).split("/"); const pdsMember = uriPath.length === 2; if (!entryExists) { - const resp = await ZoweExplorerApiRegister.getMvsApi(uriInfo.profile).dataSet(uriPath[0], { attributes: true }); + const resp = await ZoweExplorerApiRegister.getMvsApi(uriInfo.profile).dataSet(entryIsDir ? uriPath[0] : path.parse(uriPath[0]).name, { + attributes: true, + }); if (resp.success) { const dsorg: string = resp.apiResponse?.items?.[0]?.dsorg; entryIsDir = pdsMember ? false : dsorg?.startsWith("PO") ?? false; @@ -446,13 +427,11 @@ export class DatasetFSProvider extends BaseProvider implements vscode.FileSystem private async uploadEntry(parent: DirEntry, entry: DsEntry, content: Uint8Array, forceUpload?: boolean): Promise { const statusMsg = Gui.setStatusBarMessage(vscode.l10n.t("$(sync~spin) Saving data set...")); - const isPdsMember = FsDatasetsUtils.isPdsEntry(parent) && !FsAbstractUtils.isFilterEntry(parent); - const fullName = isPdsMember ? `${parent.name}(${entry.name})` : entry.name; let resp: IZosFilesResponse; try { const mvsApi = ZoweExplorerApiRegister.getMvsApi(entry.metadata.profile); const profileEncoding = entry.encoding ? null : entry.metadata.profile.profile?.encoding; - resp = await mvsApi.uploadFromBuffer(Buffer.from(content), fullName, { + resp = await mvsApi.uploadFromBuffer(Buffer.from(content), entry.metadata.dsName, { binary: entry.encoding?.kind === "binary", encoding: entry.encoding?.kind === "other" ? entry.encoding.codepage : profileEncoding, etag: forceUpload ? undefined : entry.etag, @@ -495,7 +474,8 @@ export class DatasetFSProvider extends BaseProvider implements vscode.FileSystem try { if (!entry) { - entry = new DsEntry(basename, FsDatasetsUtils.isPdsEntry(parent)); + const isPdsMember = FsDatasetsUtils.isPdsEntry(parent); + entry = new DsEntry(basename, isPdsMember); entry.data = content; const profInfo = parent.metadata ? new DsEntryMetadata({ @@ -561,13 +541,18 @@ export class DatasetFSProvider extends BaseProvider implements vscode.FileSystem } public async delete(uri: vscode.Uri, _options: { readonly recursive: boolean }): Promise { - const entry = this.lookup(uri, false); + const entry = this.lookup(uri, false) as DsEntry | PdsEntry; const parent = this._lookupParentDirectory(uri); let fullName: string = ""; if (FsDatasetsUtils.isPdsEntry(parent)) { - fullName = `${parent.name}(${entry.name})`; + // PDS member + fullName = (entry as DsEntry).metadata.dsName; } else { - fullName = entry.name; + if (FsDatasetsUtils.isPdsEntry(entry)) { + fullName = entry.name; + } else { + fullName = entry.metadata.dsName; + } } try { @@ -604,11 +589,15 @@ export class DatasetFSProvider extends BaseProvider implements vscode.FileSystem const newName = path.posix.basename(newUri.path); try { - if (FsDatasetsUtils.isPdsEntry(entry)) { + if (FsDatasetsUtils.isPdsEntry(entry) || !entry.isMember) { await ZoweExplorerApiRegister.getMvsApi(entry.metadata.profile).renameDataSet(oldName, newName); } else { const pdsName = path.basename(path.posix.join(entry.metadata.path, "..")); - await ZoweExplorerApiRegister.getMvsApi(entry.metadata.profile).renameDataSetMember(pdsName, oldName, newName); + await ZoweExplorerApiRegister.getMvsApi(entry.metadata.profile).renameDataSetMember( + pdsName, + path.parse(oldName).name, + path.parse(newName).name + ); } } catch (err) { await Gui.errorMessage( diff --git a/packages/zowe-explorer/src/trees/dataset/DatasetInit.ts b/packages/zowe-explorer/src/trees/dataset/DatasetInit.ts index 58fc048597..3575bf2404 100644 --- a/packages/zowe-explorer/src/trees/dataset/DatasetInit.ts +++ b/packages/zowe-explorer/src/trees/dataset/DatasetInit.ts @@ -182,7 +182,6 @@ export class DatasetInit { await datasetProvider.onDidChangeConfiguration(e); }) ); - context.subscriptions.push(vscode.workspace.onDidOpenTextDocument(DatasetFSProvider.onDidOpenTextDocument)); SharedInit.initSubscribers(context, datasetProvider); return datasetProvider; diff --git a/packages/zowe-explorer/src/trees/dataset/DatasetUtils.ts b/packages/zowe-explorer/src/trees/dataset/DatasetUtils.ts index d93365a807..032b1f322d 100644 --- a/packages/zowe-explorer/src/trees/dataset/DatasetUtils.ts +++ b/packages/zowe-explorer/src/trees/dataset/DatasetUtils.ts @@ -66,46 +66,51 @@ export class DatasetUtils { return Constants.MEMBER_NAME_REGEX_CHECK.test(member); } + public static withExtension(label: string): string { + const extension = this.getExtension(label); + return extension ? label.concat(extension) : label; + } + /** * Get the language ID of a Data Set for use with `vscode.languages.setTextDocumentLanguage` */ - public static getLanguageId(label: string): string | null { + public static getExtension(label: string): string | null { const limit = 5; const bracket = label.indexOf("("); const split = bracket > -1 ? label.substring(0, bracket).split(".", limit) : label.split(".", limit); for (let i = split.length - 1; i > 0; i--) { if (split[i] === "C") { - return "c"; + return ".c"; } if (["JCL", "JCLLIB", "CNTL", "PROC", "PROCLIB"].includes(split[i])) { - return "jcl"; + return ".jcl"; } if (["COBOL", "CBL", "COB", "SCBL"].includes(split[i])) { - return "cobol"; + return ".cobol"; } if (["COPYBOOK", "COPY", "CPY", "COBCOPY"].includes(split[i])) { - return "copybook"; + return ".cpy"; } if (["INC", "INCLUDE", "PLINC"].includes(split[i])) { - return "inc"; + return ".inc"; } if (["PLI", "PL1", "PLX", "PCX"].includes(split[i])) { - return "pli"; + return ".pli"; } if (["SH", "SHELL"].includes(split[i])) { - return "shellscript"; + return ".sh"; } if (["REXX", "REXEC", "EXEC"].includes(split[i])) { - return "rexx"; + return ".rexx"; } if (split[i] === "XML") { - return "xml"; + return ".xml"; } if (split[i] === "ASM" || split[i].indexOf("ASSEMBL") > -1) { - return "asm"; + return ".asm"; } if (split[i] === "LOG" || split[i].indexOf("SPFLOG") > -1) { - return "log"; + return ".log"; } } return null; diff --git a/packages/zowe-explorer/src/trees/dataset/ZoweDatasetNode.ts b/packages/zowe-explorer/src/trees/dataset/ZoweDatasetNode.ts index 4c55b307b3..cd5b9b93bf 100644 --- a/packages/zowe-explorer/src/trees/dataset/ZoweDatasetNode.ts +++ b/packages/zowe-explorer/src/trees/dataset/ZoweDatasetNode.ts @@ -39,6 +39,7 @@ import { AuthUtils } from "../../utils/AuthUtils"; import type { Definitions } from "../../configuration/Definitions"; import type { DatasetTree } from "./DatasetTree"; import { SharedTreeProviders } from "../shared/SharedTreeProviders"; +import { DatasetUtils } from "./DatasetUtils"; /** * A type of TreeItem used to represent sessions and data sets @@ -117,6 +118,8 @@ export class ZoweDatasetNode extends ZoweTreeNode implements IZoweDatasetTreeNod path: `/${sessionLabel}/${this.label as string}`, }); if (this.contextValue === Constants.DS_DS_CONTEXT) { + const extension = DatasetUtils.getExtension(this.label as string); + this.resourceUri = this.resourceUri.with({ path: `${this.resourceUri.path}${extension ?? ""}` }); this.command = { command: "vscode.open", title: "", @@ -124,9 +127,10 @@ export class ZoweDatasetNode extends ZoweTreeNode implements IZoweDatasetTreeNod }; } } else if (this.contextValue === Constants.DS_MEMBER_CONTEXT) { + const extension = DatasetUtils.getExtension(this.getParent().label as string); this.resourceUri = vscode.Uri.from({ scheme: ZoweScheme.DS, - path: `/${sessionLabel}/${this.getParent().label as string}/${this.label as string}`, + path: `/${sessionLabel}/${this.getParent().label as string}/${this.label as string}${extension ?? ""}`, }); this.command = { command: "vscode.open", From c3f18cc88a70c232c16df658a91c26a7d37b6e5a Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Thu, 19 Sep 2024 12:00:19 -0400 Subject: [PATCH 02/19] chore: update ZE changelog Signed-off-by: Trae Yelovich --- packages/zowe-explorer/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/zowe-explorer/CHANGELOG.md b/packages/zowe-explorer/CHANGELOG.md index c1c40f0fff..9fb7f64984 100644 --- a/packages/zowe-explorer/CHANGELOG.md +++ b/packages/zowe-explorer/CHANGELOG.md @@ -10,6 +10,7 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen - The "Zowe Resources" panel is now hidden by default until Zowe Explorer reveals it to display a table or other data. [#3113](https://github.com/zowe/zowe-explorer-vscode/issues/3113) - 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 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) ## `3.0.0-next.202409132122` From 39af8cb781e7235686b5de84e5945567f1b88bf7 Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Thu, 19 Sep 2024 13:32:13 -0400 Subject: [PATCH 03/19] tests: Add patch coverage for extensionRemovedFromPath Signed-off-by: Trae Yelovich --- .../__unit__/fs/types/datasets.unit.test.ts | 19 +++++++++++++++++++ .../src/fs/types/datasets.ts | 2 +- .../trees/dataset/DatasetUtils.unit.test.ts | 2 +- .../src/trees/dataset/DatasetFSProvider.ts | 8 +++----- .../src/trees/dataset/DatasetUtils.ts | 2 +- 5 files changed, 25 insertions(+), 8 deletions(-) diff --git a/packages/zowe-explorer-api/__tests__/__unit__/fs/types/datasets.unit.test.ts b/packages/zowe-explorer-api/__tests__/__unit__/fs/types/datasets.unit.test.ts index 7825970886..8597cabf0e 100644 --- a/packages/zowe-explorer-api/__tests__/__unit__/fs/types/datasets.unit.test.ts +++ b/packages/zowe-explorer-api/__tests__/__unit__/fs/types/datasets.unit.test.ts @@ -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"); + }); + }); }); diff --git a/packages/zowe-explorer-api/src/fs/types/datasets.ts b/packages/zowe-explorer-api/src/fs/types/datasets.ts index fd1fd073a4..03f7f07035 100644 --- a/packages/zowe-explorer-api/src/fs/types/datasets.ts +++ b/packages/zowe-explorer-api/src/fs/types/datasets.ts @@ -48,7 +48,7 @@ export class DsEntryMetadata implements EntryMetadata { } private extensionRemovedFromPath(): string { - for (const ext of [".c", ".jcl", ".cobol", ".cpy", ".inc", ".pli", ".sh", ".rexx", ".xml", ".asm", ".log"]) { + for (const ext of [".c", ".jcl", ".cbl", ".cpy", ".inc", ".pli", ".sh", ".rexx", ".xml", ".asm", ".log"]) { if (this.path.endsWith(ext)) { return this.path.replace(ext, ""); } diff --git a/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetUtils.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetUtils.unit.test.ts index 950e4e4cd2..51239893bb 100644 --- a/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetUtils.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetUtils.unit.test.ts @@ -17,7 +17,7 @@ describe("Dataset utils unit tests - function getExtension", () => { { name: "TEST.DS.C", extension: ".c" }, { name: "TEST.PDS.C(MEMBER)", extension: ".c" }, { name: "TEST.DS.JCL", extension: ".jcl" }, - { name: "TEST.DS.CBL", extension: ".cobol" }, + { 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" }, diff --git a/packages/zowe-explorer/src/trees/dataset/DatasetFSProvider.ts b/packages/zowe-explorer/src/trees/dataset/DatasetFSProvider.ts index eed601fe87..a1907a2d65 100644 --- a/packages/zowe-explorer/src/trees/dataset/DatasetFSProvider.ts +++ b/packages/zowe-explorer/src/trees/dataset/DatasetFSProvider.ts @@ -547,12 +547,10 @@ export class DatasetFSProvider extends BaseProvider implements vscode.FileSystem if (FsDatasetsUtils.isPdsEntry(parent)) { // PDS member fullName = (entry as DsEntry).metadata.dsName; + } else if (FsDatasetsUtils.isPdsEntry(entry)) { + fullName = entry.name; } else { - if (FsDatasetsUtils.isPdsEntry(entry)) { - fullName = entry.name; - } else { - fullName = entry.metadata.dsName; - } + fullName = entry.metadata.dsName; } try { diff --git a/packages/zowe-explorer/src/trees/dataset/DatasetUtils.ts b/packages/zowe-explorer/src/trees/dataset/DatasetUtils.ts index 032b1f322d..863ddfd88b 100644 --- a/packages/zowe-explorer/src/trees/dataset/DatasetUtils.ts +++ b/packages/zowe-explorer/src/trees/dataset/DatasetUtils.ts @@ -86,7 +86,7 @@ export class DatasetUtils { return ".jcl"; } if (["COBOL", "CBL", "COB", "SCBL"].includes(split[i])) { - return ".cobol"; + return ".cbl"; } if (["COPYBOOK", "COPY", "CPY", "COBCOPY"].includes(split[i])) { return ".cpy"; From 16a3bf7d994f037b9e47004302a019eba3913f7f Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Thu, 19 Sep 2024 13:49:12 -0400 Subject: [PATCH 04/19] tests: add case for deleting a pds Signed-off-by: Trae Yelovich --- .../dataset/DatasetFSProvider.unit.test.ts | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetFSProvider.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetFSProvider.unit.test.ts index e3f80ddae7..2ad45e21e5 100644 --- a/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetFSProvider.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetFSProvider.unit.test.ts @@ -11,7 +11,18 @@ import { Disposable, FilePermission, FileSystemError, FileType, TextEditor, Uri } from "vscode"; import { createIProfile } from "../../../__mocks__/mockCreators/shared"; -import { DirEntry, DsEntry, DsEntryMetadata, 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"; @@ -856,6 +867,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() }; From 4f416b16bbaf779d0e2207fa07efce3e70822cc9 Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Thu, 19 Sep 2024 13:51:09 -0400 Subject: [PATCH 05/19] chore: update ZE API changelog Signed-off-by: Trae Yelovich --- packages/zowe-explorer-api/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/zowe-explorer-api/CHANGELOG.md b/packages/zowe-explorer-api/CHANGELOG.md index c9cd9c52c0..d1d0a56abf 100644 --- a/packages/zowe-explorer-api/CHANGELOG.md +++ b/packages/zowe-explorer-api/CHANGELOG.md @@ -8,6 +8,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) +- 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 From 192e796bdf0c6832d8dbbd27a94f074789b8ef22 Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Thu, 19 Sep 2024 13:54:51 -0400 Subject: [PATCH 06/19] chore: update typedoc for DatasetUtils.getExtension Signed-off-by: Trae Yelovich --- packages/zowe-explorer/src/trees/dataset/DatasetUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/zowe-explorer/src/trees/dataset/DatasetUtils.ts b/packages/zowe-explorer/src/trees/dataset/DatasetUtils.ts index 863ddfd88b..82c3a53487 100644 --- a/packages/zowe-explorer/src/trees/dataset/DatasetUtils.ts +++ b/packages/zowe-explorer/src/trees/dataset/DatasetUtils.ts @@ -72,7 +72,7 @@ export class DatasetUtils { } /** - * Get the language ID of a Data Set for use with `vscode.languages.setTextDocumentLanguage` + * Get the file extension for a Data Set (or data set member) based on its name or its PDS name. */ public static getExtension(label: string): string | null { const limit = 5; From f0a5baa5190f1ad60e5cc25db42ee9f9b9f5b28b Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Thu, 19 Sep 2024 13:59:48 -0400 Subject: [PATCH 07/19] refactor: remove unneeded withExtension method and update ref Signed-off-by: Trae Yelovich --- .../zowe-explorer/src/trees/dataset/DatasetFSProvider.ts | 3 ++- packages/zowe-explorer/src/trees/dataset/DatasetUtils.ts | 5 ----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/zowe-explorer/src/trees/dataset/DatasetFSProvider.ts b/packages/zowe-explorer/src/trees/dataset/DatasetFSProvider.ts index a1907a2d65..bf6ddc1455 100644 --- a/packages/zowe-explorer/src/trees/dataset/DatasetFSProvider.ts +++ b/packages/zowe-explorer/src/trees/dataset/DatasetFSProvider.ts @@ -152,7 +152,8 @@ export class DatasetFSProvider extends BaseProvider implements vscode.FileSystem continue; } else { // PS or migrated - name = DatasetUtils.withExtension(ds.dsname as string); + const extension = DatasetUtils.getExtension(ds.dsname); + name = extension ? (ds.dsname as string).concat(extension) : ds.dsname; tempEntry = new DsEntry(name, false); } tempEntry.metadata = new DsEntryMetadata({ diff --git a/packages/zowe-explorer/src/trees/dataset/DatasetUtils.ts b/packages/zowe-explorer/src/trees/dataset/DatasetUtils.ts index 82c3a53487..792681e9a6 100644 --- a/packages/zowe-explorer/src/trees/dataset/DatasetUtils.ts +++ b/packages/zowe-explorer/src/trees/dataset/DatasetUtils.ts @@ -66,11 +66,6 @@ export class DatasetUtils { return Constants.MEMBER_NAME_REGEX_CHECK.test(member); } - public static withExtension(label: string): string { - const extension = this.getExtension(label); - return extension ? label.concat(extension) : label; - } - /** * Get the file extension for a Data Set (or data set member) based on its name or its PDS name. */ From 0714f25def6dc8c7cee1fbb19ed95b9db1426031 Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Thu, 19 Sep 2024 15:35:48 -0400 Subject: [PATCH 08/19] fix(uss): Copy/paste bugs Signed-off-by: Trae Yelovich --- packages/zowe-explorer/src/trees/uss/UssFSProvider.ts | 6 +++--- packages/zowe-explorer/src/trees/uss/ZoweUSSNode.ts | 7 ++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/zowe-explorer/src/trees/uss/UssFSProvider.ts b/packages/zowe-explorer/src/trees/uss/UssFSProvider.ts index a355242e02..2ab379e6fd 100644 --- a/packages/zowe-explorer/src/trees/uss/UssFSProvider.ts +++ b/packages/zowe-explorer/src/trees/uss/UssFSProvider.ts @@ -620,11 +620,11 @@ export class UssFSProvider extends BaseProvider implements vscode.FileSystemProv const hasCopyApi = api.copy != null; - const apiResponse = await api.fileList(path.posix.join(destInfo.path, "..")); + const apiResponse = await api.fileList(path.posix.join(destInfo.path)); const fileList = apiResponse.apiResponse?.items; - const fileName = this.buildFileName(fileList, path.basename(destInfo.path)); - const outputPath = path.posix.join(destInfo.path, "..", fileName); + const fileName = this.buildFileName(fileList, path.basename(sourceInfo.path)); + const outputPath = path.posix.join(destInfo.path, fileName); if (hasCopyApi && sourceInfo.profile.profile === destInfo.profile.profile) { await api.copy(outputPath, { diff --git a/packages/zowe-explorer/src/trees/uss/ZoweUSSNode.ts b/packages/zowe-explorer/src/trees/uss/ZoweUSSNode.ts index 4d5c496c02..0fe4354e75 100644 --- a/packages/zowe-explorer/src/trees/uss/ZoweUSSNode.ts +++ b/packages/zowe-explorer/src/trees/uss/ZoweUSSNode.ts @@ -655,8 +655,13 @@ export class ZoweUSSNode extends ZoweTreeNode implements IZoweUSSTreeNode { if (!uss.api.fileList || (!hasCopy && !hasUploadFromBuffer)) { throw new Error(vscode.l10n.t("Required API functions for pasting (fileList and copy/uploadFromBuffer) were not found.")); } + const localUri = vscode.Uri.from({ + scheme: ZoweScheme.USS, + path: uss.tree.localUri.path, + query: `tree=${encodeURIComponent(JSON.stringify(uss.tree))}`, + }); - await UssFSProvider.instance.copy(uss.tree.localUri.with({ query: `tree=${encodeURIComponent(JSON.stringify(uss.tree))}` }), destUri, { + await UssFSProvider.instance.copy(localUri, destUri, { overwrite: true, }); } From 77a4f211fd4064a64d222841dd5c4dfd6efc68c8 Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Thu, 19 Sep 2024 15:40:07 -0400 Subject: [PATCH 09/19] tests(uss): comment copyTree tests for refactor Signed-off-by: Trae Yelovich --- .../trees/uss/UssFSProvider.unit.test.ts | 404 +++++++++--------- 1 file changed, 202 insertions(+), 202 deletions(-) diff --git a/packages/zowe-explorer/__tests__/__unit__/trees/uss/UssFSProvider.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/trees/uss/UssFSProvider.unit.test.ts index c43dca7f86..48877f254c 100644 --- a/packages/zowe-explorer/__tests__/__unit__/trees/uss/UssFSProvider.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/trees/uss/UssFSProvider.unit.test.ts @@ -1028,208 +1028,208 @@ describe("buildFileName", () => { }); }); -describe("copyTree", () => { - describe("copying a file tree - same profiles (copy API)", () => { - it("with naming collisions", async () => { - const getInfoFromUri = jest - .spyOn(UssFSProvider.instance as any, "_getInfoFromUri") - // destination info - .mockReturnValueOnce({ - profile: testProfile, - path: "/bFile.txt", - }) - // source info - .mockReturnValueOnce({ - profile: testProfile, - path: "/aFile.txt", - }); - const mockUssApi = { - copy: jest.fn(), - create: jest.fn(), - fileList: jest.fn().mockResolvedValueOnce({ - apiResponse: { - items: [{ name: "bFile.txt" }], - }, - }), - uploadFromBuffer: jest.fn(), - }; - jest.spyOn(ZoweExplorerApiRegister, "getUssApi").mockReturnValueOnce(mockUssApi as any); - await (UssFSProvider.instance as any).copyTree( - testUris.file, - testUris.file.with({ - path: "/sestest/bFile.txt", - }), - { tree: { type: USSFileStructure.UssFileType.File } } - ); - expect(mockUssApi.copy).toHaveBeenCalledWith("/bFile (1).txt", { - from: "/aFile.txt", - recursive: false, - overwrite: true, - }); - getInfoFromUri.mockRestore(); - }); - - it("without naming collisions", async () => { - const getInfoFromUri = jest - .spyOn((UssFSProvider as any).prototype, "_getInfoFromUri") - // destination info - .mockReturnValueOnce({ - profile: testProfile, - path: "/bFile.txt", - }) - // source info - .mockReturnValueOnce({ - profile: testProfile, - path: "/aFile.txt", - }); - const mockUssApi = { - copy: jest.fn(), - create: jest.fn(), - fileList: jest.fn().mockResolvedValueOnce({ - apiResponse: { - items: [{ name: "aFile.txt" }], - }, - }), - uploadFromBuffer: jest.fn(), - }; - jest.spyOn(ZoweExplorerApiRegister, "getUssApi").mockReturnValueOnce(mockUssApi as any); - await (UssFSProvider.instance as any).copyTree( - testUris.file, - testUris.file.with({ - path: "/sestest/bFile.txt", - }), - { tree: { type: USSFileStructure.UssFileType.File } } - ); - expect(mockUssApi.copy).toHaveBeenCalledWith("/bFile.txt", { - from: "/aFile.txt", - recursive: false, - overwrite: true, - }); - getInfoFromUri.mockRestore(); - }); - }); - - describe("copying - different profiles", () => { - it("file: with naming collisions", async () => { - const getInfoFromUri = jest - .spyOn((UssFSProvider as any).prototype, "_getInfoFromUri") - // destination info - .mockReturnValueOnce({ - profile: testProfileB, - path: "/aFile.txt", - }) - // source info - .mockReturnValueOnce({ - profile: testProfile, - path: "/aFile.txt", - }); - const mockUssApi = { - copy: jest.fn(), - create: jest.fn(), - fileList: jest.fn().mockResolvedValueOnce({ - apiResponse: { - items: [{ name: "bFile.txt" }], - }, - }), - uploadFromBuffer: jest.fn(), - }; - jest.spyOn(ZoweExplorerApiRegister, "getUssApi").mockReturnValueOnce(mockUssApi as any); - jest.spyOn((UssFSProvider as any).instance, "lookup").mockReturnValueOnce(testEntries.file); - jest.spyOn((UssFSProvider as any).instance, "readFile").mockResolvedValueOnce(testEntries.file.data); - await (UssFSProvider.instance as any).copyTree( - testUris.file, - testUris.file.with({ - path: "/sestest2/bFile.txt", - }), - { tree: { type: USSFileStructure.UssFileType.File } } - ); - expect(mockUssApi.uploadFromBuffer).toHaveBeenCalledWith(Buffer.from(testEntries.file.data ?? []), "/aFile.txt"); - getInfoFromUri.mockRestore(); - }); - it("file: without naming collisions", async () => { - const getInfoFromUri = jest - .spyOn((UssFSProvider as any).prototype, "_getInfoFromUri") - // destination info - .mockReturnValueOnce({ - profile: testProfileB, - path: "/aFile.txt", - }) - // source info - .mockReturnValueOnce({ - profile: testProfile, - path: "/aFile.txt", - }); - const mockUssApi = { - copy: jest.fn(), - create: jest.fn(), - fileList: jest.fn().mockResolvedValueOnce({ - apiResponse: { - items: [{ name: "aFile.txt" }], - }, - }), - uploadFromBuffer: jest.fn(), - }; - jest.spyOn(ZoweExplorerApiRegister, "getUssApi").mockReturnValueOnce(mockUssApi as any); - jest.spyOn((UssFSProvider as any).instance, "lookup").mockReturnValueOnce(testEntries.file); - jest.spyOn((UssFSProvider as any).instance, "readFile").mockResolvedValueOnce(testEntries.file.data); - await (UssFSProvider.instance as any).copyTree( - testUris.file, - testUris.file.with({ - path: "/sestest2/aFile.txt", - }), - { tree: { type: USSFileStructure.UssFileType.File } } - ); - expect(mockUssApi.uploadFromBuffer).toHaveBeenCalledWith(Buffer.from(testEntries.file.data ?? []), "/aFile (1).txt"); - getInfoFromUri.mockRestore(); - }); - it("folder", async () => { - const getInfoFromUri = jest - .spyOn((UssFSProvider as any).prototype, "_getInfoFromUri") - // destination info - .mockReturnValueOnce({ - profile: testProfileB, - path: "/aFolder", - }) - // source info - .mockReturnValueOnce({ - profile: testProfile, - path: "/aFolder", - }); - const mockUssApi = { - create: jest.fn(), - fileList: jest.fn().mockResolvedValue({ - apiResponse: { - items: [{ name: "aFile.txt" }], - }, - }), - uploadFromBuffer: jest.fn(), - }; - const ussApiMock = jest.spyOn(ZoweExplorerApiRegister, "getUssApi").mockReturnValue(mockUssApi as any); - - const copyTreeSpy = jest.spyOn(UssFSProvider.instance as any, "copyTree"); - const fileInPathTree = { - baseName: "someFile.txt", - localUri: Uri.from({ scheme: ZoweScheme.USS, path: "/sestest/aFolder/someFile.txt" }), - type: USSFileStructure.UssFileType.File, - }; - const ussFileTree = { - type: USSFileStructure.UssFileType.Directory, - children: [fileInPathTree], - }; - await (UssFSProvider.instance as any).copyTree( - testUris.folder, - testUris.folder.with({ - path: "/sestest2/aFolder", - }), - { tree: ussFileTree } - ); - expect(mockUssApi.create).toHaveBeenCalledWith("/aFolder", "directory"); - expect(copyTreeSpy).toHaveBeenCalledTimes(2); - getInfoFromUri.mockRestore(); - ussApiMock.mockRestore(); - }); - }); -}); +// describe("copyTree", () => { +// describe("copying a file tree - same profiles (copy API)", () => { +// it("with naming collisions", async () => { +// const getInfoFromUri = jest +// .spyOn(UssFSProvider.instance as any, "_getInfoFromUri") +// // destination info +// .mockReturnValueOnce({ +// profile: testProfile, +// path: "/bFile.txt", +// }) +// // source info +// .mockReturnValueOnce({ +// profile: testProfile, +// path: "/aFile.txt", +// }); +// const mockUssApi = { +// copy: jest.fn(), +// create: jest.fn(), +// fileList: jest.fn().mockResolvedValueOnce({ +// apiResponse: { +// items: [{ name: "bFile.txt" }], +// }, +// }), +// uploadFromBuffer: jest.fn(), +// }; +// jest.spyOn(ZoweExplorerApiRegister, "getUssApi").mockReturnValueOnce(mockUssApi as any); +// await (UssFSProvider.instance as any).copyTree( +// testUris.file, +// testUris.file.with({ +// path: "/sestest/bFile.txt", +// }), +// { tree: { type: USSFileStructure.UssFileType.File } } +// ); +// expect(mockUssApi.copy).toHaveBeenCalledWith("/bFile (1).txt", { +// from: "/aFile.txt", +// recursive: false, +// overwrite: true, +// }); +// getInfoFromUri.mockRestore(); +// }); + +// it("without naming collisions", async () => { +// const getInfoFromUri = jest +// .spyOn((UssFSProvider as any).prototype, "_getInfoFromUri") +// // destination info +// .mockReturnValueOnce({ +// profile: testProfile, +// path: "/bFile.txt", +// }) +// // source info +// .mockReturnValueOnce({ +// profile: testProfile, +// path: "/aFile.txt", +// }); +// const mockUssApi = { +// copy: jest.fn(), +// create: jest.fn(), +// fileList: jest.fn().mockResolvedValueOnce({ +// apiResponse: { +// items: [{ name: "aFile.txt" }], +// }, +// }), +// uploadFromBuffer: jest.fn(), +// }; +// jest.spyOn(ZoweExplorerApiRegister, "getUssApi").mockReturnValueOnce(mockUssApi as any); +// await (UssFSProvider.instance as any).copyTree( +// testUris.file, +// testUris.file.with({ +// path: "/sestest/bFile.txt", +// }), +// { tree: { type: USSFileStructure.UssFileType.File } } +// ); +// expect(mockUssApi.copy).toHaveBeenCalledWith("/bFile.txt", { +// from: "/aFile.txt", +// recursive: false, +// overwrite: true, +// }); +// getInfoFromUri.mockRestore(); +// }); +// }); + +// describe("copying - different profiles", () => { +// it("file: with naming collisions", async () => { +// const getInfoFromUri = jest +// .spyOn((UssFSProvider as any).prototype, "_getInfoFromUri") +// // destination info +// .mockReturnValueOnce({ +// profile: testProfileB, +// path: "/aFile.txt", +// }) +// // source info +// .mockReturnValueOnce({ +// profile: testProfile, +// path: "/aFile.txt", +// }); +// const mockUssApi = { +// copy: jest.fn(), +// create: jest.fn(), +// fileList: jest.fn().mockResolvedValueOnce({ +// apiResponse: { +// items: [{ name: "bFile.txt" }], +// }, +// }), +// uploadFromBuffer: jest.fn(), +// }; +// jest.spyOn(ZoweExplorerApiRegister, "getUssApi").mockReturnValueOnce(mockUssApi as any); +// jest.spyOn((UssFSProvider as any).instance, "lookup").mockReturnValueOnce(testEntries.file); +// jest.spyOn((UssFSProvider as any).instance, "readFile").mockResolvedValueOnce(testEntries.file.data); +// await (UssFSProvider.instance as any).copyTree( +// testUris.file, +// testUris.file.with({ +// path: "/sestest2/bFile.txt", +// }), +// { tree: { type: USSFileStructure.UssFileType.File } } +// ); +// expect(mockUssApi.uploadFromBuffer).toHaveBeenCalledWith(Buffer.from(testEntries.file.data ?? []), "/aFile.txt"); +// getInfoFromUri.mockRestore(); +// }); +// it("file: without naming collisions", async () => { +// const getInfoFromUri = jest +// .spyOn((UssFSProvider as any).prototype, "_getInfoFromUri") +// // destination info +// .mockReturnValueOnce({ +// profile: testProfileB, +// path: "/aFile.txt", +// }) +// // source info +// .mockReturnValueOnce({ +// profile: testProfile, +// path: "/aFile.txt", +// }); +// const mockUssApi = { +// copy: jest.fn(), +// create: jest.fn(), +// fileList: jest.fn().mockResolvedValueOnce({ +// apiResponse: { +// items: [{ name: "aFile.txt" }], +// }, +// }), +// uploadFromBuffer: jest.fn(), +// }; +// jest.spyOn(ZoweExplorerApiRegister, "getUssApi").mockReturnValueOnce(mockUssApi as any); +// jest.spyOn((UssFSProvider as any).instance, "lookup").mockReturnValueOnce(testEntries.file); +// jest.spyOn((UssFSProvider as any).instance, "readFile").mockResolvedValueOnce(testEntries.file.data); +// await (UssFSProvider.instance as any).copyTree( +// testUris.file, +// testUris.file.with({ +// path: "/sestest2/aFile.txt", +// }), +// { tree: { type: USSFileStructure.UssFileType.File } } +// ); +// expect(mockUssApi.uploadFromBuffer).toHaveBeenCalledWith(Buffer.from(testEntries.file.data ?? []), "/aFile (1).txt"); +// getInfoFromUri.mockRestore(); +// }); +// it("folder", async () => { +// const getInfoFromUri = jest +// .spyOn((UssFSProvider as any).prototype, "_getInfoFromUri") +// // destination info +// .mockReturnValueOnce({ +// profile: testProfileB, +// path: "/aFolder", +// }) +// // source info +// .mockReturnValueOnce({ +// profile: testProfile, +// path: "/aFolder", +// }); +// const mockUssApi = { +// create: jest.fn(), +// fileList: jest.fn().mockResolvedValue({ +// apiResponse: { +// items: [{ name: "aFile.txt" }], +// }, +// }), +// uploadFromBuffer: jest.fn(), +// }; +// const ussApiMock = jest.spyOn(ZoweExplorerApiRegister, "getUssApi").mockReturnValue(mockUssApi as any); + +// const copyTreeSpy = jest.spyOn(UssFSProvider.instance as any, "copyTree"); +// const fileInPathTree = { +// baseName: "someFile.txt", +// localUri: Uri.from({ scheme: ZoweScheme.USS, path: "/sestest/aFolder/someFile.txt" }), +// type: USSFileStructure.UssFileType.File, +// }; +// const ussFileTree = { +// type: USSFileStructure.UssFileType.Directory, +// children: [fileInPathTree], +// }; +// await (UssFSProvider.instance as any).copyTree( +// testUris.folder, +// testUris.folder.with({ +// path: "/sestest2/aFolder", +// }), +// { tree: ussFileTree } +// ); +// expect(mockUssApi.create).toHaveBeenCalledWith("/aFolder", "directory"); +// expect(copyTreeSpy).toHaveBeenCalledTimes(2); +// getInfoFromUri.mockRestore(); +// ussApiMock.mockRestore(); +// }); +// }); +// }); describe("createDirectory", () => { it("creates a session directory with the given URI", () => { From 21e729e310b30a9ffc53e78a60027bde8cc2efda Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Thu, 19 Sep 2024 16:25:43 -0400 Subject: [PATCH 10/19] wip(tests): Same profile cases for copyTree Signed-off-by: Trae Yelovich --- .../trees/uss/UssFSProvider.unit.test.ts | 378 ++++++++---------- 1 file changed, 175 insertions(+), 203 deletions(-) diff --git a/packages/zowe-explorer/__tests__/__unit__/trees/uss/UssFSProvider.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/trees/uss/UssFSProvider.unit.test.ts index 48877f254c..6057aef92d 100644 --- a/packages/zowe-explorer/__tests__/__unit__/trees/uss/UssFSProvider.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/trees/uss/UssFSProvider.unit.test.ts @@ -18,7 +18,6 @@ import { UssFSProvider } from "../../../../src/trees/uss/UssFSProvider"; import { USSFileStructure } from "../../../../src/trees/uss/USSFileStructure"; const testProfile = createIProfile(); -const testProfileB = { ...createIProfile(), name: "sestest2", profile: { ...testProfile.profile, host: "fake2" } }; type TestUris = Record>; const testUris: TestUris = { @@ -1028,208 +1027,181 @@ describe("buildFileName", () => { }); }); -// describe("copyTree", () => { -// describe("copying a file tree - same profiles (copy API)", () => { -// it("with naming collisions", async () => { -// const getInfoFromUri = jest -// .spyOn(UssFSProvider.instance as any, "_getInfoFromUri") -// // destination info -// .mockReturnValueOnce({ -// profile: testProfile, -// path: "/bFile.txt", -// }) -// // source info -// .mockReturnValueOnce({ -// profile: testProfile, -// path: "/aFile.txt", -// }); -// const mockUssApi = { -// copy: jest.fn(), -// create: jest.fn(), -// fileList: jest.fn().mockResolvedValueOnce({ -// apiResponse: { -// items: [{ name: "bFile.txt" }], -// }, -// }), -// uploadFromBuffer: jest.fn(), -// }; -// jest.spyOn(ZoweExplorerApiRegister, "getUssApi").mockReturnValueOnce(mockUssApi as any); -// await (UssFSProvider.instance as any).copyTree( -// testUris.file, -// testUris.file.with({ -// path: "/sestest/bFile.txt", -// }), -// { tree: { type: USSFileStructure.UssFileType.File } } -// ); -// expect(mockUssApi.copy).toHaveBeenCalledWith("/bFile (1).txt", { -// from: "/aFile.txt", -// recursive: false, -// overwrite: true, -// }); -// getInfoFromUri.mockRestore(); -// }); - -// it("without naming collisions", async () => { -// const getInfoFromUri = jest -// .spyOn((UssFSProvider as any).prototype, "_getInfoFromUri") -// // destination info -// .mockReturnValueOnce({ -// profile: testProfile, -// path: "/bFile.txt", -// }) -// // source info -// .mockReturnValueOnce({ -// profile: testProfile, -// path: "/aFile.txt", -// }); -// const mockUssApi = { -// copy: jest.fn(), -// create: jest.fn(), -// fileList: jest.fn().mockResolvedValueOnce({ -// apiResponse: { -// items: [{ name: "aFile.txt" }], -// }, -// }), -// uploadFromBuffer: jest.fn(), -// }; -// jest.spyOn(ZoweExplorerApiRegister, "getUssApi").mockReturnValueOnce(mockUssApi as any); -// await (UssFSProvider.instance as any).copyTree( -// testUris.file, -// testUris.file.with({ -// path: "/sestest/bFile.txt", -// }), -// { tree: { type: USSFileStructure.UssFileType.File } } -// ); -// expect(mockUssApi.copy).toHaveBeenCalledWith("/bFile.txt", { -// from: "/aFile.txt", -// recursive: false, -// overwrite: true, -// }); -// getInfoFromUri.mockRestore(); -// }); -// }); - -// describe("copying - different profiles", () => { -// it("file: with naming collisions", async () => { -// const getInfoFromUri = jest -// .spyOn((UssFSProvider as any).prototype, "_getInfoFromUri") -// // destination info -// .mockReturnValueOnce({ -// profile: testProfileB, -// path: "/aFile.txt", -// }) -// // source info -// .mockReturnValueOnce({ -// profile: testProfile, -// path: "/aFile.txt", -// }); -// const mockUssApi = { -// copy: jest.fn(), -// create: jest.fn(), -// fileList: jest.fn().mockResolvedValueOnce({ -// apiResponse: { -// items: [{ name: "bFile.txt" }], -// }, -// }), -// uploadFromBuffer: jest.fn(), -// }; -// jest.spyOn(ZoweExplorerApiRegister, "getUssApi").mockReturnValueOnce(mockUssApi as any); -// jest.spyOn((UssFSProvider as any).instance, "lookup").mockReturnValueOnce(testEntries.file); -// jest.spyOn((UssFSProvider as any).instance, "readFile").mockResolvedValueOnce(testEntries.file.data); -// await (UssFSProvider.instance as any).copyTree( -// testUris.file, -// testUris.file.with({ -// path: "/sestest2/bFile.txt", -// }), -// { tree: { type: USSFileStructure.UssFileType.File } } -// ); -// expect(mockUssApi.uploadFromBuffer).toHaveBeenCalledWith(Buffer.from(testEntries.file.data ?? []), "/aFile.txt"); -// getInfoFromUri.mockRestore(); -// }); -// it("file: without naming collisions", async () => { -// const getInfoFromUri = jest -// .spyOn((UssFSProvider as any).prototype, "_getInfoFromUri") -// // destination info -// .mockReturnValueOnce({ -// profile: testProfileB, -// path: "/aFile.txt", -// }) -// // source info -// .mockReturnValueOnce({ -// profile: testProfile, -// path: "/aFile.txt", -// }); -// const mockUssApi = { -// copy: jest.fn(), -// create: jest.fn(), -// fileList: jest.fn().mockResolvedValueOnce({ -// apiResponse: { -// items: [{ name: "aFile.txt" }], -// }, -// }), -// uploadFromBuffer: jest.fn(), -// }; -// jest.spyOn(ZoweExplorerApiRegister, "getUssApi").mockReturnValueOnce(mockUssApi as any); -// jest.spyOn((UssFSProvider as any).instance, "lookup").mockReturnValueOnce(testEntries.file); -// jest.spyOn((UssFSProvider as any).instance, "readFile").mockResolvedValueOnce(testEntries.file.data); -// await (UssFSProvider.instance as any).copyTree( -// testUris.file, -// testUris.file.with({ -// path: "/sestest2/aFile.txt", -// }), -// { tree: { type: USSFileStructure.UssFileType.File } } -// ); -// expect(mockUssApi.uploadFromBuffer).toHaveBeenCalledWith(Buffer.from(testEntries.file.data ?? []), "/aFile (1).txt"); -// getInfoFromUri.mockRestore(); -// }); -// it("folder", async () => { -// const getInfoFromUri = jest -// .spyOn((UssFSProvider as any).prototype, "_getInfoFromUri") -// // destination info -// .mockReturnValueOnce({ -// profile: testProfileB, -// path: "/aFolder", -// }) -// // source info -// .mockReturnValueOnce({ -// profile: testProfile, -// path: "/aFolder", -// }); -// const mockUssApi = { -// create: jest.fn(), -// fileList: jest.fn().mockResolvedValue({ -// apiResponse: { -// items: [{ name: "aFile.txt" }], -// }, -// }), -// uploadFromBuffer: jest.fn(), -// }; -// const ussApiMock = jest.spyOn(ZoweExplorerApiRegister, "getUssApi").mockReturnValue(mockUssApi as any); - -// const copyTreeSpy = jest.spyOn(UssFSProvider.instance as any, "copyTree"); -// const fileInPathTree = { -// baseName: "someFile.txt", -// localUri: Uri.from({ scheme: ZoweScheme.USS, path: "/sestest/aFolder/someFile.txt" }), -// type: USSFileStructure.UssFileType.File, -// }; -// const ussFileTree = { -// type: USSFileStructure.UssFileType.Directory, -// children: [fileInPathTree], -// }; -// await (UssFSProvider.instance as any).copyTree( -// testUris.folder, -// testUris.folder.with({ -// path: "/sestest2/aFolder", -// }), -// { tree: ussFileTree } -// ); -// expect(mockUssApi.create).toHaveBeenCalledWith("/aFolder", "directory"); -// expect(copyTreeSpy).toHaveBeenCalledTimes(2); -// getInfoFromUri.mockRestore(); -// ussApiMock.mockRestore(); -// }); -// }); -// }); +describe("copyTree", () => { + const getBlockMocks = (hasCopy: boolean = false) => { + const fileList = jest.fn(); + const copy = jest.fn(); + const create = jest.fn(); + const uploadFromBuffer = jest.fn(); + const ussApi = jest.spyOn(ZoweExplorerApiRegister, "getUssApi").mockReturnValue( + hasCopy + ? { + fileList, + copy, + create, + uploadFromBuffer, + } + : ({ fileList, create, uploadFromBuffer } as any) + ); + const getInfoFromUri = jest.spyOn(UssFSProvider.instance as any, "_getInfoFromUri"); + + return { + profile: createIProfile(), + getInfoFromUri, + ussApi, + apiFuncs: { + fileList, + copy, + create, + uploadFromBuffer, + }, + }; + }; + describe("same profiles", () => { + it("copies a file into a destination folder - no collisions", async () => { + const blockMocks = getBlockMocks(true); + const sourceUri = Uri.from({ + scheme: ZoweScheme.USS, + path: "/sestest/folderA/file", + }); + const destUri = Uri.from({ + scheme: ZoweScheme.USS, + path: "/sestest/folderB", + }); + blockMocks.getInfoFromUri + .mockReturnValueOnce({ + profile: blockMocks.profile, + path: "/folderB", + }) + .mockReturnValueOnce({ + profile: blockMocks.profile, + path: "/folderA/file", + }); + blockMocks.apiFuncs.fileList.mockReturnValueOnce({ apiResponse: { items: [] } }); + await (UssFSProvider.instance as any).copyTree(sourceUri, destUri, { + overwrite: true, + tree: { + localUri: sourceUri, + ussPath: "/folderA/file", + baseName: "file", + sessionName: "lpar.zosmf", + type: USSFileStructure.UssFileType.File, + }, + }); + expect(blockMocks.apiFuncs.copy).toHaveBeenCalledWith("/folderB/file", { + from: "/folderA/file", + recursive: false, + overwrite: true, + }); + }); + it("copies a file into a destination folder - collision", async () => { + const blockMocks = getBlockMocks(true); + const sourceUri = Uri.from({ + scheme: ZoweScheme.USS, + path: "/sestest/folderA/file", + }); + const destUri = Uri.from({ + scheme: ZoweScheme.USS, + path: "/sestest/folderB", + }); + blockMocks.getInfoFromUri + .mockReturnValueOnce({ + profile: blockMocks.profile, + path: "/folderB", + }) + .mockReturnValueOnce({ + profile: blockMocks.profile, + path: "/folderA/file", + }); + blockMocks.apiFuncs.fileList.mockReturnValueOnce({ apiResponse: { items: [{ name: "file" }] } }); + await (UssFSProvider.instance as any).copyTree(sourceUri, destUri, { + overwrite: true, + tree: { + localUri: sourceUri, + ussPath: "/folderA/file", + baseName: "file", + sessionName: "lpar.zosmf", + type: USSFileStructure.UssFileType.File, + }, + }); + expect(blockMocks.apiFuncs.copy).toHaveBeenCalledWith("/folderB/file (1)", { + from: "/folderA/file", + recursive: false, + overwrite: true, + }); + }); + it("copies a folder into a destination folder - no collisions", async () => { + const blockMocks = getBlockMocks(true); + const sourceUri = Uri.from({ + scheme: ZoweScheme.USS, + path: "/sestest/folderA/innerFolder", + }); + const destUri = Uri.from({ + scheme: ZoweScheme.USS, + path: "/sestest/folderB", + }); + blockMocks.getInfoFromUri + .mockReturnValueOnce({ + profile: blockMocks.profile, + path: "/folderB", + }) + .mockReturnValueOnce({ + profile: blockMocks.profile, + path: "/folderA/innerFolder", + }); + blockMocks.apiFuncs.fileList.mockReturnValueOnce({ apiResponse: { items: [] } }); + await (UssFSProvider.instance as any).copyTree(sourceUri, destUri, { + overwrite: true, + tree: { + localUri: sourceUri, + ussPath: "/folderA/innerFolder", + sessionName: "lpar.zosmf", + type: USSFileStructure.UssFileType.Directory, + }, + }); + expect(blockMocks.apiFuncs.copy).toHaveBeenCalledWith("/folderB/innerFolder", { + from: "/folderA/innerFolder", + recursive: true, + overwrite: true, + }); + }); + it("copies a folder into a destination folder - collision", async () => { + const blockMocks = getBlockMocks(true); + const sourceUri = Uri.from({ + scheme: ZoweScheme.USS, + path: "/sestest/folderA/innerFolder", + }); + const destUri = Uri.from({ + scheme: ZoweScheme.USS, + path: "/sestest/folderB", + }); + blockMocks.getInfoFromUri + .mockReturnValueOnce({ + profile: blockMocks.profile, + path: "/folderB", + }) + .mockReturnValueOnce({ + profile: blockMocks.profile, + path: "/folderA/innerFolder", + }); + blockMocks.apiFuncs.fileList.mockReturnValueOnce({ apiResponse: { items: [{ name: "innerFolder" }] } }); + await (UssFSProvider.instance as any).copyTree(sourceUri, destUri, { + overwrite: true, + tree: { + localUri: sourceUri, + ussPath: "/folderA/innerFolder", + sessionName: "lpar.zosmf", + type: USSFileStructure.UssFileType.Directory, + }, + }); + expect(blockMocks.apiFuncs.copy).toHaveBeenCalledWith("/folderB/innerFolder (1)", { + from: "/folderA/innerFolder", + recursive: true, + overwrite: true, + }); + }); + }); +}); describe("createDirectory", () => { it("creates a session directory with the given URI", () => { From 5a7a669db5d132d9ed9e1d3db9bd6572b9778804 Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Thu, 19 Sep 2024 16:56:50 -0400 Subject: [PATCH 11/19] wip(tests): copyTree w/ folder and diff profiles (collisions) Signed-off-by: Trae Yelovich --- .../trees/uss/UssFSProvider.unit.test.ts | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/packages/zowe-explorer/__tests__/__unit__/trees/uss/UssFSProvider.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/trees/uss/UssFSProvider.unit.test.ts index 6057aef92d..21016f2d93 100644 --- a/packages/zowe-explorer/__tests__/__unit__/trees/uss/UssFSProvider.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/trees/uss/UssFSProvider.unit.test.ts @@ -1047,6 +1047,7 @@ describe("copyTree", () => { return { profile: createIProfile(), + profile2: { ...createIProfile(), name: "sestest2" }, getInfoFromUri, ussApi, apiFuncs: { @@ -1201,6 +1202,39 @@ describe("copyTree", () => { }); }); }); + describe("different profiles", () => { + it("copies a folder into a destination folder - collision", async () => { + const blockMocks = getBlockMocks(); + const sourceUri = Uri.from({ + scheme: ZoweScheme.USS, + path: "/sestest/folderA/innerFolder", + }); + const destUri = Uri.from({ + scheme: ZoweScheme.USS, + path: "/sestest2/folderB", + }); + blockMocks.getInfoFromUri + .mockReturnValueOnce({ + profile: blockMocks.profile, + path: "/folderB", + }) + .mockReturnValueOnce({ + profile: blockMocks.profile, + path: "/folderA/innerFolder", + }); + blockMocks.apiFuncs.fileList.mockReturnValueOnce({ apiResponse: { items: [{ name: "innerFolder" }] } }); + await (UssFSProvider.instance as any).copyTree(sourceUri, destUri, { + overwrite: true, + tree: { + localUri: sourceUri, + ussPath: "/folderA/innerFolder", + sessionName: "lpar.zosmf", + type: USSFileStructure.UssFileType.Directory, + }, + }); + expect(blockMocks.apiFuncs.create).toHaveBeenCalledWith("/folderB/innerFolder (1)", "directory"); + }); + }); }); describe("createDirectory", () => { From 674d4bb141f0526c03f46a73ff8b1677ba257b48 Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Thu, 19 Sep 2024 18:08:38 -0400 Subject: [PATCH 12/19] tests: Remaining cases for copyTree, add success check & test case Signed-off-by: Trae Yelovich --- .../trees/uss/UssFSProvider.unit.test.ts | 110 +++++++++++++++++- .../src/trees/uss/UssFSProvider.ts | 17 ++- 2 files changed, 116 insertions(+), 11 deletions(-) diff --git a/packages/zowe-explorer/__tests__/__unit__/trees/uss/UssFSProvider.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/trees/uss/UssFSProvider.unit.test.ts index 21016f2d93..446f20b28f 100644 --- a/packages/zowe-explorer/__tests__/__unit__/trees/uss/UssFSProvider.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/trees/uss/UssFSProvider.unit.test.ts @@ -1058,6 +1058,24 @@ describe("copyTree", () => { }, }; }; + it("throws error if file list for destination path was unsuccessful", async () => { + const blockMocks = getBlockMocks(); + const sourceUri = Uri.from({ + scheme: ZoweScheme.USS, + path: "/sestest/folderA/file", + }); + const destUri = Uri.from({ + scheme: ZoweScheme.USS, + path: "/sestest/folderB", + }); + blockMocks.apiFuncs.fileList.mockReturnValueOnce({ + success: false, + errorMessage: "Unknown server-side error", + }); + await expect((UssFSProvider.instance as any).copyTree(sourceUri, destUri)).rejects.toThrow( + "Error fetching destination /folderB for paste action: Unknown server-side error" + ); + }); describe("same profiles", () => { it("copies a file into a destination folder - no collisions", async () => { const blockMocks = getBlockMocks(true); @@ -1078,7 +1096,7 @@ describe("copyTree", () => { profile: blockMocks.profile, path: "/folderA/file", }); - blockMocks.apiFuncs.fileList.mockReturnValueOnce({ apiResponse: { items: [] } }); + blockMocks.apiFuncs.fileList.mockResolvedValueOnce({ success: true, apiResponse: { items: [] } }); await (UssFSProvider.instance as any).copyTree(sourceUri, destUri, { overwrite: true, tree: { @@ -1114,7 +1132,7 @@ describe("copyTree", () => { profile: blockMocks.profile, path: "/folderA/file", }); - blockMocks.apiFuncs.fileList.mockReturnValueOnce({ apiResponse: { items: [{ name: "file" }] } }); + blockMocks.apiFuncs.fileList.mockResolvedValueOnce({ success: true, apiResponse: { items: [{ name: "file" }] } }); await (UssFSProvider.instance as any).copyTree(sourceUri, destUri, { overwrite: true, tree: { @@ -1150,7 +1168,7 @@ describe("copyTree", () => { profile: blockMocks.profile, path: "/folderA/innerFolder", }); - blockMocks.apiFuncs.fileList.mockReturnValueOnce({ apiResponse: { items: [] } }); + blockMocks.apiFuncs.fileList.mockResolvedValueOnce({ success: true, apiResponse: { items: [] } }); await (UssFSProvider.instance as any).copyTree(sourceUri, destUri, { overwrite: true, tree: { @@ -1185,7 +1203,7 @@ describe("copyTree", () => { profile: blockMocks.profile, path: "/folderA/innerFolder", }); - blockMocks.apiFuncs.fileList.mockReturnValueOnce({ apiResponse: { items: [{ name: "innerFolder" }] } }); + blockMocks.apiFuncs.fileList.mockResolvedValueOnce({ success: true, apiResponse: { items: [{ name: "innerFolder" }] } }); await (UssFSProvider.instance as any).copyTree(sourceUri, destUri, { overwrite: true, tree: { @@ -1203,6 +1221,55 @@ describe("copyTree", () => { }); }); describe("different profiles", () => { + it("copies a file into a destination folder - collision", async () => { + const blockMocks = getBlockMocks(); + const sourceUri = Uri.from({ + scheme: ZoweScheme.USS, + path: "/sestest/folderA/file", + }); + const destUri = Uri.from({ + scheme: ZoweScheme.USS, + path: "/sestest2/folderB", + }); + blockMocks.getInfoFromUri + .mockReturnValueOnce({ + profile: blockMocks.profile2, + path: "/folderB", + }) + .mockReturnValueOnce({ + profile: blockMocks.profile, + path: "/folderA/file", + }); + blockMocks.apiFuncs.fileList.mockResolvedValueOnce({ success: true, apiResponse: { items: [{ name: "file" }] } }); + const lookupMock = jest.spyOn(UssFSProvider.instance, "lookup").mockReturnValue({ + name: "file", + type: FileType.File, + metadata: { + path: "/sestest/folderA/file", + profile: blockMocks.profile, + }, + wasAccessed: false, + data: new Uint8Array(), + ctime: 0, + mtime: 0, + size: 0, + }); + const readFileMock = jest.spyOn(UssFSProvider.instance, "readFile").mockResolvedValue(new Uint8Array([1, 2, 3])); + await (UssFSProvider.instance as any).copyTree(sourceUri, destUri, { + overwrite: true, + tree: { + localUri: sourceUri, + ussPath: "/folderA/file", + baseName: "file", + sessionName: "sestest", + type: USSFileStructure.UssFileType.File, + }, + }); + expect(lookupMock).toHaveBeenCalledWith(sourceUri); + expect(readFileMock).toHaveBeenCalledWith(sourceUri); + lookupMock.mockRestore(); + readFileMock.mockRestore(); + }); it("copies a folder into a destination folder - collision", async () => { const blockMocks = getBlockMocks(); const sourceUri = Uri.from({ @@ -1215,14 +1282,14 @@ describe("copyTree", () => { }); blockMocks.getInfoFromUri .mockReturnValueOnce({ - profile: blockMocks.profile, + profile: blockMocks.profile2, path: "/folderB", }) .mockReturnValueOnce({ profile: blockMocks.profile, path: "/folderA/innerFolder", }); - blockMocks.apiFuncs.fileList.mockReturnValueOnce({ apiResponse: { items: [{ name: "innerFolder" }] } }); + blockMocks.apiFuncs.fileList.mockResolvedValueOnce({ success: true, apiResponse: { items: [{ name: "innerFolder" }] } }); await (UssFSProvider.instance as any).copyTree(sourceUri, destUri, { overwrite: true, tree: { @@ -1234,6 +1301,37 @@ describe("copyTree", () => { }); expect(blockMocks.apiFuncs.create).toHaveBeenCalledWith("/folderB/innerFolder (1)", "directory"); }); + it("copies a folder into a destination folder - no collision", async () => { + const blockMocks = getBlockMocks(); + const sourceUri = Uri.from({ + scheme: ZoweScheme.USS, + path: "/sestest/folderA/innerFolder", + }); + const destUri = Uri.from({ + scheme: ZoweScheme.USS, + path: "/sestest2/folderB", + }); + blockMocks.getInfoFromUri + .mockReturnValueOnce({ + profile: blockMocks.profile2, + path: "/folderB", + }) + .mockReturnValueOnce({ + profile: blockMocks.profile, + path: "/folderA/innerFolder", + }); + blockMocks.apiFuncs.fileList.mockResolvedValueOnce({ success: true, apiResponse: { items: [] } }); + await (UssFSProvider.instance as any).copyTree(sourceUri, destUri, { + overwrite: true, + tree: { + localUri: sourceUri, + ussPath: "/folderA/innerFolder", + sessionName: "sestest", + type: USSFileStructure.UssFileType.Directory, + }, + }); + expect(blockMocks.apiFuncs.create).toHaveBeenCalledWith("/folderB/innerFolder", "directory"); + }); }); }); diff --git a/packages/zowe-explorer/src/trees/uss/UssFSProvider.ts b/packages/zowe-explorer/src/trees/uss/UssFSProvider.ts index 2ab379e6fd..ec37eaf1a7 100644 --- a/packages/zowe-explorer/src/trees/uss/UssFSProvider.ts +++ b/packages/zowe-explorer/src/trees/uss/UssFSProvider.ts @@ -620,9 +620,20 @@ export class UssFSProvider extends BaseProvider implements vscode.FileSystemProv const hasCopyApi = api.copy != null; + // Get the up-to-date list of files in the destination directory const apiResponse = await api.fileList(path.posix.join(destInfo.path)); + if (!apiResponse.success) { + throw vscode.FileSystemError.Unavailable( + vscode.l10n.t({ + message: "Error fetching destination {0} for paste action: {1}", + args: [destInfo.path, apiResponse.errorMessage ?? vscode.l10n.t("No error details given")], + comment: ["USS path", "Error message"], + }) + ); + } const fileList = apiResponse.apiResponse?.items; + // Build the name of the file/folder to copy by resolving any potential conflicts const fileName = this.buildFileName(fileList, path.basename(sourceInfo.path)); const outputPath = path.posix.join(destInfo.path, fileName); @@ -649,11 +660,7 @@ export class UssFSProvider extends BaseProvider implements vscode.FileSystemProv } } } else { - const fileEntry = this.lookup(source, true); - if (fileEntry == null) { - return; - } - + const fileEntry = this.lookup(source); if (!fileEntry.wasAccessed) { // must fetch contents of file first before pasting in new path await this.readFile(source); From 2ad426e417d025cda9182ac8e1978a19780bd639 Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Thu, 19 Sep 2024 18:10:43 -0400 Subject: [PATCH 13/19] chore: run prepublish, update comment in code Signed-off-by: Trae Yelovich --- packages/zowe-explorer/l10n/bundle.l10n.json | 44 +++++++++++-------- packages/zowe-explorer/l10n/poeditor.json | 14 +++--- .../src/trees/uss/UssFSProvider.ts | 2 +- 3 files changed, 35 insertions(+), 25 deletions(-) diff --git a/packages/zowe-explorer/l10n/bundle.l10n.json b/packages/zowe-explorer/l10n/bundle.l10n.json index 14f20dab26..a8bc27e595 100644 --- a/packages/zowe-explorer/l10n/bundle.l10n.json +++ b/packages/zowe-explorer/l10n/bundle.l10n.json @@ -172,24 +172,6 @@ "Required API functions for pasting (fileList and copy/uploadFromBuffer) were not found.": "Required API functions for pasting (fileList and copy/uploadFromBuffer) were not found.", "Uploading USS files...": "Uploading USS files...", "Error uploading files": "Error uploading files", - "The 'move' function is not implemented for this USS API.": "The 'move' function is not implemented for this USS API.", - "Could not list USS files: Empty path provided in URI": "Could not list USS files: Empty path provided in URI", - "Profile does not exist for this file.": "Profile does not exist for this file.", - "$(sync~spin) Saving USS file...": "$(sync~spin) Saving USS file...", - "Renaming {0} failed due to API error: {1}/File pathError message": { - "message": "Renaming {0} failed due to API error: {1}", - "comment": [ - "File path", - "Error message" - ] - }, - "Deleting {0} failed due to API error: {1}/File nameError message": { - "message": "Deleting {0} failed due to API error: {1}", - "comment": [ - "File name", - "Error message" - ] - }, "Downloaded: {0}/Download time": { "message": "Downloaded: {0}", "comment": [ @@ -260,6 +242,32 @@ "initializeUSSFavorites.error.buttonRemove": "initializeUSSFavorites.error.buttonRemove", "File does not exist. It may have been deleted.": "File does not exist. It may have been deleted.", "$(sync~spin) Pulling from Mainframe...": "$(sync~spin) Pulling from Mainframe...", + "The 'move' function is not implemented for this USS API.": "The 'move' function is not implemented for this USS API.", + "Could not list USS files: Empty path provided in URI": "Could not list USS files: Empty path provided in URI", + "Profile does not exist for this file.": "Profile does not exist for this file.", + "$(sync~spin) Saving USS file...": "$(sync~spin) Saving USS file...", + "Renaming {0} failed due to API error: {1}/File pathError message": { + "message": "Renaming {0} failed due to API error: {1}", + "comment": [ + "File path", + "Error message" + ] + }, + "Deleting {0} failed due to API error: {1}/File nameError message": { + "message": "Deleting {0} failed due to API error: {1}", + "comment": [ + "File name", + "Error message" + ] + }, + "No error details given": "No error details given", + "Error fetching destination {0} for paste action: {1}/USS pathError message": { + "message": "Error fetching destination {0} for paste action: {1}", + "comment": [ + "USS path", + "Error message" + ] + }, "{0} location/Node type": { "message": "{0} location", "comment": [ diff --git a/packages/zowe-explorer/l10n/poeditor.json b/packages/zowe-explorer/l10n/poeditor.json index 2c32d7577d..eb524dac8c 100644 --- a/packages/zowe-explorer/l10n/poeditor.json +++ b/packages/zowe-explorer/l10n/poeditor.json @@ -495,12 +495,6 @@ "Required API functions for pasting (fileList and copy/uploadFromBuffer) were not found.": "", "Uploading USS files...": "", "Error uploading files": "", - "The 'move' function is not implemented for this USS API.": "", - "Could not list USS files: Empty path provided in URI": "", - "Profile does not exist for this file.": "", - "$(sync~spin) Saving USS file...": "", - "Renaming {0} failed due to API error: {1}": "", - "Deleting {0} failed due to API error: {1}": "", "Downloaded: {0}": "", "Encoding: {0}": "", "Binary": "", @@ -529,6 +523,14 @@ "initializeUSSFavorites.error.buttonRemove": "", "File does not exist. It may have been deleted.": "", "$(sync~spin) Pulling from Mainframe...": "", + "The 'move' function is not implemented for this USS API.": "", + "Could not list USS files: Empty path provided in URI": "", + "Profile does not exist for this file.": "", + "$(sync~spin) Saving USS file...": "", + "Renaming {0} failed due to API error: {1}": "", + "Deleting {0} failed due to API error: {1}": "", + "No error details given": "", + "Error fetching destination {0} for paste action: {1}": "", "{0} location": "", "Choose a location to create the {0}": "", "Name of file or directory": "", diff --git a/packages/zowe-explorer/src/trees/uss/UssFSProvider.ts b/packages/zowe-explorer/src/trees/uss/UssFSProvider.ts index ec37eaf1a7..d775df7e6d 100644 --- a/packages/zowe-explorer/src/trees/uss/UssFSProvider.ts +++ b/packages/zowe-explorer/src/trees/uss/UssFSProvider.ts @@ -633,7 +633,7 @@ export class UssFSProvider extends BaseProvider implements vscode.FileSystemProv } const fileList = apiResponse.apiResponse?.items; - // Build the name of the file/folder to copy by resolving any potential conflicts + // Build the name of the destination file/folder, handling any potential name collisions const fileName = this.buildFileName(fileList, path.basename(sourceInfo.path)); const outputPath = path.posix.join(destInfo.path, fileName); From c66f1964044e2c1c42d26e88fdd0dd423fc4d5e6 Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Thu, 19 Sep 2024 18:12:45 -0400 Subject: [PATCH 14/19] refactor: make extensionRemovedFromPath public, add typedoc Signed-off-by: Trae Yelovich --- packages/zowe-explorer-api/src/fs/types/datasets.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/zowe-explorer-api/src/fs/types/datasets.ts b/packages/zowe-explorer-api/src/fs/types/datasets.ts index 03f7f07035..fb87617dce 100644 --- a/packages/zowe-explorer-api/src/fs/types/datasets.ts +++ b/packages/zowe-explorer-api/src/fs/types/datasets.ts @@ -47,7 +47,10 @@ export class DsEntryMetadata implements EntryMetadata { this.path = metadata.path; } - private extensionRemovedFromPath(): string { + /** + * @returns the data set's file system path without the extension + */ + public extensionRemovedFromPath(): string { for (const ext of [".c", ".jcl", ".cbl", ".cpy", ".inc", ".pli", ".sh", ".rexx", ".xml", ".asm", ".log"]) { if (this.path.endsWith(ext)) { return this.path.replace(ext, ""); From 76e7366fbf688fbfebc4814ca5ceb176ad561864 Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Thu, 19 Sep 2024 18:16:25 -0400 Subject: [PATCH 15/19] chore: add USS bugfix to changelog Signed-off-by: Trae Yelovich --- packages/zowe-explorer/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/zowe-explorer/CHANGELOG.md b/packages/zowe-explorer/CHANGELOG.md index 9fb7f64984..9f34a13fe2 100644 --- a/packages/zowe-explorer/CHANGELOG.md +++ b/packages/zowe-explorer/CHANGELOG.md @@ -11,6 +11,7 @@ All notable changes to the "vscode-extension-for-zowe" extension will be documen - The "Zowe Resources" panel is now hidden by default until Zowe Explorer reveals it to display a table or other data. [#3113](https://github.com/zowe/zowe-explorer-vscode/issues/3113) - 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 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` From d7a30e4d8a5b34e7ce6264f0eb81d43a44c9ab9b Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Fri, 20 Sep 2024 18:33:18 -0400 Subject: [PATCH 16/19] refactor: Move matches & extensions into map Signed-off-by: Trae Yelovich --- .../src/fs/types/datasets.ts | 16 +++++- .../src/trees/dataset/DatasetUtils.ts | 52 +++++++------------ 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/packages/zowe-explorer-api/src/fs/types/datasets.ts b/packages/zowe-explorer-api/src/fs/types/datasets.ts index fb87617dce..d6af739923 100644 --- a/packages/zowe-explorer-api/src/fs/types/datasets.ts +++ b/packages/zowe-explorer-api/src/fs/types/datasets.ts @@ -17,6 +17,20 @@ interface DsEntryProps { stats: Types.DatasetStats; } +export const DS_EXTENSION_MAP: Map = 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; @@ -51,7 +65,7 @@ export class DsEntryMetadata implements EntryMetadata { * @returns the data set's file system path without the extension */ public extensionRemovedFromPath(): string { - for (const ext of [".c", ".jcl", ".cbl", ".cpy", ".inc", ".pli", ".sh", ".rexx", ".xml", ".asm", ".log"]) { + for (const ext of DS_EXTENSION_MAP.keys()) { if (this.path.endsWith(ext)) { return this.path.replace(ext, ""); } diff --git a/packages/zowe-explorer/src/trees/dataset/DatasetUtils.ts b/packages/zowe-explorer/src/trees/dataset/DatasetUtils.ts index 792681e9a6..ed97ad2f8d 100644 --- a/packages/zowe-explorer/src/trees/dataset/DatasetUtils.ts +++ b/packages/zowe-explorer/src/trees/dataset/DatasetUtils.ts @@ -10,7 +10,7 @@ */ import * as vscode from "vscode"; -import { Types } from "@zowe/zowe-explorer-api"; +import { DS_EXTENSION_MAP, Types } from "@zowe/zowe-explorer-api"; import { Constants } from "../../configuration/Constants"; import { ZoweLogger } from "../../tools/ZoweLogger"; @@ -74,38 +74,24 @@ export class DatasetUtils { const bracket = label.indexOf("("); const split = bracket > -1 ? label.substring(0, bracket).split(".", limit) : label.split(".", limit); for (let i = split.length - 1; i > 0; i--) { - if (split[i] === "C") { - return ".c"; - } - if (["JCL", "JCLLIB", "CNTL", "PROC", "PROCLIB"].includes(split[i])) { - return ".jcl"; - } - if (["COBOL", "CBL", "COB", "SCBL"].includes(split[i])) { - return ".cbl"; - } - if (["COPYBOOK", "COPY", "CPY", "COBCOPY"].includes(split[i])) { - return ".cpy"; - } - if (["INC", "INCLUDE", "PLINC"].includes(split[i])) { - return ".inc"; - } - if (["PLI", "PL1", "PLX", "PCX"].includes(split[i])) { - return ".pli"; - } - if (["SH", "SHELL"].includes(split[i])) { - return ".sh"; - } - if (["REXX", "REXEC", "EXEC"].includes(split[i])) { - return ".rexx"; - } - if (split[i] === "XML") { - return ".xml"; - } - if (split[i] === "ASM" || split[i].indexOf("ASSEMBL") > -1) { - return ".asm"; - } - if (split[i] === "LOG" || split[i].indexOf("SPFLOG") > -1) { - return ".log"; + for (const [ext, matches] of DS_EXTENSION_MAP.entries()) { + switch (ext) { + case ".asm": + if (split[i] === matches[0] || split[i].indexOf("ASSEMBL") > -1) { + return ext; + } + break; + case ".log": + if (split[i] === matches[0] || split[i].indexOf("SPFLOG") > -1) { + return ext; + } + break; + default: + if (matches.includes(split[i])) { + return ext; + } + break; + } } } return null; From 6c417428a28d9116c665bddbafc3dc5d2b57926f Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Fri, 20 Sep 2024 18:43:49 -0400 Subject: [PATCH 17/19] tests: add 'ASSEMBLY' and 'SPFLOGS' to getExtension test case Signed-off-by: Trae Yelovich --- .../__tests__/__unit__/trees/dataset/DatasetUtils.unit.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetUtils.unit.test.ts b/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetUtils.unit.test.ts index 51239893bb..7f79ff5e82 100644 --- a/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetUtils.unit.test.ts +++ b/packages/zowe-explorer/__tests__/__unit__/trees/dataset/DatasetUtils.unit.test.ts @@ -25,7 +25,9 @@ describe("Dataset utils unit tests - function getExtension", () => { { 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.getExtension(pair.name)).toBe(pair.extension); From eb06448efd0fd8c5e55e42b7de69fddb4d2f3884 Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Fri, 20 Sep 2024 22:00:30 -0400 Subject: [PATCH 18/19] refactor: Support RegExp in extension map Signed-off-by: Trae Yelovich --- .../src/fs/types/datasets.ts | 6 +++--- .../src/trees/dataset/DatasetUtils.ts | 20 ++++++------------- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/packages/zowe-explorer-api/src/fs/types/datasets.ts b/packages/zowe-explorer-api/src/fs/types/datasets.ts index d6af739923..e809f9c6c9 100644 --- a/packages/zowe-explorer-api/src/fs/types/datasets.ts +++ b/packages/zowe-explorer-api/src/fs/types/datasets.ts @@ -17,7 +17,7 @@ interface DsEntryProps { stats: Types.DatasetStats; } -export const DS_EXTENSION_MAP: Map = new Map([ +export const DS_EXTENSION_MAP: Map = new Map([ [".c", ["C"]], [".jcl", ["JCL", "JCLLIB", "CNTL", "PROC", "PROCLIB"]], [".cbl", ["COBOL", "CBL", "COB", "SCBL"]], @@ -27,8 +27,8 @@ export const DS_EXTENSION_MAP: Map = new Map([ [".sh", ["SH", "SHELL"]], [".rexx", ["REXX", "REXEC", "EXEC"]], [".xml", ["XML"]], - [".asm", ["ASM", "ASSEMBL"]], - [".log", ["LOG", "SPFLOG"]], + [".asm", ["ASM", /ASSEMBL/]], + [".log", ["LOG", /SPFLOG/]], ]); export class DsEntry extends FileEntry implements DsEntryProps { diff --git a/packages/zowe-explorer/src/trees/dataset/DatasetUtils.ts b/packages/zowe-explorer/src/trees/dataset/DatasetUtils.ts index ed97ad2f8d..1483cf89a0 100644 --- a/packages/zowe-explorer/src/trees/dataset/DatasetUtils.ts +++ b/packages/zowe-explorer/src/trees/dataset/DatasetUtils.ts @@ -75,22 +75,14 @@ export class DatasetUtils { const split = bracket > -1 ? label.substring(0, bracket).split(".", limit) : label.split(".", limit); for (let i = split.length - 1; i > 0; i--) { for (const [ext, matches] of DS_EXTENSION_MAP.entries()) { - switch (ext) { - case ".asm": - if (split[i] === matches[0] || split[i].indexOf("ASSEMBL") > -1) { + for (const match of matches) { + if (match instanceof RegExp) { + if (match.test(split[i])) { return ext; } - break; - case ".log": - if (split[i] === matches[0] || split[i].indexOf("SPFLOG") > -1) { - return ext; - } - break; - default: - if (matches.includes(split[i])) { - return ext; - } - break; + } else if (match.includes(split[i])) { + return ext; + } } } } From ef996c81b6af4a4267fe31f4f96f28952598c4b7 Mon Sep 17 00:00:00 2001 From: Billie Simmons <49491949+JillieBeanSim@users.noreply.github.com> Date: Mon, 23 Sep 2024 09:50:06 -0400 Subject: [PATCH 19/19] changes running pre-publish Signed-off-by: Billie Simmons <49491949+JillieBeanSim@users.noreply.github.com> --- packages/zowe-explorer/l10n/bundle.l10n.json | 52 ++++++++++---------- packages/zowe-explorer/l10n/poeditor.json | 16 +++--- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/packages/zowe-explorer/l10n/bundle.l10n.json b/packages/zowe-explorer/l10n/bundle.l10n.json index a8bc27e595..a478e1e650 100644 --- a/packages/zowe-explorer/l10n/bundle.l10n.json +++ b/packages/zowe-explorer/l10n/bundle.l10n.json @@ -172,6 +172,32 @@ "Required API functions for pasting (fileList and copy/uploadFromBuffer) were not found.": "Required API functions for pasting (fileList and copy/uploadFromBuffer) were not found.", "Uploading USS files...": "Uploading USS files...", "Error uploading files": "Error uploading files", + "The 'move' function is not implemented for this USS API.": "The 'move' function is not implemented for this USS API.", + "Could not list USS files: Empty path provided in URI": "Could not list USS files: Empty path provided in URI", + "Profile does not exist for this file.": "Profile does not exist for this file.", + "$(sync~spin) Saving USS file...": "$(sync~spin) Saving USS file...", + "Renaming {0} failed due to API error: {1}/File pathError message": { + "message": "Renaming {0} failed due to API error: {1}", + "comment": [ + "File path", + "Error message" + ] + }, + "Deleting {0} failed due to API error: {1}/File nameError message": { + "message": "Deleting {0} failed due to API error: {1}", + "comment": [ + "File name", + "Error message" + ] + }, + "No error details given": "No error details given", + "Error fetching destination {0} for paste action: {1}/USS pathError message": { + "message": "Error fetching destination {0} for paste action: {1}", + "comment": [ + "USS path", + "Error message" + ] + }, "Downloaded: {0}/Download time": { "message": "Downloaded: {0}", "comment": [ @@ -242,32 +268,6 @@ "initializeUSSFavorites.error.buttonRemove": "initializeUSSFavorites.error.buttonRemove", "File does not exist. It may have been deleted.": "File does not exist. It may have been deleted.", "$(sync~spin) Pulling from Mainframe...": "$(sync~spin) Pulling from Mainframe...", - "The 'move' function is not implemented for this USS API.": "The 'move' function is not implemented for this USS API.", - "Could not list USS files: Empty path provided in URI": "Could not list USS files: Empty path provided in URI", - "Profile does not exist for this file.": "Profile does not exist for this file.", - "$(sync~spin) Saving USS file...": "$(sync~spin) Saving USS file...", - "Renaming {0} failed due to API error: {1}/File pathError message": { - "message": "Renaming {0} failed due to API error: {1}", - "comment": [ - "File path", - "Error message" - ] - }, - "Deleting {0} failed due to API error: {1}/File nameError message": { - "message": "Deleting {0} failed due to API error: {1}", - "comment": [ - "File name", - "Error message" - ] - }, - "No error details given": "No error details given", - "Error fetching destination {0} for paste action: {1}/USS pathError message": { - "message": "Error fetching destination {0} for paste action: {1}", - "comment": [ - "USS path", - "Error message" - ] - }, "{0} location/Node type": { "message": "{0} location", "comment": [ diff --git a/packages/zowe-explorer/l10n/poeditor.json b/packages/zowe-explorer/l10n/poeditor.json index eb524dac8c..7b0ed474b4 100644 --- a/packages/zowe-explorer/l10n/poeditor.json +++ b/packages/zowe-explorer/l10n/poeditor.json @@ -495,6 +495,14 @@ "Required API functions for pasting (fileList and copy/uploadFromBuffer) were not found.": "", "Uploading USS files...": "", "Error uploading files": "", + "The 'move' function is not implemented for this USS API.": "", + "Could not list USS files: Empty path provided in URI": "", + "Profile does not exist for this file.": "", + "$(sync~spin) Saving USS file...": "", + "Renaming {0} failed due to API error: {1}": "", + "Deleting {0} failed due to API error: {1}": "", + "No error details given": "", + "Error fetching destination {0} for paste action: {1}": "", "Downloaded: {0}": "", "Encoding: {0}": "", "Binary": "", @@ -523,14 +531,6 @@ "initializeUSSFavorites.error.buttonRemove": "", "File does not exist. It may have been deleted.": "", "$(sync~spin) Pulling from Mainframe...": "", - "The 'move' function is not implemented for this USS API.": "", - "Could not list USS files: Empty path provided in URI": "", - "Profile does not exist for this file.": "", - "$(sync~spin) Saving USS file...": "", - "Renaming {0} failed due to API error: {1}": "", - "Deleting {0} failed due to API error: {1}": "", - "No error details given": "", - "Error fetching destination {0} for paste action: {1}": "", "{0} location": "", "Choose a location to create the {0}": "", "Name of file or directory": "",