@nocobase/plugin-workflow-action-trigger 0.21.0-alpha.1 → 0.21.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import { SchemaInitializerItemType, useCollectionDataSource } from '@nocobase/client';
2
- import { Trigger, useWorkflowAnyExecuted } from '@nocobase/plugin-workflow/client';
2
+ import { Trigger, useWorkflowAnyExecuted, CheckboxGroupWithTooltip, RadioWithTooltip } from '@nocobase/plugin-workflow/client';
3
3
  export default class extends Trigger {
4
4
  title: string;
5
5
  description: string;
@@ -8,10 +8,12 @@ export default class extends Trigger {
8
8
  type: string;
9
9
  required: boolean;
10
10
  'x-decorator': string;
11
+ 'x-decorator-props': {
12
+ tooltip: string;
13
+ };
11
14
  'x-component': string;
12
15
  'x-disabled': string;
13
16
  title: string;
14
- description: string;
15
17
  'x-reactions': {
16
18
  target: string;
17
19
  effects: string[];
@@ -22,6 +24,50 @@ export default class extends Trigger {
22
24
  };
23
25
  }[];
24
26
  };
27
+ global: {
28
+ type: string;
29
+ title: string;
30
+ 'x-decorator': string;
31
+ 'x-component': string;
32
+ 'x-component-props': {
33
+ direction: string;
34
+ options: {
35
+ label: string;
36
+ value: boolean;
37
+ }[];
38
+ };
39
+ default: boolean;
40
+ 'x-reactions': {
41
+ dependencies: string[];
42
+ fulfill: {
43
+ state: {
44
+ visible: string;
45
+ };
46
+ };
47
+ }[];
48
+ };
49
+ actions: {
50
+ type: string;
51
+ title: string;
52
+ 'x-decorator': string;
53
+ 'x-component': string;
54
+ 'x-component-props': {
55
+ direction: string;
56
+ options: {
57
+ label: string;
58
+ value: string;
59
+ }[];
60
+ };
61
+ required: boolean;
62
+ 'x-reactions': {
63
+ dependencies: string[];
64
+ fulfill: {
65
+ state: {
66
+ visible: string;
67
+ };
68
+ };
69
+ }[];
70
+ };
25
71
  appends: {
26
72
  type: string;
27
73
  title: string;
@@ -47,6 +93,10 @@ export default class extends Trigger {
47
93
  useCollectionDataSource: typeof useCollectionDataSource;
48
94
  useWorkflowAnyExecuted: typeof useWorkflowAnyExecuted;
49
95
  };
96
+ components: {
97
+ RadioWithTooltip: typeof RadioWithTooltip;
98
+ CheckboxGroupWithTooltip: typeof CheckboxGroupWithTooltip;
99
+ };
50
100
  isActionTriggerable: (config: any, context: any) => boolean;
51
101
  useVariables(config: any, options: any): import("@nocobase/plugin-workflow/client").VariableOption[];
52
102
  useInitializers(config: any): SchemaInitializerItemType | null;
@@ -1 +1 @@
1
- (function(o,e){typeof exports=="object"&&typeof module!="undefined"?e(exports,require("@nocobase/client"),require("@nocobase/plugin-workflow/client"),require("@formily/react"),require("react-i18next")):typeof define=="function"&&define.amd?define(["exports","@nocobase/client","@nocobase/plugin-workflow/client","@formily/react","react-i18next"],e):(o=typeof globalThis!="undefined"?globalThis:o||self,e(o["@nocobase/plugin-workflow-action-trigger"]={},o["@nocobase/client"],o["@nocobase/plugin-workflow"],o["@formily/react"],o["react-i18next"]))})(this,function(o,e,t,w,m){"use strict";var M=Object.defineProperty,D=Object.defineProperties;var R=Object.getOwnPropertyDescriptors;var y=Object.getOwnPropertySymbols;var q=Object.prototype.hasOwnProperty,$=Object.prototype.propertyIsEnumerable;var z=(o,e,t)=>e in o?M(o,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):o[e]=t,p=(o,e)=>{for(var t in e||(e={}))q.call(e,t)&&z(o,t,e[t]);if(y)for(var t of y(e))$.call(e,t)&&z(o,t,e[t]);return o},f=(o,e)=>D(o,R(e));var a=(o,e,t)=>(z(o,typeof e!="symbol"?e+"":e,t),t);var S=(o,e,t)=>new Promise((w,m)=>{var s=r=>{try{l(t.next(r))}catch(n){m(n)}},c=r=>{try{l(t.throw(r))}catch(n){m(n)}},l=r=>r.done?w(r.value):Promise.resolve(r.value).then(s,c);l((t=t.apply(o,e)).next())});const s="workflow-action-trigger";function c(u,A={}){const{t:i}=l(A);return i(u)}function l(u){return m.useTranslation(s,u)}class r extends t.Trigger{constructor(){super(...arguments);a(this,"title",`{{t("Action event", { ns: "${s}" })}}`);a(this,"description",`{{t("Triggers after specific operations on data are submitted, such as create, update, delete, etc., or directly submitting a record to the workflow.", { ns: "${s}" })}}`);a(this,"fieldset",{collection:{type:"string",required:!0,"x-decorator":"FormItem","x-component":"DataSourceCollectionCascader","x-disabled":"{{ useWorkflowAnyExecuted() }}",title:`{{t("Collection", { ns: "${s}" })}}`,description:`{{t("Which collection record belongs to.", { ns: "${s}" })}}`,"x-reactions":[{target:"appends",effects:["onFieldValueChange"],fulfill:{state:{value:[]}}}]},appends:{type:"array",title:`{{t("Associations to use", { ns: "${s}" })}}`,description:'{{t("Please select the associated fields that need to be accessed in subsequent nodes. With more than two levels of to-many associations may cause performance issue, please use with caution.", { ns: "workflow" })}}',"x-decorator":"FormItem","x-component":"AppendsTreeSelect","x-component-props":{title:"Preload associations",multiple:!0,useCollection(){const{values:i}=w.useForm();return i==null?void 0:i.collection}},"x-reactions":[{dependencies:["collection"],fulfill:{state:{visible:"{{!!$deps[0]}}"}}}]}});a(this,"scope",{useCollectionDataSource:e.useCollectionDataSource,useWorkflowAnyExecuted:t.useWorkflowAnyExecuted});a(this,"isActionTriggerable",(i,g)=>["create","update","customize:update","customize:triggerWorkflows"].includes(g.action))}useVariables(i,g){var C;const h=e.useCompile(),{getCollectionFields:I}=e.useCollectionManager_deprecated(),b=c("Trigger data"),T=c("User submitted action"),x=c("Role of user submitted action"),W=[{collectionName:i.collection,name:"data",type:"hasOne",target:i.collection,uiSchema:{title:b}},{collectionName:"users",name:"user",type:"hasOne",target:"users",uiSchema:{title:T}},{name:"roleName",uiSchema:{title:x}}];return t.getCollectionFieldOptions(f(p({appends:["data","user",...((C=i.appends)==null?void 0:C.map(P=>`data.${P}`))||[]]},g),{fields:W,compile:h,getCollectionFields:I}))}useInitializers(i){return i.collection?{name:"triggerData",type:"item",key:"triggerData",title:`{{t("Trigger data", { ns: "${s}" })}}`,Component:t.CollectionBlockInitializer,collection:i.collection,dataPath:"$context.data"}:null}}const n={name:"submitToWorkflow",title:'{{t("Submit to workflow", { ns: "workflow" })}}',Component:"CustomizeActionInitializer",schema:{title:'{{t("Submit to workflow", { ns: "workflow" })}}',"x-component":"Action","x-component-props":{useProps:"{{ useTriggerWorkflowsActionProps }}"},"x-designer":"Action.Designer","x-action-settings":{skipValidator:!1,onSuccess:{manualClose:!0,redirecting:!1,successMessage:'{{t("Submitted successfully")}}'},triggerWorkflows:[]},"x-action":"customize:triggerWorkflows"}},d={name:"submitToWorkflow",title:'{{t("Submit to workflow", { ns: "workflow" })}}',Component:"CustomizeActionInitializer",schema:{title:'{{t("Submit to workflow", { ns: "workflow" })}}',"x-component":"Action","x-component-props":{useProps:"{{ useRecordTriggerWorkflowsActionProps }}"},"x-designer":"Action.Designer","x-action-settings":{onSuccess:{manualClose:!0,redirecting:!1,successMessage:'{{t("Submitted successfully")}}'},triggerWorkflows:[]},"x-action":"customize:triggerWorkflows"}},k=f(p({},d),{schema:f(p({},d.schema),{"x-component":"Action.Link"})});class F extends e.Plugin{load(){return S(this,null,function*(){this.app.pm.get("workflow").registerTrigger("action",r),this.app.addScopes({useTriggerWorkflowsActionProps:t.useTriggerWorkflowsActionProps,useRecordTriggerWorkflowsActionProps:t.useRecordTriggerWorkflowsActionProps}),this.app.schemaInitializerManager.get("FormActionInitializers").add("customize.submitToWorkflow",n),this.app.schemaInitializerManager.get("createForm:configureActions").add("customize.submitToWorkflow",n),this.app.schemaInitializerManager.get("editForm:configureActions").add("customize.submitToWorkflow",n),this.app.schemaInitializerManager.get("detailsWithPaging:configureActions").add("customize.submitToWorkflow",d),this.app.schemaInitializerManager.get("details:configureActions").add("customize.submitToWorkflow",d),this.app.schemaInitializerManager.get("table:configureItemActions").add("customize.submitToWorkflow",k),this.app.schemaInitializerManager.get("gridCard:configureItemActions").add("customize.submitToWorkflow",k),this.app.schemaInitializerManager.get("list:configureItemActions").add("customize.submitToWorkflow",k)})}}o.default=F,Object.defineProperties(o,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
1
+ (function(o,e){typeof exports=="object"&&typeof module!="undefined"?e(exports,require("@nocobase/client"),require("@nocobase/plugin-workflow/client"),require("@formily/react"),require("react-i18next")):typeof define=="function"&&define.amd?define(["exports","@nocobase/client","@nocobase/plugin-workflow/client","@formily/react","react-i18next"],e):(o=typeof globalThis!="undefined"?globalThis:o||self,e(o["@nocobase/plugin-workflow-action-trigger"]={},o["@nocobase/client"],o["@nocobase/plugin-workflow"],o["@formily/react"],o["react-i18next"]))})(this,function(o,e,t,w,m){"use strict";var E=Object.defineProperty,$=Object.defineProperties;var M=Object.getOwnPropertyDescriptors;var S=Object.getOwnPropertySymbols;var v=Object.prototype.hasOwnProperty,D=Object.prototype.propertyIsEnumerable;var k=(o,e,t)=>e in o?E(o,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):o[e]=t,g=(o,e)=>{for(var t in e||(e={}))v.call(e,t)&&k(o,t,e[t]);if(S)for(var t of S(e))D.call(e,t)&&k(o,t,e[t]);return o},f=(o,e)=>$(o,M(e));var s=(o,e,t)=>(k(o,typeof e!="symbol"?e+"":e,t),t);var F=(o,e,t)=>new Promise((w,m)=>{var i=r=>{try{c(t.next(r))}catch(l){m(l)}},a=r=>{try{c(t.throw(r))}catch(l){m(l)}},c=r=>r.done?w(r.value):Promise.resolve(r.value).then(i,a);c((t=t.apply(o,e)).next())});const i="workflow-action-trigger";function a(u,A={}){const{t:n}=c(A);return n(u)}function c(u){return m.useTranslation(i,u)}const r={CREATE:"create",UPDATE:"update",UPSERT:"updateOrCreate",DESTROY:"destroy"};class l extends t.Trigger{constructor(){super(...arguments);s(this,"title",`{{t("Post-action event", { ns: "${i}" })}}`);s(this,"description",`{{t('Triggered after the completion of a request initiated through an action button or API, such as after adding, updating, deleting data, or "submit to workflow". Suitable for data processing, sending notifications, etc., after actions are completed.', { ns: "${i}" })}}`);s(this,"fieldset",{collection:{type:"string",required:!0,"x-decorator":"FormItem","x-decorator-props":{tooltip:`{{t("The collection to which the triggered data belongs.", { ns: "${i}" })}}`},"x-component":"DataSourceCollectionCascader","x-disabled":"{{ useWorkflowAnyExecuted() }}",title:`{{t("Collection", { ns: "${i}" })}}`,"x-reactions":[{target:"appends",effects:["onFieldValueChange"],fulfill:{state:{value:[]}}}]},global:{type:"boolean",title:`{{t("Trigger mode", { ns: "${i}" })}}`,"x-decorator":"FormItem","x-component":"RadioWithTooltip","x-component-props":{direction:"vertical",options:[{label:`{{t("Local mode, triggered after the completion of actions bound to this workflow", { ns: "${i}" })}}`,value:!1},{label:`{{t("Global mode, triggered after the completion of the following actions", { ns: "${i}" })}}`,value:!0}]},default:!1,"x-reactions":[{dependencies:["collection"],fulfill:{state:{visible:"{{!!$deps[0]}}"}}}]},actions:{type:"number",title:`{{t("Select actions", { ns: "${i}" })}}`,"x-decorator":"FormItem","x-component":"CheckboxGroupWithTooltip","x-component-props":{direction:"vertical",options:[{label:`{{t("Create record action", { ns: "${i}" })}}`,value:r.CREATE},{label:`{{t("Update record action", { ns: "${i}" })}}`,value:r.UPDATE}]},required:!0,"x-reactions":[{dependencies:["collection","global"],fulfill:{state:{visible:"{{!!$deps[0] && !!$deps[1]}}"}}}]},appends:{type:"array",title:`{{t("Associations to use", { ns: "${i}" })}}`,description:'{{t("Please select the associated fields that need to be accessed in subsequent nodes. With more than two levels of to-many associations may cause performance issue, please use with caution.", { ns: "workflow" })}}',"x-decorator":"FormItem","x-component":"AppendsTreeSelect","x-component-props":{title:"Preload associations",multiple:!0,useCollection(){const{values:n}=w.useForm();return n==null?void 0:n.collection}},"x-reactions":[{dependencies:["collection"],fulfill:{state:{visible:"{{!!$deps[0]}}"}}}]}});s(this,"scope",{useCollectionDataSource:e.useCollectionDataSource,useWorkflowAnyExecuted:t.useWorkflowAnyExecuted});s(this,"components",{RadioWithTooltip:t.RadioWithTooltip,CheckboxGroupWithTooltip:t.CheckboxGroupWithTooltip});s(this,"isActionTriggerable",(n,d)=>d.action==="customize:triggerWorkflows"||["create","update","customize:update"].includes(d.action)&&!n.global)}useVariables(n,d){var y;const T=e.useCompile(),{getCollectionFields:I}=e.useCollectionManager_deprecated(),z=a("Trigger data"),x=a("User submitted action"),C=a("Role of user submitted action"),W=[{collectionName:n.collection,name:"data",type:"hasOne",target:n.collection,uiSchema:{title:z}},{collectionName:"users",name:"user",type:"hasOne",target:"users",uiSchema:{title:x}},{name:"roleName",uiSchema:{title:C}}];return t.getCollectionFieldOptions(f(g({appends:["data","user",...((y=n.appends)==null?void 0:y.map(R=>`data.${R}`))||[]]},d),{fields:W,compile:T,getCollectionFields:I}))}useInitializers(n){return n.collection?{name:"triggerData",type:"item",key:"triggerData",title:`{{t("Trigger data", { ns: "${i}" })}}`,Component:t.CollectionBlockInitializer,collection:n.collection,dataPath:"$context.data"}:null}}const h={name:"submitToWorkflow",title:'{{t("Submit to workflow", { ns: "workflow" })}}',Component:"CustomizeActionInitializer",schema:{title:'{{t("Submit to workflow", { ns: "workflow" })}}',"x-component":"Action","x-use-component-props":"useTriggerWorkflowsActionProps","x-designer":"Action.Designer","x-action-settings":{skipValidator:!1,onSuccess:{manualClose:!0,redirecting:!1,successMessage:'{{t("Submitted successfully")}}'},triggerWorkflows:[]},"x-action":"customize:triggerWorkflows"}},p={name:"submitToWorkflow",title:'{{t("Submit to workflow", { ns: "workflow" })}}',Component:"CustomizeActionInitializer",schema:{title:'{{t("Submit to workflow", { ns: "workflow" })}}',"x-component":"Action","x-use-component-props":"useRecordTriggerWorkflowsActionProps","x-designer":"Action.Designer","x-action-settings":{onSuccess:{manualClose:!0,redirecting:!1,successMessage:'{{t("Submitted successfully")}}'},triggerWorkflows:[]},"x-action":"customize:triggerWorkflows"}},b=f(g({},p),{schema:f(g({},p.schema),{"x-component":"Action.Link"})});class P extends e.Plugin{load(){return F(this,null,function*(){this.app.pm.get("workflow").registerTrigger("action",l),this.app.addScopes({useTriggerWorkflowsActionProps:t.useTriggerWorkflowsActionProps,useRecordTriggerWorkflowsActionProps:t.useRecordTriggerWorkflowsActionProps}),this.app.schemaInitializerManager.get("FormActionInitializers").add("customize.submitToWorkflow",h),this.app.schemaInitializerManager.get("createForm:configureActions").add("customize.submitToWorkflow",h),this.app.schemaInitializerManager.get("editForm:configureActions").add("customize.submitToWorkflow",h),this.app.schemaInitializerManager.get("detailsWithPaging:configureActions").add("customize.submitToWorkflow",p),this.app.schemaInitializerManager.get("details:configureActions").add("customize.submitToWorkflow",p),this.app.schemaInitializerManager.get("table:configureItemActions").add("customize.submitToWorkflow",b),this.app.schemaInitializerManager.get("gridCard:configureItemActions").add("customize.submitToWorkflow",b),this.app.schemaInitializerManager.get("list:configureItemActions").add("customize.submitToWorkflow",b)})}}o.default=P,Object.defineProperties(o,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
@@ -1,15 +1,15 @@
1
1
  module.exports = {
2
2
  "@formily/react": "2.3.0",
3
- "@nocobase/client": "0.21.0-alpha.1",
4
- "@nocobase/plugin-workflow": "0.21.0-alpha.1",
3
+ "@nocobase/client": "0.21.0-alpha.2",
4
+ "@nocobase/plugin-workflow": "0.21.0-alpha.2",
5
5
  "react-i18next": "11.18.6",
6
6
  "lodash": "4.17.21",
7
7
  "sequelize": "6.35.2",
8
- "@nocobase/database": "0.21.0-alpha.1",
9
- "@nocobase/server": "0.21.0-alpha.1",
10
- "@nocobase/actions": "0.21.0-alpha.1",
11
- "@nocobase/data-source-manager": "0.21.0-alpha.1",
12
- "@nocobase/plugin-workflow-test": "0.21.0-alpha.1",
13
- "@nocobase/test": "0.21.0-alpha.1",
14
- "@nocobase/utils": "0.21.0-alpha.1"
8
+ "@nocobase/database": "0.21.0-alpha.2",
9
+ "@nocobase/server": "0.21.0-alpha.2",
10
+ "@nocobase/actions": "0.21.0-alpha.2",
11
+ "@nocobase/data-source-manager": "0.21.0-alpha.2",
12
+ "@nocobase/plugin-workflow-test": "0.21.0-alpha.2",
13
+ "@nocobase/test": "0.21.0-alpha.2",
14
+ "@nocobase/utils": "0.21.0-alpha.2"
15
15
  };
@@ -1,8 +1,16 @@
1
1
  {
2
- "Action event": "操作事件",
3
- "Triggers after specific operations on data are submitted, such as create, update, delete, etc., or directly submitting a record to the workflow.": "在对数据的特定操作提交后触发,如创建、更新、删除等,或直接提交一条数据至工作流。",
2
+ "Post-action event": "操作后事件",
3
+ "Triggered after the completion of a request initiated through an action button or API, such as after adding, updating, deleting data, or \"submit to workflow\". Suitable for data processing, sending notifications, etc., after actions are completed.":
4
+ "通过操作按钮或 API 发起请求并在执行完成后触发,比如新增、更新、删除数据或者“提交至工作流”之后。适用于在操作完成后进行数据处理、发送通知等。",
4
5
  "Collection": "数据表",
5
- "Which collection record belongs to.": "数据所属的数据表。",
6
+ "The collection to which the triggered data belongs.": "触发数据所属的数据表。",
7
+ "Trigger mode": "触发模式",
8
+ "Local mode, triggered after the completion of actions bound to this workflow": "局部模式,绑定该工作流的操作执行完成后触发",
9
+ "Global mode, triggered after the completion of the following actions": "全局模式,以下操作执行完成后都触发",
10
+ "Action to submit to workflow directly is only supported on bound buttons, and will not be affected under global mode.": "直接提交至工作流的操作仅支持使用按钮绑定,不受全局模式的影响。",
11
+ "Select actions": "选择操作",
12
+ "Create record action": "创建记录操作",
13
+ "Update record action": "更新记录操作",
6
14
  "Associations to use": "待使用的关系数据",
7
15
  "Trigger data": "触发器数据",
8
16
  "User submitted action": "提交操作的用户",
@@ -4,10 +4,11 @@ import WorkflowPlugin, { Trigger, WorkflowModel } from '@nocobase/plugin-workflo
4
4
  interface Context extends ActionContext, DefaultContext {
5
5
  }
6
6
  export default class extends Trigger {
7
+ static TYPE: string;
7
8
  constructor(workflow: WorkflowPlugin);
8
- triggerAction(context: Context, next: Next): Promise<never>;
9
+ workflowTriggerAction(context: Context, next: Next): Promise<void>;
9
10
  middleware: (context: Context, next: Next) => Promise<void>;
10
- private trigger;
11
+ private collectionTriggerAction;
11
12
  on(workflow: WorkflowModel): void;
12
13
  off(workflow: WorkflowModel): void;
13
14
  }
@@ -25,40 +25,39 @@ var import_database = require("@nocobase/database");
25
25
  var import_plugin_workflow = require("@nocobase/plugin-workflow");
26
26
  var import_data_source_manager = require("@nocobase/data-source-manager");
27
27
  class ActionTrigger_default extends import_plugin_workflow.Trigger {
28
+ static TYPE = "action";
28
29
  constructor(workflow) {
29
30
  super(workflow);
30
31
  workflow.app.use(this.middleware, { after: "dataSource" });
31
32
  }
32
- async triggerAction(context, next) {
33
+ async workflowTriggerAction(context, next) {
33
34
  const { triggerWorkflows } = context.action.params;
34
35
  if (!triggerWorkflows) {
35
36
  return context.throw(400);
36
37
  }
37
38
  context.status = 202;
38
39
  await next();
39
- this.trigger(context);
40
+ return this.collectionTriggerAction(context);
40
41
  }
41
42
  middleware = async (context, next) => {
42
- const {
43
- resourceName,
44
- actionName,
45
- params: { triggerWorkflows }
46
- } = context.action;
43
+ const { resourceName, actionName } = context.action;
47
44
  if (resourceName === "workflows" && actionName === "trigger") {
48
- return this.triggerAction(context, next);
45
+ return this.workflowTriggerAction(context, next);
49
46
  }
50
47
  await next();
51
- if (!triggerWorkflows) {
52
- return;
53
- }
54
48
  if (!["create", "update"].includes(actionName)) {
55
49
  return;
56
50
  }
57
- return this.trigger(context);
51
+ return this.collectionTriggerAction(context);
58
52
  };
59
- async trigger(context) {
60
- const { triggerWorkflows = "", values } = context.action.params;
53
+ async collectionTriggerAction(context) {
54
+ const {
55
+ resourceName,
56
+ actionName,
57
+ params: { triggerWorkflows = "", values }
58
+ } = context.action;
61
59
  const dataSourceHeader = context.get("x-data-source") || "main";
60
+ const fullCollectionName = (0, import_data_source_manager.joinCollectionName)(dataSourceHeader, resourceName);
62
61
  const { currentUser, currentRole } = context.state;
63
62
  const { model: UserModel } = this.workflow.db.getCollection("users");
64
63
  const userInfo = {
@@ -66,21 +65,42 @@ class ActionTrigger_default extends import_plugin_workflow.Trigger {
66
65
  roleName: currentRole
67
66
  };
68
67
  const triggers = triggerWorkflows.split(",").map((trigger) => trigger.split("!"));
69
- const workflowRepo = this.workflow.db.getRepository("workflows");
70
- const workflows = (await workflowRepo.find({
71
- filter: {
72
- key: triggers.map((trigger) => trigger[0]),
73
- current: true,
74
- type: "action",
75
- enabled: true
68
+ const triggersKeysMap = new Map(triggers);
69
+ const workflows = Array.from(this.workflow.enabledCache.values()).filter(
70
+ (item) => item.type === "action" && item.config.collection
71
+ );
72
+ const globalWorkflows = /* @__PURE__ */ new Map();
73
+ const localWorkflows = /* @__PURE__ */ new Map();
74
+ workflows.forEach((item) => {
75
+ var _a;
76
+ if (resourceName === "workflows" && actionName === "trigger") {
77
+ localWorkflows.set(item.key, item);
78
+ } else if (item.config.collection === fullCollectionName) {
79
+ if (item.config.global) {
80
+ if ((_a = item.config.actions) == null ? void 0 : _a.includes(actionName)) {
81
+ globalWorkflows.set(item.key, item);
82
+ }
83
+ } else {
84
+ localWorkflows.set(item.key, item);
85
+ }
86
+ }
87
+ });
88
+ const triggeringLocalWorkflows = [];
89
+ const uniqueTriggersMap = /* @__PURE__ */ new Map();
90
+ triggers.forEach((trigger) => {
91
+ const [key] = trigger;
92
+ const workflow = localWorkflows.get(key);
93
+ if (workflow && !uniqueTriggersMap.has(key)) {
94
+ triggeringLocalWorkflows.push(workflow);
95
+ uniqueTriggersMap.set(key, true);
76
96
  }
77
- })).filter((workflow) => Boolean(workflow.config.collection));
97
+ });
78
98
  const syncGroup = [];
79
99
  const asyncGroup = [];
80
- for (const workflow of workflows) {
100
+ for (const workflow of triggeringLocalWorkflows.concat(...globalWorkflows.values())) {
81
101
  const { collection, appends = [] } = workflow.config;
82
102
  const [dataSourceName, collectionName] = (0, import_data_source_manager.parseCollectionName)(collection);
83
- const trigger = triggers.find((trigger2) => trigger2[0] == workflow.key);
103
+ const dataPath = triggersKeysMap.get(workflow.key);
84
104
  const event = [workflow];
85
105
  if (context.action.resourceName !== "workflows") {
86
106
  if (!context.body) {
@@ -92,9 +112,12 @@ class ActionTrigger_default extends import_plugin_workflow.Trigger {
92
112
  const { body: data } = context;
93
113
  for (const row of Array.isArray(data) ? data : [data]) {
94
114
  let payload = row;
95
- if (trigger[1]) {
96
- const paths = trigger[1].split(".");
115
+ if (dataPath) {
116
+ const paths = dataPath.split(".");
97
117
  for (const field of paths) {
118
+ if (!payload) {
119
+ break;
120
+ }
98
121
  if (payload.get(field)) {
99
122
  payload = payload.get(field);
100
123
  } else {
@@ -103,14 +126,14 @@ class ActionTrigger_default extends import_plugin_workflow.Trigger {
103
126
  }
104
127
  }
105
128
  }
106
- const model = payload.constructor;
107
129
  if (payload instanceof import_database.Model) {
130
+ const model = payload.constructor;
108
131
  if (collectionName !== model.collection.name) {
109
132
  continue;
110
133
  }
111
134
  if (appends.length) {
112
135
  payload = await model.collection.repository.findOne({
113
- filterByTk: payload.get(model.primaryKeyAttribute),
136
+ filterByTk: payload.get(model.collection.filterTargetKey),
114
137
  appends
115
138
  });
116
139
  }
@@ -118,9 +141,9 @@ class ActionTrigger_default extends import_plugin_workflow.Trigger {
118
141
  event.push({ data: (0, import_plugin_workflow.toJSON)(payload), ...userInfo });
119
142
  }
120
143
  } else {
121
- const { model, repository } = context.app.dataSourceManager.dataSources.get(dataSourceName).collectionManager.getCollection(collectionName);
122
- let data = trigger[1] ? (0, import_lodash.get)(values, trigger[1]) : values;
123
- const pk = (0, import_lodash.get)(data, model.primaryKeyAttribute);
144
+ const { filterTargetKey, repository } = context.app.dataSourceManager.dataSources.get(dataSourceName).collectionManager.getCollection(collectionName);
145
+ let data = dataPath ? (0, import_lodash.get)(values, dataPath) : values;
146
+ const pk = (0, import_lodash.get)(data, filterTargetKey);
124
147
  if (appends.length && pk != null) {
125
148
  data = await repository.findOne({
126
149
  filterByTk: pk,
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@nocobase/plugin-workflow-action-trigger",
3
- "displayName": "Workflow: Action trigger",
4
- "displayName.zh-CN": "工作流:数据操作触发器",
5
- "description": "Bind action buttons to trigger workflow events when clicked.",
6
- "description.zh-CN": "可对数据操作按钮绑定,在点击后触发对应的工作流事件。",
7
- "version": "0.21.0-alpha.1",
3
+ "displayName": "Workflow: Post-action event",
4
+ "displayName.zh-CN": "工作流:操作后事件",
5
+ "description": "Triggered after the completion of a request initiated through an action button or API, such as after adding, updating, deleting data, or \"submit to workflow\". Suitable for data processing, sending notifications, etc., after actions are completed.",
6
+ "description.zh-CN": "通过操作按钮或 API 发起请求并在执行完成后触发,比如新增、更新、删除数据或者“提交至工作流”之后。适用于在操作完成后进行数据处理、发送通知等。",
7
+ "version": "0.21.0-alpha.2",
8
8
  "license": "AGPL-3.0",
9
9
  "main": "./dist/server/index.js",
10
10
  "homepage": "https://docs.nocobase.com/plugins/workflow-action-trigger",
@@ -21,7 +21,7 @@
21
21
  "@nocobase/server": "0.x",
22
22
  "@nocobase/test": "0.x"
23
23
  },
24
- "gitHead": "afd2f3d1341b85ea9daa7b2667dd4ace1fafb7ff",
24
+ "gitHead": "90628f2e2da846208fb2d7966ddb4e467d187ffb",
25
25
  "keywords": [
26
26
  "Workflow"
27
27
  ]
@@ -11,21 +11,32 @@ import {
11
11
  CollectionBlockInitializer,
12
12
  getCollectionFieldOptions,
13
13
  useWorkflowAnyExecuted,
14
+ CheckboxGroupWithTooltip,
15
+ RadioWithTooltip,
14
16
  } from '@nocobase/plugin-workflow/client';
15
17
  import { NAMESPACE, useLang } from '../locale';
16
18
 
19
+ const COLLECTION_TRIGGER_ACTION = {
20
+ CREATE: 'create',
21
+ UPDATE: 'update',
22
+ UPSERT: 'updateOrCreate',
23
+ DESTROY: 'destroy',
24
+ };
25
+
17
26
  export default class extends Trigger {
18
- title = `{{t("Action event", { ns: "${NAMESPACE}" })}}`;
19
- description = `{{t("Triggers after specific operations on data are submitted, such as create, update, delete, etc., or directly submitting a record to the workflow.", { ns: "${NAMESPACE}" })}}`;
27
+ title = `{{t("Post-action event", { ns: "${NAMESPACE}" })}}`;
28
+ description = `{{t('Triggered after the completion of a request initiated through an action button or API, such as after adding, updating, deleting data, or "submit to workflow". Suitable for data processing, sending notifications, etc., after actions are completed.', { ns: "${NAMESPACE}" })}}`;
20
29
  fieldset = {
21
30
  collection: {
22
31
  type: 'string',
23
32
  required: true,
24
33
  'x-decorator': 'FormItem',
34
+ 'x-decorator-props': {
35
+ tooltip: `{{t("The collection to which the triggered data belongs.", { ns: "${NAMESPACE}" })}}`,
36
+ },
25
37
  'x-component': 'DataSourceCollectionCascader',
26
38
  'x-disabled': '{{ useWorkflowAnyExecuted() }}',
27
39
  title: `{{t("Collection", { ns: "${NAMESPACE}" })}}`,
28
- description: `{{t("Which collection record belongs to.", { ns: "${NAMESPACE}" })}}`,
29
40
  'x-reactions': [
30
41
  {
31
42
  target: 'appends',
@@ -38,6 +49,65 @@ export default class extends Trigger {
38
49
  },
39
50
  ],
40
51
  },
52
+ global: {
53
+ type: 'boolean',
54
+ title: `{{t("Trigger mode", { ns: "${NAMESPACE}" })}}`,
55
+ 'x-decorator': 'FormItem',
56
+ 'x-component': 'RadioWithTooltip',
57
+ 'x-component-props': {
58
+ direction: 'vertical',
59
+ options: [
60
+ {
61
+ label: `{{t("Local mode, triggered after the completion of actions bound to this workflow", { ns: "${NAMESPACE}" })}}`,
62
+ value: false,
63
+ },
64
+ {
65
+ label: `{{t("Global mode, triggered after the completion of the following actions", { ns: "${NAMESPACE}" })}}`,
66
+ value: true,
67
+ },
68
+ ],
69
+ },
70
+ default: false,
71
+ 'x-reactions': [
72
+ {
73
+ dependencies: ['collection'],
74
+ fulfill: {
75
+ state: {
76
+ visible: '{{!!$deps[0]}}',
77
+ },
78
+ },
79
+ },
80
+ ],
81
+ },
82
+ actions: {
83
+ type: 'number',
84
+ title: `{{t("Select actions", { ns: "${NAMESPACE}" })}}`,
85
+ 'x-decorator': 'FormItem',
86
+ 'x-component': 'CheckboxGroupWithTooltip',
87
+ 'x-component-props': {
88
+ direction: 'vertical',
89
+ options: [
90
+ { label: `{{t("Create record action", { ns: "${NAMESPACE}" })}}`, value: COLLECTION_TRIGGER_ACTION.CREATE },
91
+ { label: `{{t("Update record action", { ns: "${NAMESPACE}" })}}`, value: COLLECTION_TRIGGER_ACTION.UPDATE },
92
+ // { label: `{{t("upsert", { ns: "${NAMESPACE}" })}}`, value: COLLECTION_TRIGGER_ACTION.UPSERT },
93
+ // {
94
+ // label: `{{t("Delete single or many records", { ns: "${NAMESPACE}" })}}`,
95
+ // value: COLLECTION_TRIGGER_ACTION.DESTROY,
96
+ // },
97
+ ],
98
+ },
99
+ required: true,
100
+ 'x-reactions': [
101
+ {
102
+ dependencies: ['collection', 'global'],
103
+ fulfill: {
104
+ state: {
105
+ visible: '{{!!$deps[0] && !!$deps[1]}}',
106
+ },
107
+ },
108
+ },
109
+ ],
110
+ },
41
111
  appends: {
42
112
  type: 'array',
43
113
  title: `{{t("Associations to use", { ns: "${NAMESPACE}" })}}`,
@@ -68,8 +138,15 @@ export default class extends Trigger {
68
138
  useCollectionDataSource,
69
139
  useWorkflowAnyExecuted,
70
140
  };
141
+ components = {
142
+ RadioWithTooltip,
143
+ CheckboxGroupWithTooltip,
144
+ };
71
145
  isActionTriggerable = (config, context) => {
72
- return ['create', 'update', 'customize:update', 'customize:triggerWorkflows'].includes(context.action);
146
+ return (
147
+ context.action === 'customize:triggerWorkflows' ||
148
+ (['create', 'update', 'customize:update'].includes(context.action) && !config.global)
149
+ );
73
150
  };
74
151
  useVariables(config, options) {
75
152
  // eslint-disable-next-line react-hooks/rules-of-hooks
@@ -56,7 +56,7 @@ test.describe('Add new', () => {
56
56
  const workFlowName = faker.string.alphanumeric(5);
57
57
  await createWorkFlow.name.fill(workFlowName);
58
58
  await createWorkFlow.triggerType.click();
59
- await page.getByRole('option', { name: 'Action event' }).click();
59
+ await page.getByTitle('Post-action event').click();
60
60
  await page.getByLabel('action-Action-Submit-workflows').click();
61
61
 
62
62
  // 3、预期结果:列表中出现新建的工作流
@@ -13,9 +13,7 @@ const submitToWorkflowActionInitializer: SchemaInitializerItemType = {
13
13
  schema: {
14
14
  title: '{{t("Submit to workflow", { ns: "workflow" })}}',
15
15
  'x-component': 'Action',
16
- 'x-component-props': {
17
- useProps: '{{ useTriggerWorkflowsActionProps }}',
18
- },
16
+ 'x-use-component-props': 'useTriggerWorkflowsActionProps',
19
17
  'x-designer': 'Action.Designer',
20
18
  'x-action-settings': {
21
19
  // assignedValues: {},
@@ -38,9 +36,7 @@ const recordTriggerWorkflowActionInitializer: SchemaInitializerItemType = {
38
36
  schema: {
39
37
  title: '{{t("Submit to workflow", { ns: "workflow" })}}',
40
38
  'x-component': 'Action',
41
- 'x-component-props': {
42
- useProps: '{{ useRecordTriggerWorkflowsActionProps }}',
43
- },
39
+ 'x-use-component-props': 'useRecordTriggerWorkflowsActionProps',
44
40
  'x-designer': 'Action.Designer',
45
41
  'x-action-settings': {
46
42
  // assignedValues: {},
@@ -1,8 +1,16 @@
1
1
  {
2
- "Action event": "操作事件",
3
- "Triggers after specific operations on data are submitted, such as create, update, delete, etc., or directly submitting a record to the workflow.": "在对数据的特定操作提交后触发,如创建、更新、删除等,或直接提交一条数据至工作流。",
2
+ "Post-action event": "操作后事件",
3
+ "Triggered after the completion of a request initiated through an action button or API, such as after adding, updating, deleting data, or \"submit to workflow\". Suitable for data processing, sending notifications, etc., after actions are completed.":
4
+ "通过操作按钮或 API 发起请求并在执行完成后触发,比如新增、更新、删除数据或者“提交至工作流”之后。适用于在操作完成后进行数据处理、发送通知等。",
4
5
  "Collection": "数据表",
5
- "Which collection record belongs to.": "数据所属的数据表。",
6
+ "The collection to which the triggered data belongs.": "触发数据所属的数据表。",
7
+ "Trigger mode": "触发模式",
8
+ "Local mode, triggered after the completion of actions bound to this workflow": "局部模式,绑定该工作流的操作执行完成后触发",
9
+ "Global mode, triggered after the completion of the following actions": "全局模式,以下操作执行完成后都触发",
10
+ "Action to submit to workflow directly is only supported on bound buttons, and will not be affected under global mode.": "直接提交至工作流的操作仅支持使用按钮绑定,不受全局模式的影响。",
11
+ "Select actions": "选择操作",
12
+ "Create record action": "创建记录操作",
13
+ "Update record action": "更新记录操作",
6
14
  "Associations to use": "待使用的关系数据",
7
15
  "Trigger data": "触发器数据",
8
16
  "User submitted action": "提交操作的用户",
@@ -5,18 +5,20 @@ import Application, { DefaultContext } from '@nocobase/server';
5
5
  import { Context as ActionContext, Next } from '@nocobase/actions';
6
6
 
7
7
  import WorkflowPlugin, { Trigger, WorkflowModel, toJSON } from '@nocobase/plugin-workflow';
8
- import { parseCollectionName } from '@nocobase/data-source-manager';
8
+ import { joinCollectionName, parseCollectionName } from '@nocobase/data-source-manager';
9
9
 
10
10
  interface Context extends ActionContext, DefaultContext {}
11
11
 
12
12
  export default class extends Trigger {
13
+ static TYPE = 'action';
14
+
13
15
  constructor(workflow: WorkflowPlugin) {
14
16
  super(workflow);
15
17
 
16
18
  workflow.app.use(this.middleware, { after: 'dataSource' });
17
19
  }
18
20
 
19
- async triggerAction(context: Context, next: Next) {
21
+ async workflowTriggerAction(context: Context, next: Next) {
20
22
  const { triggerWorkflows } = context.action.params;
21
23
 
22
24
  if (!triggerWorkflows) {
@@ -26,37 +28,33 @@ export default class extends Trigger {
26
28
  context.status = 202;
27
29
  await next();
28
30
 
29
- this.trigger(context);
31
+ return this.collectionTriggerAction(context);
30
32
  }
31
33
 
32
34
  middleware = async (context: Context, next: Next) => {
33
- const {
34
- resourceName,
35
- actionName,
36
- params: { triggerWorkflows },
37
- } = context.action;
35
+ const { resourceName, actionName } = context.action;
38
36
 
39
37
  if (resourceName === 'workflows' && actionName === 'trigger') {
40
- return this.triggerAction(context, next);
38
+ return this.workflowTriggerAction(context, next);
41
39
  }
42
40
 
43
41
  await next();
44
42
 
45
- if (!triggerWorkflows) {
46
- return;
47
- }
48
-
49
43
  if (!['create', 'update'].includes(actionName)) {
50
44
  return;
51
45
  }
52
46
 
53
- return this.trigger(context);
47
+ return this.collectionTriggerAction(context);
54
48
  };
55
49
 
56
- private async trigger(context: Context) {
57
- const { triggerWorkflows = '', values } = context.action.params;
50
+ private async collectionTriggerAction(context: Context) {
51
+ const {
52
+ resourceName,
53
+ actionName,
54
+ params: { triggerWorkflows = '', values },
55
+ } = context.action;
58
56
  const dataSourceHeader = context.get('x-data-source') || 'main';
59
-
57
+ const fullCollectionName = joinCollectionName(dataSourceHeader, resourceName);
60
58
  const { currentUser, currentRole } = context.state;
61
59
  const { model: UserModel } = this.workflow.db.getCollection('users');
62
60
  const userInfo = {
@@ -65,23 +63,41 @@ export default class extends Trigger {
65
63
  };
66
64
 
67
65
  const triggers = triggerWorkflows.split(',').map((trigger) => trigger.split('!'));
68
- const workflowRepo = this.workflow.db.getRepository('workflows');
69
- const workflows = (
70
- await workflowRepo.find({
71
- filter: {
72
- key: triggers.map((trigger) => trigger[0]),
73
- current: true,
74
- type: 'action',
75
- enabled: true,
76
- },
77
- })
78
- ).filter((workflow) => Boolean(workflow.config.collection));
66
+ const triggersKeysMap = new Map<string, string>(triggers);
67
+ const workflows = Array.from(this.workflow.enabledCache.values()).filter(
68
+ (item) => item.type === 'action' && item.config.collection,
69
+ );
70
+ const globalWorkflows = new Map();
71
+ const localWorkflows = new Map();
72
+ workflows.forEach((item) => {
73
+ if (resourceName === 'workflows' && actionName === 'trigger') {
74
+ localWorkflows.set(item.key, item);
75
+ } else if (item.config.collection === fullCollectionName) {
76
+ if (item.config.global) {
77
+ if (item.config.actions?.includes(actionName)) {
78
+ globalWorkflows.set(item.key, item);
79
+ }
80
+ } else {
81
+ localWorkflows.set(item.key, item);
82
+ }
83
+ }
84
+ });
85
+ const triggeringLocalWorkflows = [];
86
+ const uniqueTriggersMap = new Map();
87
+ triggers.forEach((trigger) => {
88
+ const [key] = trigger;
89
+ const workflow = localWorkflows.get(key);
90
+ if (workflow && !uniqueTriggersMap.has(key)) {
91
+ triggeringLocalWorkflows.push(workflow);
92
+ uniqueTriggersMap.set(key, true);
93
+ }
94
+ });
79
95
  const syncGroup = [];
80
96
  const asyncGroup = [];
81
- for (const workflow of workflows) {
97
+ for (const workflow of triggeringLocalWorkflows.concat(...globalWorkflows.values())) {
82
98
  const { collection, appends = [] } = workflow.config;
83
99
  const [dataSourceName, collectionName] = parseCollectionName(collection);
84
- const trigger = triggers.find((trigger) => trigger[0] == workflow.key);
100
+ const dataPath = triggersKeysMap.get(workflow.key);
85
101
  const event = [workflow];
86
102
  if (context.action.resourceName !== 'workflows') {
87
103
  if (!context.body) {
@@ -93,9 +109,12 @@ export default class extends Trigger {
93
109
  const { body: data } = context;
94
110
  for (const row of Array.isArray(data) ? data : [data]) {
95
111
  let payload = row;
96
- if (trigger[1]) {
97
- const paths = trigger[1].split('.');
112
+ if (dataPath) {
113
+ const paths = dataPath.split('.');
98
114
  for (const field of paths) {
115
+ if (!payload) {
116
+ break;
117
+ }
99
118
  if (payload.get(field)) {
100
119
  payload = payload.get(field);
101
120
  } else {
@@ -104,37 +123,32 @@ export default class extends Trigger {
104
123
  }
105
124
  }
106
125
  }
107
- const model = payload.constructor;
108
126
  if (payload instanceof Model) {
127
+ const model = payload.constructor as unknown as Model;
109
128
  if (collectionName !== model.collection.name) {
110
129
  continue;
111
130
  }
112
131
  if (appends.length) {
113
132
  payload = await model.collection.repository.findOne({
114
- filterByTk: payload.get(model.primaryKeyAttribute),
133
+ filterByTk: payload.get(model.collection.filterTargetKey),
115
134
  appends,
116
135
  });
117
136
  }
118
137
  }
119
- // this.workflow.trigger(workflow, { data: toJSON(payload), ...userInfo });
120
138
  event.push({ data: toJSON(payload), ...userInfo });
121
139
  }
122
140
  } else {
123
- const { model, repository } = (<Application>context.app).dataSourceManager.dataSources
141
+ const { filterTargetKey, repository } = (<Application>context.app).dataSourceManager.dataSources
124
142
  .get(dataSourceName)
125
143
  .collectionManager.getCollection(collectionName);
126
- let data = trigger[1] ? get(values, trigger[1]) : values;
127
- const pk = get(data, model.primaryKeyAttribute);
144
+ let data = dataPath ? get(values, dataPath) : values;
145
+ const pk = get(data, filterTargetKey);
128
146
  if (appends.length && pk != null) {
129
147
  data = await repository.findOne({
130
148
  filterByTk: pk,
131
149
  appends,
132
150
  });
133
151
  }
134
- // this.workflow.trigger(workflow, {
135
- // data,
136
- // ...userInfo,
137
- // });
138
152
  event.push({ data, ...userInfo });
139
153
  }
140
154
  (workflow.sync ? syncGroup : asyncGroup).push(event);
@@ -572,6 +572,126 @@ describe('workflow > action-trigger', () => {
572
572
  });
573
573
  });
574
574
 
575
+ describe('global workflow', () => {
576
+ it('no action configured should not be triggered', async () => {
577
+ const workflow = await WorkflowModel.create({
578
+ enabled: true,
579
+ type: 'action',
580
+ config: {
581
+ collection: 'posts',
582
+ global: true,
583
+ },
584
+ });
585
+
586
+ const res1 = await userAgents[0].resource('posts').create({
587
+ values: { title: 't1' },
588
+ });
589
+ expect(res1.status).toBe(200);
590
+
591
+ await sleep(500);
592
+
593
+ const e1 = await workflow.getExecutions();
594
+ expect(e1.length).toBe(0);
595
+
596
+ const res2 = await userAgents[0].resource('posts').create({
597
+ values: { title: 't1' },
598
+ triggerWorkflows: `${workflow.key}`,
599
+ });
600
+ expect(res2.status).toBe(200);
601
+
602
+ await sleep(500);
603
+
604
+ const e2 = await workflow.getExecutions();
605
+ expect(e2.length).toBe(0);
606
+ });
607
+
608
+ it('trigger on both create and update actions', async () => {
609
+ const workflow = await WorkflowModel.create({
610
+ enabled: true,
611
+ type: 'action',
612
+ config: {
613
+ collection: 'posts',
614
+ global: true,
615
+ actions: ['create', 'update'],
616
+ },
617
+ });
618
+
619
+ const res1 = await userAgents[0].resource('posts').create({
620
+ values: { title: 't1' },
621
+ });
622
+ expect(res1.status).toBe(200);
623
+
624
+ await sleep(500);
625
+
626
+ const e1 = await workflow.getExecutions();
627
+ expect(e1.length).toBe(1);
628
+ expect(e1[0].status).toBe(EXECUTION_STATUS.RESOLVED);
629
+ expect(e1[0].context.data).toMatchObject({ title: 't1' });
630
+
631
+ const res2 = await userAgents[0].resource('posts').update({
632
+ filterByTk: res1.body.data.id,
633
+ values: { title: 't2' },
634
+ });
635
+
636
+ await sleep(500);
637
+
638
+ const e2 = await workflow.getExecutions({ order: [['id', 'ASC']] });
639
+ expect(e2.length).toBe(2);
640
+ expect(e2[1].status).toBe(EXECUTION_STATUS.RESOLVED);
641
+ expect(e2[1].context.data).toMatchObject({ title: 't2' });
642
+ });
643
+
644
+ it('trigger on action when bound to button', async () => {
645
+ const workflow = await WorkflowModel.create({
646
+ enabled: true,
647
+ type: 'action',
648
+ config: {
649
+ collection: 'posts',
650
+ global: true,
651
+ actions: ['create', 'update'],
652
+ },
653
+ });
654
+
655
+ const res1 = await userAgents[0].resource('posts').create({
656
+ values: { title: 't1' },
657
+ triggerWorkflows: `${workflow.key}`,
658
+ });
659
+ expect(res1.status).toBe(200);
660
+
661
+ await sleep(500);
662
+
663
+ const e1 = await workflow.getExecutions();
664
+ expect(e1.length).toBe(1);
665
+ expect(e1[0].status).toBe(EXECUTION_STATUS.RESOLVED);
666
+ expect(e1[0].context.data).toMatchObject({ title: 't1' });
667
+ });
668
+
669
+ it('trigger on action directly submit to workflow', async () => {
670
+ const workflow = await WorkflowModel.create({
671
+ enabled: true,
672
+ type: 'action',
673
+ config: {
674
+ collection: 'posts',
675
+ global: true,
676
+ actions: ['create', 'update'],
677
+ },
678
+ });
679
+
680
+ const res1 = await userAgents[0].resource('workflows').trigger({
681
+ values: { title: 't1' },
682
+ triggerWorkflows: `${workflow.key}`,
683
+ });
684
+ expect(res1.status).toBe(202);
685
+
686
+ await sleep(500);
687
+
688
+ const e1 = await workflow.getExecutions();
689
+ expect(e1.length).toBe(1);
690
+ expect(e1[0].status).toBe(EXECUTION_STATUS.RESOLVED);
691
+ expect(e1[0].context.data).toMatchObject({ title: 't1' });
692
+ });
693
+ });
694
+
575
695
  describe('multiple data source', () => {
576
696
  it('trigger on different data source', async () => {
577
697
  const workflow = await WorkflowModel.create({