Skip to content

Commit

Permalink
Merge pull request #292 from sasjs/issue-284
Browse files Browse the repository at this point in the history
feat(job-execute): added 'returnStatusOnly' and 'ignoreWarnings' flags
  • Loading branch information
YuryShkoda authored Dec 2, 2020
2 parents 128922d + 47a2b0d commit 082d65d
Show file tree
Hide file tree
Showing 8 changed files with 167 additions and 46 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
},
"dependencies": {
"@sasjs/adapter": "^1.18.7",
"@sasjs/core": "^1.11.2",
"@sasjs/core": "^1.12.3",
"base64-img": "^1.0.4",
"btoa": "^1.2.1",
"chalk": "^4.1.0",
Expand Down
8 changes: 5 additions & 3 deletions src/commands/help.js
Original file line number Diff line number Diff line change
Expand Up @@ -248,12 +248,14 @@ export async function printHelpText() {
`[2spaces]* ${chalk.cyanBright(
'execute'
)} - triggers job for execution.`,
`[2spaces]command example: sasjs job execute /Public/job -t targetName --output ./outputFolder/output.json`,
`[2spaces]command example: sasjs job execute /Public/job -t targetName --wait --log ./logFolder/log.json`,
`[2spaces]command example: sasjs job execute /Public/job -t targetName --output ./outputFolder/output.json --returnStatusOnly --ignoreWarnings`,
`[2spaces]command example: sasjs job execute /Public/job -t targetName --wait --log ./logFolder/log.json -r -i`,
``,
`[2spaces]NOTE: Providing wait flag (--wait or -w) is optional. If present, CLI will wait for job completion.`,
`[2spaces]NOTE: Providing output flag (--output or -o) is optional. If present, CLI will immediately print out the response JSON. If value is provided, it will be treated as file path to save the response JSON.`,
`[2spaces]NOTE: Providing log flag (--log or -l) is optional. If present, CLI will fetch and save job log to local file.`
`[2spaces]NOTE: Providing log flag (--log or -l) is optional. If present, CLI will fetch and save job log to local file.`,
`[2spaces]NOTE: Providing return status only (--returnStatusOnly or -r) flag is optional. If present and wait flag is provided, CLI will job status only (0 = success, 1 = warning, 3 = error).`,
`[2spaces]NOTE: Providing ignore warnings (--ignoreWarnings or -i) flag is optional. If present and return status only is provided, CLI will return status '0', when the job state is warning.`
]
}
]
Expand Down
133 changes: 95 additions & 38 deletions src/commands/job/execute.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import path from 'path'
* @param {boolean | string} output - flag indicating if CLI should print out job output. If string was provided, it will be treated as file path to store output. If filepath wasn't provided, output.json file will be created in current folder.
* @param {boolean | string} logFile - flag indicating if CLI should fetch and save log to provided file path. If filepath wasn't provided, {job}.log file will be created in current folder.
* @param {boolean | string} statusFile - flag indicating if CLI should fetch and save status to the local file. If filepath wasn't provided, it will only print on console.
* @param {boolean | undefined} returnStatusOnly - flag indicating if CLI should return status only (0 = success, 1 = warning, 3 = error). Works only if waitForJob flag was provided.
* @param {boolean | undefined} ignoreWarnings - flag indicating if CLI should return status '0', when the job state is warning.
*/
export async function execute(
sasjs,
Expand All @@ -25,13 +27,26 @@ export async function execute(
waitForJob,
output,
logFile,
statusFile
statusFile,
returnStatusOnly,
ignoreWarnings
) {
if (returnStatusOnly && !waitForJob) waitForJob = true

if (ignoreWarnings && !returnStatusOnly) {
console.log(
chalk.yellowBright(
`WARNING: using 'ignoreWarnings' flag without 'returnStatusOnly' flag will not affect the command.`
)
)
}

let statusReturned = false
let result

const startTime = new Date().getTime()

if (statusFile !== undefined)
if (statusFile !== undefined && !returnStatusOnly)
await displayStatus({ state: 'Initiating' }, statusFile)

const spinner = ora(
Expand All @@ -40,9 +55,9 @@ export async function execute(
)} has been submitted for execution...\n`
)

spinner.start()
if (!returnStatusOnly) spinner.start()

const contextName = getContextName(target)
const contextName = getContextName(target, returnStatusOnly)

const submittedJob = await sasjs
.startComputeJob(
Expand All @@ -53,10 +68,18 @@ export async function execute(
waitForJob || logFile !== undefined ? true : false
)
.catch((err) => {
result =
typeof err === 'object' && Object.keys(err).length
? JSON.stringify({ state: err.job.state })
: `${err}`
if (returnStatusOnly) {
console.log(2)

statusReturned = true
}

result = returnStatusOnly
? 2
: typeof err === 'object' && Object.keys(err).length
? JSON.stringify({ state: err.job.state })
: `${err}`

if (err.job) {
return err.job
}
Expand All @@ -66,9 +89,9 @@ export async function execute(

const endTime = new Date().getTime()

if (result)
if (result && !returnStatusOnly)
displayResult(result, 'An error has occurred when executing a job.', null)
if (statusFile !== undefined)
if (statusFile !== undefined && !returnStatusOnly)
await displayStatus(submittedJob, statusFile, result, true)

if (submittedJob && submittedJob.links) {
Expand All @@ -78,13 +101,16 @@ export async function execute(
(l) => l.method === 'GET' && l.rel === 'self'
).href

displayResult(
null,
null,
(waitForJob
? `Job located at '${jobPath}' has been executed.\nJob details`
: `Job session`) + ` can be found at ${target.serverUrl + sessionLink}`
)
if (!returnStatusOnly) {
displayResult(
null,
null,
(waitForJob
? `Job located at '${jobPath}' has been executed.\nJob details`
: `Job session`) +
` can be found at ${target.serverUrl + sessionLink}`
)
}

if (output !== undefined || logFile !== undefined) {
try {
Expand All @@ -107,9 +133,10 @@ export async function execute(

await createFile(outputPath, outputJson)

displayResult(null, null, `Output saved to: ${outputPath}`)
if (!returnStatusOnly)
displayResult(null, null, `Output saved to: ${outputPath}`)
} else if (output) {
console.log(outputJson)
if (!returnStatusOnly) console.log(outputJson)
}

if (logFile !== undefined) {
Expand Down Expand Up @@ -148,48 +175,78 @@ export async function execute(

await createFile(logPath, logLines)

displayResult(null, null, `Log saved to: ${logPath}`)
if (!returnStatusOnly)
displayResult(null, null, `Log saved to: ${logPath}`)
}
}

result = submittedJob
} catch (error) {
result = false

displayResult(
null,
'An error has occurred when parsing an output of the job.',
null
)
if (!returnStatusOnly) {
displayResult(
null,
'An error has occurred when parsing an output of the job.',
null
)
}
}
}

if (waitForJob && returnStatusOnly && submittedJob.state) {
if (!statusReturned) {
switch (submittedJob.state) {
case 'completed':
result = 0
console.log(result)
break
case 'warning':
result = ignoreWarnings ? 0 : 1
console.log(result)
break
case 'error':
result = 2
console.log(result)
break
default:
break
}

statusReturned = true
}
}
}

console.log(
chalk.whiteBright(
`This operation took ${(endTime - startTime) / 1000} seconds`
if (!returnStatusOnly) {
console.log(
chalk.whiteBright(
`This operation took ${(endTime - startTime) / 1000} seconds`
)
)
)
}

return result
}

export function getContextName(target) {
export function getContextName(target, returnStatusOnly) {
const defaultContextName = 'SAS Job Execution compute context'
if (target && target.contextName) {
return target.contextName
}

console.log(
chalk.yellowBright(
`contextName was not provided. Using ${defaultContextName} by default.`
if (!returnStatusOnly) {
console.log(
chalk.yellowBright(
`contextName was not provided. Using ${defaultContextName} by default.`
)
)
)
console.log(
chalk.whiteBright(
`You can specify the context name in your target configuration.`
console.log(
chalk.whiteBright(
`You can specify the context name in your target configuration.`
)
)
)
}

return defaultContextName
}
Expand Down
6 changes: 5 additions & 1 deletion src/commands/job/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ export async function processJob(commandLine) {
const output = command.getFlagValue('output')
const log = command.getFlagValue('logFile')
const status = command.getFlagValue('status')
const returnStatusOnly = command.getFlagValue('returnStatusOnly')
const ignoreWarnings = command.getFlagValue('ignoreWarnings')

const target = await getBuildTarget(targetName)

Expand Down Expand Up @@ -61,7 +63,9 @@ export async function processJob(commandLine) {
waitForJob,
output,
log,
status
status,
returnStatusOnly,
ignoreWarnings
)

break
Expand Down
8 changes: 6 additions & 2 deletions src/utils/command.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ const initialFlags = arrToObj([
'output',
'force',
'logFile',
'status'
'status',
'returnStatusOnly',
'ignoreWarnings'
])
])

Expand Down Expand Up @@ -105,7 +107,9 @@ const commandFlags = [
initialFlags.wait,
initialFlags.output,
initialFlags.logFile,
initialFlags.status
initialFlags.status,
initialFlags.returnStatusOnly,
initialFlags.ignoreWarnings
]
}
]
Expand Down
2 changes: 1 addition & 1 deletion test/commands/cbd/testJob/failingJob.sas
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ data;
do x=1 to 1e6;
output;
end;
run;
run;
4 changes: 4 additions & 0 deletions test/commands/cbd/testJob/jobWithWarning.sas
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
data yury; was='here';
data new; was='toolong';
proc append base=yury data=new force;
run;
50 changes: 50 additions & 0 deletions test/commands/job.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,56 @@ describe('sasjs job', () => {
},
60 * 1000
)

it(
`should submit a job that completes and return it's status`,
async () => {
const command = `job execute testJob/job -t ${targetName} --wait --returnStatusOnly`

await expect(processJob(command)).resolves.toEqual(0)
},
60 * 1000
)

it(
`should submit a job that completes with a warning and return it's status`,
async () => {
const command = `job execute testJob/jobWithWarning -t ${targetName} --returnStatusOnly`

await expect(processJob(command)).resolves.toEqual(1)
},
60 * 1000
)

it(
`should submit a job that completes with ignored warning and return it's status`,
async () => {
const command = `job execute testJob/jobWithWarning -t ${targetName} --returnStatusOnly --ignoreWarnings`

await expect(processJob(command)).resolves.toEqual(0)
},
60 * 1000
)

it(
`should submit a job that fails and return it's status`,
async () => {
const command = `job execute testJob/failingJob -t ${targetName} --returnStatusOnly`

await expect(processJob(command)).resolves.toEqual(2)
},
60 * 1000
)

it(
`should submit a job that does not exist and return it's status`,
async () => {
const command = `job execute testJob/failingJob_DOES_NOT_EXIST -t ${targetName} --returnStatusOnly`

await expect(processJob(command)).resolves.toEqual(2)
},
60 * 1000
)
})

afterAll(async () => {
Expand Down

0 comments on commit 082d65d

Please sign in to comment.