diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 8bb8e266d1..299262a883 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -4,6 +4,7 @@ All notable changes to the Zowe CLI package will be documented in this file. ## Recent Changes +- Enhancement: Added `--recordRange` flag to `zowe jobs download output` command to allow users to select a specific range of records to output from a spool file. [#2411](https://github.com/zowe/zowe-cli/pull/2411) - BugFix: The `zowe zos-files copy data-set` command overwrites the contents of the target data set without user confirmation. A `--safe-replace` option was added which prompts the user to confirm before overwriting the contents of the target data set. [#2369] (https://github.com/zowe/zowe-cli/issues/2369) ## `8.12.0` diff --git a/packages/cli/__tests__/zosjobs/__integration__/download/__snapshots__/cli.zos-jobs.download.output.integration.test.ts.snap b/packages/cli/__tests__/zosjobs/__integration__/download/__snapshots__/cli.zos-jobs.download.output.integration.test.ts.snap index 6dd3b29ae4..41ee20981f 100644 --- a/packages/cli/__tests__/zosjobs/__integration__/download/__snapshots__/cli.zos-jobs.download.output.integration.test.ts.snap +++ b/packages/cli/__tests__/zosjobs/__integration__/download/__snapshots__/cli.zos-jobs.download.output.integration.test.ts.snap @@ -101,6 +101,10 @@ exports[`zos-jobs download output command should display the help 1`] = ` Wait for the job to enter OUTPUT status before completing the command. + --record-range | --rr (string) + + Zero indexed range of records to download from a spool file. (example: 0-100) + ZOSMF CONNECTION OPTIONS ------------------------ @@ -199,12 +203,17 @@ exports[`zos-jobs download output command should display the help 1`] = ` $ zowe zos-jobs download output JOB00234 + - Download the records in the range of 0 to 100 from a job + spool.: + + $ zowe zos-jobs download output --record-range '0-100' + { \\"success\\": true, \\"exitCode\\": 0, \\"message\\": \\"The help was constructed for command: output.\\", - \\"stdout\\": \\"\\\\n COMMAND NAME\\\\n ------------\\\\n\\\\n output | o\\\\n\\\\n DESCRIPTION\\\\n -----------\\\\n\\\\n Download all job output to a local directory. Each spool DD will be downloaded\\\\n to its own file in the directory.\\\\n\\\\n USAGE\\\\n -----\\\\n\\\\n zowe zos-jobs download output [options]\\\\n\\\\n POSITIONAL ARGUMENTS\\\\n --------------------\\\\n\\\\n jobid\\\\t\\\\t (string)\\\\n\\\\n The z/OS JOBID of the job containing the spool files you want to view. No\\\\n pre-validation of the JOBID is performed.\\\\n\\\\n OPTIONS\\\\n -------\\\\n\\\\n --directory | -d | --dir (string)\\\\n\\\\n The local directory you would like to download the output for the job to.\\\\n\\\\n --extension | -e (string)\\\\n\\\\n A file extension to save the job output with. Defaults to '.txt'.\\\\n\\\\n --omit-jobid-directory | --ojd (boolean)\\\\n\\\\n If specified, job output will be saved directly to the specified directory\\\\n rather than creating a subdirectory named after the ID of the job.\\\\n\\\\n --binary | -b (boolean)\\\\n\\\\n If specified, job output will be downloaded in binary format instead of\\\\n performing text conversion. Conflicts with record.\\\\n\\\\n --record | -r (boolean)\\\\n\\\\n If specified, job output will be downloaded in record format instead of\\\\n performing text conversion. Conflicts with binary.\\\\n\\\\n --encoding | --ec (string)\\\\n\\\\n Download the spool file content with encoding mode, which means that data\\\\n conversion is performed using the file encoding specified.\\\\n\\\\n --wait-for-active | --wfa (boolean)\\\\n\\\\n Wait for the job to enter ACTIVE status before completing the command.\\\\n\\\\n --wait-for-output | --wfo (boolean)\\\\n\\\\n Wait for the job to enter OUTPUT status before completing the command.\\\\n\\\\n ZOSMF CONNECTION OPTIONS\\\\n ------------------------\\\\n\\\\n --host | -H (string)\\\\n\\\\n The z/OSMF server host name.\\\\n\\\\n --port | -P (number)\\\\n\\\\n The z/OSMF server port.\\\\n\\\\n Default value: 443\\\\n\\\\n --user | -u (string)\\\\n\\\\n Mainframe (z/OSMF) user name, which can be the same as your TSO login.\\\\n\\\\n --password | --pass | --pw (string)\\\\n\\\\n Mainframe (z/OSMF) password, which can be the same as your TSO password.\\\\n\\\\n --reject-unauthorized | --ru (boolean)\\\\n\\\\n Reject self-signed certificates.\\\\n\\\\n Default value: true\\\\n\\\\n --base-path | --bp (string)\\\\n\\\\n The base path for your API mediation layer instance. Specify this option to\\\\n prepend the base path to all z/OSMF resources when making REST requests. Do not\\\\n specify this option if you are not using an API mediation layer.\\\\n\\\\n --protocol (string)\\\\n\\\\n The protocol used (HTTP or HTTPS)\\\\n\\\\n Default value: https\\\\n Allowed values: http, https\\\\n\\\\n --cert-file (local file path)\\\\n\\\\n The file path to a certificate file to use for authentication\\\\n\\\\n --cert-key-file (local file path)\\\\n\\\\n The file path to a certificate key file to use for authentication\\\\n\\\\n PROFILE OPTIONS\\\\n ---------------\\\\n\\\\n --zosmf-profile | --zosmf-p (string)\\\\n\\\\n The name of a (zosmf) profile to load for this command execution.\\\\n\\\\n --base-profile | --base-p (string)\\\\n\\\\n The name of a (base) profile to load for this command execution.\\\\n\\\\n BASE CONNECTION OPTIONS\\\\n -----------------------\\\\n\\\\n --token-type | --tt (string)\\\\n\\\\n The type of token to get and use for the API. Omit this option to use the\\\\n default token type, which is provided by 'zowe auth login'.\\\\n\\\\n --token-value | --tv (string)\\\\n\\\\n The value of the token to pass to the API.\\\\n\\\\n GLOBAL OPTIONS\\\\n --------------\\\\n\\\\n --show-inputs-only (boolean)\\\\n\\\\n Show command inputs and do not run the command\\\\n\\\\n --response-format-json | --rfj (boolean)\\\\n\\\\n Produce JSON formatted data from a command\\\\n\\\\n --help | -h (boolean)\\\\n\\\\n Display help text\\\\n\\\\n --help-web | --hw (boolean)\\\\n\\\\n Display HTML help in browser\\\\n\\\\n EXAMPLES\\\\n --------\\\\n\\\\n - Download all the output of the job with job ID JOB00234 to\\\\n an automatically generated directory.:\\\\n\\\\n $ zowe zos-jobs download output JOB00234\\\\n\\\\n\\", + \\"stdout\\": \\"\\\\n COMMAND NAME\\\\n ------------\\\\n\\\\n output | o\\\\n\\\\n DESCRIPTION\\\\n -----------\\\\n\\\\n Download all job output to a local directory. Each spool DD will be downloaded\\\\n to its own file in the directory.\\\\n\\\\n USAGE\\\\n -----\\\\n\\\\n zowe zos-jobs download output [options]\\\\n\\\\n POSITIONAL ARGUMENTS\\\\n --------------------\\\\n\\\\n jobid\\\\t\\\\t (string)\\\\n\\\\n The z/OS JOBID of the job containing the spool files you want to view. No\\\\n pre-validation of the JOBID is performed.\\\\n\\\\n OPTIONS\\\\n -------\\\\n\\\\n --directory | -d | --dir (string)\\\\n\\\\n The local directory you would like to download the output for the job to.\\\\n\\\\n --extension | -e (string)\\\\n\\\\n A file extension to save the job output with. Defaults to '.txt'.\\\\n\\\\n --omit-jobid-directory | --ojd (boolean)\\\\n\\\\n If specified, job output will be saved directly to the specified directory\\\\n rather than creating a subdirectory named after the ID of the job.\\\\n\\\\n --binary | -b (boolean)\\\\n\\\\n If specified, job output will be downloaded in binary format instead of\\\\n performing text conversion. Conflicts with record.\\\\n\\\\n --record | -r (boolean)\\\\n\\\\n If specified, job output will be downloaded in record format instead of\\\\n performing text conversion. Conflicts with binary.\\\\n\\\\n --encoding | --ec (string)\\\\n\\\\n Download the spool file content with encoding mode, which means that data\\\\n conversion is performed using the file encoding specified.\\\\n\\\\n --wait-for-active | --wfa (boolean)\\\\n\\\\n Wait for the job to enter ACTIVE status before completing the command.\\\\n\\\\n --wait-for-output | --wfo (boolean)\\\\n\\\\n Wait for the job to enter OUTPUT status before completing the command.\\\\n\\\\n --record-range | --rr (string)\\\\n\\\\n Zero indexed range of records to download from a spool file. (example: 0-100)\\\\n\\\\n ZOSMF CONNECTION OPTIONS\\\\n ------------------------\\\\n\\\\n --host | -H (string)\\\\n\\\\n The z/OSMF server host name.\\\\n\\\\n --port | -P (number)\\\\n\\\\n The z/OSMF server port.\\\\n\\\\n Default value: 443\\\\n\\\\n --user | -u (string)\\\\n\\\\n Mainframe (z/OSMF) user name, which can be the same as your TSO login.\\\\n\\\\n --password | --pass | --pw (string)\\\\n\\\\n Mainframe (z/OSMF) password, which can be the same as your TSO password.\\\\n\\\\n --reject-unauthorized | --ru (boolean)\\\\n\\\\n Reject self-signed certificates.\\\\n\\\\n Default value: true\\\\n\\\\n --base-path | --bp (string)\\\\n\\\\n The base path for your API mediation layer instance. Specify this option to\\\\n prepend the base path to all z/OSMF resources when making REST requests. Do not\\\\n specify this option if you are not using an API mediation layer.\\\\n\\\\n --protocol (string)\\\\n\\\\n The protocol used (HTTP or HTTPS)\\\\n\\\\n Default value: https\\\\n Allowed values: http, https\\\\n\\\\n --cert-file (local file path)\\\\n\\\\n The file path to a certificate file to use for authentication\\\\n\\\\n --cert-key-file (local file path)\\\\n\\\\n The file path to a certificate key file to use for authentication\\\\n\\\\n PROFILE OPTIONS\\\\n ---------------\\\\n\\\\n --zosmf-profile | --zosmf-p (string)\\\\n\\\\n The name of a (zosmf) profile to load for this command execution.\\\\n\\\\n --base-profile | --base-p (string)\\\\n\\\\n The name of a (base) profile to load for this command execution.\\\\n\\\\n BASE CONNECTION OPTIONS\\\\n -----------------------\\\\n\\\\n --token-type | --tt (string)\\\\n\\\\n The type of token to get and use for the API. Omit this option to use the\\\\n default token type, which is provided by 'zowe auth login'.\\\\n\\\\n --token-value | --tv (string)\\\\n\\\\n The value of the token to pass to the API.\\\\n\\\\n GLOBAL OPTIONS\\\\n --------------\\\\n\\\\n --show-inputs-only (boolean)\\\\n\\\\n Show command inputs and do not run the command\\\\n\\\\n --response-format-json | --rfj (boolean)\\\\n\\\\n Produce JSON formatted data from a command\\\\n\\\\n --help | -h (boolean)\\\\n\\\\n Display help text\\\\n\\\\n --help-web | --hw (boolean)\\\\n\\\\n Display HTML help in browser\\\\n\\\\n EXAMPLES\\\\n --------\\\\n\\\\n - Download all the output of the job with job ID JOB00234 to\\\\n an automatically generated directory.:\\\\n\\\\n $ zowe zos-jobs download output JOB00234\\\\n\\\\n - Download the records in the range of 0 to 100 from a job\\\\n spool.:\\\\n\\\\n $ zowe zos-jobs download output --record-range '0-100'\\\\n\\\\n\\", \\"stderr\\": \\"\\", - \\"data\\": \\"\\\\n COMMAND NAME\\\\n ------------\\\\n\\\\n output | o\\\\n\\\\n DESCRIPTION\\\\n -----------\\\\n\\\\n Download all job output to a local directory. Each spool DD will be downloaded\\\\n to its own file in the directory.\\\\n\\\\n USAGE\\\\n -----\\\\n\\\\n zowe zos-jobs download output [options]\\\\n\\\\n POSITIONAL ARGUMENTS\\\\n --------------------\\\\n\\\\n jobid\\\\t\\\\t (string)\\\\n\\\\n The z/OS JOBID of the job containing the spool files you want to view. No\\\\n pre-validation of the JOBID is performed.\\\\n\\\\n OPTIONS\\\\n -------\\\\n\\\\n --directory | -d | --dir (string)\\\\n\\\\n The local directory you would like to download the output for the job to.\\\\n\\\\n --extension | -e (string)\\\\n\\\\n A file extension to save the job output with. Defaults to '.txt'.\\\\n\\\\n --omit-jobid-directory | --ojd (boolean)\\\\n\\\\n If specified, job output will be saved directly to the specified directory\\\\n rather than creating a subdirectory named after the ID of the job.\\\\n\\\\n --binary | -b (boolean)\\\\n\\\\n If specified, job output will be downloaded in binary format instead of\\\\n performing text conversion. Conflicts with record.\\\\n\\\\n --record | -r (boolean)\\\\n\\\\n If specified, job output will be downloaded in record format instead of\\\\n performing text conversion. Conflicts with binary.\\\\n\\\\n --encoding | --ec (string)\\\\n\\\\n Download the spool file content with encoding mode, which means that data\\\\n conversion is performed using the file encoding specified.\\\\n\\\\n --wait-for-active | --wfa (boolean)\\\\n\\\\n Wait for the job to enter ACTIVE status before completing the command.\\\\n\\\\n --wait-for-output | --wfo (boolean)\\\\n\\\\n Wait for the job to enter OUTPUT status before completing the command.\\\\n\\\\n ZOSMF CONNECTION OPTIONS\\\\n ------------------------\\\\n\\\\n --host | -H (string)\\\\n\\\\n The z/OSMF server host name.\\\\n\\\\n --port | -P (number)\\\\n\\\\n The z/OSMF server port.\\\\n\\\\n Default value: 443\\\\n\\\\n --user | -u (string)\\\\n\\\\n Mainframe (z/OSMF) user name, which can be the same as your TSO login.\\\\n\\\\n --password | --pass | --pw (string)\\\\n\\\\n Mainframe (z/OSMF) password, which can be the same as your TSO password.\\\\n\\\\n --reject-unauthorized | --ru (boolean)\\\\n\\\\n Reject self-signed certificates.\\\\n\\\\n Default value: true\\\\n\\\\n --base-path | --bp (string)\\\\n\\\\n The base path for your API mediation layer instance. Specify this option to\\\\n prepend the base path to all z/OSMF resources when making REST requests. Do not\\\\n specify this option if you are not using an API mediation layer.\\\\n\\\\n --protocol (string)\\\\n\\\\n The protocol used (HTTP or HTTPS)\\\\n\\\\n Default value: https\\\\n Allowed values: http, https\\\\n\\\\n --cert-file (local file path)\\\\n\\\\n The file path to a certificate file to use for authentication\\\\n\\\\n --cert-key-file (local file path)\\\\n\\\\n The file path to a certificate key file to use for authentication\\\\n\\\\n PROFILE OPTIONS\\\\n ---------------\\\\n\\\\n --zosmf-profile | --zosmf-p (string)\\\\n\\\\n The name of a (zosmf) profile to load for this command execution.\\\\n\\\\n --base-profile | --base-p (string)\\\\n\\\\n The name of a (base) profile to load for this command execution.\\\\n\\\\n BASE CONNECTION OPTIONS\\\\n -----------------------\\\\n\\\\n --token-type | --tt (string)\\\\n\\\\n The type of token to get and use for the API. Omit this option to use the\\\\n default token type, which is provided by 'zowe auth login'.\\\\n\\\\n --token-value | --tv (string)\\\\n\\\\n The value of the token to pass to the API.\\\\n\\\\n GLOBAL OPTIONS\\\\n --------------\\\\n\\\\n --show-inputs-only (boolean)\\\\n\\\\n Show command inputs and do not run the command\\\\n\\\\n --response-format-json | --rfj (boolean)\\\\n\\\\n Produce JSON formatted data from a command\\\\n\\\\n --help | -h (boolean)\\\\n\\\\n Display help text\\\\n\\\\n --help-web | --hw (boolean)\\\\n\\\\n Display HTML help in browser\\\\n\\\\n EXAMPLES\\\\n --------\\\\n\\\\n - Download all the output of the job with job ID JOB00234 to\\\\n an automatically generated directory.:\\\\n\\\\n $ zowe zos-jobs download output JOB00234\\\\n\\\\n\\" + \\"data\\": \\"\\\\n COMMAND NAME\\\\n ------------\\\\n\\\\n output | o\\\\n\\\\n DESCRIPTION\\\\n -----------\\\\n\\\\n Download all job output to a local directory. Each spool DD will be downloaded\\\\n to its own file in the directory.\\\\n\\\\n USAGE\\\\n -----\\\\n\\\\n zowe zos-jobs download output [options]\\\\n\\\\n POSITIONAL ARGUMENTS\\\\n --------------------\\\\n\\\\n jobid\\\\t\\\\t (string)\\\\n\\\\n The z/OS JOBID of the job containing the spool files you want to view. No\\\\n pre-validation of the JOBID is performed.\\\\n\\\\n OPTIONS\\\\n -------\\\\n\\\\n --directory | -d | --dir (string)\\\\n\\\\n The local directory you would like to download the output for the job to.\\\\n\\\\n --extension | -e (string)\\\\n\\\\n A file extension to save the job output with. Defaults to '.txt'.\\\\n\\\\n --omit-jobid-directory | --ojd (boolean)\\\\n\\\\n If specified, job output will be saved directly to the specified directory\\\\n rather than creating a subdirectory named after the ID of the job.\\\\n\\\\n --binary | -b (boolean)\\\\n\\\\n If specified, job output will be downloaded in binary format instead of\\\\n performing text conversion. Conflicts with record.\\\\n\\\\n --record | -r (boolean)\\\\n\\\\n If specified, job output will be downloaded in record format instead of\\\\n performing text conversion. Conflicts with binary.\\\\n\\\\n --encoding | --ec (string)\\\\n\\\\n Download the spool file content with encoding mode, which means that data\\\\n conversion is performed using the file encoding specified.\\\\n\\\\n --wait-for-active | --wfa (boolean)\\\\n\\\\n Wait for the job to enter ACTIVE status before completing the command.\\\\n\\\\n --wait-for-output | --wfo (boolean)\\\\n\\\\n Wait for the job to enter OUTPUT status before completing the command.\\\\n\\\\n --record-range | --rr (string)\\\\n\\\\n Zero indexed range of records to download from a spool file. (example: 0-100)\\\\n\\\\n ZOSMF CONNECTION OPTIONS\\\\n ------------------------\\\\n\\\\n --host | -H (string)\\\\n\\\\n The z/OSMF server host name.\\\\n\\\\n --port | -P (number)\\\\n\\\\n The z/OSMF server port.\\\\n\\\\n Default value: 443\\\\n\\\\n --user | -u (string)\\\\n\\\\n Mainframe (z/OSMF) user name, which can be the same as your TSO login.\\\\n\\\\n --password | --pass | --pw (string)\\\\n\\\\n Mainframe (z/OSMF) password, which can be the same as your TSO password.\\\\n\\\\n --reject-unauthorized | --ru (boolean)\\\\n\\\\n Reject self-signed certificates.\\\\n\\\\n Default value: true\\\\n\\\\n --base-path | --bp (string)\\\\n\\\\n The base path for your API mediation layer instance. Specify this option to\\\\n prepend the base path to all z/OSMF resources when making REST requests. Do not\\\\n specify this option if you are not using an API mediation layer.\\\\n\\\\n --protocol (string)\\\\n\\\\n The protocol used (HTTP or HTTPS)\\\\n\\\\n Default value: https\\\\n Allowed values: http, https\\\\n\\\\n --cert-file (local file path)\\\\n\\\\n The file path to a certificate file to use for authentication\\\\n\\\\n --cert-key-file (local file path)\\\\n\\\\n The file path to a certificate key file to use for authentication\\\\n\\\\n PROFILE OPTIONS\\\\n ---------------\\\\n\\\\n --zosmf-profile | --zosmf-p (string)\\\\n\\\\n The name of a (zosmf) profile to load for this command execution.\\\\n\\\\n --base-profile | --base-p (string)\\\\n\\\\n The name of a (base) profile to load for this command execution.\\\\n\\\\n BASE CONNECTION OPTIONS\\\\n -----------------------\\\\n\\\\n --token-type | --tt (string)\\\\n\\\\n The type of token to get and use for the API. Omit this option to use the\\\\n default token type, which is provided by 'zowe auth login'.\\\\n\\\\n --token-value | --tv (string)\\\\n\\\\n The value of the token to pass to the API.\\\\n\\\\n GLOBAL OPTIONS\\\\n --------------\\\\n\\\\n --show-inputs-only (boolean)\\\\n\\\\n Show command inputs and do not run the command\\\\n\\\\n --response-format-json | --rfj (boolean)\\\\n\\\\n Produce JSON formatted data from a command\\\\n\\\\n --help | -h (boolean)\\\\n\\\\n Display help text\\\\n\\\\n --help-web | --hw (boolean)\\\\n\\\\n Display HTML help in browser\\\\n\\\\n EXAMPLES\\\\n --------\\\\n\\\\n - Download all the output of the job with job ID JOB00234 to\\\\n an automatically generated directory.:\\\\n\\\\n $ zowe zos-jobs download output JOB00234\\\\n\\\\n - Download the records in the range of 0 to 100 from a job\\\\n spool.:\\\\n\\\\n $ zowe zos-jobs download output --record-range '0-100'\\\\n\\\\n\\" }" `; diff --git a/packages/cli/__tests__/zosjobs/__unit__/download/download-output/__snapshots__/Output.definition.unit.test.ts.snap b/packages/cli/__tests__/zosjobs/__unit__/download/download-output/__snapshots__/Output.definition.unit.test.ts.snap index f73cf31e05..3eedcb8f07 100644 --- a/packages/cli/__tests__/zosjobs/__unit__/download/download-output/__snapshots__/Output.definition.unit.test.ts.snap +++ b/packages/cli/__tests__/zosjobs/__unit__/download/download-output/__snapshots__/Output.definition.unit.test.ts.snap @@ -11,6 +11,10 @@ Object { "description": "Download all the output of the job with job ID JOB00234 to an automatically generated directory.", "options": "JOB00234", }, + Object { + "description": "Download the records in the range of 0 to 100 from a job spool.", + "options": "--record-range '0-100'", + }, ], "name": "output", "options": Array [ @@ -92,6 +96,15 @@ Object { "name": "wait-for-output", "type": "boolean", }, + Object { + "aliases": Array [ + "rr", + ], + "description": "Zero indexed range of records to download from a spool file. (example: 0-100)", + "name": "record-range", + "optional": true, + "type": "string", + }, ], "positionals": Array [ Object { diff --git a/packages/cli/src/zosjobs/download/download-output/Output.definition.ts b/packages/cli/src/zosjobs/download/download-output/Output.definition.ts index 75e5c89d42..83e59f8b12 100644 --- a/packages/cli/src/zosjobs/download/download-output/Output.definition.ts +++ b/packages/cli/src/zosjobs/download/download-output/Output.definition.ts @@ -86,11 +86,22 @@ export const OutputDefinition: ICommandDefinition = { type: "boolean", conflictsWith: ["wait-for-active"] }, + { + name: "record-range", + aliases: ["rr"], + description: "Zero indexed range of records to download from a spool file. (example: 0-100)", + type: "string", + optional: true + } ] as ICommandOptionDefinition[]), examples: [ { description: "Download all the output of the job with job ID JOB00234 to an automatically generated directory.", options: "JOB00234" + }, + { + description: "Download the records in the range of 0 to 100 from a job spool.", + options: "--record-range '0-100'" } ] }; diff --git a/packages/cli/src/zosjobs/download/download-output/Output.handler.ts b/packages/cli/src/zosjobs/download/download-output/Output.handler.ts index 76725fbba0..f732369f61 100644 --- a/packages/cli/src/zosjobs/download/download-output/Output.handler.ts +++ b/packages/cli/src/zosjobs/download/download-output/Output.handler.ts @@ -37,6 +37,8 @@ export default class OutputHandler extends ZosmfBaseHandler { const encoding: string = this.mArguments.encoding; const waitForActive: boolean = this.mArguments.waitForActive; const waitForOutput: boolean = this.mArguments.waitForOutput; + const recordRange: string = this.mArguments.recordRange; + // Get the job details const job: IJob = await GetJobs.getJob(this.mSession, jobid); const options: IDownloadAllSpoolContentParms = { @@ -49,7 +51,8 @@ export default class OutputHandler extends ZosmfBaseHandler { record, encoding, waitForActive, - waitForOutput + waitForOutput, + recordRange }; // Download 'em all await DownloadJobs.downloadAllSpoolContentCommon(this.mSession, options); diff --git a/packages/zosjobs/CHANGELOG.md b/packages/zosjobs/CHANGELOG.md index 33947ef779..0adbdd6185 100644 --- a/packages/zosjobs/CHANGELOG.md +++ b/packages/zosjobs/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to the Zowe z/OS jobs SDK package will be documented in this file. +## Recent Changes + +- Enhancement: Added `--recordRange` flag logic handling to `DownloadJobs.downloadSpoolContentCommon()` to to allow users to select a specific range of records to output from a spool file. [#2411](https://github.com/zowe/zowe-cli/pull/2411) + ## `8.10.2` - BugFix: Check if encoding is set and not empty now works for numeric-only value in encoding (GetJobs.ts). [#2392] (https://github.com/zowe/zowe-cli/pull/2392). diff --git a/packages/zosjobs/__tests__/__system__/DownloadJobs.system.test.ts b/packages/zosjobs/__tests__/__system__/DownloadJobs.system.test.ts index ba34e0f1ed..0d2c767f13 100644 --- a/packages/zosjobs/__tests__/__system__/DownloadJobs.system.test.ts +++ b/packages/zosjobs/__tests__/__system__/DownloadJobs.system.test.ts @@ -57,6 +57,7 @@ describe("Download Jobs - System tests", () => { const job = await SubmitJobs.submitJclNotifyCommon(REAL_SESSION, { jcl: iefbr14JCL }); + testEnvironment.resources.jobs.push(job); jobid = job.jobid; jobname = job.jobname; @@ -289,8 +290,89 @@ describe("Download Jobs - System tests", () => { } } }, LONG_TIMEOUT); - }); + it("should be able to download all DDs from job output with a record range (0-5)", async () => { + for (const file of jobFiles) { + if (file.ddname === "JESMSGLG") { + jesJCLJobFile = file; + } + } + + await DownloadJobs.downloadAllSpoolContentCommon(REAL_SESSION, { + outDir: outputDirectory, + jobid, + jobname, + recordRange: "0-5" + }); + const expectedFile = DownloadJobs.getSpoolDownloadFilePath( + { + jobFile: jesJCLJobFile, + omitJobidDirectory: false, + outDir: outputDirectory + } + ); + expect(IO.existsSync(expectedFile)).toEqual(true); + expect(IO.readFileSync(expectedFile).toString()).toBeDefined(); + expect(IO.readFileSync(expectedFile).toString()).toContain("J E S 2 J O B L O G"); + expect(IO.readFileSync(expectedFile).toString()).not.toContain("0------ JES2 JOB STATISTICS ------"); + expect(IO.readFileSync(expectedFile).toString().trim().split('\n').length).toEqual(6); + }); + + it("should be able to download all DDs from job output with a record range (2-8)", async () => { + for (const file of jobFiles) { + if (file.ddname === "JESMSGLG") { + jesJCLJobFile = file; + } + } + + await DownloadJobs.downloadAllSpoolContentCommon(REAL_SESSION, { + outDir: outputDirectory, + jobid, + jobname, + recordRange: "2-8" + }); + const expectedFile = DownloadJobs.getSpoolDownloadFilePath( + { + jobFile: jesJCLJobFile, + omitJobidDirectory: false, + outDir: outputDirectory + } + ); + expect(IO.existsSync(expectedFile)).toEqual(true); + expect(IO.readFileSync(expectedFile).toString()).toBeDefined(); + expect(IO.readFileSync(expectedFile).toString()).not.toContain("J E S 2 J O B L O G"); + expect(IO.readFileSync(expectedFile).toString()).not.toContain("0------ JES2 JOB STATISTICS ------"); + expect(IO.readFileSync(expectedFile).toString().trim().split('\n').length).toEqual(7); + }); + + it("should be able to download all DDs from job output with a record range (0-100)", async () => { + for (const file of jobFiles) { + if (file.ddname === "JESMSGLG") { + jesJCLJobFile = file; + } + } + + await DownloadJobs.downloadAllSpoolContentCommon(REAL_SESSION, { + outDir: outputDirectory, + jobid, + jobname, + recordRange: "0-100" + }); + const expectedFile = DownloadJobs.getSpoolDownloadFilePath( + { + jobFile: jesJCLJobFile, + omitJobidDirectory: false, + outDir: outputDirectory + } + ); + expect(IO.existsSync(expectedFile)).toEqual(true); + expect(IO.readFileSync(expectedFile).toString()).toBeDefined(); + expect(IO.readFileSync(expectedFile).toString()).toContain("J E S 2 J O B L O G"); + expect(IO.readFileSync(expectedFile).toString()).toContain("0------ JES2 JOB STATISTICS ------"); + expect(IO.readFileSync(expectedFile).toString()).toContain("MINUTES EXECUTION TIME"); + expect(IO.readFileSync(expectedFile).toString().trim().split('\n').length).toEqual(16); //only 16 records in spool file + }); + }); describe("Negative tests", () => { let badJobFile: IJobFile; @@ -345,6 +427,101 @@ describe("Download Jobs - System tests", () => { expect(JSON.parse(err.causeErrors).message).toContain("does not contain spool file"); }); + it("should be able to download all DDs from job output with a record range (0-0)", async () => { + for (const file of jobFiles) { + if (file.ddname === "JESMSGLG") { + jesJCLJobFile = file; + } + } + let err; + let expectedFile; + try{ + await DownloadJobs.downloadAllSpoolContentCommon(REAL_SESSION, { + outDir: outputDirectory, + jobid, + jobname, + recordRange: "0-0" + }); + expectedFile = DownloadJobs.getSpoolDownloadFilePath( + { + jobFile: jesJCLJobFile, + omitJobidDirectory: false, + outDir: outputDirectory + } + ); + } + catch(e){ + err = e; + } + + expect(err).toBeDefined(); + expect(err.message).toEqual('Invalid record range specified: 0-0. Ensure the format is x-y with x < y.'); + expect(expectedFile).toBeUndefined(); + }); + + it("should be able to download all DDs from job output with a record range (2-1)", async () => { + for (const file of jobFiles) { + if (file.ddname === "JESMSGLG") { + jesJCLJobFile = file; + } + } + let err; + let expectedFile; + try{ + await DownloadJobs.downloadAllSpoolContentCommon(REAL_SESSION, { + outDir: outputDirectory, + jobid, + jobname, + recordRange: "2-1" + }); + expectedFile = DownloadJobs.getSpoolDownloadFilePath( + { + jobFile: jesJCLJobFile, + omitJobidDirectory: false, + outDir: outputDirectory + } + ); + } + catch(e){ + err = e; + } + + expect(err).toBeDefined(); + expect(err.message).toEqual('Invalid record range specified: 2-1. Ensure the format is x-y with x < y.'); + expect(expectedFile).toBeUndefined(); + }); + + it("should be able to download all DDs from job output with a record range (0 50)", async () => { + for (const file of jobFiles) { + if (file.ddname === "JESMSGLG") { + jesJCLJobFile = file; + } + } + let err; + let expectedFile; + try{ + await DownloadJobs.downloadAllSpoolContentCommon(REAL_SESSION, { + outDir: outputDirectory, + jobid, + jobname, + recordRange: "0 50" + }); + expectedFile = DownloadJobs.getSpoolDownloadFilePath( + { + jobFile: jesJCLJobFile, + omitJobidDirectory: false, + outDir: outputDirectory + } + ); + } + catch(e){ + err = e; + } + + expect(err).toBeDefined(); + expect(err.message).toEqual('Invalid record range format: 0 50. Expected format is x-y.'); + expect(expectedFile).toBeUndefined(); + }); }); }); @@ -531,5 +708,87 @@ describe("Download Jobs - System tests - Encoded", () => { } } }, LONG_TIMEOUT); + + it("should be able to download all DDs from job output with a record range (0-5) - encoded", async () => { + for (const file of jobFiles) { + if (file.ddname === "JESMSGLG") { + jesJCLJobFile = file; + } + } + + await DownloadJobs.downloadAllSpoolContentCommon(REAL_SESSION, { + outDir: outputDirectory, + jobid, + jobname, + recordRange: "0-5" + }); + const expectedFile = DownloadJobs.getSpoolDownloadFilePath( + { + jobFile: jesJCLJobFile, + omitJobidDirectory: false, + outDir: outputDirectory + } + ); + expect(IO.existsSync(expectedFile)).toEqual(true); + expect(IO.readFileSync(expectedFile).toString()).toBeDefined(); + expect(IO.readFileSync(expectedFile).toString()).toContain("J E S 2 J O B L O G"); + expect(IO.readFileSync(expectedFile).toString()).not.toContain("0------ JES2 JOB STATISTICS ------"); + expect(IO.readFileSync(expectedFile).toString().trim().split('\n').length).toEqual(6); + }); + + it("should be able to download all DDs from job output with a record range (2-8) - encoded", async () => { + for (const file of jobFiles) { + if (file.ddname === "JESMSGLG") { + jesJCLJobFile = file; + } + } + + await DownloadJobs.downloadAllSpoolContentCommon(REAL_SESSION, { + outDir: outputDirectory, + jobid, + jobname, + recordRange: "2-8" + }); + const expectedFile = DownloadJobs.getSpoolDownloadFilePath( + { + jobFile: jesJCLJobFile, + omitJobidDirectory: false, + outDir: outputDirectory + } + ); + expect(IO.existsSync(expectedFile)).toEqual(true); + expect(IO.readFileSync(expectedFile).toString()).toBeDefined(); + expect(IO.readFileSync(expectedFile).toString()).not.toContain("J E S 2 J O B L O G"); + expect(IO.readFileSync(expectedFile).toString()).not.toContain("0------ JES2 JOB STATISTICS ------"); + expect(IO.readFileSync(expectedFile).toString().trim().split('\n').length).toEqual(7); + }); + + it("should be able to download all DDs from job output with a record range (0-100) - encoded", async () => { + for (const file of jobFiles) { + if (file.ddname === "JESMSGLG") { + jesJCLJobFile = file; + } + } + + await DownloadJobs.downloadAllSpoolContentCommon(REAL_SESSION, { + outDir: outputDirectory, + jobid, + jobname, + recordRange: "0-100" + }); + const expectedFile = DownloadJobs.getSpoolDownloadFilePath( + { + jobFile: jesJCLJobFile, + omitJobidDirectory: false, + outDir: outputDirectory + } + ); + expect(IO.existsSync(expectedFile)).toEqual(true); + expect(IO.readFileSync(expectedFile).toString()).toBeDefined(); + expect(IO.readFileSync(expectedFile).toString()).toContain("J E S 2 J O B L O G"); + expect(IO.readFileSync(expectedFile).toString()).toContain("0------ JES2 JOB STATISTICS ------"); + expect(IO.readFileSync(expectedFile).toString()).toContain("MINUTES EXECUTION TIME"); + expect(IO.readFileSync(expectedFile).toString().trim().split('\n').length).toEqual(16); //only 16 records in spool file + }); }); }); diff --git a/packages/zosjobs/__tests__/__unit__/DownloadJobs.unit.test.ts b/packages/zosjobs/__tests__/__unit__/DownloadJobs.unit.test.ts index 2005f52df9..c0ecdeed28 100644 --- a/packages/zosjobs/__tests__/__unit__/DownloadJobs.unit.test.ts +++ b/packages/zosjobs/__tests__/__unit__/DownloadJobs.unit.test.ts @@ -473,6 +473,22 @@ describe("DownloadJobs", () => { expect(IO.createDirsSyncFromFilePath).toHaveBeenCalledWith(downloadFilePath); expect(downloadFilePath).not.toContain(spoolParms.jobid); }); + + it("should allow users to call downloadSpoolContentCommon with correct parameters (record range)", async () => { + const jobFile: IJobFile = JSON.parse(JSON.stringify(jobFiles[0])); + const spoolParms: IDownloadSpoolContentParms = { + jobFile: jobFile, + jobid: fakeJobID, + jobname: fakeJobName, + recordRange: "0-100" + }; + const downloadFilePath = DownloadJobs.getSpoolDownloadFilePath(spoolParms); + + await DownloadJobs.downloadSpoolContentCommon(fakeSession, spoolParms); + + expect(IO.createDirsSyncFromFilePath).toHaveBeenCalledWith(downloadFilePath); + expect(downloadFilePath).toContain(DownloadJobs.DEFAULT_JOBS_OUTPUT_DIR); + }); }); }); describe("Error catching - async/ await", () => { @@ -566,6 +582,44 @@ describe("DownloadJobs", () => { }); }); /* eslint-enable jest/no-done-callback */ + + it("should throw error regarding record range on spoolParms (0 100)", async () => { + const jobFile: IJobFile = JSON.parse(JSON.stringify(jobFiles[0])); + const spoolParms: IDownloadSpoolContentParms = { + jobFile: jobFile, + jobid: fakeJobID, + jobname: fakeJobName, + recordRange: "0 100" + }; + let err; + try { + await DownloadJobs.downloadSpoolContentCommon(fakeSession, spoolParms); + } catch (e) { + err = e; + } + + expect(err).toBeDefined(); + expect(err.message).toContain(`Invalid record range format: ${spoolParms.recordRange}. Expected format is x-y.`); + }); + + it("should throw error regarding record range on spoolParms (100-0)", async () => { + const jobFile: IJobFile = JSON.parse(JSON.stringify(jobFiles[0])); + const spoolParms: IDownloadSpoolContentParms = { + jobFile: jobFile, + jobid: fakeJobID, + jobname: fakeJobName, + recordRange: "100-0" + }; + let err; + try { + await DownloadJobs.downloadSpoolContentCommon(fakeSession, spoolParms); + } catch (e) { + err = e; + } + + expect(err).toBeDefined(); + expect(err.message).toContain(`Invalid record range specified: ${spoolParms.recordRange}. Ensure the format is x-y with x < y.`); + }); }); describe("Parameter validation tests", () => { diff --git a/packages/zosjobs/src/DownloadJobs.ts b/packages/zosjobs/src/DownloadJobs.ts index 14728422eb..780a2e7ca7 100644 --- a/packages/zosjobs/src/DownloadJobs.ts +++ b/packages/zosjobs/src/DownloadJobs.ts @@ -143,9 +143,30 @@ export class DownloadJobs { parameters += "?fileEncoding=" + parms.encoding; } + const headers = [Headers.TEXT_PLAIN_UTF8]; + + // Handle record range + if (parms.recordRange) { + const recordRangeMatch = parms.recordRange.match(/^(\d+)-(\d+)$/); // Match multi-digit numbers + if (recordRangeMatch) { + const start = parseInt(recordRangeMatch[1], 10); + const end = parseInt(recordRangeMatch[2], 10); + + if (start >= 0 && end > start) { + if (parms.recordRange) { + headers.push({ "X-IBM-Record-Range": `${start}-${end}` }); + } + } else { + throw new Error(`Invalid record range specified: ${parms.recordRange}. Ensure the format is x-y with x < y.`); + } + } else { + throw new Error(`Invalid record range format: ${parms.recordRange}. Expected format is x-y.`); + } + } + const writeStream = parms.stream ?? IO.createWriteStream(file); const normalizeResponseNewLines = !(parms.binary || parms.record); - await ZosmfRestClient.getStreamed(session, JobsConstants.RESOURCE + parameters, [Headers.TEXT_PLAIN_UTF8], writeStream, + await ZosmfRestClient.getStreamed(session, JobsConstants.RESOURCE + parameters, headers, writeStream, normalizeResponseNewLines); } diff --git a/packages/zosjobs/src/doc/input/IDownloadAllSpoolContentParms.ts b/packages/zosjobs/src/doc/input/IDownloadAllSpoolContentParms.ts index 13ce91987c..f0968e8af5 100644 --- a/packages/zosjobs/src/doc/input/IDownloadAllSpoolContentParms.ts +++ b/packages/zosjobs/src/doc/input/IDownloadAllSpoolContentParms.ts @@ -78,6 +78,14 @@ export interface IDownloadAllSpoolContentParms { */ encoding?: string; + /** + * Optional record range + * e.g. 0-100 + * @type {string} + * @memberof IDownloadAllSpoolContentParms + */ + recordRange?: string; + /** * Wait for the job to reach output status */