From c5a80b47a526d0beeefcdc521f94ee9239732b03 Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Mon, 13 Jan 2025 17:16:18 -0500 Subject: [PATCH 01/14] wip(feat): add maxLength for dataSetsMatchingPattern; add start for allMembers Signed-off-by: Trae Yelovich --- packages/zosfiles/src/methods/list/List.ts | 5 ++++- packages/zosfiles/src/methods/list/doc/IDsmListOptions.ts | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/zosfiles/src/methods/list/List.ts b/packages/zosfiles/src/methods/list/List.ts index d613d1e9d7..0d4729df2f 100644 --- a/packages/zosfiles/src/methods/list/List.ts +++ b/packages/zosfiles/src/methods/list/List.ts @@ -57,6 +57,9 @@ export class List { if (options.pattern) { endpoint += `?pattern=${encodeURIComponent(options.pattern)}`; } + if (options.start) { + endpoint = `${endpoint}&start=${encodeURIComponent(options.start)}`; + } const reqHeaders: IHeaderContent[] = [ZosmfHeaders.ACCEPT_ENCODING]; if (options.attributes) { @@ -426,7 +429,7 @@ export class List { for (const pattern of patterns) { let response: any; try { - response = await List.dataSet(session, pattern, { attributes: true }); + response = await List.dataSet(session, pattern, { attributes: true, maxLength: options.maxLength }); } catch (err) { if (!(err instanceof ImperativeError && err.errorCode?.toString().startsWith("5"))) { throw err; diff --git a/packages/zosfiles/src/methods/list/doc/IDsmListOptions.ts b/packages/zosfiles/src/methods/list/doc/IDsmListOptions.ts index cc7bfc3ccf..4049f2e577 100644 --- a/packages/zosfiles/src/methods/list/doc/IDsmListOptions.ts +++ b/packages/zosfiles/src/methods/list/doc/IDsmListOptions.ts @@ -32,6 +32,11 @@ export interface IDsmListOptions extends IZosFilesOptions { */ maxConcurrentRequests?: number; + /** + * The indicator that we want to show less data sets or members + */ + maxLength?: number; + /** * Task status object used by CLI handlers to create progress bars * Optional From ee05d36d28bc63a1d892aa0ee2a3d917adf1d11d Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Tue, 14 Jan 2025 09:36:59 -0500 Subject: [PATCH 02/14] tests: Coverage for start & maxLength in List fns Signed-off-by: Trae Yelovich --- .../__unit__/methods/list/List.unit.test.ts | 123 ++++++++++++++++-- 1 file changed, 110 insertions(+), 13 deletions(-) diff --git a/packages/zosfiles/__tests__/__unit__/methods/list/List.unit.test.ts b/packages/zosfiles/__tests__/__unit__/methods/list/List.unit.test.ts index ef6cd556e6..536681f1db 100644 --- a/packages/zosfiles/__tests__/__unit__/methods/list/List.unit.test.ts +++ b/packages/zosfiles/__tests__/__unit__/methods/list/List.unit.test.ts @@ -49,6 +49,8 @@ describe("z/OS Files - List", () => { afterAll(() => { jest.restoreAllMocks(); }); + + const endpoint = posix.join(ZosFilesConstants.RESOURCE, ZosFilesConstants.RES_DS_FILES, dsname, ZosFilesConstants.RES_DS_MEMBERS); describe("allMembers", () => { beforeEach(() => { @@ -56,6 +58,21 @@ describe("z/OS Files - List", () => { expectStringSpy.mockImplementation(async () => listApiResponseString); }); + it("should use X-IBM-Max-Items to limit the number of members returned for a data set", async () => { + const maxLength = 100; + const response = await List.allMembers(dummySession, dsname, { maxLength }); + + expect(expectStringSpy).toHaveBeenCalledTimes(1); + expect(expectStringSpy).toHaveBeenCalledWith(dummySession, endpoint, [ZosmfHeaders.ACCEPT_ENCODING, { "X-IBM-Max-Items": maxLength.toString() }]); + }); + + it("should pass start option in URL search params if provided", async () => { + const response = await List.allMembers(dummySession, dsname, { start: "MEMBER1" }); + + expect(expectStringSpy).toHaveBeenCalledTimes(1); + expect(expectStringSpy).toHaveBeenCalledWith(dummySession, endpoint.concat("?start=MEMBER1"), [ZosmfHeaders.ACCEPT_ENCODING, ZosmfHeaders.X_IBM_MAX_ITEMS]); + }); + it("should throw an error if the data set name is not specified", async () => { let response; let caughtError; @@ -108,8 +125,6 @@ describe("z/OS Files - List", () => { caughtError = e; } - const endpoint = posix.join(ZosFilesConstants.RESOURCE, ZosFilesConstants.RES_DS_FILES, dsname, ZosFilesConstants.RES_DS_MEMBERS); - expect(caughtError).toBeUndefined(); expect(response).toEqual({ success: true, @@ -130,8 +145,6 @@ describe("z/OS Files - List", () => { caughtError = e; } - const endpoint = posix.join(ZosFilesConstants.RESOURCE, ZosFilesConstants.RES_DS_FILES, dsname, ZosFilesConstants.RES_DS_MEMBERS); - expect(caughtError).toBeUndefined(); expect(response).toEqual({ success: true, @@ -153,8 +166,6 @@ describe("z/OS Files - List", () => { caughtError = e; } - const endpoint = posix.join(ZosFilesConstants.RESOURCE, ZosFilesConstants.RES_DS_FILES, dsname, ZosFilesConstants.RES_DS_MEMBERS); - expect(caughtError).toBeUndefined(); expect(response).toEqual({ success: true, @@ -191,8 +202,6 @@ describe("z/OS Files - List", () => { caughtError = e; } - const endpoint = posix.join(ZosFilesConstants.RESOURCE, ZosFilesConstants.RES_DS_FILES, dsname, ZosFilesConstants.RES_DS_MEMBERS); - expect(caughtError).toBeUndefined(); expect(response).toEqual({ success: true, @@ -219,8 +228,6 @@ describe("z/OS Files - List", () => { caughtError = e; } - const endpoint = posix.join(ZosFilesConstants.RESOURCE, ZosFilesConstants.RES_DS_FILES, dsname, ZosFilesConstants.RES_DS_MEMBERS + query); - expect(caughtError).toBeUndefined(); expect(response).toEqual({ success: true, @@ -228,7 +235,7 @@ describe("z/OS Files - List", () => { apiResponse: listApiResponse }); expect(expectStringSpy).toHaveBeenCalledTimes(1); - expect(expectStringSpy).toHaveBeenCalledWith(dummySession, endpoint, [ZosmfHeaders.ACCEPT_ENCODING, ZosmfHeaders.X_IBM_MAX_ITEMS]); + expect(expectStringSpy).toHaveBeenCalledWith(dummySession, endpoint.concat(query), [ZosmfHeaders.ACCEPT_ENCODING, ZosmfHeaders.X_IBM_MAX_ITEMS]); }); it("should list members from given data set with additional attributes", async () => { @@ -243,8 +250,6 @@ describe("z/OS Files - List", () => { caughtError = e; } - const endpoint = posix.join(ZosFilesConstants.RESOURCE, ZosFilesConstants.RES_DS_FILES, dsname, ZosFilesConstants.RES_DS_MEMBERS); - expect(caughtError).toBeUndefined(); expect(response).toEqual({ success: true, @@ -1356,6 +1361,66 @@ describe("z/OS Files - List", () => { listDataSetSpy.mockResolvedValue({} as any); }); + it("should pass maxLength option to List.dataSet API", async () => { + const pattern = "TEST.**.DATA.SET"; + let response; + let caughtError; + + listDataSetSpy.mockImplementation(async (): Promise => { + return { + apiResponse: { + items: [dataSetPS, dataSetPO] + } + }; + }); + + try { + response = await List.dataSetsMatchingPattern(dummySession, [pattern], { maxLength: 2 }); + } catch (e) { + caughtError = e; + } + + expect(caughtError).toBeUndefined(); + expect(response).toEqual({ + success: true, + commandResponse: util.format(ZosFilesMessages.dataSetsMatchedPattern.message, 2), + apiResponse: [dataSetPS, dataSetPO] + }); + + expect(listDataSetSpy).toHaveBeenCalledTimes(1); + expect(listDataSetSpy).toHaveBeenCalledWith(dummySession, pattern, { attributes: true, maxLength: 2 }); + }); + + it("should pass start option to List.dataSet API", async () => { + const pattern = "TEST.**.DATA.SET"; + let response; + let caughtError; + + listDataSetSpy.mockImplementation(async (): Promise => { + return { + apiResponse: { + items: [dataSetPO] + } + }; + }); + + try { + response = await List.dataSetsMatchingPattern(dummySession, [pattern], { start: dataSetPO.dsname }); + } catch (e) { + caughtError = e; + } + + expect(caughtError).toBeUndefined(); + expect(response).toEqual({ + success: true, + commandResponse: util.format(ZosFilesMessages.dataSetsMatchedPattern.message, 1), + apiResponse: [dataSetPO] + }); + + expect(listDataSetSpy).toHaveBeenCalledTimes(1); + expect(listDataSetSpy).toHaveBeenCalledWith(dummySession, pattern, { attributes: true, start: dataSetPO.dsname }); + }); + it("should successfully list PS and PO data sets using the List.dataSet API", async () => { const pattern = "TEST.**.DATA.SET"; let response; @@ -1549,6 +1614,38 @@ describe("z/OS Files - List", () => { listDataSetSpy.mockResolvedValue({} as any); }); + it("should pass maxLength option to List.allMembers API", async () => { + const pattern = "M*"; + const maxLength = 2; + listDataSetSpy.mockImplementation(async (): Promise => { + return { + apiResponse: { + items: [memberData1, memberData2] + } + }; + }); + await List.membersMatchingPattern(dummySession, dsname, [pattern], { maxLength }); + + expect(listDataSetSpy).toHaveBeenCalledTimes(1); + expect(listDataSetSpy).toHaveBeenCalledWith(dummySession, dsname, { pattern, maxLength }); + }); + + it("should pass start option to List.allMembers API", async () => { + const pattern = "M*"; + const start = "M1"; + listDataSetSpy.mockImplementation(async (): Promise => { + return { + apiResponse: { + items: [memberData1, memberData2] + } + }; + }); + await List.membersMatchingPattern(dummySession, dsname, [pattern], { start }); + + expect(listDataSetSpy).toHaveBeenCalledTimes(1); + expect(listDataSetSpy).toHaveBeenCalledWith(dummySession, dsname, { pattern, start }); + }); + it("should successfully list M1 & M2 using the List.allMembers API", async () => { const dsname = "TEST.PS"; const pattern = "M*"; From abdb80dfa0c5d5d28b2417e3dd4c6badcc4e3f14 Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Tue, 14 Jan 2025 09:38:02 -0500 Subject: [PATCH 03/14] feat: cleanup query params in allMembers, add start to matchingPattern fns Signed-off-by: Trae Yelovich --- packages/zosfiles/src/methods/list/List.ts | 13 +++++++------ .../src/methods/list/doc/IDsmListOptions.ts | 5 +++++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/zosfiles/src/methods/list/List.ts b/packages/zosfiles/src/methods/list/List.ts index 0d4729df2f..15a5262d16 100644 --- a/packages/zosfiles/src/methods/list/List.ts +++ b/packages/zosfiles/src/methods/list/List.ts @@ -48,17 +48,18 @@ export class List { try { // Format the endpoint to send the request to - let endpoint = posix.join( + const endpoint = posix.join( ZosFilesConstants.RESOURCE, ZosFilesConstants.RES_DS_FILES, encodeURIComponent(dataSetName), ZosFilesConstants.RES_DS_MEMBERS); + const params = new URLSearchParams(); if (options.pattern) { - endpoint += `?pattern=${encodeURIComponent(options.pattern)}`; + params.set("pattern", encodeURIComponent(options.pattern)); } if (options.start) { - endpoint = `${endpoint}&start=${encodeURIComponent(options.start)}`; + params.set("start", encodeURIComponent(options.start)); } const reqHeaders: IHeaderContent[] = [ZosmfHeaders.ACCEPT_ENCODING]; @@ -76,7 +77,7 @@ export class List { this.log.debug(`Endpoint: ${endpoint}`); - const data = await ZosmfRestClient.getExpectString(session, endpoint, reqHeaders); + const data = await ZosmfRestClient.getExpectString(session, endpoint.concat(params.size > 0 ? `?${params.toString()}` : ""), reqHeaders); let response: any; try { response = JSONUtils.parse(data); @@ -127,7 +128,7 @@ export class List { const zosmfResponses: IZosmfListResponse[] = []; for(const pattern of patterns) { - const response = await List.allMembers(session, dataSetName, { pattern}); + const response = await List.allMembers(session, dataSetName, { pattern, maxLength: options.maxLength, start: options.start }); zosmfResponses.push(...response.apiResponse.items); } @@ -429,7 +430,7 @@ export class List { for (const pattern of patterns) { let response: any; try { - response = await List.dataSet(session, pattern, { attributes: true, maxLength: options.maxLength }); + response = await List.dataSet(session, pattern, { attributes: true, maxLength: options.maxLength, start: options.start }); } catch (err) { if (!(err instanceof ImperativeError && err.errorCode?.toString().startsWith("5"))) { throw err; diff --git a/packages/zosfiles/src/methods/list/doc/IDsmListOptions.ts b/packages/zosfiles/src/methods/list/doc/IDsmListOptions.ts index 4049f2e577..b474bd01b9 100644 --- a/packages/zosfiles/src/methods/list/doc/IDsmListOptions.ts +++ b/packages/zosfiles/src/methods/list/doc/IDsmListOptions.ts @@ -37,6 +37,11 @@ export interface IDsmListOptions extends IZosFilesOptions { */ maxLength?: number; + /** + * An optional search parameter that specifies the first data set name to return in the response document + */ + start?: string; + /** * Task status object used by CLI handlers to create progress bars * Optional From bc74851d98371ae130f5bac4dd1e337e1060a192 Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Tue, 14 Jan 2025 10:13:50 -0500 Subject: [PATCH 04/14] tests: System tests for listing data set members Signed-off-by: Trae Yelovich --- .../methods/list/List.system.test.ts | 100 ++++++++++++++++-- 1 file changed, 92 insertions(+), 8 deletions(-) diff --git a/packages/zosfiles/__tests__/__system__/methods/list/List.system.test.ts b/packages/zosfiles/__tests__/__system__/methods/list/List.system.test.ts index e529caf9a6..954de695e0 100644 --- a/packages/zosfiles/__tests__/__system__/methods/list/List.system.test.ts +++ b/packages/zosfiles/__tests__/__system__/methods/list/List.system.test.ts @@ -48,12 +48,16 @@ describe("List command group", () => { describe("All Members", () => { describe("Success Scenarios", () => { - const testString = "test"; + const memberOne = "test"; + const memberTwo = "test2"; beforeEach(async () => { await Create.dataSet(REAL_SESSION, CreateDataSetTypeEnum.DATA_SET_PARTITIONED, dsname, { volser: defaultSystem.datasets.vol }); await wait(waitTime); //wait 2 seconds - await Upload.bufferToDataSet(REAL_SESSION, Buffer.from(testString), `${dsname}(${testString})`); + // first data set member + await Upload.bufferToDataSet(REAL_SESSION, Buffer.from(memberOne), `${dsname}(${memberOne})`); + // second data set member + await Upload.bufferToDataSet(REAL_SESSION, Buffer.from(memberOne), `${dsname}(${memberTwo})`); await wait(waitTime); //wait 2 seconds }); @@ -77,8 +81,47 @@ describe("List command group", () => { expect(response).toBeTruthy(); expect(response.success).toBeTruthy(); expect(response.commandResponse).toBe(null); + expect(response.apiResponse.items.length).toBe(2); + expect(response.apiResponse.items[0].member).toEqual(memberOne.toUpperCase()); + expect(response.apiResponse.items[1].member).toEqual(memberTwo.toUpperCase()); + }); + + it("should limit number of members when maxLength is provided", async () => { + let error; + let response: IZosFilesResponse; + + try { + response = await List.allMembers(REAL_SESSION, dsname, {maxLength: 1}); + Imperative.console.info("Response: " + inspect(response)); + } catch (err) { + error = err; + Imperative.console.info("Error: " + inspect(error)); + } + expect(error).toBeFalsy(); + expect(response).toBeTruthy(); + expect(response.success).toBeTruthy(); + expect(response.commandResponse).toBe(null); expect(response.apiResponse.items.length).toBe(1); - expect(response.apiResponse.items[0].member).toEqual(testString.toUpperCase()); + expect(response.apiResponse.items[0].member).toEqual(memberOne.toUpperCase()); + }); + + it("should return a list starting with the given member in the start option", async () => { + let error; + let response: IZosFilesResponse; + + try { + response = await List.allMembers(REAL_SESSION, dsname, { start: memberTwo }); + Imperative.console.info("Response: " + inspect(response)); + } catch (err) { + error = err; + Imperative.console.info("Error: " + inspect(error)); + } + expect(error).toBeFalsy(); + expect(response).toBeTruthy(); + expect(response.success).toBeTruthy(); + expect(response.commandResponse).toBe(null); + expect(response.apiResponse.items.length).toBe(1); + expect(response.apiResponse.items[0].member).toEqual(memberTwo.toUpperCase()); }); it("should list all members of a data set with response timeout", async () => { @@ -96,8 +139,9 @@ describe("List command group", () => { expect(response).toBeTruthy(); expect(response.success).toBeTruthy(); expect(response.commandResponse).toBe(null); - expect(response.apiResponse.items.length).toBe(1); - expect(response.apiResponse.items[0].member).toEqual(testString.toUpperCase()); + expect(response.apiResponse.items.length).toBe(2); + expect(response.apiResponse.items[0].member).toEqual(memberOne.toUpperCase()); + expect(response.apiResponse.items[1].member).toEqual(memberTwo.toUpperCase()); }); it("should list all members of a data set with attributes", async () => { @@ -118,8 +162,9 @@ describe("List command group", () => { expect(response).toBeTruthy(); expect(response.success).toBeTruthy(); expect(response.commandResponse).toBe(null); - expect(response.apiResponse.items.length).toBe(1); - expect(response.apiResponse.items[0].member).toEqual(testString.toUpperCase()); + expect(response.apiResponse.items.length).toBe(2); + expect(response.apiResponse.items[0].member).toEqual(memberOne.toUpperCase()); + expect(response.apiResponse.items[1].member).toEqual(memberTwo.toUpperCase()); expect(response.apiResponse.items[0].user).toBeDefined(); }); @@ -129,7 +174,8 @@ describe("List command group", () => { let error; try { - await Delete.dataSet(REAL_SESSION, `${dsname}(${testString})`); + await Delete.dataSet(REAL_SESSION, `${dsname}(${memberOne})`); + await Delete.dataSet(REAL_SESSION, `${dsname}(${memberTwo})`); await wait(waitTime); //wait 2 seconds response = await List.allMembers(REAL_SESSION, dsname); } catch (err) { @@ -728,6 +774,44 @@ describe("List command group", () => { expect(response).toBeDefined(); expect(response.commandResponse).toContain("There are no members that match"); }); + + it("should limit number of members when maxLength is provided", async () => { + let error; + let response: IZosFilesResponse; + + try { + response = await List.membersMatchingPattern(REAL_SESSION, dsname, [pattern], { maxLength: 1 }); + Imperative.console.info("Response: " + inspect(response)); + } catch (err) { + error = err; + Imperative.console.info("Error: " + inspect(error)); + } + expect(error).toBeFalsy(); + expect(response).toBeTruthy(); + expect(response.success).toBeTruthy(); + expect(response.commandResponse).toBe(null); + expect(response.apiResponse.items.length).toBe(1); + expect(response.apiResponse.items[0].member).toEqual(members[0]); + }); + + it("should return a list starting with the given member in the start option", async () => { + let error; + let response: IZosFilesResponse; + + try { + response = await List.membersMatchingPattern(REAL_SESSION, dsname, [pattern], { start: "M1A" }); + Imperative.console.info("Response: " + inspect(response)); + } catch (err) { + error = err; + Imperative.console.info("Error: " + inspect(error)); + } + expect(error).toBeFalsy(); + expect(response).toBeTruthy(); + expect(response.success).toBeTruthy(); + expect(response.commandResponse).toBe(null); + expect(response.apiResponse.items.length).toBe(3); + expect(response.apiResponse.items[0].member).toEqual(members[1]); + }); }); }); From c5b5bef367f3abcc6c1097b1f35fc0b7e03a072e Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Tue, 14 Jan 2025 10:24:03 -0500 Subject: [PATCH 05/14] tests: fix system tests for List.dataSet and add cases Signed-off-by: Trae Yelovich --- .../methods/list/List.system.test.ts | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/packages/zosfiles/__tests__/__system__/methods/list/List.system.test.ts b/packages/zosfiles/__tests__/__system__/methods/list/List.system.test.ts index 954de695e0..1c8fdf17f4 100644 --- a/packages/zosfiles/__tests__/__system__/methods/list/List.system.test.ts +++ b/packages/zosfiles/__tests__/__system__/methods/list/List.system.test.ts @@ -21,6 +21,7 @@ let REAL_SESSION: Session; let testEnvironment: ITestEnvironment; let defaultSystem: ITestPropertiesSchema; let dsname: string; +let dsname2: string; let path: string; let filename: string; @@ -34,6 +35,7 @@ describe("List command group", () => { REAL_SESSION = TestEnvironment.createZosmfSession(testEnvironment); dsname = getUniqueDatasetName(`${defaultSystem.zosmf.user}.ZOSFILE.LIST`, false, 1); + dsname2 = getUniqueDatasetName(`${defaultSystem.zosmf.user}.ZOSFILE.LIST2`, false, 1); Imperative.console.info("Using dsname:" + dsname); const user = `${defaultSystem.zosmf.user.trim()}`.replace(/\./g, ""); @@ -226,15 +228,18 @@ describe("List command group", () => { beforeEach(async () => { await Create.dataSet(REAL_SESSION, CreateDataSetTypeEnum.DATA_SET_SEQUENTIAL, dsname, { volser: defaultSystem.datasets.vol }); + await Create.dataSet(REAL_SESSION, CreateDataSetTypeEnum.DATA_SET_SEQUENTIAL, dsname2, + { volser: defaultSystem.datasets.vol }); await wait(waitTime); //wait 2 seconds }); afterEach(async () => { await Delete.dataSet(REAL_SESSION, dsname); + await Delete.dataSet(REAL_SESSION, dsname2); await wait(waitTime); //wait 2 seconds }); - it("should list a data set", async () => { + it("should list data set matching dsname", async () => { let error; let response: IZosFilesResponse; @@ -253,16 +258,35 @@ describe("List command group", () => { expect(response.apiResponse.items[0].dsname).toEqual(dsname); }); + it("should limit amount of data sets with maxLength", async () => { + let error; + let response: IZosFilesResponse; + + try { + response = await List.dataSet(REAL_SESSION, `${dsname}*`, { maxLength: 1 }); + Imperative.console.info("Response: " + inspect(response)); + } catch (err) { + error = err; + Imperative.console.info("Error: " + inspect(error)); + } + expect(error).toBeFalsy(); + expect(response).toBeTruthy(); + expect(response.success).toBeTruthy(); + expect(response.commandResponse).toBe(null); + expect(response.apiResponse.items.length).toBe(1); + expect(response.apiResponse.items[0].dsname).toEqual(dsname); + }); + it("should list a data set with attributes and start options", async () => { let error; let response: IZosFilesResponse; const option: IListOptions = { attributes: true, - start: dsname + start: dsname2 }; try { - response = await List.dataSet(REAL_SESSION, dsname, option); + response = await List.dataSet(REAL_SESSION, `${dsname}*`, option); Imperative.console.info("Response: " + inspect(response)); } catch (err) { error = err; @@ -273,7 +297,7 @@ describe("List command group", () => { expect(response.success).toBeTruthy(); expect(response.commandResponse).toBe(null); expect(response.apiResponse.items.length).toBe(1); - expect(response.apiResponse.items[0].dsname).toEqual(dsname); + expect(response.apiResponse.items[0].dsname).toEqual(dsname2); expect(response.apiResponse.items[0].dsorg).toBeDefined(); }); From aa7603fd12ad14567e855dad27dd932b06dfd309 Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Tue, 14 Jan 2025 15:02:37 -0500 Subject: [PATCH 06/14] test: fix commandResponse in system tests Signed-off-by: Trae Yelovich --- .../__tests__/__system__/methods/list/List.system.test.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/zosfiles/__tests__/__system__/methods/list/List.system.test.ts b/packages/zosfiles/__tests__/__system__/methods/list/List.system.test.ts index 1c8fdf17f4..b825e0878b 100644 --- a/packages/zosfiles/__tests__/__system__/methods/list/List.system.test.ts +++ b/packages/zosfiles/__tests__/__system__/methods/list/List.system.test.ts @@ -253,7 +253,7 @@ describe("List command group", () => { expect(error).toBeFalsy(); expect(response).toBeTruthy(); expect(response.success).toBeTruthy(); - expect(response.commandResponse).toBe(null); + expect(response.commandResponse).toBe("1 members(s) were found matching pattern."); expect(response.apiResponse.items.length).toBe(1); expect(response.apiResponse.items[0].dsname).toEqual(dsname); }); @@ -746,6 +746,7 @@ describe("List command group", () => { await Delete.dataSet(REAL_SESSION, dsname); await wait(waitTime); //wait 2 seconds }); + it("should find data sets that match a pattern", async () => { let error; let response: IZosFilesResponse; @@ -813,7 +814,7 @@ describe("List command group", () => { expect(error).toBeFalsy(); expect(response).toBeTruthy(); expect(response.success).toBeTruthy(); - expect(response.commandResponse).toBe(null); + expect(response.commandResponse).toBe("1 members(s) were found matching pattern."); expect(response.apiResponse.items.length).toBe(1); expect(response.apiResponse.items[0].member).toEqual(members[0]); }); @@ -832,7 +833,7 @@ describe("List command group", () => { expect(error).toBeFalsy(); expect(response).toBeTruthy(); expect(response.success).toBeTruthy(); - expect(response.commandResponse).toBe(null); + expect(response.commandResponse).toBe("3 members(s) were found matching pattern."); expect(response.apiResponse.items.length).toBe(3); expect(response.apiResponse.items[0].member).toEqual(members[1]); }); From 353a261ed83afad5955637e2f1da9fc1315a9eff Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Tue, 14 Jan 2025 16:00:09 -0500 Subject: [PATCH 07/14] tests: fix dsname2 variable for system tests Signed-off-by: Trae Yelovich --- .../__tests__/__system__/methods/list/List.system.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/zosfiles/__tests__/__system__/methods/list/List.system.test.ts b/packages/zosfiles/__tests__/__system__/methods/list/List.system.test.ts index b825e0878b..1336b67df7 100644 --- a/packages/zosfiles/__tests__/__system__/methods/list/List.system.test.ts +++ b/packages/zosfiles/__tests__/__system__/methods/list/List.system.test.ts @@ -35,7 +35,7 @@ describe("List command group", () => { REAL_SESSION = TestEnvironment.createZosmfSession(testEnvironment); dsname = getUniqueDatasetName(`${defaultSystem.zosmf.user}.ZOSFILE.LIST`, false, 1); - dsname2 = getUniqueDatasetName(`${defaultSystem.zosmf.user}.ZOSFILE.LIST2`, false, 1); + dsname2 = dsname + '2'; Imperative.console.info("Using dsname:" + dsname); const user = `${defaultSystem.zosmf.user.trim()}`.replace(/\./g, ""); From 54141b9e0f06e6f708c5699a4a94b061a358cc37 Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Tue, 14 Jan 2025 16:10:35 -0500 Subject: [PATCH 08/14] fix lint warnings Signed-off-by: Trae Yelovich --- .../__unit__/methods/list/List.unit.test.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/zosfiles/__tests__/__unit__/methods/list/List.unit.test.ts b/packages/zosfiles/__tests__/__unit__/methods/list/List.unit.test.ts index 536681f1db..7cc6c7eecd 100644 --- a/packages/zosfiles/__tests__/__unit__/methods/list/List.unit.test.ts +++ b/packages/zosfiles/__tests__/__unit__/methods/list/List.unit.test.ts @@ -49,7 +49,7 @@ describe("z/OS Files - List", () => { afterAll(() => { jest.restoreAllMocks(); }); - + const endpoint = posix.join(ZosFilesConstants.RESOURCE, ZosFilesConstants.RES_DS_FILES, dsname, ZosFilesConstants.RES_DS_MEMBERS); describe("allMembers", () => { @@ -61,16 +61,18 @@ describe("z/OS Files - List", () => { it("should use X-IBM-Max-Items to limit the number of members returned for a data set", async () => { const maxLength = 100; const response = await List.allMembers(dummySession, dsname, { maxLength }); - + expect(expectStringSpy).toHaveBeenCalledTimes(1); - expect(expectStringSpy).toHaveBeenCalledWith(dummySession, endpoint, [ZosmfHeaders.ACCEPT_ENCODING, { "X-IBM-Max-Items": maxLength.toString() }]); + expect(expectStringSpy).toHaveBeenCalledWith(dummySession, endpoint, + [ZosmfHeaders.ACCEPT_ENCODING, { "X-IBM-Max-Items": maxLength.toString() }]); }); - + it("should pass start option in URL search params if provided", async () => { const response = await List.allMembers(dummySession, dsname, { start: "MEMBER1" }); expect(expectStringSpy).toHaveBeenCalledTimes(1); - expect(expectStringSpy).toHaveBeenCalledWith(dummySession, endpoint.concat("?start=MEMBER1"), [ZosmfHeaders.ACCEPT_ENCODING, ZosmfHeaders.X_IBM_MAX_ITEMS]); + expect(expectStringSpy).toHaveBeenCalledWith(dummySession, endpoint.concat("?start=MEMBER1"), + [ZosmfHeaders.ACCEPT_ENCODING, ZosmfHeaders.X_IBM_MAX_ITEMS]); }); it("should throw an error if the data set name is not specified", async () => { @@ -235,7 +237,8 @@ describe("z/OS Files - List", () => { apiResponse: listApiResponse }); expect(expectStringSpy).toHaveBeenCalledTimes(1); - expect(expectStringSpy).toHaveBeenCalledWith(dummySession, endpoint.concat(query), [ZosmfHeaders.ACCEPT_ENCODING, ZosmfHeaders.X_IBM_MAX_ITEMS]); + expect(expectStringSpy).toHaveBeenCalledWith(dummySession, endpoint.concat(query), + [ZosmfHeaders.ACCEPT_ENCODING, ZosmfHeaders.X_IBM_MAX_ITEMS]); }); it("should list members from given data set with additional attributes", async () => { From e2d8735d461d06e28dc2ace2386204e674e692f0 Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Tue, 14 Jan 2025 16:21:45 -0500 Subject: [PATCH 09/14] remove unused vars in unit tests Signed-off-by: Trae Yelovich --- .../__tests__/__unit__/methods/list/List.unit.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/zosfiles/__tests__/__unit__/methods/list/List.unit.test.ts b/packages/zosfiles/__tests__/__unit__/methods/list/List.unit.test.ts index 7cc6c7eecd..4e79ff22d4 100644 --- a/packages/zosfiles/__tests__/__unit__/methods/list/List.unit.test.ts +++ b/packages/zosfiles/__tests__/__unit__/methods/list/List.unit.test.ts @@ -60,7 +60,7 @@ describe("z/OS Files - List", () => { it("should use X-IBM-Max-Items to limit the number of members returned for a data set", async () => { const maxLength = 100; - const response = await List.allMembers(dummySession, dsname, { maxLength }); + await List.allMembers(dummySession, dsname, { maxLength }); expect(expectStringSpy).toHaveBeenCalledTimes(1); expect(expectStringSpy).toHaveBeenCalledWith(dummySession, endpoint, @@ -68,7 +68,7 @@ describe("z/OS Files - List", () => { }); it("should pass start option in URL search params if provided", async () => { - const response = await List.allMembers(dummySession, dsname, { start: "MEMBER1" }); + await List.allMembers(dummySession, dsname, { start: "MEMBER1" }); expect(expectStringSpy).toHaveBeenCalledTimes(1); expect(expectStringSpy).toHaveBeenCalledWith(dummySession, endpoint.concat("?start=MEMBER1"), From 14c3826798247195939c44ddde10515d65907d72 Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Wed, 15 Jan 2025 09:55:35 -0500 Subject: [PATCH 10/14] adjust system test logic to match actual apiResponse Signed-off-by: Trae Yelovich --- .../__system__/methods/list/List.system.test.ts | 10 +++++----- packages/zosfiles/src/methods/list/List.ts | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/zosfiles/__tests__/__system__/methods/list/List.system.test.ts b/packages/zosfiles/__tests__/__system__/methods/list/List.system.test.ts index 1336b67df7..a10a0ec88c 100644 --- a/packages/zosfiles/__tests__/__system__/methods/list/List.system.test.ts +++ b/packages/zosfiles/__tests__/__system__/methods/list/List.system.test.ts @@ -253,7 +253,7 @@ describe("List command group", () => { expect(error).toBeFalsy(); expect(response).toBeTruthy(); expect(response.success).toBeTruthy(); - expect(response.commandResponse).toBe("1 members(s) were found matching pattern."); + expect(response.commandResponse).toBe(null); expect(response.apiResponse.items.length).toBe(1); expect(response.apiResponse.items[0].dsname).toEqual(dsname); }); @@ -815,8 +815,8 @@ describe("List command group", () => { expect(response).toBeTruthy(); expect(response.success).toBeTruthy(); expect(response.commandResponse).toBe("1 members(s) were found matching pattern."); - expect(response.apiResponse.items.length).toBe(1); - expect(response.apiResponse.items[0].member).toEqual(members[0]); + expect(response.apiResponse.length).toBe(1); + expect(response.apiResponse[0].member).toEqual(members[0]); }); it("should return a list starting with the given member in the start option", async () => { @@ -834,8 +834,8 @@ describe("List command group", () => { expect(response).toBeTruthy(); expect(response.success).toBeTruthy(); expect(response.commandResponse).toBe("3 members(s) were found matching pattern."); - expect(response.apiResponse.items.length).toBe(3); - expect(response.apiResponse.items[0].member).toEqual(members[1]); + expect(response.apiResponse.length).toBe(3); + expect(response.apiResponse[0].member).toEqual(members[1]); }); }); diff --git a/packages/zosfiles/src/methods/list/List.ts b/packages/zosfiles/src/methods/list/List.ts index 15a5262d16..ac6cafbc49 100644 --- a/packages/zosfiles/src/methods/list/List.ts +++ b/packages/zosfiles/src/methods/list/List.ts @@ -143,7 +143,7 @@ export class List { // Exclude names of members for (const pattern of options.excludePatterns || []) { - const response = await List.allMembers(session, dataSetName, {pattern}); + const response = await List.allMembers(session, dataSetName, { pattern }); response.apiResponse.items.forEach((membersObj: IZosmfListResponse) => { const responseIndex = zosmfResponses.findIndex(response=> response.member === membersObj.member); if (responseIndex !== -1) { From b941bc514f86d6f97166d587188f826fae798a4b Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Wed, 15 Jan 2025 10:42:54 -0500 Subject: [PATCH 11/14] chore: update z/OS Files SDK changelog Signed-off-by: Trae Yelovich --- packages/zosfiles/CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/zosfiles/CHANGELOG.md b/packages/zosfiles/CHANGELOG.md index e72cf748c9..7c0a6d2825 100644 --- a/packages/zosfiles/CHANGELOG.md +++ b/packages/zosfiles/CHANGELOG.md @@ -2,11 +2,17 @@ All notable changes to the Zowe z/OS files SDK package will be documented in this file. +## Recent Changes + +- Enhancement: Added the `maxLength` option to List SDK functions (`allMembers, dataSetsMatchingPattern, membersMatchingPattern`) to specify the maximum number of items to return. +- Enhancement: Added the `start` option to List SDK functions (`allMembers, dataSetsMatchingPattern, membersMatchingPattern`) to specify the first data set/member name to return in the response. + ## `8.10.3` - BugFix: The `Copy.dataset` method no longer copies all partitioned data set members if a member is passed to the function. [#2402](https://github.com/zowe/zowe-cli/pull/2402) ## `8.10.0` + - Enhancement: The `Copy.dataset` method now recognizes partitioned data sets and can copy members of a source PDS into an existing target PDS. [#2386](https://github.com/zowe/zowe-cli/pull/2386) ## `8.9.1` @@ -15,6 +21,7 @@ All notable changes to the Zowe z/OS files SDK package will be documented in thi - BugFix: Corrected the `Upload.BufferToUssFile()` SDK function to properly tag uploaded files. [#2378](https://github.com/zowe/zowe-cli/pull/2378) ## `8.9.0` + - Enhancement: Added a `List.membersMatchingPattern` method to download all members that match a specific pattern.[#2359](https://github.com/zowe/zowe-cli/pull/2359) ## `8.8.4` From 186606e6238567edc0da6a3362dfc35371e98cd2 Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Wed, 15 Jan 2025 11:37:29 -0500 Subject: [PATCH 12/14] chore: add PR link to changelog entries Signed-off-by: Trae Yelovich --- packages/zosfiles/CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/zosfiles/CHANGELOG.md b/packages/zosfiles/CHANGELOG.md index 7c0a6d2825..8bfeab77a5 100644 --- a/packages/zosfiles/CHANGELOG.md +++ b/packages/zosfiles/CHANGELOG.md @@ -4,8 +4,8 @@ All notable changes to the Zowe z/OS files SDK package will be documented in thi ## Recent Changes -- Enhancement: Added the `maxLength` option to List SDK functions (`allMembers, dataSetsMatchingPattern, membersMatchingPattern`) to specify the maximum number of items to return. -- Enhancement: Added the `start` option to List SDK functions (`allMembers, dataSetsMatchingPattern, membersMatchingPattern`) to specify the first data set/member name to return in the response. +- Enhancement: Added the `maxLength` option to List SDK functions (`allMembers, dataSetsMatchingPattern, membersMatchingPattern`) to specify the maximum number of items to return. [#2409](https://github.com/zowe/zowe-cli/pull/2409) +- Enhancement: Added the `start` option to List SDK functions (`allMembers, dataSetsMatchingPattern, membersMatchingPattern`) to specify the first data set/member name to return in the response. [#2409](https://github.com/zowe/zowe-cli/pull/2409) ## `8.10.3` From 0a1f0568a2e8128e7619ad2787e31ed5efc5372d Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Wed, 15 Jan 2025 14:30:37 -0500 Subject: [PATCH 13/14] do not use encodeURIComponent for URLSearchParams Signed-off-by: Trae Yelovich --- packages/zosfiles/src/methods/list/List.ts | 505 ++++++++++++++++----- 1 file changed, 380 insertions(+), 125 deletions(-) diff --git a/packages/zosfiles/src/methods/list/List.ts b/packages/zosfiles/src/methods/list/List.ts index ac6cafbc49..b99e139cde 100644 --- a/packages/zosfiles/src/methods/list/List.ts +++ b/packages/zosfiles/src/methods/list/List.ts @@ -1,20 +1,32 @@ /* -* This program and the accompanying materials are made available under the terms of the -* Eclipse Public License v2.0 which accompanies this distribution, and is available at -* https://www.eclipse.org/legal/epl-v20.html -* -* SPDX-License-Identifier: EPL-2.0 -* -* Copyright Contributors to the Zowe Project. -* -*/ - -import { AbstractSession, IHeaderContent, ImperativeError, ImperativeExpect, JSONUtils, Logger, TaskProgress } from "@zowe/imperative"; + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Copyright Contributors to the Zowe Project. + * + */ + +import { + AbstractSession, + IHeaderContent, + ImperativeError, + ImperativeExpect, + JSONUtils, + Logger, + TaskProgress, +} from "@zowe/imperative"; import { posix } from "path"; import * as util from "util"; -import { ZosmfRestClient, ZosmfHeaders, asyncPool } from "@zowe/core-for-zowe-sdk"; +import { + ZosmfRestClient, + ZosmfHeaders, + asyncPool, +} from "@zowe/core-for-zowe-sdk"; import { ZosFilesConstants } from "../../constants/ZosFiles.constants"; import { ZosFilesMessages } from "../../constants/ZosFiles.messages"; import { IZosFilesResponse } from "../../doc/IZosFilesResponse"; @@ -41,10 +53,21 @@ export class List { * * @see https://www.ibm.com/support/knowledgecenter/en/SSLTBW_2.3.0/com.ibm.zos.v2r3.izua700/IZUHPINFO_API_GetListDataSetMembers.htm */ - public static async allMembers(session: AbstractSession, dataSetName: string, options: IListOptions = {}): Promise { + public static async allMembers( + session: AbstractSession, + dataSetName: string, + options: IListOptions = {} + ): Promise { // required - ImperativeExpect.toNotBeNullOrUndefined(dataSetName, ZosFilesMessages.missingDatasetName.message); - ImperativeExpect.toNotBeEqual(dataSetName, "", ZosFilesMessages.missingDatasetName.message); + ImperativeExpect.toNotBeNullOrUndefined( + dataSetName, + ZosFilesMessages.missingDatasetName.message + ); + ImperativeExpect.toNotBeEqual( + dataSetName, + "", + ZosFilesMessages.missingDatasetName.message + ); try { // Format the endpoint to send the request to @@ -52,14 +75,15 @@ export class List { ZosFilesConstants.RESOURCE, ZosFilesConstants.RES_DS_FILES, encodeURIComponent(dataSetName), - ZosFilesConstants.RES_DS_MEMBERS); + ZosFilesConstants.RES_DS_MEMBERS + ); const params = new URLSearchParams(); if (options.pattern) { - params.set("pattern", encodeURIComponent(options.pattern)); + params.set("pattern", options.pattern); } if (options.start) { - params.set("start", encodeURIComponent(options.start)); + params.set("start", options.start); } const reqHeaders: IHeaderContent[] = [ZosmfHeaders.ACCEPT_ENCODING]; @@ -67,17 +91,24 @@ export class List { reqHeaders.push(ZosmfHeaders.X_IBM_ATTRIBUTES_BASE); } if (options.maxLength) { - reqHeaders.push({"X-IBM-Max-Items": `${options.maxLength}`}); + reqHeaders.push({ "X-IBM-Max-Items": `${options.maxLength}` }); } else { reqHeaders.push(ZosmfHeaders.X_IBM_MAX_ITEMS); } if (options.responseTimeout != null) { - reqHeaders.push({[ZosmfHeaders.X_IBM_RESPONSE_TIMEOUT]: options.responseTimeout.toString()}); + reqHeaders.push({ + [ZosmfHeaders.X_IBM_RESPONSE_TIMEOUT]: + options.responseTimeout.toString(), + }); } this.log.debug(`Endpoint: ${endpoint}`); - const data = await ZosmfRestClient.getExpectString(session, endpoint.concat(params.size > 0 ? `?${params.toString()}` : ""), reqHeaders); + const data = await ZosmfRestClient.getExpectString( + session, + endpoint.concat(params.size > 0 ? `?${params.toString()}` : ""), + reqHeaders + ); let response: any; try { response = JSONUtils.parse(data); @@ -85,13 +116,18 @@ export class List { const match = /in JSON at position (\d+)/.exec(err.message); if (match != null) { // Remove invalid member names from end of list and try to parse again - const lineNum = data.slice(0, parseInt(match[1])).split("\n").length - 1; + const lineNum = + data.slice(0, parseInt(match[1])).split("\n").length - + 1; const lines = data.trim().split("\n"); lines[lineNum - 1] = lines[lineNum - 1].replace(/,$/, ""); lines.splice(lineNum, lines.length - lineNum - 1); response = JSONUtils.parse(lines.join("\n")); - const invalidMemberCount = response.returnedRows - response.items.length; - this.log.warn(`${invalidMemberCount} members failed to load due to invalid name errors for ${dataSetName}`); + const invalidMemberCount = + response.returnedRows - response.items.length; + this.log.warn( + `${invalidMemberCount} members failed to load due to invalid name errors for ${dataSetName}` + ); } else { throw err; } @@ -100,7 +136,7 @@ export class List { return { success: true, commandResponse: null, - apiResponse: response + apiResponse: response, }; } catch (error) { this.log.error(error); @@ -117,18 +153,39 @@ export class List { * * @example */ - public static async membersMatchingPattern(session: AbstractSession, dataSetName: string, patterns: string[], - options: IDsmListOptions = {}): Promise { - - ImperativeExpect.toNotBeNullOrUndefined(dataSetName, ZosFilesMessages.missingDatasetName.message); - ImperativeExpect.toNotBeEqual(dataSetName, "", ZosFilesMessages.missingDatasetName.message); - ImperativeExpect.toNotBeNullOrUndefined(patterns, ZosFilesMessages.missingPatterns.message); + public static async membersMatchingPattern( + session: AbstractSession, + dataSetName: string, + patterns: string[], + options: IDsmListOptions = {} + ): Promise { + ImperativeExpect.toNotBeNullOrUndefined( + dataSetName, + ZosFilesMessages.missingDatasetName.message + ); + ImperativeExpect.toNotBeEqual( + dataSetName, + "", + ZosFilesMessages.missingDatasetName.message + ); + ImperativeExpect.toNotBeNullOrUndefined( + patterns, + ZosFilesMessages.missingPatterns.message + ); patterns = patterns.filter(Boolean); - ImperativeExpect.toNotBeEqual(patterns.length, 0, ZosFilesMessages.missingPatterns.message); + ImperativeExpect.toNotBeEqual( + patterns.length, + 0, + ZosFilesMessages.missingPatterns.message + ); const zosmfResponses: IZosmfListResponse[] = []; - for(const pattern of patterns) { - const response = await List.allMembers(session, dataSetName, { pattern, maxLength: options.maxLength, start: options.start }); + for (const pattern of patterns) { + const response = await List.allMembers(session, dataSetName, { + pattern, + maxLength: options.maxLength, + start: options.start, + }); zosmfResponses.push(...response.apiResponse.items); } @@ -136,20 +193,27 @@ export class List { if (zosmfResponses.length === 0) { return { success: false, - commandResponse: ZosFilesMessages.noMembersMatchingPattern.message, - apiResponse: [] + commandResponse: + ZosFilesMessages.noMembersMatchingPattern.message, + apiResponse: [], }; } // Exclude names of members for (const pattern of options.excludePatterns || []) { - const response = await List.allMembers(session, dataSetName, { pattern }); - response.apiResponse.items.forEach((membersObj: IZosmfListResponse) => { - const responseIndex = zosmfResponses.findIndex(response=> response.member === membersObj.member); - if (responseIndex !== -1) { - zosmfResponses.splice(responseIndex, 1); - } + const response = await List.allMembers(session, dataSetName, { + pattern, }); + response.apiResponse.items.forEach( + (membersObj: IZosmfListResponse) => { + const responseIndex = zosmfResponses.findIndex( + (response) => response.member === membersObj.member + ); + if (responseIndex !== -1) { + zosmfResponses.splice(responseIndex, 1); + } + } + ); } // Check if exclude pattern has left any members in the list @@ -157,14 +221,17 @@ export class List { return { success: false, commandResponse: ZosFilesMessages.noMembersInList.message, - apiResponse: [] + apiResponse: [], }; } return { success: true, - commandResponse: util.format(ZosFilesMessages.membersMatchedPattern.message, zosmfResponses.length), - apiResponse: zosmfResponses + commandResponse: util.format( + ZosFilesMessages.membersMatchedPattern.message, + zosmfResponses.length + ), + apiResponse: zosmfResponses, }; } /** @@ -179,19 +246,37 @@ export class List { * @throws {ImperativeError} data set name must be set * @throws {Error} When the {@link ZosmfRestClient} throws an error */ - public static async dataSet(session: AbstractSession, dataSetName: string, options: IListOptions = {}): Promise { - - ImperativeExpect.toNotBeNullOrUndefined(dataSetName, ZosFilesMessages.missingDatasetName.message); - ImperativeExpect.toNotBeEqual(dataSetName, "", ZosFilesMessages.missingDatasetName.message); + public static async dataSet( + session: AbstractSession, + dataSetName: string, + options: IListOptions = {} + ): Promise { + ImperativeExpect.toNotBeNullOrUndefined( + dataSetName, + ZosFilesMessages.missingDatasetName.message + ); + ImperativeExpect.toNotBeEqual( + dataSetName, + "", + ZosFilesMessages.missingDatasetName.message + ); try { - let endpoint = posix.join(ZosFilesConstants.RESOURCE, - `${ZosFilesConstants.RES_DS_FILES}?${ZosFilesConstants.RES_DS_LEVEL}=${encodeURIComponent(dataSetName)}`); + let endpoint = posix.join( + ZosFilesConstants.RESOURCE, + `${ZosFilesConstants.RES_DS_FILES}?${ + ZosFilesConstants.RES_DS_LEVEL + }=${encodeURIComponent(dataSetName)}` + ); if (options.volume) { - endpoint = `${endpoint}&volser=${encodeURIComponent(options.volume)}`; + endpoint = `${endpoint}&volser=${encodeURIComponent( + options.volume + )}`; } if (options.start) { - endpoint = `${endpoint}&start=${encodeURIComponent(options.start)}`; + endpoint = `${endpoint}&start=${encodeURIComponent( + options.start + )}`; } const reqHeaders: IHeaderContent[] = [ZosmfHeaders.ACCEPT_ENCODING]; @@ -199,37 +284,50 @@ export class List { reqHeaders.push(ZosmfHeaders.X_IBM_ATTRIBUTES_BASE); } if (options.maxLength) { - reqHeaders.push({"X-IBM-Max-Items": `${options.maxLength}`}); + reqHeaders.push({ "X-IBM-Max-Items": `${options.maxLength}` }); } else { reqHeaders.push(ZosmfHeaders.X_IBM_MAX_ITEMS); } if (options.responseTimeout != null) { - reqHeaders.push({[ZosmfHeaders.X_IBM_RESPONSE_TIMEOUT]: options.responseTimeout.toString()}); + reqHeaders.push({ + [ZosmfHeaders.X_IBM_RESPONSE_TIMEOUT]: + options.responseTimeout.toString(), + }); } // Migrated recall options if (options.recall) { switch (options.recall.toLowerCase()) { case "wait": - reqHeaders.push(ZosmfHeaders.X_IBM_MIGRATED_RECALL_WAIT); + reqHeaders.push( + ZosmfHeaders.X_IBM_MIGRATED_RECALL_WAIT + ); break; case "nowait": - reqHeaders.push(ZosmfHeaders.X_IBM_MIGRATED_RECALL_NO_WAIT); + reqHeaders.push( + ZosmfHeaders.X_IBM_MIGRATED_RECALL_NO_WAIT + ); break; case "error": - reqHeaders.push(ZosmfHeaders.X_IBM_MIGRATED_RECALL_ERROR); + reqHeaders.push( + ZosmfHeaders.X_IBM_MIGRATED_RECALL_ERROR + ); break; } } this.log.debug(`Endpoint: ${endpoint}`); - const response: any = await ZosmfRestClient.getExpectJSON(session, endpoint, reqHeaders); + const response: any = await ZosmfRestClient.getExpectJSON( + session, + endpoint, + reqHeaders + ); return { success: true, commandResponse: null, - apiResponse: response + apiResponse: response, }; } catch (error) { this.log.error(error); @@ -249,61 +347,140 @@ export class List { * @throws {ImperativeError} data set name must be set * @throws {Error} When the {@link ZosmfRestClient} throws an error */ - public static async fileList(session: AbstractSession, path: string, options: IUSSListOptions = {}): Promise { - ImperativeExpect.toNotBeNullOrUndefined(path, ZosFilesMessages.missingUSSFileName.message); - ImperativeExpect.toNotBeEqual(path.trim(), "", ZosFilesMessages.missingUSSFileName.message); + public static async fileList( + session: AbstractSession, + path: string, + options: IUSSListOptions = {} + ): Promise { + ImperativeExpect.toNotBeNullOrUndefined( + path, + ZosFilesMessages.missingUSSFileName.message + ); + ImperativeExpect.toNotBeEqual( + path.trim(), + "", + ZosFilesMessages.missingUSSFileName.message + ); // Error out if someone tries to use a second table parameter without specifying a first table parameter - if (options.depth || options.filesys != null || options.symlinks != null){ - if (!(options.group || options.user || options.name || options.size || options.mtime || options.perm || options.type)) { - throw new ImperativeError({msg: ZosFilesMessages.missingRequiredTableParameters.message}); + if ( + options.depth || + options.filesys != null || + options.symlinks != null + ) { + if ( + !( + options.group || + options.user || + options.name || + options.size || + options.mtime || + options.perm || + options.type + ) + ) { + throw new ImperativeError({ + msg: ZosFilesMessages.missingRequiredTableParameters + .message, + }); } } // Remove a trailing slash from the path, if one exists // Do not remove if requesting the root directory - if (path.trim().length > 1 && path.endsWith("/")) { path = path.slice(0, -1); } + if (path.trim().length > 1 && path.endsWith("/")) { + path = path.slice(0, -1); + } try { - let endpoint = posix.join(ZosFilesConstants.RESOURCE, - `${ZosFilesConstants.RES_USS_FILES}?${ZosFilesConstants.RES_PATH}=${encodeURIComponent(path)}`); + let endpoint = posix.join( + ZosFilesConstants.RESOURCE, + `${ZosFilesConstants.RES_USS_FILES}?${ + ZosFilesConstants.RES_PATH + }=${encodeURIComponent(path)}` + ); const reqHeaders: IHeaderContent[] = [ZosmfHeaders.ACCEPT_ENCODING]; if (options.maxLength) { - reqHeaders.push({"X-IBM-Max-Items": `${options.maxLength}`}); + reqHeaders.push({ "X-IBM-Max-Items": `${options.maxLength}` }); } else { reqHeaders.push(ZosmfHeaders.X_IBM_MAX_ITEMS); } if (options.responseTimeout != null) { - reqHeaders.push({[ZosmfHeaders.X_IBM_RESPONSE_TIMEOUT]: options.responseTimeout.toString()}); + reqHeaders.push({ + [ZosmfHeaders.X_IBM_RESPONSE_TIMEOUT]: + options.responseTimeout.toString(), + }); } // Start modifying the endpoint with the query parameters that were passed in - if (options.group) { endpoint += `&${ZosFilesConstants.RES_GROUP}=${encodeURIComponent(options.group)}`; } - if (options.user) { endpoint += `&${ZosFilesConstants.RES_USER}=${encodeURIComponent(options.user)}`; } - if (options.name) { endpoint += `&${ZosFilesConstants.RES_NAME}=${encodeURIComponent(options.name)}`; } - if (options.size) { endpoint += `&${ZosFilesConstants.RES_SIZE}=${encodeURIComponent(options.size)}`; } - if (options.mtime) { endpoint += `&${ZosFilesConstants.RES_MTIME}=${encodeURIComponent(options.mtime)}`; } - if (options.perm) { endpoint += `&${ZosFilesConstants.RES_PERM}=${encodeURIComponent(options.perm)}`; } - if (options.type) { endpoint += `&${ZosFilesConstants.RES_TYPE}=${encodeURIComponent(options.type)}`; } - if (options.depth) { endpoint += `&${ZosFilesConstants.RES_DEPTH}=${encodeURIComponent(options.depth)}`; } + if (options.group) { + endpoint += `&${ + ZosFilesConstants.RES_GROUP + }=${encodeURIComponent(options.group)}`; + } + if (options.user) { + endpoint += `&${ + ZosFilesConstants.RES_USER + }=${encodeURIComponent(options.user)}`; + } + if (options.name) { + endpoint += `&${ + ZosFilesConstants.RES_NAME + }=${encodeURIComponent(options.name)}`; + } + if (options.size) { + endpoint += `&${ + ZosFilesConstants.RES_SIZE + }=${encodeURIComponent(options.size)}`; + } + if (options.mtime) { + endpoint += `&${ + ZosFilesConstants.RES_MTIME + }=${encodeURIComponent(options.mtime)}`; + } + if (options.perm) { + endpoint += `&${ + ZosFilesConstants.RES_PERM + }=${encodeURIComponent(options.perm)}`; + } + if (options.type) { + endpoint += `&${ + ZosFilesConstants.RES_TYPE + }=${encodeURIComponent(options.type)}`; + } + if (options.depth) { + endpoint += `&${ + ZosFilesConstants.RES_DEPTH + }=${encodeURIComponent(options.depth)}`; + } if (options.filesys != null) { - if (options.filesys === true) { endpoint += `&${ZosFilesConstants.RES_FILESYS}=all`; } - else { endpoint += `&${ZosFilesConstants.RES_FILESYS}=same`; } + if (options.filesys === true) { + endpoint += `&${ZosFilesConstants.RES_FILESYS}=all`; + } else { + endpoint += `&${ZosFilesConstants.RES_FILESYS}=same`; + } } if (options.symlinks != null) { - if (options.symlinks === true) { endpoint += `&${ZosFilesConstants.RES_SYMLINKS}=report`; } - else { endpoint += `&${ZosFilesConstants.RES_SYMLINKS}=follow`; } + if (options.symlinks === true) { + endpoint += `&${ZosFilesConstants.RES_SYMLINKS}=report`; + } else { + endpoint += `&${ZosFilesConstants.RES_SYMLINKS}=follow`; + } } this.log.debug(`Endpoint: ${endpoint}`); - const response: any = await ZosmfRestClient.getExpectJSON(session, endpoint, reqHeaders); + const response: any = await ZosmfRestClient.getExpectJSON( + session, + endpoint, + reqHeaders + ); return { success: true, commandResponse: null, - apiResponse: response + apiResponse: response, }; } catch (error) { this.log.error(error); @@ -322,12 +499,22 @@ export class List { * @throws {ImperativeError} data set name must be set * @throws {Error} When the {@link ZosmfRestClient} throws an error */ - public static async fs(session: AbstractSession, options: IFsOptions = {}): Promise { + public static async fs( + session: AbstractSession, + options: IFsOptions = {} + ): Promise { try { - let endpoint = posix.join(ZosFilesConstants.RESOURCE, - `${ZosFilesConstants.RES_MFS}`); + let endpoint = posix.join( + ZosFilesConstants.RESOURCE, + `${ZosFilesConstants.RES_MFS}` + ); if (options.fsname) { - endpoint = posix.join(endpoint, `?${ZosFilesConstants.RES_FSNAME}=${encodeURIComponent(options.fsname)}`); + endpoint = posix.join( + endpoint, + `?${ZosFilesConstants.RES_FSNAME}=${encodeURIComponent( + options.fsname + )}` + ); } const reqHeaders: IHeaderContent[] = [ZosmfHeaders.ACCEPT_ENCODING]; @@ -335,22 +522,29 @@ export class List { // reqHeaders.push(ZosmfHeaders.X_IBM_ATTRIBUTES_BASE); // } if (options.maxLength) { - reqHeaders.push({"X-IBM-Max-Items": `${options.maxLength}`}); + reqHeaders.push({ "X-IBM-Max-Items": `${options.maxLength}` }); } else { reqHeaders.push(ZosmfHeaders.X_IBM_MAX_ITEMS); } if (options.responseTimeout != null) { - reqHeaders.push({[ZosmfHeaders.X_IBM_RESPONSE_TIMEOUT]: options.responseTimeout.toString()}); + reqHeaders.push({ + [ZosmfHeaders.X_IBM_RESPONSE_TIMEOUT]: + options.responseTimeout.toString(), + }); } this.log.debug(`Endpoint: ${endpoint}`); - const response: any = await ZosmfRestClient.getExpectJSON(session, endpoint, reqHeaders); + const response: any = await ZosmfRestClient.getExpectJSON( + session, + endpoint, + reqHeaders + ); return { success: true, commandResponse: null, - apiResponse: response + apiResponse: response, }; } catch (error) { this.log.error(error); @@ -370,32 +564,49 @@ export class List { * @throws {Error} When the {@link ZosmfRestClient} throws an error */ - public static async fsWithPath(session: AbstractSession, options: IFsOptions = {}): Promise { + public static async fsWithPath( + session: AbstractSession, + options: IFsOptions = {} + ): Promise { try { - let endpoint = posix.join(ZosFilesConstants.RESOURCE, - `${ZosFilesConstants.RES_MFS}`); + let endpoint = posix.join( + ZosFilesConstants.RESOURCE, + `${ZosFilesConstants.RES_MFS}` + ); if (options.path) { - endpoint = posix.join(endpoint, `?${ZosFilesConstants.RES_PATH}=${encodeURIComponent(options.path)}`); + endpoint = posix.join( + endpoint, + `?${ZosFilesConstants.RES_PATH}=${encodeURIComponent( + options.path + )}` + ); } const reqHeaders: IHeaderContent[] = [ZosmfHeaders.ACCEPT_ENCODING]; if (options.maxLength) { - reqHeaders.push({"X-IBM-Max-Items": `${options.maxLength}`}); + reqHeaders.push({ "X-IBM-Max-Items": `${options.maxLength}` }); } else { reqHeaders.push(ZosmfHeaders.X_IBM_MAX_ITEMS); } if (options.responseTimeout != null) { - reqHeaders.push({[ZosmfHeaders.X_IBM_RESPONSE_TIMEOUT]: options.responseTimeout.toString()}); + reqHeaders.push({ + [ZosmfHeaders.X_IBM_RESPONSE_TIMEOUT]: + options.responseTimeout.toString(), + }); } this.log.debug(`Endpoint: ${endpoint}`); - const response: any = await ZosmfRestClient.getExpectJSON(session, endpoint, reqHeaders); + const response: any = await ZosmfRestClient.getExpectJSON( + session, + endpoint, + reqHeaders + ); return { success: true, commandResponse: null, - apiResponse: response + apiResponse: response, }; } catch (error) { this.log.error(error); @@ -417,22 +628,40 @@ export class List { * await List.dataSetsMatchingPattern(session, "USER.**.DATASET"); * ``` */ - public static async dataSetsMatchingPattern(session: AbstractSession, patterns: string[], - options: IDsmListOptions = {}): Promise { - + public static async dataSetsMatchingPattern( + session: AbstractSession, + patterns: string[], + options: IDsmListOptions = {} + ): Promise { // Pattern is required to be non-empty - ImperativeExpect.toNotBeNullOrUndefined(patterns, ZosFilesMessages.missingPatterns.message); + ImperativeExpect.toNotBeNullOrUndefined( + patterns, + ZosFilesMessages.missingPatterns.message + ); patterns = patterns.filter(Boolean); - ImperativeExpect.toNotBeEqual(patterns.length, 0, ZosFilesMessages.missingPatterns.message); + ImperativeExpect.toNotBeEqual( + patterns.length, + 0, + ZosFilesMessages.missingPatterns.message + ); const zosmfResponses: IZosmfListResponse[] = []; // Get names of all data sets for (const pattern of patterns) { let response: any; try { - response = await List.dataSet(session, pattern, { attributes: true, maxLength: options.maxLength, start: options.start }); + response = await List.dataSet(session, pattern, { + attributes: true, + maxLength: options.maxLength, + start: options.start, + }); } catch (err) { - if (!(err instanceof ImperativeError && err.errorCode?.toString().startsWith("5"))) { + if ( + !( + err instanceof ImperativeError && + err.errorCode?.toString().startsWith("5") + ) + ) { throw err; } // Listing data sets with attributes may fail sometimes, for @@ -446,14 +675,23 @@ export class List { let listsInitiated = 0; const createListPromise = (dataSetObj: any) => { if (options.task != null) { - options.task.percentComplete = Math.floor(TaskProgress.ONE_HUNDRED_PERCENT * - (listsInitiated / response.apiResponse.items.length)); + options.task.percentComplete = Math.floor( + TaskProgress.ONE_HUNDRED_PERCENT * + (listsInitiated / + response.apiResponse.items.length) + ); listsInitiated++; } - return List.dataSet(session, dataSetObj.dsname, { attributes: true, maxLength: 1 }).then( + return List.dataSet(session, dataSetObj.dsname, { + attributes: true, + maxLength: 1, + }).then( (tempResponse) => { - Object.assign(dataSetObj, tempResponse.apiResponse.items[0]); + Object.assign( + dataSetObj, + tempResponse.apiResponse.items[0] + ); }, (tempErr) => { Object.assign(dataSetObj, { error: tempErr }); @@ -461,11 +699,20 @@ export class List { ); }; - const maxConcurrentRequests = options.maxConcurrentRequests == null ? 1 : options.maxConcurrentRequests; + const maxConcurrentRequests = + options.maxConcurrentRequests == null + ? 1 + : options.maxConcurrentRequests; if (maxConcurrentRequests === 0) { - await Promise.all(response.apiResponse.items.map(createListPromise)); + await Promise.all( + response.apiResponse.items.map(createListPromise) + ); } else { - await asyncPool(maxConcurrentRequests, response.apiResponse.items, createListPromise); + await asyncPool( + maxConcurrentRequests, + response.apiResponse.items, + createListPromise + ); } } zosmfResponses.push(...response.apiResponse.items); @@ -475,20 +722,25 @@ export class List { if (zosmfResponses.length === 0) { return { success: false, - commandResponse: ZosFilesMessages.noDataSetsMatchingPattern.message, - apiResponse: [] + commandResponse: + ZosFilesMessages.noDataSetsMatchingPattern.message, + apiResponse: [], }; } // Exclude names of data sets for (const pattern of options.excludePatterns || []) { const response = await List.dataSet(session, pattern); - response.apiResponse.items.forEach((dataSetObj: IZosmfListResponse) => { - const responseIndex = zosmfResponses.findIndex(response => response.dsname === dataSetObj.dsname); - if (responseIndex !== -1) { - zosmfResponses.splice(responseIndex, 1); + response.apiResponse.items.forEach( + (dataSetObj: IZosmfListResponse) => { + const responseIndex = zosmfResponses.findIndex( + (response) => response.dsname === dataSetObj.dsname + ); + if (responseIndex !== -1) { + zosmfResponses.splice(responseIndex, 1); + } } - }); + ); } // Check if exclude pattern has left any data sets in the list @@ -496,14 +748,17 @@ export class List { return { success: false, commandResponse: ZosFilesMessages.noDataSetsInList.message, - apiResponse: [] + apiResponse: [], }; } return { success: true, - commandResponse: util.format(ZosFilesMessages.dataSetsMatchedPattern.message, zosmfResponses.length), - apiResponse: zosmfResponses + commandResponse: util.format( + ZosFilesMessages.dataSetsMatchedPattern.message, + zosmfResponses.length + ), + apiResponse: zosmfResponses, }; } From 342f8f1688336b6f6dfb71d5ff60b9eb2b95ef23 Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Wed, 15 Jan 2025 14:38:17 -0500 Subject: [PATCH 14/14] fix formatting of List.ts Signed-off-by: Trae Yelovich --- packages/zosfiles/src/methods/list/List.ts | 501 +++++---------------- 1 file changed, 123 insertions(+), 378 deletions(-) diff --git a/packages/zosfiles/src/methods/list/List.ts b/packages/zosfiles/src/methods/list/List.ts index b99e139cde..1412b37193 100644 --- a/packages/zosfiles/src/methods/list/List.ts +++ b/packages/zosfiles/src/methods/list/List.ts @@ -1,32 +1,20 @@ /* - * This program and the accompanying materials are made available under the terms of the - * Eclipse Public License v2.0 which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-v20.html - * - * SPDX-License-Identifier: EPL-2.0 - * - * Copyright Contributors to the Zowe Project. - * - */ - -import { - AbstractSession, - IHeaderContent, - ImperativeError, - ImperativeExpect, - JSONUtils, - Logger, - TaskProgress, -} from "@zowe/imperative"; +* This program and the accompanying materials are made available under the terms of the +* Eclipse Public License v2.0 which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Copyright Contributors to the Zowe Project. +* +*/ + +import { AbstractSession, IHeaderContent, ImperativeError, ImperativeExpect, JSONUtils, Logger, TaskProgress } from "@zowe/imperative"; import { posix } from "path"; import * as util from "util"; -import { - ZosmfRestClient, - ZosmfHeaders, - asyncPool, -} from "@zowe/core-for-zowe-sdk"; +import { ZosmfRestClient, ZosmfHeaders, asyncPool } from "@zowe/core-for-zowe-sdk"; import { ZosFilesConstants } from "../../constants/ZosFiles.constants"; import { ZosFilesMessages } from "../../constants/ZosFiles.messages"; import { IZosFilesResponse } from "../../doc/IZosFilesResponse"; @@ -53,21 +41,10 @@ export class List { * * @see https://www.ibm.com/support/knowledgecenter/en/SSLTBW_2.3.0/com.ibm.zos.v2r3.izua700/IZUHPINFO_API_GetListDataSetMembers.htm */ - public static async allMembers( - session: AbstractSession, - dataSetName: string, - options: IListOptions = {} - ): Promise { + public static async allMembers(session: AbstractSession, dataSetName: string, options: IListOptions = {}): Promise { // required - ImperativeExpect.toNotBeNullOrUndefined( - dataSetName, - ZosFilesMessages.missingDatasetName.message - ); - ImperativeExpect.toNotBeEqual( - dataSetName, - "", - ZosFilesMessages.missingDatasetName.message - ); + ImperativeExpect.toNotBeNullOrUndefined(dataSetName, ZosFilesMessages.missingDatasetName.message); + ImperativeExpect.toNotBeEqual(dataSetName, "", ZosFilesMessages.missingDatasetName.message); try { // Format the endpoint to send the request to @@ -75,8 +52,7 @@ export class List { ZosFilesConstants.RESOURCE, ZosFilesConstants.RES_DS_FILES, encodeURIComponent(dataSetName), - ZosFilesConstants.RES_DS_MEMBERS - ); + ZosFilesConstants.RES_DS_MEMBERS); const params = new URLSearchParams(); if (options.pattern) { @@ -91,24 +67,17 @@ export class List { reqHeaders.push(ZosmfHeaders.X_IBM_ATTRIBUTES_BASE); } if (options.maxLength) { - reqHeaders.push({ "X-IBM-Max-Items": `${options.maxLength}` }); + reqHeaders.push({"X-IBM-Max-Items": `${options.maxLength}`}); } else { reqHeaders.push(ZosmfHeaders.X_IBM_MAX_ITEMS); } if (options.responseTimeout != null) { - reqHeaders.push({ - [ZosmfHeaders.X_IBM_RESPONSE_TIMEOUT]: - options.responseTimeout.toString(), - }); + reqHeaders.push({[ZosmfHeaders.X_IBM_RESPONSE_TIMEOUT]: options.responseTimeout.toString()}); } this.log.debug(`Endpoint: ${endpoint}`); - const data = await ZosmfRestClient.getExpectString( - session, - endpoint.concat(params.size > 0 ? `?${params.toString()}` : ""), - reqHeaders - ); + const data = await ZosmfRestClient.getExpectString(session, endpoint.concat(params.size > 0 ? `?${params.toString()}` : ""), reqHeaders); let response: any; try { response = JSONUtils.parse(data); @@ -116,18 +85,13 @@ export class List { const match = /in JSON at position (\d+)/.exec(err.message); if (match != null) { // Remove invalid member names from end of list and try to parse again - const lineNum = - data.slice(0, parseInt(match[1])).split("\n").length - - 1; + const lineNum = data.slice(0, parseInt(match[1])).split("\n").length - 1; const lines = data.trim().split("\n"); lines[lineNum - 1] = lines[lineNum - 1].replace(/,$/, ""); lines.splice(lineNum, lines.length - lineNum - 1); response = JSONUtils.parse(lines.join("\n")); - const invalidMemberCount = - response.returnedRows - response.items.length; - this.log.warn( - `${invalidMemberCount} members failed to load due to invalid name errors for ${dataSetName}` - ); + const invalidMemberCount = response.returnedRows - response.items.length; + this.log.warn(`${invalidMemberCount} members failed to load due to invalid name errors for ${dataSetName}`); } else { throw err; } @@ -136,7 +100,7 @@ export class List { return { success: true, commandResponse: null, - apiResponse: response, + apiResponse: response }; } catch (error) { this.log.error(error); @@ -153,39 +117,18 @@ export class List { * * @example */ - public static async membersMatchingPattern( - session: AbstractSession, - dataSetName: string, - patterns: string[], - options: IDsmListOptions = {} - ): Promise { - ImperativeExpect.toNotBeNullOrUndefined( - dataSetName, - ZosFilesMessages.missingDatasetName.message - ); - ImperativeExpect.toNotBeEqual( - dataSetName, - "", - ZosFilesMessages.missingDatasetName.message - ); - ImperativeExpect.toNotBeNullOrUndefined( - patterns, - ZosFilesMessages.missingPatterns.message - ); + public static async membersMatchingPattern(session: AbstractSession, dataSetName: string, patterns: string[], + options: IDsmListOptions = {}): Promise { + + ImperativeExpect.toNotBeNullOrUndefined(dataSetName, ZosFilesMessages.missingDatasetName.message); + ImperativeExpect.toNotBeEqual(dataSetName, "", ZosFilesMessages.missingDatasetName.message); + ImperativeExpect.toNotBeNullOrUndefined(patterns, ZosFilesMessages.missingPatterns.message); patterns = patterns.filter(Boolean); - ImperativeExpect.toNotBeEqual( - patterns.length, - 0, - ZosFilesMessages.missingPatterns.message - ); + ImperativeExpect.toNotBeEqual(patterns.length, 0, ZosFilesMessages.missingPatterns.message); const zosmfResponses: IZosmfListResponse[] = []; - for (const pattern of patterns) { - const response = await List.allMembers(session, dataSetName, { - pattern, - maxLength: options.maxLength, - start: options.start, - }); + for(const pattern of patterns) { + const response = await List.allMembers(session, dataSetName, { pattern, maxLength: options.maxLength, start: options.start }); zosmfResponses.push(...response.apiResponse.items); } @@ -193,27 +136,20 @@ export class List { if (zosmfResponses.length === 0) { return { success: false, - commandResponse: - ZosFilesMessages.noMembersMatchingPattern.message, - apiResponse: [], + commandResponse: ZosFilesMessages.noMembersMatchingPattern.message, + apiResponse: [] }; } // Exclude names of members for (const pattern of options.excludePatterns || []) { - const response = await List.allMembers(session, dataSetName, { - pattern, - }); - response.apiResponse.items.forEach( - (membersObj: IZosmfListResponse) => { - const responseIndex = zosmfResponses.findIndex( - (response) => response.member === membersObj.member - ); - if (responseIndex !== -1) { - zosmfResponses.splice(responseIndex, 1); - } + const response = await List.allMembers(session, dataSetName, { pattern }); + response.apiResponse.items.forEach((membersObj: IZosmfListResponse) => { + const responseIndex = zosmfResponses.findIndex(response=> response.member === membersObj.member); + if (responseIndex !== -1) { + zosmfResponses.splice(responseIndex, 1); } - ); + }); } // Check if exclude pattern has left any members in the list @@ -221,17 +157,14 @@ export class List { return { success: false, commandResponse: ZosFilesMessages.noMembersInList.message, - apiResponse: [], + apiResponse: [] }; } return { success: true, - commandResponse: util.format( - ZosFilesMessages.membersMatchedPattern.message, - zosmfResponses.length - ), - apiResponse: zosmfResponses, + commandResponse: util.format(ZosFilesMessages.membersMatchedPattern.message, zosmfResponses.length), + apiResponse: zosmfResponses }; } /** @@ -246,37 +179,19 @@ export class List { * @throws {ImperativeError} data set name must be set * @throws {Error} When the {@link ZosmfRestClient} throws an error */ - public static async dataSet( - session: AbstractSession, - dataSetName: string, - options: IListOptions = {} - ): Promise { - ImperativeExpect.toNotBeNullOrUndefined( - dataSetName, - ZosFilesMessages.missingDatasetName.message - ); - ImperativeExpect.toNotBeEqual( - dataSetName, - "", - ZosFilesMessages.missingDatasetName.message - ); + public static async dataSet(session: AbstractSession, dataSetName: string, options: IListOptions = {}): Promise { + + ImperativeExpect.toNotBeNullOrUndefined(dataSetName, ZosFilesMessages.missingDatasetName.message); + ImperativeExpect.toNotBeEqual(dataSetName, "", ZosFilesMessages.missingDatasetName.message); try { - let endpoint = posix.join( - ZosFilesConstants.RESOURCE, - `${ZosFilesConstants.RES_DS_FILES}?${ - ZosFilesConstants.RES_DS_LEVEL - }=${encodeURIComponent(dataSetName)}` - ); + let endpoint = posix.join(ZosFilesConstants.RESOURCE, + `${ZosFilesConstants.RES_DS_FILES}?${ZosFilesConstants.RES_DS_LEVEL}=${encodeURIComponent(dataSetName)}`); if (options.volume) { - endpoint = `${endpoint}&volser=${encodeURIComponent( - options.volume - )}`; + endpoint = `${endpoint}&volser=${encodeURIComponent(options.volume)}`; } if (options.start) { - endpoint = `${endpoint}&start=${encodeURIComponent( - options.start - )}`; + endpoint = `${endpoint}&start=${encodeURIComponent(options.start)}`; } const reqHeaders: IHeaderContent[] = [ZosmfHeaders.ACCEPT_ENCODING]; @@ -284,50 +199,37 @@ export class List { reqHeaders.push(ZosmfHeaders.X_IBM_ATTRIBUTES_BASE); } if (options.maxLength) { - reqHeaders.push({ "X-IBM-Max-Items": `${options.maxLength}` }); + reqHeaders.push({"X-IBM-Max-Items": `${options.maxLength}`}); } else { reqHeaders.push(ZosmfHeaders.X_IBM_MAX_ITEMS); } if (options.responseTimeout != null) { - reqHeaders.push({ - [ZosmfHeaders.X_IBM_RESPONSE_TIMEOUT]: - options.responseTimeout.toString(), - }); + reqHeaders.push({[ZosmfHeaders.X_IBM_RESPONSE_TIMEOUT]: options.responseTimeout.toString()}); } // Migrated recall options if (options.recall) { switch (options.recall.toLowerCase()) { case "wait": - reqHeaders.push( - ZosmfHeaders.X_IBM_MIGRATED_RECALL_WAIT - ); + reqHeaders.push(ZosmfHeaders.X_IBM_MIGRATED_RECALL_WAIT); break; case "nowait": - reqHeaders.push( - ZosmfHeaders.X_IBM_MIGRATED_RECALL_NO_WAIT - ); + reqHeaders.push(ZosmfHeaders.X_IBM_MIGRATED_RECALL_NO_WAIT); break; case "error": - reqHeaders.push( - ZosmfHeaders.X_IBM_MIGRATED_RECALL_ERROR - ); + reqHeaders.push(ZosmfHeaders.X_IBM_MIGRATED_RECALL_ERROR); break; } } this.log.debug(`Endpoint: ${endpoint}`); - const response: any = await ZosmfRestClient.getExpectJSON( - session, - endpoint, - reqHeaders - ); + const response: any = await ZosmfRestClient.getExpectJSON(session, endpoint, reqHeaders); return { success: true, commandResponse: null, - apiResponse: response, + apiResponse: response }; } catch (error) { this.log.error(error); @@ -347,140 +249,61 @@ export class List { * @throws {ImperativeError} data set name must be set * @throws {Error} When the {@link ZosmfRestClient} throws an error */ - public static async fileList( - session: AbstractSession, - path: string, - options: IUSSListOptions = {} - ): Promise { - ImperativeExpect.toNotBeNullOrUndefined( - path, - ZosFilesMessages.missingUSSFileName.message - ); - ImperativeExpect.toNotBeEqual( - path.trim(), - "", - ZosFilesMessages.missingUSSFileName.message - ); + public static async fileList(session: AbstractSession, path: string, options: IUSSListOptions = {}): Promise { + ImperativeExpect.toNotBeNullOrUndefined(path, ZosFilesMessages.missingUSSFileName.message); + ImperativeExpect.toNotBeEqual(path.trim(), "", ZosFilesMessages.missingUSSFileName.message); // Error out if someone tries to use a second table parameter without specifying a first table parameter - if ( - options.depth || - options.filesys != null || - options.symlinks != null - ) { - if ( - !( - options.group || - options.user || - options.name || - options.size || - options.mtime || - options.perm || - options.type - ) - ) { - throw new ImperativeError({ - msg: ZosFilesMessages.missingRequiredTableParameters - .message, - }); + if (options.depth || options.filesys != null || options.symlinks != null){ + if (!(options.group || options.user || options.name || options.size || options.mtime || options.perm || options.type)) { + throw new ImperativeError({msg: ZosFilesMessages.missingRequiredTableParameters.message}); } } // Remove a trailing slash from the path, if one exists // Do not remove if requesting the root directory - if (path.trim().length > 1 && path.endsWith("/")) { - path = path.slice(0, -1); - } + if (path.trim().length > 1 && path.endsWith("/")) { path = path.slice(0, -1); } try { - let endpoint = posix.join( - ZosFilesConstants.RESOURCE, - `${ZosFilesConstants.RES_USS_FILES}?${ - ZosFilesConstants.RES_PATH - }=${encodeURIComponent(path)}` - ); + let endpoint = posix.join(ZosFilesConstants.RESOURCE, + `${ZosFilesConstants.RES_USS_FILES}?${ZosFilesConstants.RES_PATH}=${encodeURIComponent(path)}`); const reqHeaders: IHeaderContent[] = [ZosmfHeaders.ACCEPT_ENCODING]; if (options.maxLength) { - reqHeaders.push({ "X-IBM-Max-Items": `${options.maxLength}` }); + reqHeaders.push({"X-IBM-Max-Items": `${options.maxLength}`}); } else { reqHeaders.push(ZosmfHeaders.X_IBM_MAX_ITEMS); } if (options.responseTimeout != null) { - reqHeaders.push({ - [ZosmfHeaders.X_IBM_RESPONSE_TIMEOUT]: - options.responseTimeout.toString(), - }); + reqHeaders.push({[ZosmfHeaders.X_IBM_RESPONSE_TIMEOUT]: options.responseTimeout.toString()}); } // Start modifying the endpoint with the query parameters that were passed in - if (options.group) { - endpoint += `&${ - ZosFilesConstants.RES_GROUP - }=${encodeURIComponent(options.group)}`; - } - if (options.user) { - endpoint += `&${ - ZosFilesConstants.RES_USER - }=${encodeURIComponent(options.user)}`; - } - if (options.name) { - endpoint += `&${ - ZosFilesConstants.RES_NAME - }=${encodeURIComponent(options.name)}`; - } - if (options.size) { - endpoint += `&${ - ZosFilesConstants.RES_SIZE - }=${encodeURIComponent(options.size)}`; - } - if (options.mtime) { - endpoint += `&${ - ZosFilesConstants.RES_MTIME - }=${encodeURIComponent(options.mtime)}`; - } - if (options.perm) { - endpoint += `&${ - ZosFilesConstants.RES_PERM - }=${encodeURIComponent(options.perm)}`; - } - if (options.type) { - endpoint += `&${ - ZosFilesConstants.RES_TYPE - }=${encodeURIComponent(options.type)}`; - } - if (options.depth) { - endpoint += `&${ - ZosFilesConstants.RES_DEPTH - }=${encodeURIComponent(options.depth)}`; - } + if (options.group) { endpoint += `&${ZosFilesConstants.RES_GROUP}=${encodeURIComponent(options.group)}`; } + if (options.user) { endpoint += `&${ZosFilesConstants.RES_USER}=${encodeURIComponent(options.user)}`; } + if (options.name) { endpoint += `&${ZosFilesConstants.RES_NAME}=${encodeURIComponent(options.name)}`; } + if (options.size) { endpoint += `&${ZosFilesConstants.RES_SIZE}=${encodeURIComponent(options.size)}`; } + if (options.mtime) { endpoint += `&${ZosFilesConstants.RES_MTIME}=${encodeURIComponent(options.mtime)}`; } + if (options.perm) { endpoint += `&${ZosFilesConstants.RES_PERM}=${encodeURIComponent(options.perm)}`; } + if (options.type) { endpoint += `&${ZosFilesConstants.RES_TYPE}=${encodeURIComponent(options.type)}`; } + if (options.depth) { endpoint += `&${ZosFilesConstants.RES_DEPTH}=${encodeURIComponent(options.depth)}`; } if (options.filesys != null) { - if (options.filesys === true) { - endpoint += `&${ZosFilesConstants.RES_FILESYS}=all`; - } else { - endpoint += `&${ZosFilesConstants.RES_FILESYS}=same`; - } + if (options.filesys === true) { endpoint += `&${ZosFilesConstants.RES_FILESYS}=all`; } + else { endpoint += `&${ZosFilesConstants.RES_FILESYS}=same`; } } if (options.symlinks != null) { - if (options.symlinks === true) { - endpoint += `&${ZosFilesConstants.RES_SYMLINKS}=report`; - } else { - endpoint += `&${ZosFilesConstants.RES_SYMLINKS}=follow`; - } + if (options.symlinks === true) { endpoint += `&${ZosFilesConstants.RES_SYMLINKS}=report`; } + else { endpoint += `&${ZosFilesConstants.RES_SYMLINKS}=follow`; } } this.log.debug(`Endpoint: ${endpoint}`); - const response: any = await ZosmfRestClient.getExpectJSON( - session, - endpoint, - reqHeaders - ); + const response: any = await ZosmfRestClient.getExpectJSON(session, endpoint, reqHeaders); return { success: true, commandResponse: null, - apiResponse: response, + apiResponse: response }; } catch (error) { this.log.error(error); @@ -499,22 +322,12 @@ export class List { * @throws {ImperativeError} data set name must be set * @throws {Error} When the {@link ZosmfRestClient} throws an error */ - public static async fs( - session: AbstractSession, - options: IFsOptions = {} - ): Promise { + public static async fs(session: AbstractSession, options: IFsOptions = {}): Promise { try { - let endpoint = posix.join( - ZosFilesConstants.RESOURCE, - `${ZosFilesConstants.RES_MFS}` - ); + let endpoint = posix.join(ZosFilesConstants.RESOURCE, + `${ZosFilesConstants.RES_MFS}`); if (options.fsname) { - endpoint = posix.join( - endpoint, - `?${ZosFilesConstants.RES_FSNAME}=${encodeURIComponent( - options.fsname - )}` - ); + endpoint = posix.join(endpoint, `?${ZosFilesConstants.RES_FSNAME}=${encodeURIComponent(options.fsname)}`); } const reqHeaders: IHeaderContent[] = [ZosmfHeaders.ACCEPT_ENCODING]; @@ -522,29 +335,22 @@ export class List { // reqHeaders.push(ZosmfHeaders.X_IBM_ATTRIBUTES_BASE); // } if (options.maxLength) { - reqHeaders.push({ "X-IBM-Max-Items": `${options.maxLength}` }); + reqHeaders.push({"X-IBM-Max-Items": `${options.maxLength}`}); } else { reqHeaders.push(ZosmfHeaders.X_IBM_MAX_ITEMS); } if (options.responseTimeout != null) { - reqHeaders.push({ - [ZosmfHeaders.X_IBM_RESPONSE_TIMEOUT]: - options.responseTimeout.toString(), - }); + reqHeaders.push({[ZosmfHeaders.X_IBM_RESPONSE_TIMEOUT]: options.responseTimeout.toString()}); } this.log.debug(`Endpoint: ${endpoint}`); - const response: any = await ZosmfRestClient.getExpectJSON( - session, - endpoint, - reqHeaders - ); + const response: any = await ZosmfRestClient.getExpectJSON(session, endpoint, reqHeaders); return { success: true, commandResponse: null, - apiResponse: response, + apiResponse: response }; } catch (error) { this.log.error(error); @@ -564,49 +370,32 @@ export class List { * @throws {Error} When the {@link ZosmfRestClient} throws an error */ - public static async fsWithPath( - session: AbstractSession, - options: IFsOptions = {} - ): Promise { + public static async fsWithPath(session: AbstractSession, options: IFsOptions = {}): Promise { try { - let endpoint = posix.join( - ZosFilesConstants.RESOURCE, - `${ZosFilesConstants.RES_MFS}` - ); + let endpoint = posix.join(ZosFilesConstants.RESOURCE, + `${ZosFilesConstants.RES_MFS}`); if (options.path) { - endpoint = posix.join( - endpoint, - `?${ZosFilesConstants.RES_PATH}=${encodeURIComponent( - options.path - )}` - ); + endpoint = posix.join(endpoint, `?${ZosFilesConstants.RES_PATH}=${encodeURIComponent(options.path)}`); } const reqHeaders: IHeaderContent[] = [ZosmfHeaders.ACCEPT_ENCODING]; if (options.maxLength) { - reqHeaders.push({ "X-IBM-Max-Items": `${options.maxLength}` }); + reqHeaders.push({"X-IBM-Max-Items": `${options.maxLength}`}); } else { reqHeaders.push(ZosmfHeaders.X_IBM_MAX_ITEMS); } if (options.responseTimeout != null) { - reqHeaders.push({ - [ZosmfHeaders.X_IBM_RESPONSE_TIMEOUT]: - options.responseTimeout.toString(), - }); + reqHeaders.push({[ZosmfHeaders.X_IBM_RESPONSE_TIMEOUT]: options.responseTimeout.toString()}); } this.log.debug(`Endpoint: ${endpoint}`); - const response: any = await ZosmfRestClient.getExpectJSON( - session, - endpoint, - reqHeaders - ); + const response: any = await ZosmfRestClient.getExpectJSON(session, endpoint, reqHeaders); return { success: true, commandResponse: null, - apiResponse: response, + apiResponse: response }; } catch (error) { this.log.error(error); @@ -628,40 +417,22 @@ export class List { * await List.dataSetsMatchingPattern(session, "USER.**.DATASET"); * ``` */ - public static async dataSetsMatchingPattern( - session: AbstractSession, - patterns: string[], - options: IDsmListOptions = {} - ): Promise { + public static async dataSetsMatchingPattern(session: AbstractSession, patterns: string[], + options: IDsmListOptions = {}): Promise { + // Pattern is required to be non-empty - ImperativeExpect.toNotBeNullOrUndefined( - patterns, - ZosFilesMessages.missingPatterns.message - ); + ImperativeExpect.toNotBeNullOrUndefined(patterns, ZosFilesMessages.missingPatterns.message); patterns = patterns.filter(Boolean); - ImperativeExpect.toNotBeEqual( - patterns.length, - 0, - ZosFilesMessages.missingPatterns.message - ); + ImperativeExpect.toNotBeEqual(patterns.length, 0, ZosFilesMessages.missingPatterns.message); const zosmfResponses: IZosmfListResponse[] = []; // Get names of all data sets for (const pattern of patterns) { let response: any; try { - response = await List.dataSet(session, pattern, { - attributes: true, - maxLength: options.maxLength, - start: options.start, - }); + response = await List.dataSet(session, pattern, { attributes: true, maxLength: options.maxLength, start: options.start }); } catch (err) { - if ( - !( - err instanceof ImperativeError && - err.errorCode?.toString().startsWith("5") - ) - ) { + if (!(err instanceof ImperativeError && err.errorCode?.toString().startsWith("5"))) { throw err; } // Listing data sets with attributes may fail sometimes, for @@ -675,23 +446,14 @@ export class List { let listsInitiated = 0; const createListPromise = (dataSetObj: any) => { if (options.task != null) { - options.task.percentComplete = Math.floor( - TaskProgress.ONE_HUNDRED_PERCENT * - (listsInitiated / - response.apiResponse.items.length) - ); + options.task.percentComplete = Math.floor(TaskProgress.ONE_HUNDRED_PERCENT * + (listsInitiated / response.apiResponse.items.length)); listsInitiated++; } - return List.dataSet(session, dataSetObj.dsname, { - attributes: true, - maxLength: 1, - }).then( + return List.dataSet(session, dataSetObj.dsname, { attributes: true, maxLength: 1 }).then( (tempResponse) => { - Object.assign( - dataSetObj, - tempResponse.apiResponse.items[0] - ); + Object.assign(dataSetObj, tempResponse.apiResponse.items[0]); }, (tempErr) => { Object.assign(dataSetObj, { error: tempErr }); @@ -699,20 +461,11 @@ export class List { ); }; - const maxConcurrentRequests = - options.maxConcurrentRequests == null - ? 1 - : options.maxConcurrentRequests; + const maxConcurrentRequests = options.maxConcurrentRequests == null ? 1 : options.maxConcurrentRequests; if (maxConcurrentRequests === 0) { - await Promise.all( - response.apiResponse.items.map(createListPromise) - ); + await Promise.all(response.apiResponse.items.map(createListPromise)); } else { - await asyncPool( - maxConcurrentRequests, - response.apiResponse.items, - createListPromise - ); + await asyncPool(maxConcurrentRequests, response.apiResponse.items, createListPromise); } } zosmfResponses.push(...response.apiResponse.items); @@ -722,25 +475,20 @@ export class List { if (zosmfResponses.length === 0) { return { success: false, - commandResponse: - ZosFilesMessages.noDataSetsMatchingPattern.message, - apiResponse: [], + commandResponse: ZosFilesMessages.noDataSetsMatchingPattern.message, + apiResponse: [] }; } // Exclude names of data sets for (const pattern of options.excludePatterns || []) { const response = await List.dataSet(session, pattern); - response.apiResponse.items.forEach( - (dataSetObj: IZosmfListResponse) => { - const responseIndex = zosmfResponses.findIndex( - (response) => response.dsname === dataSetObj.dsname - ); - if (responseIndex !== -1) { - zosmfResponses.splice(responseIndex, 1); - } + response.apiResponse.items.forEach((dataSetObj: IZosmfListResponse) => { + const responseIndex = zosmfResponses.findIndex(response => response.dsname === dataSetObj.dsname); + if (responseIndex !== -1) { + zosmfResponses.splice(responseIndex, 1); } - ); + }); } // Check if exclude pattern has left any data sets in the list @@ -748,17 +496,14 @@ export class List { return { success: false, commandResponse: ZosFilesMessages.noDataSetsInList.message, - apiResponse: [], + apiResponse: [] }; } return { success: true, - commandResponse: util.format( - ZosFilesMessages.dataSetsMatchedPattern.message, - zosmfResponses.length - ), - apiResponse: zosmfResponses, + commandResponse: util.format(ZosFilesMessages.dataSetsMatchedPattern.message, zosmfResponses.length), + apiResponse: zosmfResponses }; }