|
@@ -27,49 +27,49 @@ export type FlowArgs = { api: Api; env: NodeJS.ProcessEnv; query: QueryNodeApi }
|
|
|
export type Flow = (args: FlowArgs) => Promise<void>
|
|
|
|
|
|
enum JobOutcome {
|
|
|
- Succeeded,
|
|
|
- Failed,
|
|
|
- Skipped,
|
|
|
+ Succeeded = 'Succeeded',
|
|
|
+ Failed = 'Failed',
|
|
|
+ Skipped = 'Skipped',
|
|
|
}
|
|
|
|
|
|
class Job {
|
|
|
- private required: Job[] = []
|
|
|
- private dependencies: Job[] = []
|
|
|
- private flows: Flow[]
|
|
|
- private manager: FlowManager
|
|
|
- private _outcome: InvertedPromise<JobOutcome>
|
|
|
- private locked = false
|
|
|
- private name: string
|
|
|
- private debug: Debugger.Debugger
|
|
|
-
|
|
|
- constructor(manager: FlowManager, flows: Flow[], name: string) {
|
|
|
- this.name = name
|
|
|
- this.manager = manager
|
|
|
- this.flows = flows
|
|
|
+ private _required: Job[] = []
|
|
|
+ private _after: Job[] = []
|
|
|
+ private _locked = false
|
|
|
+ private readonly _flows: Flow[]
|
|
|
+ private readonly _manager: FlowManager
|
|
|
+ private readonly _outcome: InvertedPromise<JobOutcome>
|
|
|
+ private readonly _label: string
|
|
|
+ private readonly debug: Debugger.Debugger
|
|
|
+
|
|
|
+ constructor(manager: FlowManager, flows: Flow[], label: string) {
|
|
|
+ this._label = label
|
|
|
+ this._manager = manager
|
|
|
+ this._flows = flows
|
|
|
this._outcome = new InvertedPromise<JobOutcome>()
|
|
|
- this.manager.on('run', this.run.bind(this))
|
|
|
- this.debug = Debugger(`job:${this.name}`)
|
|
|
+ this._manager.on('run', this.run.bind(this))
|
|
|
+ this.debug = Debugger(`job:${this._label}`)
|
|
|
}
|
|
|
|
|
|
// Depend on another job to complete successfully
|
|
|
public requires(job: Job): Job {
|
|
|
- if (this.locked) throw new Error('Job is locked')
|
|
|
+ if (this._locked) throw new Error('Job is locked')
|
|
|
if (job === this) throw new Error('Job Cannot depend on itself')
|
|
|
if (job.hasDependencyOn(this)) {
|
|
|
throw new Error('Job Circualr dependency')
|
|
|
}
|
|
|
- this.required.push(job)
|
|
|
+ this._required.push(job)
|
|
|
return this
|
|
|
}
|
|
|
|
|
|
// Depend on another job to complete (does not matter if it is successful)
|
|
|
public after(job: Job): Job {
|
|
|
- if (this.locked) throw new Error('Job is locked')
|
|
|
+ if (this._locked) throw new Error('Job is locked')
|
|
|
if (job === this) throw new Error('Job Cannot depend on itself')
|
|
|
if (job.hasDependencyOn(this)) {
|
|
|
throw new Error('Job Circualr dependency')
|
|
|
}
|
|
|
- this.dependencies.push(job)
|
|
|
+ this._after.push(job)
|
|
|
return this
|
|
|
}
|
|
|
|
|
@@ -79,7 +79,7 @@ class Job {
|
|
|
}
|
|
|
|
|
|
public hasDependencyOn(job: Job): boolean {
|
|
|
- return !!this.required.find((j) => j === job) || !!this.dependencies.find((j) => j === job)
|
|
|
+ return !!this._required.find((j) => j === job) || !!this._after.find((j) => j === job)
|
|
|
}
|
|
|
|
|
|
// configure to have flows run serially instead of in parallel
|
|
@@ -91,29 +91,39 @@ class Job {
|
|
|
return this._outcome.promise
|
|
|
}
|
|
|
|
|
|
+ get label(): string {
|
|
|
+ return this._label
|
|
|
+ }
|
|
|
+
|
|
|
private async run(flowArgs: FlowArgs): Promise<void> {
|
|
|
// prevent any additional changes to configuration
|
|
|
- this.locked = true
|
|
|
+ this._locked = true
|
|
|
|
|
|
// wait for all required dependencies to complete successfully
|
|
|
- const requiredOutcomes = await Promise.all(this.required.map((job) => job.outcome))
|
|
|
+ const requiredOutcomes = await Promise.all(this._required.map((job) => job.outcome))
|
|
|
if (requiredOutcomes.find((outcome) => outcome !== JobOutcome.Succeeded)) {
|
|
|
- this.debug('Skipped because required jobs not successful')
|
|
|
+ this.debug('[Skipping] - Required jobs not successful!')
|
|
|
return this._outcome.resolve(JobOutcome.Skipped)
|
|
|
}
|
|
|
|
|
|
// Wait for other jobs to complete, irrespective of outcome
|
|
|
- await Promise.all(this.dependencies.map((job) => job.outcome))
|
|
|
+ await Promise.all(this._after.map((job) => job.outcome))
|
|
|
+
|
|
|
+ this.debug('Running')
|
|
|
+ const flowRunResults = await Promise.allSettled(this._flows.map((flow) => flow(flowArgs)))
|
|
|
|
|
|
- this.debug('Running flows')
|
|
|
- const flowRunResults = await Promise.allSettled(this.flows.map((flow) => flow(flowArgs)))
|
|
|
- this.debug('Flow run complete')
|
|
|
+ flowRunResults.forEach((result, ix) => {
|
|
|
+ if (result.status === 'rejected') {
|
|
|
+ this.debug(`flow ${ix} failed:`)
|
|
|
+ console.error(result.reason)
|
|
|
+ }
|
|
|
+ })
|
|
|
|
|
|
if (flowRunResults.find((result) => result.status === 'rejected')) {
|
|
|
- this.debug('Failed')
|
|
|
+ this.debug('[Failed]')
|
|
|
this._outcome.resolve(JobOutcome.Failed)
|
|
|
} else {
|
|
|
- this.debug('Succeeded')
|
|
|
+ this.debug('[Succeeded]')
|
|
|
this._outcome.resolve(JobOutcome.Succeeded)
|
|
|
}
|
|
|
}
|
|
@@ -140,11 +150,18 @@ class FlowManager extends EventEmitter {
|
|
|
public async run(): Promise<void> {
|
|
|
this.emit('run', this.flowArgs)
|
|
|
|
|
|
- const jobOutcomes = await Promise.all(this._jobs.map((job) => job.outcome))
|
|
|
+ const outcomes = await Promise.all(this._jobs.map((job) => job.outcome))
|
|
|
+
|
|
|
+ // summary of job results
|
|
|
+ console.error('Job Results:')
|
|
|
+ outcomes.forEach((outcome, i) => {
|
|
|
+ const { label } = this._jobs[i]
|
|
|
+ console.error(`${label}: ${outcome}`)
|
|
|
+ })
|
|
|
|
|
|
- const someJobDidNotSucceed = jobOutcomes.find((outcome) => outcome !== JobOutcome.Succeeded)
|
|
|
- if (someJobDidNotSucceed) {
|
|
|
- throw new Error('Some jobs failed or skipped')
|
|
|
+ const failed = outcomes.find((outcome) => outcome !== JobOutcome.Succeeded)
|
|
|
+ if (failed) {
|
|
|
+ throw new Error('Scenario Failed')
|
|
|
}
|
|
|
}
|
|
|
}
|