@steedos-labs/plugin-workflow 3.0.0-beta.9 → 3.0.1-beta.1

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.
Files changed (82) hide show
  1. package/main/default/client/instance.client.js +6 -6
  2. package/main/default/client/object_workflows.client.js +8 -7
  3. package/main/default/client/socket.client.js +46 -3
  4. package/main/default/manager/import.js +17 -1
  5. package/main/default/manager/instance_manager.js +20 -6
  6. package/main/default/manager/push_manager.js +20 -12
  7. package/main/default/manager/uuflowManagerForInitApproval.js +794 -0
  8. package/main/default/manager/uuflow_manager.js +53 -4
  9. package/main/default/manager/workflow_manager.js +1 -1
  10. package/main/default/methods/instance_approve.js +258 -0
  11. package/main/default/methods/trace_approve_cc.js +571 -0
  12. package/main/default/objectTranslations/flows.en/flows.en.objectTranslation.yml +19 -0
  13. package/main/default/objectTranslations/flows.zh-CN/flows.zh-CN.objectTranslation.yml +19 -0
  14. package/main/default/objectTranslations/forms.en/forms.en.objectTranslation.yml +191 -0
  15. package/main/default/objectTranslations/forms.zh-CN/forms.zh-CN.objectTranslation.yml +246 -0
  16. package/main/default/objectTranslations/instance_tasks.en/instance_tasks.en.objectTranslation.yml +213 -0
  17. package/main/default/objectTranslations/instance_tasks.zh-CN/instance_tasks.zh-CN.objectTranslation.yml +213 -0
  18. package/main/default/objectTranslations/instances.en/instances.en.objectTranslation.yml +212 -0
  19. package/main/default/objectTranslations/instances.zh-CN/instances.zh-CN.objectTranslation.yml +209 -0
  20. package/main/default/objects/categories.object.yml +1 -0
  21. package/main/default/objects/flows/buttons/del.button.yml +7 -10
  22. package/main/default/objects/flows/buttons/design_form_layout.button.js +5 -2
  23. package/main/default/objects/flows/buttons/distributeAdmin.button.yml +5 -5
  24. package/main/default/objects/flows/buttons/newexport.button.yml +1 -1
  25. package/main/default/objects/flows/buttons/newimport.button.yml +2 -1
  26. package/main/default/objects/flows/flows.object.yml +12 -4
  27. package/main/default/objects/forms/forms.object.yml +85 -0
  28. package/main/default/objects/instance_tasks/buttons/instance_new.button.yml +3 -5
  29. package/main/default/objects/instances/buttons/instance_cc.button.yml +7 -7
  30. package/main/default/objects/instances/buttons/instance_delete.button.yml +2 -2
  31. package/main/default/objects/instances/buttons/instance_delete_many.button.yml +6 -6
  32. package/main/default/objects/instances/buttons/instance_distribute.button.yml +14 -13
  33. package/main/default/objects/instances/buttons/instance_flow_chart.button.yml +1 -1
  34. package/main/default/objects/instances/buttons/instance_forward.button.yml +8 -8
  35. package/main/default/objects/instances/buttons/instance_new.button.yml +3 -5
  36. package/main/default/objects/instances/buttons/instance_reassign.button.yml +1 -16
  37. package/main/default/objects/instances/buttons/instance_related.button.yml +4 -4
  38. package/main/default/objects/instances/buttons/instance_relocate.button.yml +9 -12
  39. package/main/default/objects/instances/buttons/instance_retrieve.button.yml +106 -2
  40. package/main/default/objects/instances/buttons/instance_save.button.yml +3 -9
  41. package/main/default/objects/instances/buttons/instance_submit.button.yml +1 -1
  42. package/main/default/objects/instances/buttons/instance_terminate.button.yml +7 -10
  43. package/main/default/objects/instances/listviews/monitor.listview.yml +0 -1
  44. package/main/default/pages/flowdetail.page.amis.json +11 -11
  45. package/main/default/pages/instance_detail.page.amis.json +25 -37
  46. package/main/default/pages/instance_tasks_detail.page.amis.json +21 -5
  47. package/main/default/pages/instance_tasks_list.page.amis.json +147 -110
  48. package/main/default/pages/instances_list.page.amis.json +146 -110
  49. package/main/default/routes/afterHook.js +34 -0
  50. package/main/default/routes/am.router.js +49 -2
  51. package/main/default/routes/api_cc.router.js +5 -12
  52. package/main/default/routes/api_flow_permission.router.js +7 -2
  53. package/main/default/routes/api_get_object_workflows.router.js +79 -22
  54. package/main/default/routes/api_have_read.router.js +73 -0
  55. package/main/default/routes/api_object_workflow_drafts.router.js +18 -19
  56. package/main/default/routes/api_workflow_approve_save.router.js +2 -1
  57. package/main/default/routes/api_workflow_chart.router.js +682 -0
  58. package/main/default/routes/api_workflow_engine.router.js +4 -4
  59. package/main/default/routes/api_workflow_flow_version.router.js +61 -0
  60. package/main/default/routes/api_workflow_form_version.router.js +61 -0
  61. package/main/default/routes/api_workflow_instance_return.router.js +164 -167
  62. package/main/default/routes/api_workflow_next_step_users.router.js +13 -8
  63. package/main/default/routes/api_workflow_reassign.router.js +200 -196
  64. package/main/default/routes/api_workflow_relocate.router.js +4 -3
  65. package/main/default/routes/api_workflow_retrieve.router.js +246 -237
  66. package/main/default/routes/export.router.js +5 -4
  67. package/main/default/routes/flow_form_design.ejs +33 -8
  68. package/main/default/routes/flow_form_design.router.js +4 -3
  69. package/main/default/routes/import.router.js +6 -7
  70. package/main/default/services/flows.service.js +1 -1
  71. package/main/default/translations/en.translation.yml +5 -0
  72. package/main/default/translations/zh-CN.translation.yml +2 -1
  73. package/main/default/triggers/amis_form_design.trigger.js +27 -5
  74. package/main/default/triggers/instances.trigger.js +9 -7
  75. package/main/default/utils/designerManager.js +12 -5
  76. package/package.json +4 -4
  77. package/package.service.js +21 -7
  78. package/public/workflow/index.css +4 -0
  79. package/src/instance_record_queue.js +1 -3
  80. package/src/rests/api_workflow_instance_batch_remove.js +4 -3
  81. package/src/webhook_queue.js +283 -0
  82. package/main/default/manager/index.js +0 -23
