@nocobase/plugin-workflow 0.7.5-alpha.1 → 0.7.6-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.
@@ -93,27 +93,61 @@ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { va
93
93
 
94
94
  function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
95
95
 
96
- // NOTE: observer for watching useProps
96
+ function AssociationInput(props) {
97
+ var _data$config;
98
+
99
+ const _useCollectionManager = (0, _client().useCollectionManager)(),
100
+ getCollectionFields = _useCollectionManager.getCollectionFields;
101
+
102
+ const _useField = (0, _react2().useField)(),
103
+ path = _useField.path;
104
+
105
+ const fieldName = path.segments[path.segments.length - 1];
106
+
107
+ const _useForm = (0, _react2().useForm)(),
108
+ data = _useForm.values;
109
+
110
+ const fields = getCollectionFields(data === null || data === void 0 ? void 0 : (_data$config = data.config) === null || _data$config === void 0 ? void 0 : _data$config.collection);
111
+
112
+ const _fields$find = fields.find(item => item.name === fieldName),
113
+ type = _fields$find.type;
114
+
115
+ const value = Array.isArray(props.value) ? props.value.join(',') : props.value;
116
+
117
+ function onChange(ev) {
118
+ const trimed = ev.target.value.trim();
119
+ props.onChange(['belongsTo', 'hasOne'].includes(type) ? trimed : trimed.split(/[,\s]+/));
120
+ }
121
+
122
+ return _react().default.createElement(_antd().Input, _objectSpread(_objectSpread({}, props), {}, {
123
+ value: value,
124
+ onChange: onChange
125
+ }));
126
+ } // NOTE: observer for watching useProps
127
+
128
+
97
129
  var _default = (0, _react2().observer)(({
98
130
  value,
99
131
  onChange: _onChange
100
132
  }) => {
101
- var _data$config, _data$config2;
133
+ var _data$config2;
102
134
 
103
135
  const _useTranslation = (0, _reactI18next().useTranslation)(),
104
136
  t = _useTranslation.t;
105
137
 
106
138
  const compile = (0, _client().useCompile)();
107
139
 
108
- const _useCollectionManager = (0, _client().useCollectionManager)(),
109
- getCollection = _useCollectionManager.getCollection,
110
- getCollectionFields = _useCollectionManager.getCollectionFields;
140
+ const _useCollectionManager2 = (0, _client().useCollectionManager)(),
141
+ getCollection = _useCollectionManager2.getCollection,
142
+ getCollectionFields = _useCollectionManager2.getCollectionFields;
111
143
 
112
- const _useForm = (0, _react2().useForm)(),
113
- data = _useForm.values;
144
+ const _useForm2 = (0, _react2().useForm)(),
145
+ data = _useForm2.values;
114
146
 
115
- const collectionName = data === null || data === void 0 ? void 0 : (_data$config = data.config) === null || _data$config === void 0 ? void 0 : _data$config.collection;
116
- const fields = getCollectionFields(data === null || data === void 0 ? void 0 : (_data$config2 = data.config) === null || _data$config2 === void 0 ? void 0 : _data$config2.collection).filter(field => !field.hidden && (field.uiSchema ? !field.uiSchema['x-read-pretty'] : false) && !['linkTo', 'hasMany', 'hasOne', 'belongsToMany'].includes(field.type));
147
+ const collectionName = data === null || data === void 0 ? void 0 : (_data$config2 = data.config) === null || _data$config2 === void 0 ? void 0 : _data$config2.collection;
148
+ const fields = getCollectionFields(collectionName).filter(field => !field.hidden && (field.uiSchema ? !field.uiSchema['x-read-pretty'] : false) // && (!['linkTo', 'hasMany', 'hasOne', 'belongsToMany'].includes(field.type))
149
+ );
150
+ const unassignedFields = fields.filter(field => !(field.name in value));
117
151
  return _react().default.createElement("fieldset", {
118
152
  className: (0, _css().css)`
119
153
  margin-top: .5em;
@@ -131,30 +165,20 @@ var _default = (0, _react2().observer)(({
131
165
  }, fields.filter(field => field.name in value).map(field => {
132
166
  var _field$uiSchema$title, _field$uiSchema;
133
167
 
134
- const VTypes = _objectSpread({}, _calculators.VariableTypes);
135
-
136
- let operand; // TODO: should refactor to support all types
137
-
138
- if (field.type !== 'belongsTo') {
139
- Object.assign(VTypes, {
140
- constant: {
141
- title: '{{t("Constant")}}',
142
- value: 'constant',
143
- options: undefined
144
- }
145
- });
146
- operand = typeof value[field.name] === 'string' ? (0, _calculators.parseStringValue)(value[field.name], VTypes) : {
147
- type: 'constant',
148
- value: value[field.name]
149
- };
150
- } else {
151
- delete VTypes.constant;
152
- operand = typeof value[field.name] === 'string' ? (0, _calculators.parseStringValue)(value[field.name], VTypes) : {
153
- type: '$context',
154
- value: value[field.name]
155
- };
156
- } // TODO: try to use <ObjectField> to replace this map
168
+ const VTypes = _objectSpread(_objectSpread({}, ['linkTo', 'hasMany', 'belongsToMany'].includes(field.type) ? {} : _calculators.VariableTypes), {}, {
169
+ constant: {
170
+ title: '{{t("Constant")}}',
171
+ value: 'constant',
172
+ options: undefined
173
+ }
174
+ });
157
175
 
176
+ const operand = typeof value[field.name] === 'string' ? (0, _calculators.parseStringValue)(value[field.name], VTypes) : {
177
+ type: 'constant',
178
+ value: value[field.name]
179
+ }; // constant for associations to use Input, others to use CollectionField
180
+ // dynamic values only support belongsTo/hasOne association, other association type should disable
181
+ // TODO: try to use <ObjectField> to replace this map
158
182
 
159
183
  return _react().default.createElement(_antd().Form.Item, {
160
184
  key: field.name,
@@ -187,12 +211,13 @@ var _default = (0, _react2().observer)(({
187
211
  type: 'void',
188
212
  properties: {
189
213
  [field.name]: {
190
- 'x-component': 'CollectionField'
214
+ 'x-component': ['linkTo', 'belongsTo', 'hasOne', 'hasMany', 'belongsToMany'].includes(field.type) ? 'AssociationInput' : 'CollectionField'
191
215
  }
192
216
  }
193
217
  },
194
218
  components: {
195
- CollectionField: _client().CollectionField
219
+ CollectionField: _client().CollectionField,
220
+ AssociationInput
196
221
  }
197
222
  }) // ? <SchemaComponent schema={{ ...field.uiSchema, name: field.name }} />
198
223
  : null), _react().default.createElement(_antd().Button, {
@@ -206,7 +231,7 @@ var _default = (0, _react2().observer)(({
206
231
  _onChange(rest);
207
232
  }
208
233
  })));
209
- }), Object.keys(value).length < fields.length ? _react().default.createElement(_antd().Dropdown, {
234
+ }), unassignedFields.length ? _react().default.createElement(_antd().Dropdown, {
210
235
  overlay: _react().default.createElement(_antd().Menu, {
211
236
  onClick: ({
212
237
  key
@@ -217,7 +242,7 @@ var _default = (0, _react2().observer)(({
217
242
  max-height: 300px;
218
243
  overflow-y: auto;
219
244
  `
220
- }, fields.filter(field => !(field.name in value)).map(field => {
245
+ }, unassignedFields.map(field => {
221
246
  var _field$uiSchema$title2, _field$uiSchema2;
222
247
 
223
248
  return _react().default.createElement(_antd().Menu.Item, {
@@ -154,9 +154,10 @@ function useUpdateAction() {
154
154
  _antd().message.error(t('Node in executed workflow cannot be modified'));
155
155
 
156
156
  return;
157
- }
157
+ } // TODO: how to do validation separately for each field? especially disabled for dynamic fields?
158
+ // await form.submit();
159
+
158
160
 
159
- yield form.submit();
160
161
  yield api.resource('flow_nodes', data.id).update({
161
162
  filterByTk: data.id,
162
163
  values: {
@@ -103,7 +103,7 @@ const FieldsSelect = (0, _react2().observer)(props => {
103
103
  className: (0, _css().css)`
104
104
  min-width: 6em;
105
105
  `
106
- }), fields.filter(field => !field.hidden && (field.uiSchema ? !field.uiSchema['x-read-pretty'] : true)).map(field => {
106
+ }), fields.filter(field => !field.hidden && (field.uiSchema ? !field.uiSchema['x-read-pretty'] : true) && !['linkTo', 'hasOne', 'hasMany', 'belongsToMany'].includes(field.type)).map(field => {
107
107
  var _field$uiSchema;
108
108
 
109
109
  return _react().default.createElement(_antd().Select.Option, {
@@ -15,6 +15,8 @@ export default class WorkflowPlugin extends Plugin {
15
15
  getName(): string;
16
16
  load(): Promise<void>;
17
17
  toggle(workflow: WorkflowModel, enable?: boolean): void;
18
- trigger(workflow: WorkflowModel, context: Object, options?: Transactionable): Promise<ExecutionModel | null>;
18
+ trigger(workflow: WorkflowModel, context: Object, options?: Transactionable & {
19
+ context?: any;
20
+ }): Promise<ExecutionModel | null>;
19
21
  createProcessor(execution: ExecutionModel, options?: {}): Processor;
20
22
  }
@@ -272,7 +272,8 @@ class WorkflowPlugin extends _server().Plugin {
272
272
  execution.workflow = workflow;
273
273
 
274
274
  const processor = _this3.createProcessor(execution, {
275
- transaction
275
+ transaction,
276
+ _context: options.context
276
277
  });
277
278
 
278
279
  yield processor.start(); // @ts-ignore
@@ -4,11 +4,12 @@ import ExecutionModel from './models/Execution';
4
4
  import JobModel from './models/Job';
5
5
  import FlowNodeModel from './models/FlowNode';
6
6
  export interface ProcessorOptions extends Transactionable {
7
+ _context?: any;
7
8
  plugin: Plugin;
8
9
  }
9
10
  export default class Processor {
10
11
  execution: ExecutionModel;
11
- private options;
12
+ options: ProcessorOptions;
12
13
  static StatusMap: {
13
14
  [x: number]: number;
14
15
  };
@@ -75,7 +75,20 @@ function migrateConfig(config, oldToNew) {
75
75
  return value.map(item => migrate(item));
76
76
 
77
77
  case 'string':
78
- return value.replace(/(\{\{\$jobsMapByNodeId\.)([\w-]+)/, (_, prefix, id) => `${prefix}${oldToNew.get(Number.parseInt(id, 10)).id}`);
78
+ const matcher = value.match(/(\{\{\$jobsMapByNodeId\.)([\w-]+)/);
79
+
80
+ if (!matcher) {
81
+ return value;
82
+ }
83
+
84
+ const oldNodeId = Number.parseInt(matcher[2], 10);
85
+ const newNode = oldToNew.get(oldNodeId);
86
+
87
+ if (!newNode) {
88
+ throw new Error('node configurated for result is not existed');
89
+ }
90
+
91
+ return value.replace(matcher[0], `{{$jobsMapByNodeId.${newNode.id}`);
79
92
 
80
93
  default:
81
94
  return value;
@@ -171,10 +184,18 @@ function _revision() {
171
184
  const oldNode = originalNodesMap.get(oldId);
172
185
  const newUpstream = oldNode.upstreamId ? oldToNew.get(oldNode.upstreamId) : null;
173
186
  const newDownstream = oldNode.downstreamId ? oldToNew.get(oldNode.downstreamId) : null;
187
+ let migratedConfig;
188
+
189
+ try {
190
+ migratedConfig = migrateConfig(oldNode.config, oldToNew);
191
+ } catch (err) {
192
+ return context.throw(400, err.message);
193
+ }
194
+
174
195
  yield newNode.update({
175
196
  upstreamId: (_newUpstream$id = newUpstream === null || newUpstream === void 0 ? void 0 : newUpstream.id) !== null && _newUpstream$id !== void 0 ? _newUpstream$id : null,
176
197
  downstreamId: (_newDownstream$id = newDownstream === null || newDownstream === void 0 ? void 0 : newDownstream.id) !== null && _newDownstream$id !== void 0 ? _newDownstream$id : null,
177
- config: migrateConfig(oldNode.config, oldToNew)
198
+ config: migratedConfig
178
199
  }, {
179
200
  transaction
180
201
  });
@@ -27,6 +27,7 @@ var _default = {
27
27
  const repo = node.constructor.database.getRepository(collection);
28
28
  const options = processor.getParsedValue(params);
29
29
  const result = yield repo.create(_objectSpread(_objectSpread({}, options), {}, {
30
+ context: processor.options._context,
30
31
  transaction: processor.transaction
31
32
  }));
32
33
  return {
@@ -27,6 +27,7 @@ var _default = {
27
27
  const repo = node.constructor.database.getRepository(collection);
28
28
  const options = processor.getParsedValue(params);
29
29
  const result = yield repo.destroy(_objectSpread(_objectSpread({}, options), {}, {
30
+ context: processor.options._context,
30
31
  transaction: processor.transaction
31
32
  }));
32
33
  return {
@@ -28,6 +28,7 @@ var _default = {
28
28
  const repo = node.constructor.database.getRepository(collection);
29
29
  const options = processor.getParsedValue(params);
30
30
  const result = yield (multiple ? repo.find : repo.findOne).call(repo, _objectSpread(_objectSpread({}, options), {}, {
31
+ context: processor.options._context,
31
32
  transaction: processor.transaction
32
33
  })); // NOTE: `toJSON()` to avoid getting undefined value from Proxied model instance (#380)
33
34
  // e.g. Object.prototype.hasOwnProperty.call(result, 'id') // false
@@ -29,6 +29,7 @@ var _default = {
29
29
  const repo = node.constructor.database.getRepository(collection);
30
30
  const options = processor.getParsedValue(params);
31
31
  const result = yield repo.update(_objectSpread(_objectSpread({}, options), {}, {
32
+ context: processor.options._context,
32
33
  transaction: processor.transaction
33
34
  }));
34
35
  return {
@@ -37,6 +37,16 @@ MODE_BITMAP_EVENTS.set(MODE_BITMAP.DESTROY, 'afterDestroy');
37
37
 
38
38
  function getHookId(workflow, type) {
39
39
  return `${type}#${workflow.id}`;
40
+ }
41
+
42
+ function getFieldRawName(collection, name) {
43
+ const field = collection.getField(name);
44
+
45
+ if (field && field.type === 'belongsTo') {
46
+ return field.foreignKey;
47
+ }
48
+
49
+ return name;
40
50
  } // async function, should return promise
41
51
 
42
52
 
@@ -49,29 +59,31 @@ function _handler() {
49
59
  var _condition$$and;
50
60
 
51
61
  const _workflow$config3 = workflow.config,
52
- collection = _workflow$config3.collection,
62
+ collectionName = _workflow$config3.collection,
53
63
  condition = _workflow$config3.condition,
54
- changed = _workflow$config3.changed; // NOTE: if no configured fields changed, do not trigger
64
+ changed = _workflow$config3.changed;
65
+ const collection = data.constructor.database.getCollection(collectionName); // NOTE: if no configured fields changed, do not trigger
55
66
 
56
- if (changed && changed.length && changed.every(name => !data.changed(name))) {// TODO: temp comment out
57
- // return;
67
+ if (changed && changed.length && changed.filter(name => !['linkTo', 'hasOne', 'hasMany', 'belongsToMany'].includes(collection.getField(name).type)).every(name => !data.changedWithAssociations(getFieldRawName(collection, name)))) {
68
+ // TODO: temp comment out
69
+ return;
58
70
  } // NOTE: if no configured condition match, do not trigger
59
71
 
60
72
 
61
73
  if (condition && ((_condition$$and = condition.$and) === null || _condition$$and === void 0 ? void 0 : _condition$$and.length)) {
62
74
  // TODO: change to map filter format to calculation format
63
75
  // const calculation = toCalculation(condition);
64
- const _data$constructor$dat = data.constructor.database.getCollection(collection),
65
- repository = _data$constructor$dat.repository,
66
- model = _data$constructor$dat.model;
67
-
68
- const transaction = options.transaction;
76
+ const repository = collection.repository,
77
+ model = collection.model;
78
+ const transaction = options.transaction,
79
+ context = options.context;
69
80
  const count = yield repository.count({
70
81
  filter: {
71
82
  $and: [condition, {
72
83
  [model.primaryKeyAttribute]: data[model.primaryKeyAttribute]
73
84
  }]
74
85
  },
86
+ context,
75
87
  transaction
76
88
  });
77
89
 
@@ -83,6 +95,7 @@ function _handler() {
83
95
  return this.plugin.trigger(workflow, {
84
96
  data: data.get()
85
97
  }, {
98
+ context: options.context,
86
99
  transaction: options.transaction
87
100
  });
88
101
  });
@@ -15,9 +15,10 @@ export declare const SCHEDULE_MODE: {
15
15
  readonly CONSTANT: 0;
16
16
  readonly COLLECTION_FIELD: 1;
17
17
  };
18
+ declare function matchNext(this: ScheduleTrigger, workflow: any, now: Date, range?: number): boolean;
18
19
  export default class ScheduleTrigger extends Trigger {
19
- static CacheRules: ((workflow: any, now: any) => any)[];
20
- static TriggerRules: ((workflow: any, now: any) => boolean)[];
20
+ static CacheRules: (typeof matchNext | ((workflow: any, now: any) => any))[];
21
+ static TriggerRules: ((workflow: any, now: any) => any)[];
21
22
  events: Map<any, any>;
22
23
  private timer;
23
24
  private cache;
@@ -36,3 +37,4 @@ export default class ScheduleTrigger extends Trigger {
36
37
  on(workflow: any): void;
37
38
  off(workflow: any): void;
38
39
  }
40
+ export {};
@@ -56,23 +56,26 @@ ScheduleModes.set(SCHEDULE_MODE.CONSTANT, {
56
56
  endsOn = _workflow$config.endsOn,
57
57
  repeat = _workflow$config.repeat;
58
58
  const timestamp = now.getTime();
59
+ const startTime = Date.parse(startsOn);
59
60
 
60
- if (startsOn) {
61
- const startTime = Date.parse(startsOn);
62
-
63
- if (!startTime || startTime > timestamp + this.cacheCycle) {
64
- return false;
65
- }
61
+ if (!startTime || startTime > timestamp + this.cacheCycle) {
62
+ return false;
63
+ }
66
64
 
65
+ if (repeat) {
67
66
  if (typeof repeat === 'number' && repeat > this.cacheCycle && (timestamp - startTime) % repeat > this.cacheCycle) {
68
67
  return false;
69
68
  }
70
- }
71
69
 
72
- if (endsOn) {
73
- const endTime = Date.parse(endsOn);
70
+ if (endsOn) {
71
+ const endTime = Date.parse(endsOn);
74
72
 
75
- if (!endTime || endTime <= timestamp) {
73
+ if (!endTime || endTime <= timestamp) {
74
+ return false;
75
+ }
76
+ }
77
+ } else {
78
+ if (startTime < timestamp) {
76
79
  return false;
77
80
  }
78
81
  }
@@ -80,40 +83,45 @@ ScheduleModes.set(SCHEDULE_MODE.CONSTANT, {
80
83
  return true;
81
84
  },
82
85
 
83
- trigger(workflow, date) {
86
+ trigger(workflow, now) {
84
87
  const _workflow$config2 = workflow.config,
85
88
  startsOn = _workflow$config2.startsOn,
86
89
  endsOn = _workflow$config2.endsOn,
87
90
  repeat = _workflow$config2.repeat;
91
+ const timestamp = now.getTime();
92
+ const startTime = Math.floor(Date.parse(startsOn) / 1000) * 1000;
88
93
 
89
- if (startsOn && typeof repeat === 'number') {
90
- const startTime = Math.floor(Date.parse(startsOn) / 1000) * 1000;
94
+ if (repeat) {
95
+ if (typeof repeat === 'number') {
96
+ if (Math.round(timestamp - startTime) % repeat) {
97
+ return;
98
+ }
99
+ }
91
100
 
92
- if (Math.round(date.getTime() - startTime) % repeat) {
101
+ if (endsOn) {
102
+ const endTime = Date.parse(endsOn);
103
+
104
+ if (!endTime || endTime < timestamp) {
105
+ return false;
106
+ }
107
+ }
108
+ } else {
109
+ if (startTime !== timestamp) {
93
110
  return;
94
111
  }
95
112
  }
96
113
 
97
114
  return this.plugin.trigger(workflow, {
98
- date
115
+ date: now
99
116
  });
100
117
  }
101
118
 
102
119
  });
103
120
 
104
- function getDateRangeFilter(on, now, dir) {
105
- const timestamp = now.getTime();
106
- const op = dir < 0 ? _sequelize().Op.lt : _sequelize().Op.gte;
107
-
121
+ function getOnTimestampWithOffset(on, now) {
108
122
  switch (typeof on) {
109
123
  case 'string':
110
- const time = Date.parse(on);
111
-
112
- if (!time || (dir < 0 ? timestamp < time : time <= timestamp)) {
113
- return null;
114
- }
115
-
116
- break;
124
+ return Date.parse(on);
117
125
 
118
126
  case 'object':
119
127
  const field = on.field,
@@ -123,20 +131,17 @@ function getDateRangeFilter(on, now, dir) {
123
131
  unit = _on$unit === void 0 ? 1000 : _on$unit;
124
132
 
125
133
  if (!field) {
126
- return {};
134
+ return null;
127
135
  }
128
136
 
129
- return {
130
- [field]: {
131
- [op]: new Date(timestamp + offset * unit * dir)
132
- }
133
- };
137
+ const timestamp = now.getTime(); // onDate + offset > now
138
+ // onDate > now - offset
139
+
140
+ return timestamp - offset * unit;
134
141
 
135
142
  default:
136
- break;
143
+ return null;
137
144
  }
138
-
139
- return {};
140
145
  }
141
146
 
142
147
  function getDataOptionTime(data, on, dir = 1) {
@@ -151,7 +156,7 @@ function getDataOptionTime(data, on, dir = 1) {
151
156
  offset = _on$offset2 === void 0 ? 0 : _on$offset2,
152
157
  _on$unit2 = on.unit,
153
158
  unit = _on$unit2 === void 0 ? 1000 : _on$unit2;
154
- return data[field] ? data[field].getTime() - offset * unit * dir : null;
159
+ return data.get(field) ? data.get(field).getTime() - offset * unit * dir : null;
155
160
 
156
161
  default:
157
162
  return null;
@@ -189,49 +194,49 @@ ScheduleModes.set(SCHEDULE_MODE.COLLECTION_FIELD, {
189
194
  const event = `${collection}.afterSave`;
190
195
  const name = getHookId(workflow, event);
191
196
 
192
- if (!this.events.has(name)) {
193
- // NOTE: toggle cache depends on new date
194
- const listener = /*#__PURE__*/function () {
195
- var _ref = _asyncToGenerator(function* (data, options) {
196
- const now = new Date();
197
- now.setMilliseconds(0);
198
- const timestamp = now.getTime();
199
- const startTime = getDataOptionTime(data, startsOn);
200
- const endTime = getDataOptionTime(data, endsOn, -1);
201
-
202
- if (!startTime && !repeat) {
203
- return;
204
- }
197
+ if (this.events.has(name)) {
198
+ return;
199
+ } // NOTE: toggle cache depends on new date
205
200
 
206
- if (startTime && startTime > timestamp + _this.cacheCycle) {
207
- return;
208
- }
209
201
 
210
- if (endTime && endTime <= timestamp) {
211
- return;
212
- }
202
+ const listener = /*#__PURE__*/function () {
203
+ var _ref = _asyncToGenerator(function* (data, options) {
204
+ const now = new Date();
205
+ now.setMilliseconds(0);
206
+ const timestamp = now.getTime();
207
+ const startTime = getDataOptionTime(data, startsOn);
208
+ const endTime = getDataOptionTime(data, endsOn, -1);
213
209
 
214
- if (!nextInCycle.call(_this, workflow, now)) {
215
- return;
216
- }
210
+ if (!startTime && !repeat) {
211
+ return;
212
+ }
217
213
 
218
- if (typeof repeat === 'number' && repeat > _this.cacheCycle && (timestamp - startTime) % repeat > _this.cacheCycle) {
219
- return;
220
- }
214
+ if (startTime && startTime > timestamp + _this.cacheCycle) {
215
+ return;
216
+ }
221
217
 
222
- console.log('set cache', now);
218
+ if (endTime && endTime <= timestamp) {
219
+ return;
220
+ }
223
221
 
224
- _this.setCache(workflow);
225
- });
222
+ if (!matchNext.call(_this, workflow, now)) {
223
+ return;
224
+ }
226
225
 
227
- return function listener(_x, _x2) {
228
- return _ref.apply(this, arguments);
229
- };
230
- }();
226
+ if (typeof repeat === 'number' && repeat > _this.cacheCycle && (timestamp - startTime) % repeat > _this.cacheCycle) {
227
+ return;
228
+ }
231
229
 
232
- this.events.set(name, listener);
233
- this.plugin.app.db.on(`${collection}.afterSave`, listener);
234
- }
230
+ _this.setCache(workflow);
231
+ });
232
+
233
+ return function listener(_x, _x2) {
234
+ return _ref.apply(this, arguments);
235
+ };
236
+ }();
237
+
238
+ this.events.set(name, listener);
239
+ this.plugin.app.db.on(event, listener);
235
240
  },
236
241
 
237
242
  off(workflow) {
@@ -242,7 +247,7 @@ ScheduleModes.set(SCHEDULE_MODE.COLLECTION_FIELD, {
242
247
  if (this.events.has(name)) {
243
248
  const listener = this.events.get(name);
244
249
  this.events.delete(name);
245
- this.plugin.app.db.off(`${collection}.afterSave`, listener);
250
+ this.plugin.app.db.off(event, listener);
246
251
  }
247
252
  },
248
253
 
@@ -250,34 +255,60 @@ ScheduleModes.set(SCHEDULE_MODE.COLLECTION_FIELD, {
250
255
  var _this2 = this;
251
256
 
252
257
  return _asyncToGenerator(function* () {
258
+ const db = _this2.plugin.app.db;
253
259
  const _workflow$config4 = workflow.config,
254
260
  startsOn = _workflow$config4.startsOn,
255
261
  endsOn = _workflow$config4.endsOn,
256
262
  repeat = _workflow$config4.repeat,
257
263
  collection = _workflow$config4.collection;
258
- const starts = getDateRangeFilter(startsOn, now, -1);
264
+ const timestamp = now.getTime();
265
+ const startTimestamp = getOnTimestampWithOffset(startsOn, now);
259
266
 
260
- if (!starts || !Object.keys(starts).length) {
267
+ if (!startTimestamp) {
261
268
  return false;
262
269
  }
263
270
 
264
- const ends = getDateRangeFilter(endsOn, now, 1);
271
+ const conditions = [{
272
+ [startsOn.field]: {
273
+ [_sequelize().Op.lt]: new Date(startTimestamp + _this2.cacheCycle)
274
+ }
275
+ }]; // when repeat is number, means repeat after startsOn
276
+ // (now - startsOn) % repeat <= cacheCycle
265
277
 
266
- if (!ends) {
267
- return false;
268
- }
278
+ if (repeat) {
279
+ const tsFn = DialectTimestampFnMap[db.options.dialect];
269
280
 
270
- const conditions = [starts, ends].filter(item => Boolean(Object.keys(item).length)); // when repeat is number, means repeat after startsOn
271
- // (now - startsOn) % repeat <= cacheCycle
281
+ if (typeof repeat === 'number' && repeat > _this2.cacheCycle && tsFn) {
282
+ conditions.push((0, _sequelize().where)((0, _sequelize().fn)('MOD', (0, _sequelize().literal)(`${Math.round(timestamp / 1000)} - ${tsFn(startsOn.field)}`), Math.round(repeat / 1000)), {
283
+ [_sequelize().Op.lt]: Math.round(_this2.cacheCycle / 1000)
284
+ })); // conditions.push(literal(`mod(${timestamp} - ${tsFn(startsOn.field)} * 1000, ${repeat}) < ${this.cacheCycle}`));
285
+ }
272
286
 
273
- const db = _this2.plugin.app.db;
274
- const tsFn = DialectTimestampFnMap[db.options.dialect];
287
+ if (endsOn) {
288
+ const endTimestamp = getOnTimestampWithOffset(endsOn, now);
275
289
 
276
- if (repeat && typeof repeat === 'number' && repeat > _this2.cacheCycle && tsFn) {
277
- const uts = now.getTime();
278
- conditions.push((0, _sequelize().where)((0, _sequelize().fn)('MOD', (0, _sequelize().literal)(`${Math.round(uts / 1000)} - ${tsFn(startsOn.field)}`), Math.round(repeat / 1000)), {
279
- [_sequelize().Op.lt]: Math.round(_this2.cacheCycle / 1000)
280
- })); // conditions.push(literal(`mod(${uts} - ${tsFn(startsOn.field)} * 1000, ${repeat}) < ${this.cacheCycle}`));
290
+ if (!endTimestamp) {
291
+ return false;
292
+ }
293
+
294
+ if (typeof endsOn === 'string') {
295
+ if (endTimestamp <= timestamp) {
296
+ return false;
297
+ }
298
+ } else {
299
+ conditions.push({
300
+ [endsOn.field]: {
301
+ [_sequelize().Op.gte]: new Date(endTimestamp + _this2.interval)
302
+ }
303
+ });
304
+ }
305
+ }
306
+ } else {
307
+ conditions.push({
308
+ [startsOn.field]: {
309
+ [_sequelize().Op.gte]: new Date(startTimestamp)
310
+ }
311
+ });
281
312
  }
282
313
 
283
314
  const _db$getCollection = db.getCollection(collection),
@@ -292,35 +323,29 @@ ScheduleModes.set(SCHEDULE_MODE.COLLECTION_FIELD, {
292
323
  })();
293
324
  },
294
325
 
295
- trigger(workflow, date) {
326
+ trigger(workflow, now) {
296
327
  var _this3 = this;
297
328
 
298
329
  return _asyncToGenerator(function* () {
299
- var _startsOn$offset, _startsOn$unit;
300
-
301
330
  const _workflow$config5 = workflow.config,
302
- collection = _workflow$config5.collection,
303
331
  startsOn = _workflow$config5.startsOn,
332
+ repeat = _workflow$config5.repeat,
304
333
  endsOn = _workflow$config5.endsOn,
305
- repeat = _workflow$config5.repeat;
334
+ collection = _workflow$config5.collection;
335
+ const timestamp = now.getTime();
336
+ const startTimestamp = getOnTimestampWithOffset(startsOn, now);
306
337
 
307
- if (typeof startsOn !== 'object') {
308
- return;
338
+ if (!startTimestamp) {
339
+ return false;
309
340
  }
310
341
 
311
- const timestamp = date.getTime();
312
- const startTimestamp = timestamp - ((_startsOn$offset = startsOn.offset) !== null && _startsOn$offset !== void 0 ? _startsOn$offset : 0) * ((_startsOn$unit = startsOn.unit) !== null && _startsOn$unit !== void 0 ? _startsOn$unit : 1000);
313
- const conditions = [];
342
+ const conditions = [{
343
+ [startsOn.field]: {
344
+ [_sequelize().Op.lt]: new Date(startTimestamp + _this3.interval)
345
+ }
346
+ }];
314
347
 
315
- if (!repeat) {
316
- // startsOn exactly equal to now in 1s
317
- conditions.push({
318
- [startsOn.field]: {
319
- [_sequelize().Op.gte]: new Date(startTimestamp),
320
- [_sequelize().Op.lt]: new Date(startTimestamp + 1000)
321
- }
322
- });
323
- } else {
348
+ if (repeat) {
324
349
  // startsOn not after now
325
350
  conditions.push({
326
351
  [startsOn.field]: {
@@ -335,32 +360,32 @@ ScheduleModes.set(SCHEDULE_MODE.COLLECTION_FIELD, {
335
360
  })); // conditions.push(literal(`MOD(CAST(${timestamp} AS BIGINT) - CAST((FLOOR(${tsFn(startsOn.field)}) AS BIGINT) * 1000), ${repeat}) = 0`));
336
361
  }
337
362
 
338
- switch (typeof endsOn) {
339
- case 'string':
340
- const endTime = Date.parse(endsOn);
341
-
342
- if (!endTime || endTime <= timestamp) {
343
- return;
344
- }
345
-
346
- break;
363
+ if (endsOn) {
364
+ const endTimestamp = getOnTimestampWithOffset(endsOn, now);
347
365
 
348
- case 'object':
349
- if (endsOn.field) {
350
- var _endsOn$offset, _endsOn$unit;
366
+ if (!endTimestamp) {
367
+ return false;
368
+ }
351
369
 
352
- conditions.push({
353
- [endsOn.field]: {
354
- [_sequelize().Op.gte]: new Date(timestamp - ((_endsOn$offset = endsOn.offset) !== null && _endsOn$offset !== void 0 ? _endsOn$offset : 0) * ((_endsOn$unit = endsOn.unit) !== null && _endsOn$unit !== void 0 ? _endsOn$unit : 1000) + 1000)
355
- }
356
- });
370
+ if (typeof endsOn === 'string') {
371
+ if (endTimestamp <= timestamp) {
372
+ return false;
357
373
  }
358
-
359
- break;
360
-
361
- default:
362
- break;
374
+ } else {
375
+ conditions.push({
376
+ [endsOn.field]: {
377
+ [_sequelize().Op.gte]: new Date(endTimestamp + _this3.interval)
378
+ }
379
+ });
380
+ }
363
381
  }
382
+ } else {
383
+ // startsOn exactly equal to now in 1s
384
+ conditions.push({
385
+ [startsOn.field]: {
386
+ [_sequelize().Op.gte]: new Date(startTimestamp)
387
+ }
388
+ });
364
389
  }
365
390
 
366
391
  const _this3$plugin$app$db$ = _this3.plugin.app.db.getCollection(collection),
@@ -373,12 +398,12 @@ ScheduleModes.set(SCHEDULE_MODE.COLLECTION_FIELD, {
373
398
  });
374
399
 
375
400
  if (instances.length) {
376
- console.log(instances.length, 'rows trigger at', date);
401
+ console.log(instances.length, 'rows trigger at', now);
377
402
  }
378
403
 
379
404
  instances.forEach(item => {
380
405
  _this3.plugin.trigger(workflow, {
381
- date,
406
+ date: now,
382
407
  data: item.get()
383
408
  });
384
409
  });
@@ -387,24 +412,16 @@ ScheduleModes.set(SCHEDULE_MODE.COLLECTION_FIELD, {
387
412
 
388
413
  });
389
414
 
390
- function nextInCycle(workflow, now) {
415
+ function matchNext(workflow, now, range = this.cacheCycle) {
391
416
  const repeat = workflow.config.repeat; // no repeat means no need to rerun
392
417
  // but if in current cycle, should be put in cache
393
418
  // no repeat but in current cycle means startsOn has been configured
394
419
  // so we need to more info to determine if necessary config items
395
420
 
396
- if (!repeat) {
421
+ if (typeof repeat !== 'string') {
397
422
  return true;
398
423
  }
399
424
 
400
- switch (typeof repeat) {
401
- case 'string':
402
- break;
403
-
404
- default:
405
- return true;
406
- }
407
-
408
425
  const currentDate = new Date(now);
409
426
  currentDate.setMilliseconds(-1);
410
427
  const timestamp = now.getTime();
@@ -415,7 +432,7 @@ function nextInCycle(workflow, now) {
415
432
 
416
433
  let next = interval.next(); // NOTE: cache all workflows will be matched in current cycle
417
434
 
418
- if (next.getTime() - timestamp <= this.cacheCycle) {
435
+ if (next.getTime() - timestamp <= range) {
419
436
  return true;
420
437
  }
421
438
 
@@ -452,18 +469,20 @@ class ScheduleTrigger extends _.Trigger {
452
469
  }
453
470
 
454
471
  init() {
455
- if (!this.timer) {
456
- const now = new Date(); // NOTE: assign to this.timer to avoid duplicated initialization
457
-
458
- this.timer = setTimeout(() => {
459
- this.timer = setInterval(this.run, this.interval); // initially trigger
460
- // this.onTick(now);
461
- }, // NOTE:
462
- // try to align to system time on each second starts,
463
- // after at least 1 second initialized for anything to get ready.
464
- // so jobs in 2 seconds will be missed at first start.
465
- 1000 - now.getMilliseconds());
472
+ if (this.timer) {
473
+ return;
466
474
  }
475
+
476
+ const now = new Date(); // NOTE: assign to this.timer to avoid duplicated initialization
477
+
478
+ this.timer = setTimeout(() => {
479
+ this.timer = setInterval(this.run, this.interval); // initially trigger
480
+ // this.onTick(now);
481
+ }, // NOTE:
482
+ // try to align to system time on each second starts,
483
+ // after at least 1 second initialized for anything to get ready.
484
+ // so jobs in 2 seconds will be missed at first start.
485
+ 1000 - now.getMilliseconds());
467
486
  }
468
487
 
469
488
  onTick(now) {
@@ -631,13 +650,10 @@ class ScheduleTrigger extends _.Trigger {
631
650
  }
632
651
 
633
652
  exports.default = ScheduleTrigger;
634
- ScheduleTrigger.CacheRules = [// ({ enabled }) => enabled,
635
- ({
653
+ ScheduleTrigger.CacheRules = [({
636
654
  config,
637
655
  allExecuted
638
- }) => config.limit ? allExecuted < config.limit : true, ({
639
- config
640
- }) => ['repeat', 'startsOn'].some(key => config[key]), nextInCycle, function (workflow, now) {
656
+ }) => (config.limit ? allExecuted < config.limit : true) && config.startsOn, matchNext, function (workflow, now) {
641
657
  const mode = workflow.config.mode;
642
658
  const modeHandlers = ScheduleModes.get(mode);
643
659
  return modeHandlers.shouldCache.call(this, workflow, now);
@@ -645,28 +661,6 @@ ScheduleTrigger.CacheRules = [// ({ enabled }) => enabled,
645
661
  ScheduleTrigger.TriggerRules = [({
646
662
  config,
647
663
  allExecuted
648
- }) => config.limit ? allExecuted < config.limit : true, ({
649
- config
650
- }) => ['repeat', 'startsOn'].some(key => config[key]), function (workflow, now) {
651
- const repeat = workflow.config.repeat;
652
-
653
- if (typeof repeat !== 'string') {
654
- return true;
655
- }
656
-
657
- const currentDate = new Date(now);
658
- currentDate.setMilliseconds(-1);
659
- const timestamp = now.getTime();
660
-
661
- const interval = _cronParser().default.parseExpression(repeat, {
662
- currentDate
663
- });
664
-
665
- let next = interval.next();
666
-
667
- if (next.getTime() === timestamp) {
668
- return true;
669
- }
670
-
671
- return false;
664
+ }) => (config.limit ? allExecuted < config.limit : true) && config.startsOn, function (workflow, now) {
665
+ return matchNext.call(this, workflow, now, 0);
672
666
  }];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/plugin-workflow",
3
- "version": "0.7.5-alpha.1",
3
+ "version": "0.7.6-alpha.2",
4
4
  "main": "lib/index.js",
5
5
  "license": "Apache-2.0",
6
6
  "licenses": [
@@ -10,17 +10,17 @@
10
10
  }
11
11
  ],
12
12
  "dependencies": {
13
- "@nocobase/actions": "0.7.5-alpha.1",
14
- "@nocobase/client": "0.7.5-alpha.1",
15
- "@nocobase/database": "0.7.5-alpha.1",
16
- "@nocobase/server": "0.7.5-alpha.1",
17
- "@nocobase/utils": "0.7.5-alpha.1",
13
+ "@nocobase/actions": "0.7.6-alpha.2",
14
+ "@nocobase/client": "0.7.6-alpha.2",
15
+ "@nocobase/database": "0.7.6-alpha.2",
16
+ "@nocobase/server": "0.7.6-alpha.2",
17
+ "@nocobase/utils": "0.7.6-alpha.2",
18
18
  "cron-parser": "4.4.0",
19
19
  "json-templates": "^4.2.0",
20
20
  "react-js-cron": "^1.4.0"
21
21
  },
22
22
  "devDependencies": {
23
- "@nocobase/test": "0.7.5-alpha.1"
23
+ "@nocobase/test": "0.7.6-alpha.2"
24
24
  },
25
- "gitHead": "f6eb27b68185bb0c0b4c2cfca1df84205a9b9173"
25
+ "gitHead": "2cfccff9d3ff14c521bf317fa7ee4efa18444c92"
26
26
  }