2023-06-05 16:52:43 +08:00
|
|
|
import { Registry } from '@nocobase/utils';
|
2023-12-07 21:46:58 +08:00
|
|
|
import WorkflowPlugin, { Processor, JOB_STATUS, Instruction } from '@nocobase/plugin-workflow';
|
2023-02-20 11:52:06 +08:00
|
|
|
|
2023-06-05 16:52:43 +08:00
|
|
|
import initFormTypes, { FormHandler } from './forms';
|
2023-02-20 11:52:06 +08:00
|
|
|
|
2023-04-19 09:41:18 +08:00
|
|
|
type FormType = {
|
|
|
|
type: 'custom' | 'create' | 'update';
|
|
|
|
actions: number[];
|
|
|
|
options: {
|
|
|
|
[key: string]: any;
|
2023-04-25 13:12:14 +08:00
|
|
|
};
|
2023-04-19 09:41:18 +08:00
|
|
|
};
|
2023-02-20 11:52:06 +08:00
|
|
|
|
|
|
|
export interface ManualConfig {
|
2023-04-19 09:41:18 +08:00
|
|
|
schema: { [key: string]: any };
|
|
|
|
forms: { [key: string]: FormType };
|
|
|
|
assignees?: (number | string)[];
|
2023-02-20 11:52:06 +08:00
|
|
|
mode?: number;
|
|
|
|
}
|
|
|
|
|
|
|
|
const MULTIPLE_ASSIGNED_MODE = {
|
|
|
|
SINGLE: Symbol('single'),
|
|
|
|
ALL: Symbol('all'),
|
|
|
|
ANY: Symbol('any'),
|
|
|
|
ALL_PERCENTAGE: Symbol('all percentage'),
|
2023-04-25 13:12:14 +08:00
|
|
|
ANY_PERCENTAGE: Symbol('any percentage'),
|
2023-02-20 11:52:06 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
const Modes = {
|
|
|
|
[MULTIPLE_ASSIGNED_MODE.SINGLE]: {
|
|
|
|
getStatus(distribution, assignees) {
|
2023-04-25 13:12:14 +08:00
|
|
|
const done = distribution.find((item) => item.status !== JOB_STATUS.PENDING && item.count > 0);
|
|
|
|
return done ? done.status : null;
|
|
|
|
},
|
2023-02-20 11:52:06 +08:00
|
|
|
},
|
|
|
|
[MULTIPLE_ASSIGNED_MODE.ALL]: {
|
|
|
|
getStatus(distribution, assignees) {
|
2023-04-25 13:12:14 +08:00
|
|
|
const resolved = distribution.find((item) => item.status === JOB_STATUS.RESOLVED);
|
2023-02-20 11:52:06 +08:00
|
|
|
if (resolved && resolved.count === assignees.length) {
|
|
|
|
return JOB_STATUS.RESOLVED;
|
|
|
|
}
|
2023-04-25 13:12:14 +08:00
|
|
|
const rejected = distribution.find((item) => item.status < JOB_STATUS.PENDING);
|
2023-02-20 11:52:06 +08:00
|
|
|
if (rejected && rejected.count) {
|
|
|
|
return rejected.status;
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
2023-04-25 13:12:14 +08:00
|
|
|
},
|
2023-02-20 11:52:06 +08:00
|
|
|
},
|
|
|
|
[MULTIPLE_ASSIGNED_MODE.ANY]: {
|
|
|
|
getStatus(distribution, assignees) {
|
2023-04-25 13:12:14 +08:00
|
|
|
const resolved = distribution.find((item) => item.status === JOB_STATUS.RESOLVED);
|
2023-02-20 11:52:06 +08:00
|
|
|
if (resolved && resolved.count) {
|
|
|
|
return JOB_STATUS.RESOLVED;
|
|
|
|
}
|
2023-04-25 13:12:14 +08:00
|
|
|
const rejectedCount = distribution.reduce(
|
|
|
|
(count, item) => (item.status < JOB_STATUS.PENDING ? count + item.count : count),
|
|
|
|
0,
|
|
|
|
);
|
2023-02-20 11:52:06 +08:00
|
|
|
// NOTE: all failures are considered as rejected for now
|
|
|
|
if (rejectedCount === assignees.length) {
|
|
|
|
return JOB_STATUS.REJECTED;
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
2023-04-25 13:12:14 +08:00
|
|
|
},
|
|
|
|
},
|
2023-02-20 11:52:06 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
function getMode(mode) {
|
|
|
|
switch (true) {
|
|
|
|
case mode === 1:
|
|
|
|
return Modes[MULTIPLE_ASSIGNED_MODE.ALL];
|
|
|
|
case mode === -1:
|
|
|
|
return Modes[MULTIPLE_ASSIGNED_MODE.ANY];
|
|
|
|
case mode > 0:
|
|
|
|
return Modes[MULTIPLE_ASSIGNED_MODE.ALL_PERCENTAGE];
|
|
|
|
case mode < 0:
|
|
|
|
return Modes[MULTIPLE_ASSIGNED_MODE.ANY_PERCENTAGE];
|
|
|
|
default:
|
|
|
|
return Modes[MULTIPLE_ASSIGNED_MODE.SINGLE];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-07 21:46:58 +08:00
|
|
|
export default class extends Instruction {
|
2023-06-05 16:52:43 +08:00
|
|
|
formTypes = new Registry<FormHandler>();
|
|
|
|
|
2023-12-07 21:46:58 +08:00
|
|
|
constructor(public plugin: WorkflowPlugin) {
|
|
|
|
super(plugin);
|
2023-06-05 16:52:43 +08:00
|
|
|
|
|
|
|
initFormTypes(this);
|
2023-02-20 11:52:06 +08:00
|
|
|
}
|
|
|
|
|
2023-08-22 20:13:52 +08:00
|
|
|
async run(node, prevJob, processor: Processor) {
|
2023-02-20 11:52:06 +08:00
|
|
|
const { mode, ...config } = node.config as ManualConfig;
|
2023-08-22 20:13:52 +08:00
|
|
|
const assignees = [...new Set(processor.getParsedValue(config.assignees, node.id) || [])];
|
2023-02-20 11:52:06 +08:00
|
|
|
|
|
|
|
const job = await processor.saveJob({
|
|
|
|
status: JOB_STATUS.PENDING,
|
|
|
|
result: mode ? [] : null,
|
|
|
|
nodeId: node.id,
|
2023-04-25 13:12:14 +08:00
|
|
|
upstreamId: prevJob?.id ?? null,
|
2023-02-20 11:52:06 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
// NOTE: batch create users jobs
|
|
|
|
const UserJobModel = processor.options.plugin.db.getModel('users_jobs');
|
2023-04-25 13:12:14 +08:00
|
|
|
await UserJobModel.bulkCreate(
|
|
|
|
assignees.map((userId) => ({
|
|
|
|
userId,
|
|
|
|
jobId: job.id,
|
|
|
|
nodeId: node.id,
|
|
|
|
executionId: job.executionId,
|
|
|
|
workflowId: node.workflowId,
|
|
|
|
status: JOB_STATUS.PENDING,
|
|
|
|
})),
|
|
|
|
{
|
|
|
|
transaction: processor.transaction,
|
|
|
|
},
|
|
|
|
);
|
2023-02-20 11:52:06 +08:00
|
|
|
|
|
|
|
return job;
|
|
|
|
}
|
|
|
|
|
2023-08-22 20:13:52 +08:00
|
|
|
async resume(node, job, processor: Processor) {
|
2023-02-20 11:52:06 +08:00
|
|
|
// NOTE: check all users jobs related if all done then continue as parallel
|
|
|
|
const { assignees = [], mode } = node.config as ManualConfig;
|
|
|
|
|
|
|
|
const UserJobModel = processor.options.plugin.db.getModel('users_jobs');
|
|
|
|
const distribution = await UserJobModel.count({
|
|
|
|
where: {
|
2023-04-25 13:12:14 +08:00
|
|
|
jobId: job.id,
|
2023-02-20 11:52:06 +08:00
|
|
|
},
|
2023-04-25 13:12:14 +08:00
|
|
|
group: ['status'],
|
2023-06-05 16:52:43 +08:00
|
|
|
transaction: processor.transaction,
|
2023-02-20 11:52:06 +08:00
|
|
|
});
|
|
|
|
|
2023-04-25 13:12:14 +08:00
|
|
|
const submitted = distribution.reduce(
|
|
|
|
(count, item) => (item.status !== JOB_STATUS.PENDING ? count + item.count : count),
|
|
|
|
0,
|
|
|
|
);
|
2023-06-05 16:52:43 +08:00
|
|
|
const status = job.status || (getMode(mode).getStatus(distribution, assignees) ?? JOB_STATUS.PENDING);
|
2023-02-20 11:52:06 +08:00
|
|
|
const result = mode ? (submitted || 0) / assignees.length : job.latestUserJob?.result ?? job.result;
|
2023-06-05 16:52:43 +08:00
|
|
|
processor.logger.debug(`manual resume job and next status: ${status}`);
|
2023-02-20 11:52:06 +08:00
|
|
|
job.set({
|
2023-06-05 16:52:43 +08:00
|
|
|
status,
|
2023-04-25 13:12:14 +08:00
|
|
|
result,
|
2023-02-20 11:52:06 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
return job;
|
|
|
|
}
|
2023-04-25 13:12:14 +08:00
|
|
|
}
|