Feat: plugin-workflow (#288)
* refactor(plugin-workflow): change variable type keys to align to backend * feat(plugin-workflow): add more configuration fields for model trigger * refactor(plugin-workflow): improve transaction and error handling * chore(plugin-workflow): add note for todos * fix(plugin-workflow): fix calculator onchange handlers * fix(plugin-workflow): fix transaction * refactor(plugin-workflow): change transaction to configurable and default to false in execution due to defective solution * fix(plugin-workflow): fix operand type to be consolidated and compatible with legacies
This commit is contained in:
parent
9ffe6418f9
commit
fda9c71d66
@ -75,25 +75,35 @@ const ConstantTypes = {
|
|||||||
string: {
|
string: {
|
||||||
title: '字符串',
|
title: '字符串',
|
||||||
value: 'string',
|
value: 'string',
|
||||||
component({ onChange, type, options, ...props }) {
|
component({ onChange, type, options, value }) {
|
||||||
return <Input {...props} onChange={ev => onChange(ev.target.value)} />;
|
return (
|
||||||
|
<Input
|
||||||
|
value={value}
|
||||||
|
onChange={ev => onChange({ value: ev.target.value, type, options })}
|
||||||
|
/>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
default: ''
|
default: ''
|
||||||
},
|
},
|
||||||
number: {
|
number: {
|
||||||
title: '数字',
|
title: '数字',
|
||||||
value: 'number',
|
value: 'number',
|
||||||
component({ type, options, ...props }) {
|
component({ onChange, type, options, value }) {
|
||||||
return <InputNumber {...props} />;
|
return (
|
||||||
|
<InputNumber
|
||||||
|
value={value}
|
||||||
|
onChange={v => onChange({ value: v, type, options })}
|
||||||
|
/>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
default: 0
|
default: 0
|
||||||
},
|
},
|
||||||
boolean: {
|
boolean: {
|
||||||
title: '逻辑值',
|
title: '逻辑值',
|
||||||
value: 'boolean',
|
value: 'boolean',
|
||||||
component({ type, options, ...props }) {
|
component({ onChange, type, options, value }) {
|
||||||
return (
|
return (
|
||||||
<Select {...props}>
|
<Select value={value} onChange={v => onChange({ value: v, type, options })}>
|
||||||
<Select.Option value={true}>真</Select.Option>
|
<Select.Option value={true}>真</Select.Option>
|
||||||
<Select.Option value={false}>假</Select.Option>
|
<Select.Option value={false}>假</Select.Option>
|
||||||
</Select>
|
</Select>
|
||||||
@ -104,8 +114,8 @@ const ConstantTypes = {
|
|||||||
// date: {
|
// date: {
|
||||||
// title: '日期',
|
// title: '日期',
|
||||||
// value: 'date',
|
// value: 'date',
|
||||||
// component({ type, options, ...props }) {
|
// component({ onChange, type, options, value }) {
|
||||||
// return <DatePicker {...props} />;
|
// return <DatePicker value={value} onChange={v => onChange({ value: v, type, options })}/>;
|
||||||
// },
|
// },
|
||||||
// default: new Date()
|
// default: new Date()
|
||||||
// }
|
// }
|
||||||
@ -119,13 +129,16 @@ export const VariableTypes = {
|
|||||||
value: item.value,
|
value: item.value,
|
||||||
label: item.title
|
label: item.title
|
||||||
})),
|
})),
|
||||||
component({ options: { type } = { type: 'string' } }) {
|
component({ options = { type: 'string' } }) {
|
||||||
return ConstantTypes[type]?.component ?? NullRender;
|
return ConstantTypes[options.type]?.component ?? NullRender;
|
||||||
},
|
},
|
||||||
appendTypeValue({ options = { type: 'string' } }) {
|
appendTypeValue({ options = { type: 'string' } }) {
|
||||||
return options?.type ? [options.type] : [];
|
return options?.type ? [options.type] : [];
|
||||||
},
|
},
|
||||||
onTypeChange(old, [type, optionsType], onChange) {
|
onTypeChange(old, [type, optionsType], onChange) {
|
||||||
|
if (old?.options?.type === optionsType) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const { default: value } = ConstantTypes[optionsType];
|
const { default: value } = ConstantTypes[optionsType];
|
||||||
onChange({
|
onChange({
|
||||||
value,
|
value,
|
||||||
@ -137,9 +150,9 @@ export const VariableTypes = {
|
|||||||
return { path };
|
return { path };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
job: {
|
$jobsMapByNodeId: {
|
||||||
title: '节点数据',
|
title: '节点数据',
|
||||||
value: 'job',
|
value: '$jobsMapByNodeId',
|
||||||
options() {
|
options() {
|
||||||
const node = useNodeContext();
|
const node = useNodeContext();
|
||||||
const stack = [];
|
const stack = [];
|
||||||
@ -182,7 +195,7 @@ export const VariableTypes = {
|
|||||||
return { nodeId, path: path.join('.') };
|
return { nodeId, path: path.join('.') };
|
||||||
},
|
},
|
||||||
stringify({ options }) {
|
stringify({ options }) {
|
||||||
const stack = ['job'];
|
const stack = ['$jobsMapByNodeId'];
|
||||||
if (options.nodeId) {
|
if (options.nodeId) {
|
||||||
stack.push(options.nodeId);
|
stack.push(options.nodeId);
|
||||||
if (options.path) {
|
if (options.path) {
|
||||||
@ -192,9 +205,9 @@ export const VariableTypes = {
|
|||||||
return `{{${stack.join('.')}}}`;
|
return `{{${stack.join('.')}}}`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
context: {
|
$context: {
|
||||||
title: '触发数据',
|
title: '触发数据',
|
||||||
value: 'context',
|
value: '$context',
|
||||||
component() {
|
component() {
|
||||||
const { workflow } = useFlowContext();
|
const { workflow } = useFlowContext();
|
||||||
const trigger = triggers.get(workflow.type);
|
const trigger = triggers.get(workflow.type);
|
||||||
@ -204,7 +217,7 @@ export const VariableTypes = {
|
|||||||
return { path: path.join('.') };
|
return { path: path.join('.') };
|
||||||
},
|
},
|
||||||
stringify({ options }) {
|
stringify({ options }) {
|
||||||
const stack = ['context'];
|
const stack = ['$context'];
|
||||||
if (options?.path) {
|
if (options?.path) {
|
||||||
stack.push(options.path);
|
stack.push(options.path);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import { useForm } from '@formily/react';
|
import { useForm } from '@formily/react';
|
||||||
import { action } from '@formily/reactive';
|
import { action } from '@formily/reactive';
|
||||||
import { Cascader, Select } from 'antd';
|
import { Select } from 'antd';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
|
|
||||||
|
@ -6,6 +6,8 @@ import { Select } from 'antd';
|
|||||||
import { useCollectionManager } from '../../collection-manager';
|
import { useCollectionManager } from '../../collection-manager';
|
||||||
import { useFlowContext } from '../WorkflowCanvas';
|
import { useFlowContext } from '../WorkflowCanvas';
|
||||||
import { BaseTypeSet } from '../calculators';
|
import { BaseTypeSet } from '../calculators';
|
||||||
|
import { useForm } from '@formily/react';
|
||||||
|
import { useCollectionFilterOptions } from '../../collection-manager/action-hooks';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: '数据表事件',
|
title: '数据表事件',
|
||||||
@ -20,11 +22,36 @@ export default {
|
|||||||
'x-decorator': 'FormItem',
|
'x-decorator': 'FormItem',
|
||||||
'x-component': 'Select',
|
'x-component': 'Select',
|
||||||
},
|
},
|
||||||
// mode: {
|
mode: {
|
||||||
// type: 'number',
|
type: 'number',
|
||||||
// title: '触发时机',
|
title: '触发时机',
|
||||||
// name: 'mode',
|
name: 'mode',
|
||||||
// }
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Select',
|
||||||
|
'x-component-props': {
|
||||||
|
options: [
|
||||||
|
{ value: 1, label: '新增数据' },
|
||||||
|
{ value: 2, label: '更新数据' },
|
||||||
|
{ value: 3, label: '新增或更新数据' },
|
||||||
|
{ value: 4, label: '删除数据' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
filter: {
|
||||||
|
type: 'object',
|
||||||
|
title: '满足条件',
|
||||||
|
description: 'not supported by server yet',
|
||||||
|
name: 'filter',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Filter',
|
||||||
|
'x-component-props': {
|
||||||
|
useProps() {
|
||||||
|
const { values } = useForm();
|
||||||
|
const options = useCollectionFilterOptions(values.collection);
|
||||||
|
return { options };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
scope: {
|
scope: {
|
||||||
useAsyncDataSource() {
|
useAsyncDataSource() {
|
||||||
@ -45,7 +72,7 @@ export default {
|
|||||||
const collection = collections.find(item => item.name === workflow.config.collection) ?? { fields: [] };
|
const collection = collections.find(item => item.name === workflow.config.collection) ?? { fields: [] };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select value={options.path} placeholder="选择字段" onChange={path => {
|
<Select value={options?.path.replace(/^data\./, '')} placeholder="选择字段" onChange={path => {
|
||||||
onChange({ type, options: { ...options, path: `data.${path}` } });
|
onChange({ type, options: { ...options, path: `data.${path}` } });
|
||||||
}}>
|
}}>
|
||||||
{collection.fields
|
{collection.fields
|
||||||
|
@ -119,7 +119,7 @@ describe('execution', () => {
|
|||||||
expect(jobs.length).toEqual(1);
|
expect(jobs.length).toEqual(1);
|
||||||
const { status, result } = jobs[0].get();
|
const { status, result } = jobs[0].get();
|
||||||
expect(status).toEqual(JOB_STATUS.REJECTED);
|
expect(status).toEqual(JOB_STATUS.REJECTED);
|
||||||
expect(result).toBe('Error: definite error');
|
expect(result.message).toBe('definite error');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -185,7 +185,7 @@ describe('execution', () => {
|
|||||||
const jobs = await execution.getJobs();
|
const jobs = await execution.getJobs();
|
||||||
expect(jobs.length).toEqual(1);
|
expect(jobs.length).toEqual(1);
|
||||||
expect(jobs[0].status).toEqual(JOB_STATUS.REJECTED);
|
expect(jobs[0].status).toEqual(JOB_STATUS.REJECTED);
|
||||||
expect(jobs[0].result).toEqual('Error: input failed');
|
expect(jobs[0].result.message).toEqual('input failed');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ describe('workflow > instructions > calculation', () => {
|
|||||||
calculator: 'add',
|
calculator: 'add',
|
||||||
operands: [
|
operands: [
|
||||||
{ value: 1 },
|
{ value: 1 },
|
||||||
{ type: 'context', options: { path: 'data.read' } }
|
{ type: '$context', options: { path: 'data.read' } }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,7 +104,7 @@ describe('workflow > instructions > calculation', () => {
|
|||||||
calculator: 'add',
|
calculator: 'add',
|
||||||
operands: [
|
operands: [
|
||||||
{ value: 1 },
|
{ value: 1 },
|
||||||
{ type: 'job', options: { nodeId: n1.id, path: 'data.read' } }
|
{ type: '$jobsMapByNodeId', options: { nodeId: n1.id, path: 'data.read' } }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -177,7 +177,7 @@ describe('workflow > instructions > calculation', () => {
|
|||||||
operands: [
|
operands: [
|
||||||
{ value: 1 },
|
{ value: 1 },
|
||||||
{
|
{
|
||||||
type: 'calculation',
|
type: '$calculation',
|
||||||
options: {
|
options: {
|
||||||
calculator: 'minus',
|
calculator: 'minus',
|
||||||
operands: [
|
operands: [
|
||||||
|
@ -9,7 +9,7 @@ export const calculators = new Registry<Function>();
|
|||||||
export default calculators;
|
export default calculators;
|
||||||
|
|
||||||
|
|
||||||
export type OperandType = 'context' | 'input' | 'job' | 'calculation';
|
export type OperandType = '$context' | '$input' | '$jobsMapByNodeId' | '$calculation';
|
||||||
|
|
||||||
export type ObjectGetterOptions = {
|
export type ObjectGetterOptions = {
|
||||||
path?: string
|
path?: string
|
||||||
@ -30,22 +30,22 @@ export type ConstantOperand = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type ContextOperand = {
|
export type ContextOperand = {
|
||||||
type: 'context';
|
type: '$context';
|
||||||
options: ObjectGetterOptions;
|
options: ObjectGetterOptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type InputOperand = {
|
export type InputOperand = {
|
||||||
type: 'input';
|
type: '$input';
|
||||||
options: ObjectGetterOptions;
|
options: ObjectGetterOptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type JobOperand = {
|
export type JobOperand = {
|
||||||
type: 'job';
|
type: '$jobsMapByNodeId';
|
||||||
options: JobGetterOptions;
|
options: JobGetterOptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Calculation = {
|
export type Calculation = {
|
||||||
type: 'calculation';
|
type: '$calculation';
|
||||||
options: CalculationOptions
|
options: CalculationOptions
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -68,22 +68,22 @@ export function calculate(operand: Operand, lastJob: JobModel, execution: Execut
|
|||||||
switch (operand.type) {
|
switch (operand.type) {
|
||||||
// @Deprecated
|
// @Deprecated
|
||||||
// from execution context
|
// from execution context
|
||||||
case 'context':
|
case '$context':
|
||||||
return get(execution.context, operand.options.path);
|
return get(execution.context, operand.options.path);
|
||||||
|
|
||||||
// @Deprecated
|
// @Deprecated
|
||||||
// from last job (or input job)
|
// from last job (or input job)
|
||||||
case 'input':
|
case '$input':
|
||||||
return lastJob ?? get(lastJob.result, operand.options.path);
|
return lastJob ?? get(lastJob.result, operand.options.path);
|
||||||
|
|
||||||
// @Deprecated
|
// @Deprecated
|
||||||
// from job in execution
|
// from job in execution
|
||||||
case 'job':
|
case '$jobsMapByNodeId':
|
||||||
// assume jobs have been fetched from execution before
|
// assume jobs have been fetched from execution before
|
||||||
const job = execution.jobsMapByNodeId[operand.options.nodeId];
|
const job = execution.jobsMapByNodeId[operand.options.nodeId];
|
||||||
return job && get(job, operand.options.path);
|
return job && get(job, operand.options.path);
|
||||||
|
|
||||||
case 'calculation':
|
case '$calculation':
|
||||||
const fn = calculators.get(operand.options.calculator);
|
const fn = calculators.get(operand.options.calculator);
|
||||||
if (!fn) {
|
if (!fn) {
|
||||||
throw new Error(`no calculator function registered for "${operand.options.calculator}"`);
|
throw new Error(`no calculator function registered for "${operand.options.calculator}"`);
|
||||||
|
@ -11,6 +11,12 @@ export default {
|
|||||||
name: 'workflow',
|
name: 'workflow',
|
||||||
title: '所属工作流'
|
title: '所属工作流'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'boolean',
|
||||||
|
name: 'useTransaction',
|
||||||
|
title: '使用事务',
|
||||||
|
defaultValue: false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
interface: 'linkTo',
|
interface: 'linkTo',
|
||||||
type: 'hasMany',
|
type: 'hasMany',
|
||||||
|
@ -39,6 +39,13 @@ export default {
|
|||||||
required: true,
|
required: true,
|
||||||
defaultValue: {}
|
defaultValue: {}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
interface: 'boolean',
|
||||||
|
type: 'boolean',
|
||||||
|
title: '使用事务',
|
||||||
|
name: 'useTransaction',
|
||||||
|
defaultValue: false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
interface: 'linkTo',
|
interface: 'linkTo',
|
||||||
type: 'hasMany',
|
type: 'hasMany',
|
||||||
|
@ -35,7 +35,7 @@ export default {
|
|||||||
|
|
||||||
const result = calculation
|
const result = calculation
|
||||||
? calculate({
|
? calculate({
|
||||||
type: 'calculation',
|
type: '$calculation',
|
||||||
options: execution.getParsedValue(calculation)
|
options: execution.getParsedValue(calculation)
|
||||||
}, prevJob, execution)
|
}, prevJob, execution)
|
||||||
: null;
|
: null;
|
||||||
|
@ -9,8 +9,9 @@ export default {
|
|||||||
} = this.config;
|
} = this.config;
|
||||||
|
|
||||||
const repo = (<typeof FlowNodeModel>this.constructor).database.getRepository(collection);
|
const repo = (<typeof FlowNodeModel>this.constructor).database.getRepository(collection);
|
||||||
|
const options = execution.getParsedValue(params);
|
||||||
const result = await repo.create({
|
const result = await repo.create({
|
||||||
...execution.getParsedValue(params),
|
...options,
|
||||||
transaction: execution.transaction
|
transaction: execution.transaction
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -9,8 +9,9 @@ export default {
|
|||||||
} = this.config;
|
} = this.config;
|
||||||
|
|
||||||
const repo = (<typeof FlowNodeModel>this.constructor).database.getRepository(collection);
|
const repo = (<typeof FlowNodeModel>this.constructor).database.getRepository(collection);
|
||||||
|
const options = execution.getParsedValue(params);
|
||||||
const result = await repo.destroy({
|
const result = await repo.destroy({
|
||||||
...execution.getParsedValue(params),
|
...options,
|
||||||
transaction: execution.transaction
|
transaction: execution.transaction
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -10,8 +10,9 @@ export default {
|
|||||||
} = this.config;
|
} = this.config;
|
||||||
|
|
||||||
const repo = (<typeof FlowNodeModel>this.constructor).database.getRepository(collection);
|
const repo = (<typeof FlowNodeModel>this.constructor).database.getRepository(collection);
|
||||||
|
const options = execution.getParsedValue(params);
|
||||||
const result = await repo.update({
|
const result = await repo.update({
|
||||||
...execution.getParsedValue(params),
|
...options,
|
||||||
transaction: execution.transaction
|
transaction: execution.transaction
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -19,6 +19,8 @@ export default class ExecutionModel extends Model {
|
|||||||
declare title: string;
|
declare title: string;
|
||||||
declare context: any;
|
declare context: any;
|
||||||
declare status: number;
|
declare status: number;
|
||||||
|
// NOTE: this duplicated column is for transaction in preparing cycle from workflow
|
||||||
|
declare useTransaction: boolean;
|
||||||
|
|
||||||
declare createdAt: Date;
|
declare createdAt: Date;
|
||||||
declare updatedAt: Date;
|
declare updatedAt: Date;
|
||||||
@ -71,10 +73,25 @@ export default class ExecutionModel extends Model {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTransaction() {
|
||||||
|
const { sequelize } = (<typeof WorkflowModel>this.constructor).database;
|
||||||
|
// @ts-ignore
|
||||||
|
if (!this.useTransaction || sequelize.options.dialect === 'sqlite') {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { options } = this;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
return options.transaction && !options.transaction.finished
|
||||||
|
? options.transaction
|
||||||
|
: sequelize.transaction();
|
||||||
|
}
|
||||||
|
|
||||||
async prepare(options, commit = false) {
|
async prepare(options, commit = false) {
|
||||||
this.options = options || {};
|
this.options = options || {};
|
||||||
const { transaction = await (<typeof ExecutionModel>this.constructor).database.sequelize.transaction() } =
|
// @ts-ignore
|
||||||
this.options;
|
const transaction = await this.getTransaction()
|
||||||
this.transaction = transaction;
|
this.transaction = transaction;
|
||||||
|
|
||||||
if (!this.workflow) {
|
if (!this.workflow) {
|
||||||
@ -122,7 +139,8 @@ export default class ExecutionModel extends Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async commit() {
|
private async commit() {
|
||||||
if (!this.options || !this.options.transaction) {
|
// @ts-ignore
|
||||||
|
if (this.transaction && (!this.options.transaction || this.options.transaction.finished)) {
|
||||||
await this.transaction.commit();
|
await this.transaction.commit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -138,7 +156,9 @@ export default class ExecutionModel extends Model {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
// for uncaught error, set to rejected
|
// for uncaught error, set to rejected
|
||||||
job = {
|
job = {
|
||||||
result: err instanceof Error ? err.toString() : err,
|
result: err instanceof Error
|
||||||
|
? { message: err.message, stack: process.env.NODE_ENV === 'production' ? [] : err.stack }
|
||||||
|
: err,
|
||||||
status: JOB_STATUS.REJECTED,
|
status: JOB_STATUS.REJECTED,
|
||||||
};
|
};
|
||||||
// if previous job is from resuming
|
// if previous job is from resuming
|
||||||
@ -148,7 +168,7 @@ export default class ExecutionModel extends Model {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let savedJob: JobModel;
|
let savedJob;
|
||||||
// TODO(optimize): many checking of resuming or new could be improved
|
// TODO(optimize): many checking of resuming or new could be improved
|
||||||
// could be implemented separately in exec() / resume()
|
// could be implemented separately in exec() / resume()
|
||||||
if (job instanceof Model) {
|
if (job instanceof Model) {
|
||||||
@ -162,7 +182,7 @@ export default class ExecutionModel extends Model {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (savedJob.get('status') === JOB_STATUS.RESOLVED && node.downstream) {
|
if (savedJob.status === JOB_STATUS.RESOLVED && node.downstream) {
|
||||||
// run next node
|
// run next node
|
||||||
return this.run(node.downstream, savedJob);
|
return this.run(node.downstream, savedJob);
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ export default class WorkflowModel extends Model {
|
|||||||
declare description?: string;
|
declare description?: string;
|
||||||
declare type: string;
|
declare type: string;
|
||||||
declare config: any;
|
declare config: any;
|
||||||
|
declare useTransaction: boolean;
|
||||||
|
|
||||||
declare createdAt: Date;
|
declare createdAt: Date;
|
||||||
declare updatedAt: Date;
|
declare updatedAt: Date;
|
||||||
@ -46,30 +47,48 @@ export default class WorkflowModel extends Model {
|
|||||||
return `workflow-${this.get('id')}`;
|
return `workflow-${this.get('id')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTransaction(options) {
|
||||||
|
if (!this.useTransaction) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return options.transaction && !options.transaction.finished
|
||||||
|
? options.transaction
|
||||||
|
: (<typeof WorkflowModel>this.constructor).database.sequelize.transaction();
|
||||||
|
}
|
||||||
|
|
||||||
async toggle(enable?: boolean) {
|
async toggle(enable?: boolean) {
|
||||||
const type = this.get('type');
|
const type = this.get('type');
|
||||||
const { on, off } = triggers.get(type);
|
const { on, off } = triggers.get(type);
|
||||||
if (typeof enable !== 'undefined' ? enable : this.get('enabled')) {
|
if (typeof enable !== 'undefined' ? enable : this.get('enabled')) {
|
||||||
on.call(this, this.start.bind(this));
|
on.call(this, this.trigger.bind(this));
|
||||||
} else {
|
} else {
|
||||||
off.call(this);
|
off.call(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async start(context: Object, options) {
|
async trigger(context: Object, options) {
|
||||||
// `null` means not to trigger
|
// `null` means not to trigger
|
||||||
if (context === null) {
|
if (context === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const transaction = await this.getTransaction(options);
|
||||||
|
|
||||||
const execution = await this.createExecution({
|
const execution = await this.createExecution({
|
||||||
context,
|
context,
|
||||||
status: EXECUTION_STATUS.STARTED,
|
status: EXECUTION_STATUS.STARTED,
|
||||||
});
|
useTransaction: this.useTransaction
|
||||||
|
}, { transaction });
|
||||||
|
|
||||||
execution.workflow = this;
|
execution.workflow = this;
|
||||||
|
|
||||||
await execution.start(options);
|
await execution.start({ transaction });
|
||||||
|
|
||||||
|
if (transaction && (!options.transaction || options.transaction.finished)) {
|
||||||
|
await transaction.commit();
|
||||||
|
}
|
||||||
|
|
||||||
return execution;
|
return execution;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,12 +22,18 @@ export default {
|
|||||||
name: 'model',
|
name: 'model',
|
||||||
on(this: WorkflowModel, callback: Function) {
|
on(this: WorkflowModel, callback: Function) {
|
||||||
const { database } = <typeof WorkflowModel>this.constructor;
|
const { database } = <typeof WorkflowModel>this.constructor;
|
||||||
const { collection, mode } = this.config;
|
const { collection, mode, filter } = this.config;
|
||||||
const Collection = database.getCollection(collection);
|
const Collection = database.getCollection(collection);
|
||||||
if (!Collection) {
|
if (!Collection) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const handler = (data: any, options) => callback({ data: data.get() }, options);
|
// async function, should return promise
|
||||||
|
const handler = (data: any, options) => {
|
||||||
|
if (filter) {
|
||||||
|
// TODO: check all conditions in filter against data
|
||||||
|
}
|
||||||
|
return callback({ data: data.get() }, options);
|
||||||
|
};
|
||||||
// TODO: duplication when mode change should be considered
|
// TODO: duplication when mode change should be considered
|
||||||
for (let [key, event] of MODE_BITMAP_EVENTS.entries()) {
|
for (let [key, event] of MODE_BITMAP_EVENTS.entries()) {
|
||||||
if (mode & key) {
|
if (mode & key) {
|
||||||
|
Loading…
Reference in New Issue
Block a user