1 import { FindOptions, Op, Transaction } from 'sequelize'
15 } from 'sequelize-typescript'
16 import { isUUIDValid } from '@server/helpers/custom-validators/misc'
17 import { CONSTRAINTS_FIELDS, RUNNER_JOB_STATES } from '@server/initializers/constants'
18 import { MRunnerJob, MRunnerJobRunner, MRunnerJobRunnerParent } from '@server/types/models/runners'
19 import { RunnerJob, RunnerJobAdmin, RunnerJobPayload, RunnerJobPrivatePayload, RunnerJobState, RunnerJobType } from '@shared/models'
20 import { AttributesOnly } from '@shared/typescript-utils'
21 import { getSort, searchAttribute } from '../shared'
22 import { RunnerModel } from './runner'
25 WITH_RUNNER = 'WITH_RUNNER',
26 WITH_PARENT = 'WITH_PARENT'
30 [ScopeNames.WITH_RUNNER]: {
33 model: RunnerModel.unscoped(),
38 [ScopeNames.WITH_PARENT]: {
41 model: RunnerJobModel.unscoped(),
48 tableName: 'runnerJob',
55 fields: [ 'processingJobToken' ],
59 fields: [ 'runnerId' ]
63 export class RunnerJobModel extends Model<Partial<AttributesOnly<RunnerJobModel>>> {
67 @Column(DataType.UUID)
75 @Column(DataType.JSONB)
76 payload: RunnerJobPayload
79 @Column(DataType.JSONB)
80 privatePayload: RunnerJobPrivatePayload
92 @Column(DataType.STRING(CONSTRAINTS_FIELDS.RUNNER_JOBS.ERROR_MESSAGE.max))
100 // Used to fetch the appropriate job when the runner wants to post the result
103 processingJobToken: string
123 @ForeignKey(() => RunnerJobModel)
125 dependsOnRunnerJobId: number
127 @BelongsTo(() => RunnerJobModel, {
129 name: 'dependsOnRunnerJobId',
134 DependsOnRunnerJob: RunnerJobModel
136 @ForeignKey(() => RunnerModel)
140 @BelongsTo(() => RunnerModel, {
149 // ---------------------------------------------------------------------------
151 static loadWithRunner (uuid: string) {
156 return RunnerJobModel.scope(ScopeNames.WITH_RUNNER).findOne<MRunnerJobRunner>(query)
159 static loadByRunnerAndJobTokensWithRunner (options: {
164 const { uuid, runnerToken, jobToken } = options
169 processingJobToken: jobToken
172 model: RunnerModel.unscoped(),
180 return RunnerJobModel.findOne<MRunnerJobRunner>(query)
183 static listAvailableJobs () {
186 order: getSort('priority'),
188 state: RunnerJobState.PENDING
192 return RunnerJobModel.findAll<MRunnerJob>(query)
195 static listStalledJobs (options: {
197 types: RunnerJobType[]
199 const before = new Date(Date.now() - options.staleTimeMS)
201 return RunnerJobModel.findAll<MRunnerJob>({
204 [Op.in]: options.types
206 state: RunnerJobState.PROCESSING,
214 static listChildrenOf (job: MRunnerJob, transaction?: Transaction) {
217 dependsOnRunnerJobId: job.id
222 return RunnerJobModel.findAll<MRunnerJob>(query)
225 static listForApi (options: {
231 const { start, count, sort, search } = options
233 const query: FindOptions = {
240 if (isUUIDValid(search)) {
241 query.where = { uuid: search }
245 searchAttribute(search, 'type'),
246 searchAttribute(search, '$Runner.name$')
253 RunnerJobModel.scope([ ScopeNames.WITH_RUNNER ]).count(query),
254 RunnerJobModel.scope([ ScopeNames.WITH_RUNNER, ScopeNames.WITH_PARENT ]).findAll<MRunnerJobRunnerParent>(query)
255 ]).then(([ total, data ]) => ({ total, data }))
258 static updateDependantJobsOf (runnerJob: MRunnerJob) {
260 dependsOnRunnerJobId: runnerJob.id
263 return RunnerJobModel.update({ state: RunnerJobState.PENDING }, { where })
266 static cancelAllJobs (options: { type: RunnerJobType }) {
271 return RunnerJobModel.update({ state: RunnerJobState.CANCELLED }, { where })
274 // ---------------------------------------------------------------------------
277 this.state = RunnerJobState.PENDING
278 this.processingJobToken = null
280 this.startedAt = null
285 state: RunnerJobState.PARENT_ERRORED | RunnerJobState.ERRORED | RunnerJobState.CANCELLED | RunnerJobState.PARENT_CANCELLED
288 this.processingJobToken = null
289 this.finishedAt = new Date()
292 toFormattedJSON (this: MRunnerJobRunnerParent): RunnerJob {
293 const runner = this.Runner
296 name: this.Runner.name,
297 description: this.Runner.description
301 const parent = this.DependsOnRunnerJob
303 id: this.DependsOnRunnerJob.id,
304 uuid: this.DependsOnRunnerJob.uuid,
305 type: this.DependsOnRunnerJob.type,
307 id: this.DependsOnRunnerJob.state,
308 label: RUNNER_JOB_STATES[this.DependsOnRunnerJob.state]
319 label: RUNNER_JOB_STATES[this.state]
322 progress: this.progress,
323 priority: this.priority,
324 failures: this.failures,
327 payload: this.payload,
329 startedAt: this.startedAt?.toISOString(),
330 finishedAt: this.finishedAt?.toISOString(),
332 createdAt: this.createdAt.toISOString(),
333 updatedAt: this.updatedAt.toISOString(),
340 toFormattedAdminJSON (this: MRunnerJobRunnerParent): RunnerJobAdmin {
342 ...this.toFormattedJSON(),
344 privatePayload: this.privatePayload