fix(plugin-workflow): use dual pipes to process triggers (#1187)
* fix(plugin-workflow): use dual pipes to process triggers * refactor(plugin-workflow): refactor dual pipe structure
This commit is contained in:
		
							parent
							
								
									e5a9721674
								
							
						
					
					
						commit
						c19ca1f30b
					
				@ -48,8 +48,8 @@ export interface Trigger {
 | 
				
			|||||||
  view?: ISchema;
 | 
					  view?: ISchema;
 | 
				
			||||||
  scope?: { [key: string]: any };
 | 
					  scope?: { [key: string]: any };
 | 
				
			||||||
  components?: { [key: string]: any };
 | 
					  components?: { [key: string]: any };
 | 
				
			||||||
  render?(props): React.ReactElement;
 | 
					  render?(props): React.ReactNode;
 | 
				
			||||||
  getter?(node: any): React.ReactElement;
 | 
					  getter?(node: any): React.ReactNode;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const triggers = new Registry<Trigger>();
 | 
					export const triggers = new Registry<Trigger>();
 | 
				
			||||||
@ -70,56 +70,52 @@ function TriggerExecution() {
 | 
				
			|||||||
    <SchemaComponent
 | 
					    <SchemaComponent
 | 
				
			||||||
      schema={{
 | 
					      schema={{
 | 
				
			||||||
        type: 'void',
 | 
					        type: 'void',
 | 
				
			||||||
 | 
					        name: execution.id,
 | 
				
			||||||
 | 
					        'x-component': 'Action',
 | 
				
			||||||
 | 
					        'x-component-props': {
 | 
				
			||||||
 | 
					          title: <InfoOutlined />,
 | 
				
			||||||
 | 
					          shape: 'circle',
 | 
				
			||||||
 | 
					          className: 'workflow-node-job-button',
 | 
				
			||||||
 | 
					          type: 'primary'
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        properties: {
 | 
					        properties: {
 | 
				
			||||||
          trigger: {
 | 
					          [execution.id]: {
 | 
				
			||||||
            type: 'void',
 | 
					            type: 'void',
 | 
				
			||||||
            'x-component': 'Action',
 | 
					            'x-decorator': 'Form',
 | 
				
			||||||
            'x-component-props': {
 | 
					            'x-decorator-props': {
 | 
				
			||||||
              title: <InfoOutlined />,
 | 
					              initialValue: execution
 | 
				
			||||||
              shape: 'circle',
 | 
					 | 
				
			||||||
              className: 'workflow-node-job-button',
 | 
					 | 
				
			||||||
              type: 'primary'
 | 
					 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
 | 
					            'x-component': 'Action.Modal',
 | 
				
			||||||
 | 
					            title: (
 | 
				
			||||||
 | 
					              <div className={cx(nodeTitleClass)}>
 | 
				
			||||||
 | 
					                <Tag>{compile(trigger.title)}</Tag>
 | 
				
			||||||
 | 
					                <strong>{workflow.title}</strong>
 | 
				
			||||||
 | 
					                <span className="workflow-node-id">#{execution.id}</span>
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
            properties: {
 | 
					            properties: {
 | 
				
			||||||
              [execution.id]: {
 | 
					              createdAt: {
 | 
				
			||||||
                type: 'void',
 | 
					                type: 'string',
 | 
				
			||||||
                'x-decorator': 'Form',
 | 
					                title: `{{t("Triggered at", { ns: "${NAMESPACE}" })}}`,
 | 
				
			||||||
                'x-decorator-props': {
 | 
					                'x-decorator': 'FormItem',
 | 
				
			||||||
                  initialValue: execution
 | 
					                'x-component': 'DatePicker',
 | 
				
			||||||
 | 
					                'x-component-props': {
 | 
				
			||||||
 | 
					                  showTime: true
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                'x-component': 'Action.Modal',
 | 
					                'x-read-pretty': true,
 | 
				
			||||||
                title: (
 | 
					              },
 | 
				
			||||||
                  <div className={cx(nodeTitleClass)}>
 | 
					              context: {
 | 
				
			||||||
                    <Tag>{compile(trigger.title)}</Tag>
 | 
					                type: 'object',
 | 
				
			||||||
                    <strong>{workflow.title}</strong>
 | 
					                title: `{{t("Trigger variables", { ns: "${NAMESPACE}" })}}`,
 | 
				
			||||||
                    <span className="workflow-node-id">#{execution.id}</span>
 | 
					                'x-decorator': 'FormItem',
 | 
				
			||||||
                  </div>
 | 
					                'x-component': 'Input.JSON',
 | 
				
			||||||
                ),
 | 
					                'x-component-props': {
 | 
				
			||||||
                properties: {
 | 
					                  className: css`
 | 
				
			||||||
                  createdAt: {
 | 
					                    padding: 1em;
 | 
				
			||||||
                    type: 'string',
 | 
					                    background-color: #eee;
 | 
				
			||||||
                    title: `{{t("Triggered at", { ns: "${NAMESPACE}" })}}`,
 | 
					                  `
 | 
				
			||||||
                    'x-decorator': 'FormItem',
 | 
					                },
 | 
				
			||||||
                    'x-component': 'DatePicker',
 | 
					                'x-read-pretty': true,
 | 
				
			||||||
                    'x-component-props': {
 | 
					 | 
				
			||||||
                      showTime: true
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    'x-read-pretty': true,
 | 
					 | 
				
			||||||
                  },
 | 
					 | 
				
			||||||
                  context: {
 | 
					 | 
				
			||||||
                    type: 'object',
 | 
					 | 
				
			||||||
                    title: `{{t("Trigger variables", { ns: "${NAMESPACE}" })}}`,
 | 
					 | 
				
			||||||
                    'x-decorator': 'FormItem',
 | 
					 | 
				
			||||||
                    'x-component': 'Input.JSON',
 | 
					 | 
				
			||||||
                    'x-component-props': {
 | 
					 | 
				
			||||||
                      className: css`
 | 
					 | 
				
			||||||
                        padding: 1em;
 | 
					 | 
				
			||||||
                        background-color: #eee;
 | 
					 | 
				
			||||||
                      `
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    'x-read-pretty': true,
 | 
					 | 
				
			||||||
                  }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
				
			|||||||
@ -16,14 +16,15 @@ import initTriggers, { Trigger } from './triggers';
 | 
				
			|||||||
import JobModel from './models/Job';
 | 
					import JobModel from './models/Job';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Pending = [ExecutionModel, JobModel?];
 | 
				
			||||||
export default class WorkflowPlugin extends Plugin {
 | 
					export default class WorkflowPlugin extends Plugin {
 | 
				
			||||||
  instructions: Registry<Instruction> = new Registry();
 | 
					  instructions: Registry<Instruction> = new Registry();
 | 
				
			||||||
  triggers: Registry<Trigger> = new Registry();
 | 
					  triggers: Registry<Trigger> = new Registry();
 | 
				
			||||||
  calculators = calculators;
 | 
					  calculators = calculators;
 | 
				
			||||||
  extensions = extensions;
 | 
					  extensions = extensions;
 | 
				
			||||||
  executing: Promise<any> = null;
 | 
					  executing: ExecutionModel = null;
 | 
				
			||||||
  pending: [ExecutionModel, JobModel][] = [];
 | 
					  pending: Pending[] = [];
 | 
				
			||||||
 | 
					  events: [WorkflowModel, any, { context?: any }][] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  onBeforeSave = async (instance: WorkflowModel, options) => {
 | 
					  onBeforeSave = async (instance: WorkflowModel, options) => {
 | 
				
			||||||
    const Model = <typeof WorkflowModel>instance.constructor;
 | 
					    const Model = <typeof WorkflowModel>instance.constructor;
 | 
				
			||||||
@ -102,7 +103,7 @@ export default class WorkflowPlugin extends Plugin {
 | 
				
			|||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // check for not started executions
 | 
					      // check for not started executions
 | 
				
			||||||
      await this.dispatch();
 | 
					      this.dispatch();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.app.on('beforeStop', async () => {
 | 
					    this.app.on('beforeStop', async () => {
 | 
				
			||||||
@ -132,12 +133,25 @@ export default class WorkflowPlugin extends Plugin {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public async trigger(workflow: WorkflowModel, context: Object, options: Transactionable & { context?: any } = {}): Promise<void> {
 | 
					  public trigger(workflow: WorkflowModel, context: Object, options: { context?: any } = {}): Promise<void> {
 | 
				
			||||||
    // `null` means not to trigger
 | 
					    // `null` means not to trigger
 | 
				
			||||||
    if (context == null) {
 | 
					    if (context == null) {
 | 
				
			||||||
      return null;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.events.push([workflow, context, options]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (this.events.length > 1) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // NOTE: no await for quick return
 | 
				
			||||||
 | 
					    setTimeout(this.prepare);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private prepare = async () => {
 | 
				
			||||||
 | 
					    const [workflow, context, options] = this.events[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (options.context?.executionId) {
 | 
					    if (options.context?.executionId) {
 | 
				
			||||||
      // NOTE: no transaction here for read-uncommitted execution
 | 
					      // NOTE: no transaction here for read-uncommitted execution
 | 
				
			||||||
      const existed = await workflow.countExecutions({
 | 
					      const existed = await workflow.countExecutions({
 | 
				
			||||||
@ -152,43 +166,47 @@ export default class WorkflowPlugin extends Plugin {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const transaction = await (<typeof WorkflowModel>workflow.constructor).database.sequelize.transaction();
 | 
					    await this.db.sequelize.transaction(async transaction => {
 | 
				
			||||||
 | 
					      const execution = await workflow.createExecution({
 | 
				
			||||||
 | 
					        context,
 | 
				
			||||||
 | 
					        key: workflow.key,
 | 
				
			||||||
 | 
					        status: EXECUTION_STATUS.CREATED,
 | 
				
			||||||
 | 
					        useTransaction: workflow.useTransaction,
 | 
				
			||||||
 | 
					      }, { transaction });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const execution = await workflow.createExecution({
 | 
					      const executed = await workflow.countExecutions({ transaction });
 | 
				
			||||||
      context,
 | 
					 | 
				
			||||||
      key: workflow.key,
 | 
					 | 
				
			||||||
      status: EXECUTION_STATUS.CREATED,
 | 
					 | 
				
			||||||
      useTransaction: workflow.useTransaction,
 | 
					 | 
				
			||||||
    }, { transaction });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    console.log('workflow triggered:', new Date(), workflow.id, execution.id);
 | 
					      // NOTE: not to trigger afterUpdate hook here
 | 
				
			||||||
 | 
					      await workflow.update({ executed }, { transaction, hooks: false });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const executed = await workflow.countExecutions({ transaction });
 | 
					      const allExecuted = await (<typeof ExecutionModel>execution.constructor).count({
 | 
				
			||||||
 | 
					        where: {
 | 
				
			||||||
 | 
					          key: workflow.key
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        transaction
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      await (<typeof WorkflowModel>workflow.constructor).update({
 | 
				
			||||||
 | 
					        allExecuted
 | 
				
			||||||
 | 
					      }, {
 | 
				
			||||||
 | 
					        where: {
 | 
				
			||||||
 | 
					          key: workflow.key
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        individualHooks: true,
 | 
				
			||||||
 | 
					        transaction
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // NOTE: not to trigger afterUpdate hook here
 | 
					      execution.workflow = workflow;
 | 
				
			||||||
    await workflow.update({ executed }, { transaction, hooks: false });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const allExecuted = await (<typeof ExecutionModel>execution.constructor).count({
 | 
					      return execution;
 | 
				
			||||||
      where: {
 | 
					 | 
				
			||||||
        key: workflow.key
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      transaction
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    await (<typeof WorkflowModel>workflow.constructor).update({
 | 
					 | 
				
			||||||
      allExecuted
 | 
					 | 
				
			||||||
    }, {
 | 
					 | 
				
			||||||
      where: {
 | 
					 | 
				
			||||||
        key: workflow.key
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      individualHooks: true,
 | 
					 | 
				
			||||||
      transaction
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    execution.workflow = workflow;
 | 
					    this.events.shift();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await transaction.commit();
 | 
					    if (this.events.length) {
 | 
				
			||||||
 | 
					      await this.prepare();
 | 
				
			||||||
    setTimeout(() => this.dispatch(execution));
 | 
					    } else {
 | 
				
			||||||
 | 
					      this.dispatch();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public async resume(job) {
 | 
					  public async resume(job) {
 | 
				
			||||||
@ -196,28 +214,37 @@ export default class WorkflowPlugin extends Plugin {
 | 
				
			|||||||
      job.execution = await job.getExecution();
 | 
					      job.execution = await job.getExecution();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    setTimeout(() => this.dispatch(job.execution, job));
 | 
					    this.pending.push([job.execution, job]);
 | 
				
			||||||
 | 
					    this.dispatch();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private async dispatch(execution?: ExecutionModel, job?: JobModel) {
 | 
					  private async dispatch() {
 | 
				
			||||||
    if (this.executing) {
 | 
					    if (this.executing) {
 | 
				
			||||||
      if (job) {
 | 
					 | 
				
			||||||
        this.pending.push([execution, job]);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!execution) {
 | 
					    let next: Pending;
 | 
				
			||||||
      execution = await this.db.getRepository('executions').findOne({
 | 
					    // resuming has high priority
 | 
				
			||||||
 | 
					    if (this.pending.length) {
 | 
				
			||||||
 | 
					      next = this.pending.shift();
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      const execution = await this.db.getRepository('executions').findOne({
 | 
				
			||||||
        filter: {
 | 
					        filter: {
 | 
				
			||||||
          status: EXECUTION_STATUS.CREATED
 | 
					          status: EXECUTION_STATUS.CREATED
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        sort: 'createdAt'
 | 
					        sort: 'createdAt'
 | 
				
			||||||
      }) as ExecutionModel;
 | 
					      }) as ExecutionModel;
 | 
				
			||||||
      if (!execution) {
 | 
					      if (execution) {
 | 
				
			||||||
        return;
 | 
					        next = [execution];
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    if (next) {
 | 
				
			||||||
 | 
					      this.process(...next);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async process(execution: ExecutionModel, job?: JobModel) {
 | 
				
			||||||
 | 
					    this.executing = execution;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (execution.status === EXECUTION_STATUS.CREATED) {
 | 
					    if (execution.status === EXECUTION_STATUS.CREATED) {
 | 
				
			||||||
      await execution.update({ status: EXECUTION_STATUS.STARTED });
 | 
					      await execution.update({ status: EXECUTION_STATUS.STARTED });
 | 
				
			||||||
@ -225,16 +252,13 @@ export default class WorkflowPlugin extends Plugin {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    const processor = this.createProcessor(execution);
 | 
					    const processor = this.createProcessor(execution);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.executing = job ? processor.resume(job) : processor.start();
 | 
					    console.log('workflow processing:', new Date(), execution.workflowId, execution.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await this.executing;
 | 
					    await (job ? processor.resume(job) : processor.start());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.executing = null;
 | 
					    this.executing = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    setTimeout(() => {
 | 
					    this.dispatch();
 | 
				
			||||||
      const args = this.pending.length ? this.pending.shift() : [];
 | 
					 | 
				
			||||||
      this.dispatch(...args);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private createProcessor(execution: ExecutionModel, options = {}): Processor {
 | 
					  private createProcessor(execution: ExecutionModel, options = {}): Processor {
 | 
				
			||||||
 | 
				
			|||||||
@ -70,15 +70,13 @@ export default class Processor {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    const { options } = this;
 | 
					    const { options } = this;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const { sequelize } = (<typeof ExecutionModel>this.execution.constructor).database;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // @ts-ignore
 | 
					    // @ts-ignore
 | 
				
			||||||
    return options.transaction && !options.transaction.finished
 | 
					    return options.transaction && !options.transaction.finished
 | 
				
			||||||
      ? options.transaction
 | 
					      ? options.transaction
 | 
				
			||||||
      : await sequelize.transaction();
 | 
					      : await options.plugin.db.sequelize.transaction();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async prepare(commit?: boolean) {
 | 
					  private async prepare() {
 | 
				
			||||||
    const transaction = await this.getTransaction();
 | 
					    const transaction = await this.getTransaction();
 | 
				
			||||||
    this.transaction = transaction;
 | 
					    this.transaction = transaction;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -97,10 +95,6 @@ export default class Processor {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.makeJobs(jobs);
 | 
					    this.makeJobs(jobs);
 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (commit) {
 | 
					 | 
				
			||||||
      await this.commit();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public async start() {
 | 
					  public async start() {
 | 
				
			||||||
 | 
				
			|||||||
@ -183,6 +183,41 @@ describe('workflow > Plugin', () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('cycling trigger', () => {
 | 
				
			||||||
 | 
					    it('trigger should not be triggered more than once in same execution', async () => {
 | 
				
			||||||
 | 
					      const workflow = await WorkflowModel.create({
 | 
				
			||||||
 | 
					        enabled: true,
 | 
				
			||||||
 | 
					        type: 'collection',
 | 
				
			||||||
 | 
					        config: {
 | 
				
			||||||
 | 
					          mode: 1,
 | 
				
			||||||
 | 
					          collection: 'posts'
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const n1 = await workflow.createNode({
 | 
				
			||||||
 | 
					        type: 'create',
 | 
				
			||||||
 | 
					        config: {
 | 
				
			||||||
 | 
					          collection: 'posts',
 | 
				
			||||||
 | 
					          params: {
 | 
				
			||||||
 | 
					            values: {
 | 
				
			||||||
 | 
					              title: 't2'
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const post = await PostRepo.create({ values: { title: 't1' } });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await sleep(500);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const posts = await PostRepo.find();
 | 
				
			||||||
 | 
					      expect(posts.length).toBe(2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const [execution] = await workflow.getExecutions();
 | 
				
			||||||
 | 
					      expect(execution.status).toBe(EXECUTION_STATUS.RESOLVED);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  describe('dispatcher', () => {
 | 
					  describe('dispatcher', () => {
 | 
				
			||||||
    it.skip('multiple triggers in same event', async () => {
 | 
					    it.skip('multiple triggers in same event', async () => {
 | 
				
			||||||
      const w1 = await WorkflowModel.create({
 | 
					      const w1 = await WorkflowModel.create({
 | 
				
			||||||
@ -203,15 +238,27 @@ describe('workflow > Plugin', () => {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const w3 = await WorkflowModel.create({
 | 
				
			||||||
 | 
					        enabled: true,
 | 
				
			||||||
 | 
					        type: 'collection',
 | 
				
			||||||
 | 
					        config: {
 | 
				
			||||||
 | 
					          mode: 1,
 | 
				
			||||||
 | 
					          collection: 'posts'
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const p1 = await PostRepo.create({ values: { title: 't1' } });
 | 
					      const p1 = await PostRepo.create({ values: { title: 't1' } });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      await sleep(500);
 | 
					      await sleep(1000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const [e1] = await w1.getExecutions();
 | 
					      const [e1] = await w1.getExecutions();
 | 
				
			||||||
      expect(e1.status).toBe(EXECUTION_STATUS.RESOLVED);
 | 
					      expect(e1.status).toBe(EXECUTION_STATUS.RESOLVED);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const [e2] = await w2.getExecutions();
 | 
					      const [e2] = await w2.getExecutions();
 | 
				
			||||||
      expect(e2.status).toBe(EXECUTION_STATUS.RESOLVED);
 | 
					      expect(e2.status).toBe(EXECUTION_STATUS.RESOLVED);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const [e3] = await w3.getExecutions();
 | 
				
			||||||
 | 
					      expect(e3.status).toBe(EXECUTION_STATUS.RESOLVED);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it('when server starts, process all created executions', async () => {
 | 
					    it('when server starts, process all created executions', async () => {
 | 
				
			||||||
 | 
				
			|||||||
@ -425,30 +425,4 @@ describe('workflow > Processor', () => {
 | 
				
			|||||||
      expect(jobs.length).toEqual(5);
 | 
					      expect(jobs.length).toEqual(5);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					 | 
				
			||||||
  describe('cycling trigger', () => {
 | 
					 | 
				
			||||||
    it('trigger should not be triggered more than once in same execution', async () => {
 | 
					 | 
				
			||||||
      const n1 = await workflow.createNode({
 | 
					 | 
				
			||||||
        type: 'create',
 | 
					 | 
				
			||||||
        config: {
 | 
					 | 
				
			||||||
          collection: 'posts',
 | 
					 | 
				
			||||||
          params: {
 | 
					 | 
				
			||||||
            values: {
 | 
					 | 
				
			||||||
              title: 't2'
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const post = await PostRepo.create({ values: { title: 't1' } });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      await sleep(500);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const posts = await PostRepo.find();
 | 
					 | 
				
			||||||
      expect(posts.length).toBe(2);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const [execution] = await workflow.getExecutions();
 | 
					 | 
				
			||||||
      expect(execution.status).toBe(EXECUTION_STATUS.RESOLVED);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
@ -42,10 +42,10 @@ async function handler(this: CollectionTrigger, workflow: WorkflowModel, data: M
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  // NOTE: if no configured fields changed, do not trigger
 | 
					  // NOTE: if no configured fields changed, do not trigger
 | 
				
			||||||
  if (changed
 | 
					  if (changed
 | 
				
			||||||
      && changed.length
 | 
					    && changed.length
 | 
				
			||||||
      && changed
 | 
					    && changed
 | 
				
			||||||
        .filter(name => !['linkTo', 'hasOne', 'hasMany', 'belongsToMany'].includes(collection.getField(name).type))
 | 
					      .filter(name => !['linkTo', 'hasOne', 'hasMany', 'belongsToMany'].includes(collection.getField(name).type))
 | 
				
			||||||
        .every(name => !data.changedWithAssociations(getFieldRawName(collection, name)))
 | 
					      .every(name => !data.changedWithAssociations(getFieldRawName(collection, name)))
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -70,10 +70,8 @@ async function handler(this: CollectionTrigger, workflow: WorkflowModel, data: M
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  setTimeout(() => {
 | 
					  this.plugin.trigger(workflow, { data: data.get() }, {
 | 
				
			||||||
    this.plugin.trigger(workflow, { data: data.get() }, {
 | 
					    context
 | 
				
			||||||
      context
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user