Skip to content

Commit

Permalink
Merge pull request #2078 from zowe/fnf-error-reporting
Browse files Browse the repository at this point in the history
Fnf error reporting
  • Loading branch information
zFernand0 authored Mar 13, 2024
2 parents dbbdbba + 152262f commit 62ac26d
Show file tree
Hide file tree
Showing 7 changed files with 192 additions and 130 deletions.
3 changes: 3 additions & 0 deletions npm-shrinkwrap.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions packages/cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

All notable changes to the Zowe CLI package will be documented in this file.


## Recent Changes

- BugFix: Removing stack trace for zosjobs errors. [#2078](https://github.com/zowe/zowe-cli/pull/2078)

## `8.0.0-next.202403122137`

- BugFix: Fixed default base profile missing in config generated by `zowe config auto-init` [#2088](https://github.com/zowe/zowe-cli/pull/2088)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ describe("zos-jobs submit local-file command", () => {
const response = runCliScript(__dirname + "/__scripts__/submit_invalid_local_file.sh",
TEST_ENVIRONMENT);
expect(response.status).toBe(1);
expect(response.stderr.toString().toLowerCase()).toContain("error");
expect(response.stderr.toString().toLowerCase()).toContain("no such file");
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,33 @@ describe("submit shared handler", () => {
expect(error.message).toContain("Unable to determine the JCL source. Please contact support");
});

it("should return any caught error, ie: ENOENT", async () => {
// Require the handler and create a new instance
const handlerReq = require("../../../../src/zosjobs/submit/Submit.shared.handler");
const handler = new handlerReq.default();

// Vars populated by the mocked function
let error;

// Local file doesn't exist and should be cause of failure
const theLocalFile: string = "fakefile";

const copy = Object.assign({}, LOCALFILE_PARAMETERS);
copy.arguments.localFile = theLocalFile;
try {
// Invoke the handler with a full set of mocked arguments and response functions
await handler.process(copy);
} catch (e) {
error = e;
}

expect(error).toBeDefined();
expect(error).toBeInstanceOf(ImperativeError);
expect(error.message).toContain("Node.js File System API error");
expect(error.additionalDetails).toContain("ENOENT: no such file or directory, open");
expect(error.additionalDetails).toContain("fakefile");
});

it("should not transform an error thrown by the submit JCL API", async () => {
// Require the handler and create a new instance
const handlerReq = require("../../../../src/zosjobs/submit/Submit.shared.handler");
Expand Down
275 changes: 146 additions & 129 deletions packages/cli/src/zosjobs/submit/Submit.shared.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

import { IHandlerParameters, ImperativeError, ITaskWithStatus, TaskProgress, TaskStage } from "@zowe/imperative";
import * as fs from "fs";
import { ISubmitParms, SubmitJobs, IJob, ISpoolFile } from "@zowe/zos-jobs-for-zowe-sdk";
import { ISubmitParms, SubmitJobs, IJob, ISpoolFile, ZosJobsMessages } from "@zowe/zos-jobs-for-zowe-sdk";
import { IDownloadOptions, Get } from "@zowe/zos-files-for-zowe-sdk";
import { ZosmfBaseHandler } from "@zowe/zosmf-for-zowe-sdk";
import { text } from "stream/consumers";
Expand All @@ -31,150 +31,167 @@ export default class SharedSubmitHandler extends ZosmfBaseHandler {
* @memberof SubmitDataSetHandler
*/
public async processCmd(params: IHandlerParameters): Promise<void> {
const status: ITaskWithStatus = {
statusMessage: "Submitting job",
percentComplete: TaskProgress.TEN_PERCENT,
stageName: TaskStage.IN_PROGRESS
};
// Save the needed parameters for convenience
const parms: ISubmitParms = {
jclSource: undefined,
viewAllSpoolContent: this.mArguments.viewAllSpoolContent,
directory: this.mArguments.directory,
extension: this.mArguments.extension,
volume: this.mArguments.volume,
waitForActive: this.mArguments.waitForActive,
waitForOutput: this.mArguments.waitForOutput,
task: status,
jclSymbols: this.mArguments.jclSymbols
};
const options: IDownloadOptions = {};
params.response.progress.startBar({task: status});

// Determine the positional parameter specified and invoke the correct API
// TODO: More will be added with additional commands
let sourceType: string;
if (this.mArguments.dataset) {
sourceType = "dataset";
} else if (this.mArguments.file) {
sourceType = "uss-file";
} else if (this.mArguments.localFile) {
sourceType = "local-file";
} else if (params.definition.name === "stdin") {
sourceType = "stdin";
}
let response: IJob; // Response from Submit Job
let apiObj: any; // API Object to set in the command JSON response
let spoolFilesResponse: ISpoolFile[]; // Response from view all spool content option
let source: any; // The actual JCL source (i.e. data-set name, file name, etc.)
let directory: string = this.mArguments.directory;// Path where to download spool content

// Process depending on the source type
switch (sourceType) {

// Submit the JCL from a data set
case "dataset":

// If the data set is not in catalog and volume option is provided
if (parms.volume) {
options.volume = parms.volume;

// Get JCL from data set or member
const getJcl = await Get.dataSet(this.mSession, this.mArguments.dataset, options);
source = this.mArguments.dataset;

apiObj = await SubmitJobs.submitJclString(this.mSession, getJcl.toString(), parms);
if (parms.viewAllSpoolContent) {
spoolFilesResponse = apiObj;
try{
const status: ITaskWithStatus = {
statusMessage: "Submitting job",
percentComplete: TaskProgress.TEN_PERCENT,
stageName: TaskStage.IN_PROGRESS
};
// Save the needed parameters for convenience
const parms: ISubmitParms = {
jclSource: undefined,
viewAllSpoolContent: this.mArguments.viewAllSpoolContent,
directory: this.mArguments.directory,
extension: this.mArguments.extension,
volume: this.mArguments.volume,
waitForActive: this.mArguments.waitForActive,
waitForOutput: this.mArguments.waitForOutput,
task: status,
jclSymbols: this.mArguments.jclSymbols
};
const options: IDownloadOptions = {};
params.response.progress.startBar({task: status});

// Determine the positional parameter specified and invoke the correct API
// TODO: More will be added with additional commands
let sourceType: string;
if (this.mArguments.dataset) {
sourceType = "dataset";
} else if (this.mArguments.file) {
sourceType = "uss-file";
} else if (this.mArguments.localFile) {
sourceType = "local-file";
} else if (params.definition.name === "stdin") {
sourceType = "stdin";
}
let response: IJob; // Response from Submit Job
let apiObj: any; // API Object to set in the command JSON response
let spoolFilesResponse: ISpoolFile[]; // Response from view all spool content option
let source: any; // The actual JCL source (i.e. data-set name, file name, etc.)
let directory: string = this.mArguments.directory;// Path where to download spool content

// Process depending on the source type
switch (sourceType) {

// Submit the JCL from a data set
case "dataset":

// If the data set is not in catalog and volume option is provided
if (parms.volume) {
options.volume = parms.volume;

// Get JCL from data set or member
const getJcl = await Get.dataSet(this.mSession, this.mArguments.dataset, options);
source = this.mArguments.dataset;

apiObj = await SubmitJobs.submitJclString(this.mSession, getJcl.toString(), parms);
if (parms.viewAllSpoolContent) {
spoolFilesResponse = apiObj;
}

break;
} else {
response = await SubmitJobs.submitJobCommon(this.mSession, {jobDataSet: this.mArguments.dataset,
jclSymbols: this.mArguments.jclSymbols});
apiObj = await SubmitJobs.checkSubmitOptions(this.mSession, parms, response);
source = this.mArguments.dataset;

if (parms.viewAllSpoolContent) {
spoolFilesResponse = apiObj;
}
}

break;
} else {
response = await SubmitJobs.submitJobCommon(this.mSession, {jobDataSet: this.mArguments.dataset,
// Submit JCL from a USS file
case "uss-file":
response = await SubmitJobs.submitJobCommon(this.mSession, {jobUSSFile: this.mArguments.file,
jclSymbols: this.mArguments.jclSymbols});
apiObj = await SubmitJobs.checkSubmitOptions(this.mSession, parms, response);
source = this.mArguments.dataset;
source = this.mArguments.ussfile;

if (parms.viewAllSpoolContent) {
spoolFilesResponse = apiObj;
}
}

break;
// Submit JCL from a USS file
case "uss-file":
response = await SubmitJobs.submitJobCommon(this.mSession, {jobUSSFile: this.mArguments.file,
jclSymbols: this.mArguments.jclSymbols});
apiObj = await SubmitJobs.checkSubmitOptions(this.mSession, parms, response);
source = this.mArguments.ussfile;

if (parms.viewAllSpoolContent) {
spoolFilesResponse = apiObj;
}

break;
// Submit the JCL from a local file
case "local-file": {
parms.jclSource = this.mArguments.localFile;
const JclString = fs.readFileSync(this.mArguments.localFile).toString();
apiObj = await SubmitJobs.submitJclString(this.mSession, JclString, parms);
source = this.mArguments.localFile;
if (parms.viewAllSpoolContent) {
spoolFilesResponse = apiObj;
break;
// Submit the JCL from a local file
case "local-file": {
parms.jclSource = this.mArguments.localFile;
let JclString: string;
try {
JclString = fs.readFileSync(this.mArguments.localFile).toString();
} catch (err) {
throw new ImperativeError({
msg: ZosJobsMessages.nodeJsFsError.message,
additionalDetails: err.toString(),
causeErrors: err
});
}
apiObj = await SubmitJobs.submitJclString(this.mSession, JclString, parms);
source = this.mArguments.localFile;
if (parms.viewAllSpoolContent) {
spoolFilesResponse = apiObj;
}
break;
}
break;
}
// Submit the JCL piped in on stdin
case "stdin": {
const Jcl = await text(params.stdin);
apiObj = await SubmitJobs.submitJclString(this.mSession, Jcl, parms);
source = "stdin";
if (parms.viewAllSpoolContent) {
spoolFilesResponse = apiObj;
// Submit the JCL piped in on stdin
case "stdin": {
const Jcl = await text(params.stdin);
apiObj = await SubmitJobs.submitJclString(this.mSession, Jcl, parms);
source = "stdin";
if (parms.viewAllSpoolContent) {
spoolFilesResponse = apiObj;
}
break;
}
break;
default:
throw new ImperativeError({
msg: `Internal submit error: Unable to determine the JCL source. ` +
`Please contact support.`,
additionalDetails: JSON.stringify(params)
});
}
default:
throw new ImperativeError({
msg: `Internal submit error: Unable to determine the JCL source. ` +
`Please contact support.`,
additionalDetails: JSON.stringify(params)
});
}

// Print the response to the command
if (spoolFilesResponse == null) {
params.response.format.output({
fields: ["jobid", "retcode", "jobname", "status"],
output: apiObj,
format: "object"
});
// Set the API object to the correct
this.data.setObj(apiObj);

// Print data from spool content
} else {
for (const spoolFile of spoolFilesResponse) {
if (spoolFile.procName != null && spoolFile.procName.length > 0) {
this.console.log("Spool file: %s (ID #%d, Step: %s, ProcStep: %s)",
spoolFile.ddName, spoolFile.id, spoolFile.stepName, spoolFile.procName);
} else {
this.console.log("Spool file: %s (ID #%d, Step: %s)",
spoolFile.ddName, spoolFile.id, spoolFile.stepName);
// Print the response to the command
if (spoolFilesResponse == null) {
params.response.format.output({
fields: ["jobid", "retcode", "jobname", "status"],
output: apiObj,
format: "object"
});
// Set the API object to the correct
this.data.setObj(apiObj);

// Print data from spool content
} else {
for (const spoolFile of spoolFilesResponse) {
if (spoolFile.procName != null && spoolFile.procName.length > 0) {
this.console.log("Spool file: %s (ID #%d, Step: %s, ProcStep: %s)",
spoolFile.ddName, spoolFile.id, spoolFile.stepName, spoolFile.procName);
} else {
this.console.log("Spool file: %s (ID #%d, Step: %s)",
spoolFile.ddName, spoolFile.id, spoolFile.stepName);
}
this.console.log(spoolFile.data);
}
this.console.log(spoolFile.data);
}

// Set the API object to the correct
this.data.setObj(spoolFilesResponse);
}
// Set the API object to the correct
this.data.setObj(spoolFilesResponse);
}

// Print path where spool content was downloaded
if (directory != null && spoolFilesResponse == null) {
directory = directory.includes("./") ? directory : `./${directory}`;
params.response.console.log(`Successfully downloaded output to ${directory}/${apiObj.jobid}`);
// Print path where spool content was downloaded
if (directory != null && spoolFilesResponse == null) {
directory = directory.includes("./") ? directory : `./${directory}`;
params.response.console.log(`Successfully downloaded output to ${directory}/${apiObj.jobid}`);
}
params.response.progress.endBar();
this.data.setMessage(`Submitted JCL contained in "${sourceType}": "${source}"`);
}catch (err){
if (err instanceof ImperativeError){
throw err;
} else {
throw new ImperativeError({msg: err.message, causeErrors: err});
}
}
params.response.progress.endBar();
this.data.setMessage(`Submitted JCL contained in "${sourceType}": "${source}"`);
}
}
}
4 changes: 4 additions & 0 deletions packages/zosjobs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

All notable changes to the Zowe z/OS jobs SDK package will be documented in this file.

## Recent Changes

- BugFix: Removing stack trace for zosjobs errors. Added constant to JobsMessages.ts for error handling. [#2078](https://github.com/zowe/zowe-cli/pull/2078)

## `8.0.0-next.202403041352`

- BugFix: Updated engine to Node 18.12.0. [#2074](https://github.com/zowe/zowe-cli/pull/2074)
Expand Down
7 changes: 7 additions & 0 deletions packages/zosjobs/src/JobsMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,12 @@ export const ZosJobsMessages: { [key: string]: IMessageDefinition } = {
*/
missingSearchOption: {
message: "You must specify either the `--search-string` or `--search-regex` option"
},

/**
* Message indicating that a failure has happened in the NodeJS File System API
*/
nodeJsFsError: {
message: "Node.js File System API error"
}
};

0 comments on commit 62ac26d

Please sign in to comment.