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

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.11",
4
+ "@nocobase/plugin-workflow": "0.21.0-alpha.11",
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.11",
9
+ "@nocobase/server": "0.21.0-alpha.11",
10
+ "@nocobase/actions": "0.21.0-alpha.11",
11
+ "@nocobase/data-source-manager": "0.21.0-alpha.11",
12
+ "@nocobase/plugin-workflow-test": "0.21.0-alpha.11",
13
+ "@nocobase/test": "0.21.0-alpha.11",
14
+ "@nocobase/utils": "0.21.0-alpha.11"
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,43 @@ 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 collection = context.app.dataSourceManager.dataSources.get(dataSourceHeader).collectionManager.getCollection(resourceName);
61
+ if (!collection) {
62
+ return;
63
+ }
64
+ const fullCollectionName = (0, import_data_source_manager.joinCollectionName)(dataSourceHeader, collection.name);
62
65
  const { currentUser, currentRole } = context.state;
63
66
  const { model: UserModel } = this.workflow.db.getCollection("users");
64
67
  const userInfo = {
@@ -66,21 +69,42 @@ class ActionTrigger_default extends import_plugin_workflow.Trigger {
66
69
  roleName: currentRole
67
70
  };
68
71
  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
72
+ const triggersKeysMap = new Map(triggers);
73
+ const workflows = Array.from(this.workflow.enabledCache.values()).filter(
74
+ (item) => item.type === "action" && item.config.collection
75
+ );
76
+ const globalWorkflows = /* @__PURE__ */ new Map();
77
+ const localWorkflows = /* @__PURE__ */ new Map();
78
+ workflows.forEach((item) => {
79
+ var _a;
80
+ if (resourceName === "workflows" && actionName === "trigger") {
81
+ localWorkflows.set(item.key, item);
82
+ } else if (item.config.collection === fullCollectionName) {
83
+ if (item.config.global) {
84
+ if ((_a = item.config.actions) == null ? void 0 : _a.includes(actionName)) {
85
+ globalWorkflows.set(item.key, item);
86
+ }
87
+ } else {
88
+ localWorkflows.set(item.key, item);
89
+ }
76
90
  }
77
- })).filter((workflow) => Boolean(workflow.config.collection));
91
+ });
92
+ const triggeringLocalWorkflows = [];
93
+ const uniqueTriggersMap = /* @__PURE__ */ new Map();
94
+ triggers.forEach((trigger) => {
95
+ const [key] = trigger;
96
+ const workflow = localWorkflows.get(key);
97
+ if (workflow && !uniqueTriggersMap.has(key)) {
98
+ triggeringLocalWorkflows.push(workflow);
99
+ uniqueTriggersMap.set(key, true);
100
+ }
101
+ });
78
102
  const syncGroup = [];
79
103
  const asyncGroup = [];