@@ -34,8 +34,8 @@ router.get('/api/workflow/form_design', auth.requireAuthentication, async functi
34
34
  // userId: userSession.userId,
35
35
  // authToken: userSession.authToken
36
36
  // }
37
- let locale = "zh-CN";
38
- if (req.query.locale == "en-us") {
37
+ let locale = userSession.language || process.env.STEEDOS_DEFAULT_LANGUAGE || "zh-CN";
38
+ if (req.query.locale?.startsWith('en')) {
39
39
  locale = "en-US";
40
40
  } else if (req.query.locale == "zh-cn") {
41
41
  locale = "zh-CN";
@@ -56,7 +56,8 @@ router.get('/api/workflow/form_design', auth.requireAuthentication, async functi
56
56
  authToken: userSession.authToken,
57
57
  userSession: userSession,
58
58
  id: flow.form,
59
- useOpenAPI: process.env.STEEDOS_PUBLIC_USE_OPEN_API
59
+ useOpenAPI: process.env.STEEDOS_PUBLIC_USE_OPEN_API,
60
+ locale
60
61
  }
61
62
  const options = {}
62
63
  ejs.renderFile(filename, data, options, function(err, str){
@@ -138,12 +138,12 @@ router.post('/api/workflow/import/form', requireAuthentication, async function (
138
138
  } catch (e) {
139
139
  console.log(e)
140
140
  if (e.reason && e.details) {
141
- return fail[filename] = {
141
+ fail[filename] = {
142
142
  reason: e.reason,
143
143
  details: e.details
144
144
  };
145
145
  } else {
146
- return fail[filename] = e.reason || e.message;
146
+ fail[filename] = e.reason || e.message;
147
147
  }
148
148
  }
149
149
  }
@@ -152,16 +152,15 @@ router.post('/api/workflow/import/form', requireAuthentication, async function (
152
152
  } else {
153
153
  res.statusCode = 500;
154
154
  }
155
- return res.end(JSON.stringify({
156
- multiple: multiple,
157
- fail: fail,
158
- success: success
155
+ res.end(JSON.stringify({
156
+ status: res.statusCode ? 0 : 1,
157
+ msg: fail
159
158
  }));
160
159
  } catch (error) {
161
160
  console.log(error)
162
161
  msg = "无效的JSON文件";
163
162
  res.statusCode = 500;
164
- return res.end(msg);
163
+ res.end(msg);
165
164
  }
166
165
  });
167
166
  } catch (e) {
@@ -6,7 +6,7 @@
6
6
  * @Description:
7
7
  */
8
8
  const objectql = require("@steedos/objectql");
9
- const { getStep } = require('../uuFlowManager');
9
+ const { getStep } = require('../uuflowManager');
10
10
  const _ = require("lodash");
11
11
  module.exports = {
12
12
  name: "flows",
@@ -1,2 +1,7 @@
1
+ CustomApplications:
2
+ approve_workflow:
3
+ name: Approval
4
+ description: Provide a graphical form and process design interface to customize the enterprise business approval process.
1
5
  CustomLabels:
6
+ approval_workflow: Workflow
2
7
  flows_tabs_form: Form
@@ -1,2 +1,3 @@
1
1
  CustomLabels:
2
- flows_tabs_form: 表单
2
+ flows_tabs_form: 表单
3
+ approval_workflow: 审批
@@ -56,6 +56,9 @@ const AmisInputTypes = [
56
56
 
57
57
  // getInstanceFormSchema() 在用户创建的表单中获取到type为form name为instanceForm的表单,其他的表单不要
58
58
  function getInstanceFormSchema(amis_schema) {
59
+ if(!amis_schema){
60
+ return ;
61
+ }
59
62
 
60
63
  if (amis_schema.type === 'form' && amis_schema.name === 'instanceForm' || amis_schema.type === 'steedos-flow-form') {
61
64
  const proper_schema = amis_schema;
@@ -94,7 +97,7 @@ function getFormInputFields(formSchema) {
94
97
  let flag = true;
95
98
  // 对符合条件的formSchema做解析处理
96
99
  const field = getInputFiled(bodyItem);
97
- console.log('getFormInputFields field', field);
100
+ // console.log('getFormInputFields field', field);
98
101
  if (field) {
99
102
  //进入if说明是表单项
100
103
  _.each(inputFields, (item) => {
@@ -108,7 +111,7 @@ function getFormInputFields(formSchema) {
108
111
  flag = false;
109
112
  }
110
113
  })
111
- console.log('getFormInputFields flag', flag);
114
+ // console.log('getFormInputFields flag', flag);
112
115
  if (flag) {
113
116
  inputFields.push(field);
114
117
  }
@@ -157,8 +160,8 @@ function transformFormFields(amisField) {
157
160
 
158
161
 
159
162
  if(amisField.type === 'steedos-field'){
160
- return {
161
- _id: new ObjectId().toHexString(),
163
+ const sfield = {
164
+ _id: amisField.config.name,
162
165
  code: amisField.config.name,
163
166
  name: amisField.config.label,
164
167
  is_wide: amisField.config.is_wide,
@@ -170,6 +173,17 @@ function transformFormFields(amisField) {
170
173
  type: 'steedos-field',
171
174
  config: amisField.config
172
175
  }
176
+
177
+ let tempConfig = amisField.config;
178
+
179
+ if (tempConfig.reference_to === "organizations") {
180
+ sfield.type = 'group'
181
+ sfield._type = 'steedos-field'
182
+ }else if(tempConfig.reference_to === "space_users" || tempConfig.reference_to === "users"){
183
+ sfield.type = 'user'
184
+ sfield._type = 'steedos-field'
185
+ }
186
+ return sfield;
173
187
  };
174
188
 
175
189
  let formFieldsItem = {
@@ -451,7 +465,7 @@ function transformFormFields(amisField) {
451
465
 
452
466
  if (tempConfig.reference_to === "organizations") {
453
467
  steedosField.type = 'group'
454
- }else if(tempConfig.reference_to === "space_users"){
468
+ }else if(tempConfig.reference_to === "space_users" || tempConfig.reference_to === "user"){
455
469
  // && tempConfig.reference_to_field === 'user'
456
470
  steedosField.type = 'user'
457
471
  }else{
@@ -520,6 +534,14 @@ module.exports = {
520
534
 
521
535
  // 数据库更新操作:将forms表current的fields字段更新为最终数组
522
536
  form.current.fields = getFinalFormFields(inputFields);
537
+ form.current.amis_schema = this.doc.amis_schema;
538
+ form.current.style = formSchema.style;
539
+ form.current.mode = formSchema.mode;
540
+ form.current.wizard_mode = formSchema.wizard_mode;
541
+ form.style = formSchema.style;
542
+ form.mode = formSchema.mode;
543
+ form.wizard_mode = formSchema.wizard_mode;
544
+ form.description = formSchema.description;
523
545
 
524
546
  // 以下为将解析字段存储的功能,为将解析字段存储至数据库forms表的功能
525
547
  let updatedForms = [];
@@ -22,13 +22,15 @@ module.exports = {
22
22
  },
23
23
 
24
24
  afterDelete: async function () {
25
- const { doc } = this;
26
- let recordIds = doc.record_ids;
27
- if (recordIds && recordIds.length == 1) {
28
- let recordObjName = recordIds[0].o;
29
- let recordId = recordIds[0].ids[0];
30
- if (recordObjName && recordId) {
31
- await objectql.getObject(recordObjName).update(recordId, { "instances": null, "instance_state": null, "locked": false })
25
+ const { previousDoc } = this;
26
+ if(previousDoc){
27
+ let recordIds = previousDoc.record_ids;
28
+ if (recordIds && recordIds.length == 1) {
29
+ let recordObjName = recordIds[0].o;
30
+ let recordId = recordIds[0].ids[0];
31
+ if (recordObjName && recordId) {
32
+ await objectql.getObject(recordObjName).update(recordId, { "instances": null, "instance_state": null, "locked": false })
33
+ }
32
34
  }
33
35
  }
34
36
  },
@@ -134,7 +134,7 @@ async function updateForm(formId, form, forms, flows, currentUserId) {
134
134
  let insCount = await instancesCollection.countDocuments({
135
135
  space: spaceId,
136
136
  form: formId,
137
- 'form_version': form['current']['id']
137
+ form_version: form['current']['id'] || form['current']['_id']
138
138
  });
139
139
  if (insCount > 0) {
140
140
  pass = true;
@@ -144,7 +144,7 @@ async function updateForm(formId, form, forms, flows, currentUserId) {
144
144
  let recordsCount = await recordsCollection.countDocuments({
145
145
  space: spaceId,
146
146
  form: formId,
147
- 'form_version': form['current']['id']
147
+ form_version: form['current']['id'] || form['current']['_id']
148
148
  });
149
149
  if (recordsCount > 0) {
150
150
  pass = true;
@@ -221,6 +221,10 @@ async function updateForm(formId, form, forms, flows, currentUserId) {
221
221
  current.fields = _formatFieldsID(form["current"]["fields"]);
222
222
  current.form_script = form["current"]["form_script"];
223
223
  current.name_forumla = form["current"]["name_forumla"];
224
+ current.style = form["current"]["style"];
225
+ current.mode = form["current"]["mode"];
226
+ current.wizard_mode = form["current"]["wizard_mode"];
227
+ current.amis_schema = form["current"]["amis_schema"]
224
228
 
225
229
  formUpdateObj.$set = {
226
230
  'current': current,
@@ -228,6 +232,9 @@ async function updateForm(formId, form, forms, flows, currentUserId) {
228
232
  'modified': now,
229
233
  'modified_by': currentUserId,
230
234
  'is_valid': form["is_valid"],
235
+ 'style': form["style"],
236
+ 'mode': form["mode"],
237
+ 'wizard_mode': form["wizard_mode"],
231
238
  'description': form["description"],
232
239
  'help_text': form["help_text"],
233
240
  'error_message': form["error_message"],
@@ -293,7 +300,7 @@ async function isSpaceAdmin(spaceId, userId, roles) {
293
300
  }
294
301
  }
295
302
 
296
- async function makeSteps(userId, fields = []) {
303
+ async function makeSteps(userId, fields = [], language) {
297
304
  let blank_ayy = [];
298
305
  let stepEnd = _makeNewID();
299
306
  let steps = [];
@@ -303,7 +310,7 @@ async function makeSteps(userId, fields = []) {
303
310
  start_step.approver_roles = blank_ayy;
304
311
  start_step.approver_users = blank_ayy;
305
312
  start_step.fields_modifiable = blank_ayy;
306
- start_step.name = '开始';
313
+ start_step.name = language === 'zh-CN' ? '开始' : 'Start';
307
314
  start_step.step_type = "start";
308
315
  start_step.posx = -1;
309
316
  start_step.posy = -1;
@@ -336,7 +343,7 @@ async function makeSteps(userId, fields = []) {
336
343
  end_step.approver_users = blank_ayy;
337
344
  end_step.fields_modifiable = blank_ayy;
338
345
  end_step.lines = blank_ayy;
339
- end_step.name = '结束';
346
+ end_step.name = language === 'zh-CN' ? '结束' : 'End';
340
347
  end_step.step_type = "end";
341
348
  end_step.posx = -1;
342
349
  end_step.posy = -1;
package/package.json CHANGED
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "name": "@steedos-labs/plugin-workflow",
3
- "version": "3.0.0-beta.9",
3
+ "version": "3.0.1-beta.1",
4
4
  "main": "package.service.js",
5
5
  "license": "MIT",
6
6
  "scripts": {
7
- "build": "tsc && webpack --config webpack.config.js",
8
7
  "build:watch": "tsc --watch",
9
- "release": "yarn build && npm publish --registry https://registry.npmjs.org && open https://npmmirror.com/sync/@steedos-labs/plugin-workflow && cnpm sync @steedos-labs/plugin-workflow"
8
+ "release": "npm publish --registry https://registry.npmjs.org && open https://npmmirror.com/sync/@steedos-labs/plugin-workflow && cnpm sync @steedos-labs/plugin-workflow"
10
9
  },
11
10
  "dependencies": {
12
11
  "graphql-parse-resolve-info": "^4.12.3",
13
12
  "he": "1.2.0",
14
13
  "lodash": "^4.17.21",
15
14
  "node-schedule": "^2.0.0",
16
- "formidable": "2.0.1"
15
+ "formidable": "2.0.1",
16
+ "jszip": "3.10.1"
17
17
  },
18
18
  "private": false,
19
19
  "publishConfig": {
@@ -5,6 +5,7 @@ const packageLoader = require('@steedos/service-package-loader');
5
5
 
6
6
  const rests = require('./src/rests');
7
7
  const InstanceRecordQueue = require('./src/instance_record_queue')
8
+ const WebhookQueue = require('./src/webhook_queue')
8
9
 
9
10
  /**
10
11
  * @typedef {import('moleculer').Context} Context Moleculer's Context
@@ -20,7 +21,7 @@ module.exports = {
20
21
  $package: {
21
22
  name: serviceName,
22
23
  path: __dirname,
23
- isPackage: true
24
+ isPackage: false
24
25
  }
25
26
  },
26
27
  /**
@@ -124,12 +125,25 @@ module.exports = {
124
125
  * Service started lifecycle event handler
125
126
  */
126
127
  async started(ctx) {
127
- if((process.env.STEEDOS_CRON_ENABLED === true || process.env.STEEDOS_CRON_ENABLED === 'true') && process.env.STEEDOS_CRON_INSTANCERECORDQUEUE_INTERVAL){
128
- InstanceRecordQueue.Configure({
129
- sendInterval: process.env.STEEDOS_CRON_INSTANCERECORDQUEUE_INTERVAL,
130
- sendBatchSize: 10,
131
- keepDocs: true
132
- })
128
+ if((process.env.STEEDOS_CRON_ENABLED === true || process.env.STEEDOS_CRON_ENABLED === 'true')){
129
+
130
+ if (process.env.STEEDOS_CRON_INSTANCERECORDQUEUE_INTERVAL) {
131
+ InstanceRecordQueue.Configure({
132
+ sendInterval: process.env.STEEDOS_CRON_INSTANCERECORDQUEUE_INTERVAL,
133
+ sendBatchSize: 10,
134
+ keepDocs: true
135
+ })
136
+ }
137
+
138
+ // webhooks 流程触发器 队列
139
+ if (process.env.STEEDOS_CRON_WEBHOOKQUEUE_INTERVAL) {
140
+ await WebhookQueue.Configure({
141
+ sendInterval: process.env.STEEDOS_CRON_WEBHOOKQUEUE_INTERVAL,
142
+ sendBatchSize: 10,
143
+ keepDocs: false
144
+ })
145
+ }
146
+
133
147
  }
134
148
  },
135
149
 
@@ -366,4 +366,8 @@ tbody .color-priority-muted *{
366
366
  .steedos-amis-instance-view-body .antd-Panel-title{
367
367
  font-size: 14px;
368
368
  font-weight: 500;
369
+ }
370
+
371
+ .steedos-amis-instance-view-body .approve-button{
372
+ z-index: 900;
369
373
  }
@@ -926,7 +926,7 @@ InstanceRecordQueue.syncRelatedObjectsValue = async function (mainRecordId, rela
926
926
  }
927
927
  }
928
928
  //清理申请单上被删除子表记录对应的相关表记录
929
- for (const [tableCode, tableIds] of Object.entries(tableMap)) {
929
+ for (let [tableCode, tableIds] of Object.entries(tableMap)) {
930
930
  tableIds = _.compact(tableIds);
931
931
  var docsForRemove = await objectFind(relatedObject.object_name, { filters: [[relatedObject.foreign_key, '=', mainRecordId], ['_table._code', '=', tableCode]] });
932
932
  for (const doc of docsForRemove) {
@@ -1025,10 +1025,8 @@ InstanceRecordQueue.sendDoc = async function (doc) {
1025
1025
 
1026
1026
  // 同步新附件
1027
1027
  await InstanceRecordQueue.syncAttach(sync_attachment, insId, record.space, record._id, objectName);
1028
- console.log(`1031 ===>`)
1029
1028
  // 执行公式
1030
1029
  await runQuoted(objectName, record._id);
1031
- console.log(`runQuoted===>`)
1032
1030
  } catch (error) {
1033
1031
  console.error(error);
1034
1032
 
@@ -1,8 +1,8 @@
1
1
  /*
2
2
  * @Author: 孙浩林 sunhaolin@steedos.com
3
3
  * @Date: 2023-11-04 18:16:15
4
- * @LastEditors: 孙浩林 sunhaolin@steedos.com
5
- * @LastEditTime: 2023-11-07 09:31:28
4
+ * @LastEditors: 殷亮辉 yinlianghui@hotoa.com
5
+ * @LastEditTime: 2025-09-01 16:23:40
6
6
  * @FilePath: /steedos-platform-2.3/services/service-workflow/src/rests/api_workflow_instance_batch_remove.js
7
7
  * @Description: 草稿箱列表删除按钮调用此接口批量删除草稿
8
8
  */
@@ -22,6 +22,7 @@ module.exports = {
22
22
 
23
23
  const insObj = this.getObject('instances')
24
24
  const insTaskObj = this.getObject('instance_tasks')
25
+ const lang = userSession.locale === 'zh-cn' ? 'zh-CN' : 'en';
25
26
 
26
27
  // 先检查是否符合删除条件:1、状态为草稿,2、提交人人为当前用户。
27
28
  const insDocs = await insObj.find({
@@ -62,7 +63,7 @@ module.exports = {
62
63
 
63
64
  return {
64
65
  'status': 0, // 返回 0,表示当前接口正确返回,否则按错误请求处理
65
- 'msg': '草稿已删除',
66
+ 'msg': t('instance.draft_deleted_success', {}, lang),//'草稿已删除',
66
67
  'data': {
67
68
  }
68
69
  }
@@ -0,0 +1,283 @@
1
+ /*
2
+ * @Author: 孙浩林 sunhaolin@steedos.com
3
+ * @Date: 2025-10-09 15:07:29
4
+ * @LastEditors: 孙浩林 sunhaolin@steedos.com
5
+ * @LastEditTime: 2025-10-10 11:43:04
6
+ * @FilePath: /steedos-plugins/steedos-packages/plugin-workflow/src/webhook_queue.js
7
+ * @Description:
8
+ */
9
+ const _ = require("lodash");
10
+ const axios = require('axios');
11
+
12
+ const { getCollection } = require("../main/default/utils/collection");
13
+
14
+ const WebhookQueue = {};
15
+
16
+ WebhookQueue.send = async function (options) {
17
+ var currentUser = '<SERVER>'
18
+ var webhook = _.extend({
19
+ createdAt: new Date(),
20
+ createdBy: currentUser
21
+ });
22
+
23
+ webhook.webhook = _.pick(options, ['instance', 'current_approve', 'payload_url', 'content_type', 'action', 'from_user', 'to_users']);
24
+
25
+ webhook.sent = false;
26
+ webhook.sending = 0;
27
+
28
+ return await WebhookQueue.collection.insert(webhook);
29
+ };
30
+
31
+ var isConfigured = false;
32
+ var sendWorker = async function (task, interval) {
33
+
34
+ if (WebhookQueue.debug) {
35
+ console.log('WebhookQueue: Send worker started, using interval: ' + interval);
36
+ }
37
+
38
+ return setInterval(async function () {
39
+ try {
40
+ await task();
41
+ } catch (error) {
42
+ if (WebhookQueue.debug) {
43
+ console.log('WebhookQueue: Error while sending: ' + error.message);
44
+ }
45
+ }
46
+ }, interval);
47
+ };
48
+
49
+ /*
50
+ options: {
51
+ // Controls the sending interval
52
+ sendInterval: Match.Optional(Number),
53
+ // Controls the sending batch size per interval
54
+ sendBatchSize: Match.Optional(Number),
55
+ // Allow optional keeping notifications in collection
56
+ keepWebhooks: Match.Optional(Boolean)
57
+ }
58
+ */
59
+ WebhookQueue.Configure = async function (options) {
60
+ WebhookQueue.collection = await getCollection('_webhook_queue');
61
+
62
+ var self = this;
63
+ options = _.extend({
64
+ sendTimeout: 60000, // Timeout period for webhook send
65
+ }, options);
66
+
67
+ // Block multiple calls
68
+ if (isConfigured) {
69
+ throw new Error('WebhookQueue.Configure should not be called more than once!');
70
+ }
71
+
72
+ isConfigured = true;
73
+
74
+ // Add debug info
75
+ if (WebhookQueue.debug) {
76
+ console.log('WebhookQueue.Configure', options);
77
+ }
78
+
79
+ self.sendWebhook = async function (webhook) {
80
+ if (WebhookQueue.debug) {
81
+ console.log("sendWebhook");
82
+ console.log(webhook);
83
+ }
84
+
85
+ try {
86
+ const response = await axios({
87
+ method: 'post',
88
+ url: webhook.webhook.payload_url,
89
+ headers: {
90
+ "Content-Type": webhook.webhook.content_type
91
+ },
92
+ data: {
93
+ instance: webhook.webhook.instance,
94
+ current_approve: webhook.webhook.current_approve,
95
+ action: webhook.webhook.action,
96
+ from_user: webhook.webhook.from_user,
97
+ to_users: webhook.webhook.to_users
98
+ },
99
+ });
100
+
101
+ if (!options.keepWebhooks) {
102
+ // Pr. Default we will remove webhooks
103
+ await WebhookQueue.collection.remove({
104
+ _id: webhook._id,
105
+ errMsg: {
106
+ $exists: false
107
+ }
108
+ });
109
+ } else {
110
+ // Update the webhook
111
+ await WebhookQueue.collection.updateOne({
112
+ _id: webhook._id
113
+ }, {
114
+ $set: {
115
+ // Mark as sent
116
+ sent: true,
117
+ // Set the sent date
118
+ sentAt: new Date(),
119
+ // Not being sent anymore
120
+ sending: 0
121
+ }
122
+ });
123
+
124
+ }
125
+ } catch (error) {
126
+ console.error(error);
127
+ await WebhookQueue.collection.updateOne({
128
+ _id: webhook._id
129
+ }, {
130
+ $set: {
131
+ // error message
132
+ errMsg: error
133
+ }
134
+ });
135
+ }
136
+
137
+ }
138
+
139
+ // Universal send function
140
+ var _querySend = async function (options) {
141
+
142
+ if (self.sendWebhook) {
143
+ await self.sendWebhook(options);
144
+ }
145
+
146
+ return {
147
+ webhook: [options._id]
148
+ };
149
+ };
150
+
151
+ self.serverSend = async function (options) {
152
+ options = options || {};
153
+ return await _querySend(options);
154
+ };
155
+
156
+
157
+ // This interval will allow only one webhook to be sent at a time, it
158
+ // will check for new webhooks at every `options.sendInterval`
159
+ // (default interval is 15000 ms)
160
+ //
161
+ // It looks in webhooks collection to see if theres any pending
162
+ // webhooks, if so it will try to reserve the pending webhook.
163
+ // If successfully reserved the send is started.
164
+ //
165
+ // If webhook.query is type string, it's assumed to be a json string
166
+ // version of the query selector. Making it able to carry `$` properties in
167
+ // the mongo collection.
168
+ //
169
+ // Pr. default webhooks are removed from the collection after send have
170
+ // completed. Setting `options.keepWebhooks` will update and keep the
171
+ // webhook eg. if needed for historical reasons.
172
+ //
173
+ // After the send have completed a "send" event will be emitted with a
174
+ // status object containing webhook id and the send result object.
175
+ //
176
+ var isSendingWebhook = false;
177
+
178
+ if (options.sendInterval !== null) {
179
+
180
+ // This will require index since we sort webhooks by createdAt
181
+ WebhookQueue.collection.createIndex({
182
+ createdAt: 1
183
+ });
184
+ WebhookQueue.collection.createIndex({
185
+ sent: 1
186
+ });
187
+ WebhookQueue.collection.createIndex({
188
+ sending: 1
189
+ });
190
+
191
+
192
+ var sendWebhook = async function (webhook) {
193
+ // Reserve webhook
194
+ var now = +new Date();
195
+ var timeoutAt = now + options.sendTimeout;
196
+ var reserved = await WebhookQueue.collection.updateOne({
197
+ _id: webhook._id,
198
+ sent: false, // xxx: need to make sure this is set on create
199
+ sending: {
200
+ $lt: now
201
+ }
202
+ }, {
203
+ $set: {
204
+ sending: timeoutAt,
205
+ }
206
+ });
207
+
208
+ // Make sure we only handle webhooks reserved by this
209
+ // instance
210
+ if (reserved) {
211
+
212
+ // Send the webhook
213
+ await WebhookQueue.serverSend(webhook);
214
+
215
+ } // Else could not reserve
216
+ }; // EO sendWebhook
217
+
218
+ await sendWorker(async function () {
219
+
220
+ if (isSendingWebhook) {
221
+ return;
222
+ }
223
+ // Set send fence
224
+ isSendingWebhook = true;
225
+
226
+ var batchSize = options.sendBatchSize || 1;
227
+
228
+ var now = +new Date();
229
+
230
+ // Find webhooks that are not being or already sent
231
+ var pendingWebhooks = await WebhookQueue.collection.find({
232
+ $and: [
233
+ // Message is not sent
234
+ {
235
+ sent: false
236
+ },
237
+ // And not being sent by other instances
238
+ {
239
+ sending: {
240
+ $lt: now
241
+ }
242
+ },
243
+ // And no error
244
+ {
245
+ errMsg: {
246
+ $exists: false
247
+ }
248
+ }
249
+ ]
250
+ }).sort({
251
+ createdAt: 1
252
+ }).limit(batchSize).toArray();
253
+
254
+ for (const webhook of pendingWebhooks) {
255
+ try {
256
+ await sendWebhook(webhook);
257
+ } catch (error) {
258
+ console.error(error.stack);
259
+ console.log('WebhookQueue: Could not send doc id: "' + webhook._id + '", Error: ' + error.message);
260
+ await WebhookQueue.collection.updateOne({
261
+ _id: webhook._id
262
+ }, {
263
+ $set: {
264
+ // error message
265
+ errMsg: error.message
266
+ }
267
+ });
268
+ }
269
+ }; // EO forEach
270
+
271
+ // Remove the send fence
272
+ isSendingWebhook = false;
273
+ }, options.sendInterval || 15000); // Default every 15th sec
274
+
275
+ } else {
276
+ if (WebhookQueue.debug) {
277
+ console.log('WebhookQueue: Send server is disabled');
278
+ }
279
+ }
280
+
281
+ };
282
+
283
+ module.exports = WebhookQueue;