80
- for (const workflow of workflows) {
81
- const { collection, appends = [] } = workflow.config;
82
- const [dataSourceName, collectionName] = (0, import_data_source_manager.parseCollectionName)(collection);
83
- const trigger = triggers.find((trigger2) => trigger2[0] == workflow.key);
104
+ for (const workflow of triggeringLocalWorkflows.concat(...globalWorkflows.values())) {
105
+ const { appends = [] } = workflow.config;
106
+ const [dataSourceName, collectionName] = (0, import_data_source_manager.parseCollectionName)(workflow.config.collection);
107
+ const dataPath = triggersKeysMap.get(workflow.key);
84
108
  const event = [workflow];
85
109
  if (context.action.resourceName !== "workflows") {
86
110
  if (!context.body) {
@@ -92,9 +116,12 @@ class ActionTrigger_default extends import_plugin_workflow.Trigger {
92
116
  const { body: data } = context;
93
117
  for (const row of Array.isArray(data) ? data : [data]) {
94
118
  let payload = row;
95
- if (trigger[1]) {
96
- const paths = trigger[1].split(".");
119
+ if (dataPath) {
120
+ const paths = dataPath.split(".");
97
121
  for (const field of paths) {
122
+ if (!payload) {
123
+ break;
124
+ }
98
125
  if (payload.get(field)) {
99
126
  payload = payload.get(field);
100
127
  } else {
@@ -103,14 +130,14 @@ class ActionTrigger_default extends import_plugin_workflow.Trigger {
103
130
  }
104
131
  }
105
132
  }
106
- const model = payload.constructor;
107
133
  if (payload instanceof import_database.Model) {
134
+ const model = payload.constructor;
108
135
  if (collectionName !== model.collection.name) {
109
136
  continue;
110
137
  }
111
138
  if (appends.length) {
112
139
  payload = await model.collection.repository.findOne({
113
- filterByTk: payload.get(model.primaryKeyAttribute),
140
+ filterByTk: payload.get(model.collection.filterTargetKey),
114
141
  appends
115
142
  });
116
143
  }
@@ -118,9 +145,9 @@ class ActionTrigger_default extends import_plugin_workflow.Trigger {
118
145
  event.push({ data: (0, import_plugin_workflow.toJSON)(payload), ...userInfo });
119
146
  }
120
147
  } 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);
148
+ const { filterTargetKey, repository } = context.app.dataSourceManager.dataSources.get(dataSourceName).collectionManager.getCollection(collectionName);
149
+ let data = dataPath ? (0, import_lodash.get)(values, dataPath) : values;
150
+ const pk = (0, import_lodash.get)(data, filterTargetKey);
124
151
  if (appends.length && pk != null) {
125
152
  data = await repository.findOne({
126
153
  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.11",
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": "69fe8a6d75864a3ba98c5a6d3ebfe43a585d4cd3",
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,41 @@ 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';
57
+ const collection = context.app.dataSourceManager.dataSources
58
+ .get(dataSourceHeader)
59
+ .collectionManager.getCollection(resourceName);
60
+
61
+ if (!collection) {
62
+ return;
63
+ }
59
64
 
65
+ const fullCollectionName = joinCollectionName(dataSourceHeader, collection.name);
60
66
  const { currentUser, currentRole } = context.state;
61
67
  const { model: UserModel } = this.workflow.db.getCollection('users');
62
68
  const userInfo = {
@@ -65,23 +71,41 @@ export default class extends Trigger {
65
71
  };
66
72
 
67
73
  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));
74
+ const triggersKeysMap = new Map<string, string>(triggers);
75
+ const workflows = Array.from(this.workflow.enabledCache.values()).filter(
76
+ (item) => item.type === 'action' && item.config.collection,
77
+ );
78
+ const globalWorkflows = new Map();
79
+ const localWorkflows = new Map();
80
+ workflows.forEach((item) => {
81
+ if (resourceName === 'workflows' && actionName === 'trigger') {
82
+ localWorkflows.set(item.key, item);
83
+ } else if (item.config.collection === fullCollectionName) {
84
+ if (item.config.global) {
85
+ if (item.config.actions?.includes(actionName)) {
86
+ globalWorkflows.set(item.key, item);
87
+ }
88
+ } else {
89
+ localWorkflows.set(item.key, item);
90
+ }
91
+ }
92
+ });
93
+ const triggeringLocalWorkflows = [];
94
+ const uniqueTriggersMap = new Map();
95
+ triggers.forEach((trigger) => {
96
+ const [key] = trigger;
97
+ const workflow = localWorkflows.get(key);
98
+ if (workflow && !uniqueTriggersMap.has(key)) {
99
+ triggeringLocalWorkflows.push(workflow);
100
+ uniqueTriggersMap.set(key, true);
101
+ }
102
+ });
79
103
  const syncGroup = [];
80
104
  const asyncGroup = [];
81
- for (const workflow of workflows) {
82
- const { collection, appends = [] } = workflow.config;
83
- const [dataSourceName, collectionName] = parseCollectionName(collection);
84
- const trigger = triggers.find((trigger) => trigger[0] == workflow.key);
105
+ for (const workflow of triggeringLocalWorkflows.concat(...globalWorkflows.values())) {
106
+ const { appends = [] } = workflow.config;
107
+ const [dataSourceName, collectionName] = parseCollectionName(workflow.config.collection);
108
+ const dataPath = triggersKeysMap.get(workflow.key);
85
109
  const event = [workflow];
86
110
  if (context.action.resourceName !== 'workflows') {
87
111
  if (!context.body) {
@@ -93,9 +117,12 @@ export default class extends Trigger {
93
117
  const { body: data } = context;
94
118
  for (const row of Array.isArray(data) ? data : [data]) {
95
119
  let payload = row;
96
- if (trigger[1]) {
97
- const paths = trigger[1].split('.');
120
+ if (dataPath) {
121
+ const paths = dataPath.split('.');
98
122
  for (const field of paths) {
123
+ if (!payload) {
124
+ break;
125
+ }
99
126
  if (payload.get(field)) {
100
127
  payload = payload.get(field);
101
128
  } else {
@@ -104,37 +131,32 @@ export default class extends Trigger {
104
131
  }
105
132
  }
106
133
  }
107
- const model = payload.constructor;
108
134
  if (payload instanceof Model) {
135
+ const model = payload.constructor as unknown as Model;
109
136
  if (collectionName !== model.collection.name) {
110
137
  continue;
111
138
  }
112
139
  if (appends.length) {
113
140
  payload = await model.collection.repository.findOne({
114
- filterByTk: payload.get(model.primaryKeyAttribute),
141
+ filterByTk: payload.get(model.collection.filterTargetKey),
115
142
  appends,
116
143
  });
117
144
  }
118
145
  }
119
- // this.workflow.trigger(workflow, { data: toJSON(payload), ...userInfo });
120
146
  event.push({ data: toJSON(payload), ...userInfo });
121
147
  }
122
148
  } else {
123
- const { model, repository } = (<Application>context.app).dataSourceManager.dataSources
149
+ const { filterTargetKey, repository } = (<Application>context.app).dataSourceManager.dataSources
124
150
  .get(dataSourceName)
125
151
  .collectionManager.getCollection(collectionName);
126
- let data = trigger[1] ? get(values, trigger[1]) : values;
127
- const pk = get(data, model.primaryKeyAttribute);
152
+ let data = dataPath ? get(values, dataPath) : values;
153
+ const pk = get(data, filterTargetKey);
128
154
  if (appends.length && pk != null) {
129
155
  data = await repository.findOne({
130
156
  filterByTk: pk,
131
157
  appends,
132
158
  });
133
159
  }
134
- // this.workflow.trigger(workflow, {
135
- // data,
136
- // ...userInfo,
137
- // });
138
160
  event.push({ data, ...userInfo });
139
161
  }
140
162
  (workflow.sync ? syncGroup : asyncGroup).push(event);
@@ -10,7 +10,7 @@ describe('workflow > action-trigger', () => {
10
10
  let db: Database;
11
11
  let agent;
12
12
  let PostRepo;
13
- let CommentRepo;
13
+ let CategoryRepo;
14
14
  let WorkflowModel;
15
15
  let UserRepo;
16
16
  let users;
@@ -25,7 +25,7 @@ describe('workflow > action-trigger', () => {
25
25
  db = app.db;
26
26
  WorkflowModel = db.getCollection('workflows').model;
27
27
  PostRepo = db.getCollection('posts').repository;
28
- CommentRepo = db.getCollection('comments').repository;
28
+ CategoryRepo = db.getCollection('categories').repository;
29
29
  UserRepo = db.getCollection('users').repository;
30
30
 
31
31
  users = await UserRepo.create({
@@ -461,6 +461,33 @@ describe('workflow > action-trigger', () => {
461
461
  });
462
462
  });
463
463
 
464
+ describe('associations actions', () => {
465
+ it('trigger on associated data', async () => {
466
+ const workflow = await WorkflowModel.create({
467
+ enabled: true,
468
+ type: 'action',
469
+ config: {
470
+ collection: 'posts',
471
+ },
472
+ });
473
+
474
+ const c1 = await CategoryRepo.create({ values: { title: 'c1' } });
475
+
476
+ const res1 = await userAgents[0].resource('categories.posts', c1.id).create({
477
+ values: { title: 'p1' },
478
+ triggerWorkflows: `${workflow.key}`,
479
+ });
480
+ expect(res1.status).toBe(200);
481
+
482
+ await sleep(500);
483
+
484
+ const e1s = await workflow.getExecutions();
485
+ expect(e1s.length).toBe(1);
486
+ expect(e1s[0].status).toBe(EXECUTION_STATUS.RESOLVED);
487
+ expect(e1s[0].context.data).toMatchObject({ title: 'p1', categoryId: c1.id });
488
+ });
489
+ });
490
+
464
491
  describe('workflow key', () => {
465
492
  it('revision', async () => {
466
493
  const w1 = await WorkflowModel.create({
@@ -572,6 +599,126 @@ describe('workflow > action-trigger', () => {
572
599
  });
573
600
  });
574
601
 
602
+ describe('global workflow', () => {
603
+ it('no action configured should not be triggered', async () => {
604
+ const workflow = await WorkflowModel.create({
605
+ enabled: true,
606
+ type: 'action',
607
+ config: {
608
+ collection: 'posts',
609
+ global: true,
610
+ },
611
+ });
612
+
613
+ const res1 = await userAgents[0].resource('posts').create({
614
+ values: { title: 't1' },
615
+ });
616
+ expect(res1.status).toBe(200);
617
+
618
+ await sleep(500);
619
+
620
+ const e1 = await workflow.getExecutions();
621
+ expect(e1.length).toBe(0);
622
+
623
+ const res2 = await userAgents[0].resource('posts').create({
624
+ values: { title: 't1' },
625
+ triggerWorkflows: `${workflow.key}`,
626
+ });
627
+ expect(res2.status).toBe(200);
628
+
629
+ await sleep(500);
630
+
631
+ const e2 = await workflow.getExecutions();
632
+ expect(e2.length).toBe(0);
633
+ });
634
+
635
+ it('trigger on both create and update actions', async () => {
636
+ const workflow = await WorkflowModel.create({
637
+ enabled: true,
638
+ type: 'action',
639
+ config: {
640
+ collection: 'posts',
641
+ global: true,
642
+ actions: ['create', 'update'],
643
+ },
644
+ });
645
+
646
+ const res1 = await userAgents[0].resource('posts').create({
647
+ values: { title: 't1' },
648
+ });
649
+ expect(res1.status).toBe(200);
650
+
651
+ await sleep(500);
652
+
653
+ const e1 = await workflow.getExecutions();
654
+ expect(e1.length).toBe(1);
655
+ expect(e1[0].status).toBe(EXECUTION_STATUS.RESOLVED);
656
+ expect(e1[0].context.data).toMatchObject({ title: 't1' });
657
+
658
+ const res2 = await userAgents[0].resource('posts').update({
659
+ filterByTk: res1.body.data.id,
660
+ values: { title: 't2' },
661
+ });
662
+
663
+ await sleep(500);
664
+
665
+ const e2 = await workflow.getExecutions({ order: [['id', 'ASC']] });
666
+ expect(e2.length).toBe(2);
667
+ expect(e2[1].status).toBe(EXECUTION_STATUS.RESOLVED);
668
+ expect(e2[1].context.data).toMatchObject({ title: 't2' });
669
+ });
670
+
671
+ it('trigger on action when bound to button', async () => {
672
+ const workflow = await WorkflowModel.create({
673
+ enabled: true,
674
+ type: 'action',
675
+ config: {
676
+ collection: 'posts',
677
+ global: true,
678
+ actions: ['create', 'update'],
679
+ },
680
+ });
681
+
682
+ const res1 = await userAgents[0].resource('posts').create({
683
+ values: { title: 't1' },
684
+ triggerWorkflows: `${workflow.key}`,
685
+ });
686
+ expect(res1.status).toBe(200);
687
+
688
+ await sleep(500);
689
+
690
+ const e1 = await workflow.getExecutions();
691
+ expect(e1.length).toBe(1);
692
+ expect(e1[0].status).toBe(EXECUTION_STATUS.RESOLVED);
693
+ expect(e1[0].context.data).toMatchObject({ title: 't1' });
694
+ });
695
+
696
+ it('trigger on action directly submit to workflow', async () => {
697
+ const workflow = await WorkflowModel.create({
698
+ enabled: true,
699
+ type: 'action',
700
+ config: {
701
+ collection: 'posts',
702
+ global: true,
703
+ actions: ['create', 'update'],
704
+ },
705
+ });
706
+
707
+ const res1 = await userAgents[0].resource('workflows').trigger({
708
+ values: { title: 't1' },
709
+ triggerWorkflows: `${workflow.key}`,
710
+ });
711
+ expect(res1.status).toBe(202);
712
+
713
+ await sleep(500);
714
+
715
+ const e1 = await workflow.getExecutions();
716
+ expect(e1.length).toBe(1);
717
+ expect(e1[0].status).toBe(EXECUTION_STATUS.RESOLVED);
718
+ expect(e1[0].context.data).toMatchObject({ title: 't1' });
719
+ });
720
+ });
721
+
575
722
  describe('multiple data source', () => {
576
723
  it('trigger on different data source', async () => {
577
724
  const workflow = await WorkflowModel.create({