@gadmin2n/schematics 0.0.148 → 0.0.149
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.
- package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/workflow.prisma +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/workflow-node-types.ts +66 -49
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/agenda/agenda.controller.ts +4 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/agenda/agenda.service.ts +31 -4
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-event-catalog.spec.ts +166 -17
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow-event-trigger.pg.spec.ts +82 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.dto.ts +7 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflow/workflow.service.ts +91 -36
- package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/workflowConfig/node-config-validator.service.spec.ts +57 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/en/workflow.json +11 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/locales/zh_CN/workflow.json +11 -3
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/agenda/index.tsx +41 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/business-events/RegisterBusinessEventModal.test.tsx +46 -5
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/business-events/RegisterBusinessEventModal.tsx +14 -10
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/business-events/detail.test.tsx +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/business-events/detail.tsx +2 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/business-events/index.test.tsx +1 -1
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/business-events/index.tsx +2 -2
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/EventTriggerConfigEditor.test.tsx +140 -0
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/EventTriggerConfigEditor.tsx +123 -60
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/SimplifiedNodeEditors.test.tsx +31 -14
- package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/workflow/node-instances/widgets/event-catalog.api.ts +4 -2
- package/package.json +1 -1
|
@@ -74,7 +74,7 @@ export default function BusinessEventCatalogPage() {
|
|
|
74
74
|
event.label,
|
|
75
75
|
event.schemaName,
|
|
76
76
|
event.tableName,
|
|
77
|
-
event.
|
|
77
|
+
...event.operations,
|
|
78
78
|
...event.fields.flatMap((field) => [
|
|
79
79
|
field.path,
|
|
80
80
|
field.label,
|
|
@@ -105,7 +105,7 @@ export default function BusinessEventCatalogPage() {
|
|
|
105
105
|
{event.label}
|
|
106
106
|
</Typography.Link>
|
|
107
107
|
<Typography.Text code>
|
|
108
|
-
{event.
|
|
108
|
+
{event.operations.includes('UPDATE')
|
|
109
109
|
? event.watchedFields.join(', ')
|
|
110
110
|
: t('eventCatalog.registration.allFields')}
|
|
111
111
|
</Typography.Text>
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
4
|
+
import i18n from '../../../../i18n';
|
|
5
|
+
import { EventTriggerConfigEditor } from './EventTriggerConfigEditor';
|
|
6
|
+
|
|
7
|
+
vi.mock('./event-catalog.api', () => ({
|
|
8
|
+
eventCatalogApi: {
|
|
9
|
+
list: vi.fn().mockResolvedValue([
|
|
10
|
+
{
|
|
11
|
+
registrationCode: 'request',
|
|
12
|
+
label: '申请变化',
|
|
13
|
+
schemaName: 'public',
|
|
14
|
+
tableName: 'request',
|
|
15
|
+
operations: ['INSERT', 'UPDATE'],
|
|
16
|
+
watchedFields: ['status'],
|
|
17
|
+
primaryKeyFields: ['id'],
|
|
18
|
+
fields: [
|
|
19
|
+
{
|
|
20
|
+
path: 'primaryKey.id',
|
|
21
|
+
type: 'number',
|
|
22
|
+
primaryKey: true,
|
|
23
|
+
nullable: false,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
path: 'before.status',
|
|
27
|
+
type: 'string',
|
|
28
|
+
primaryKey: false,
|
|
29
|
+
nullable: true,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
path: 'after.status',
|
|
33
|
+
type: 'string',
|
|
34
|
+
primaryKey: false,
|
|
35
|
+
nullable: false,
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
]),
|
|
40
|
+
},
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
Object.defineProperty(window, 'matchMedia', {
|
|
44
|
+
writable: true,
|
|
45
|
+
value: () => ({
|
|
46
|
+
matches: false,
|
|
47
|
+
addListener() {},
|
|
48
|
+
removeListener() {},
|
|
49
|
+
addEventListener() {},
|
|
50
|
+
removeEventListener() {},
|
|
51
|
+
dispatchEvent: () => false,
|
|
52
|
+
}),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
function Editor() {
|
|
56
|
+
const [value, setValue] = useState<
|
|
57
|
+
NonNullable<Parameters<typeof EventTriggerConfigEditor>[0]['value']>
|
|
58
|
+
>({
|
|
59
|
+
registrationCode: 'request',
|
|
60
|
+
processingMode: 'PARALLEL',
|
|
61
|
+
operationRules: [{ operation: 'INSERT' }, { operation: 'UPDATE' }],
|
|
62
|
+
});
|
|
63
|
+
return (
|
|
64
|
+
<>
|
|
65
|
+
<EventTriggerConfigEditor
|
|
66
|
+
value={value}
|
|
67
|
+
onChange={(next) => setValue(next!)}
|
|
68
|
+
schema={{ type: 'object' }}
|
|
69
|
+
/>
|
|
70
|
+
<output data-testid="config">{JSON.stringify(value)}</output>
|
|
71
|
+
</>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
describe('event operation condition editor', () => {
|
|
76
|
+
it('preserves separate operation conditions, supports value lists and disabling one operation', async () => {
|
|
77
|
+
await i18n.changeLanguage('zh_CN');
|
|
78
|
+
render(<Editor />);
|
|
79
|
+
await screen.findByText('新增时');
|
|
80
|
+
fireEvent.click(screen.getByText('满足条件时启动'));
|
|
81
|
+
fireEvent.change(screen.getByRole('textbox', { name: '条件 1 的比较值' }), {
|
|
82
|
+
target: { value: 'PENDING' },
|
|
83
|
+
});
|
|
84
|
+
fireEvent.click(screen.getByText('修改时'));
|
|
85
|
+
fireEvent.click(screen.getByText('满足条件时启动'));
|
|
86
|
+
fireEvent.mouseDown(
|
|
87
|
+
screen.getByRole('combobox', { name: '条件 1 的判断方式' }),
|
|
88
|
+
);
|
|
89
|
+
const operators = await screen.findAllByText('属于以下任一值');
|
|
90
|
+
fireEvent.click(operators[operators.length - 1]);
|
|
91
|
+
const input = screen.getByRole('combobox', { name: '条件 1 的比较值' });
|
|
92
|
+
for (const value of ['DRAFT', 'REJECTED']) {
|
|
93
|
+
fireEvent.change(input, { target: { value } });
|
|
94
|
+
fireEvent.keyDown(input, { key: 'Enter', code: 'Enter', keyCode: 13 });
|
|
95
|
+
fireEvent.keyUp(input, { key: 'Enter', code: 'Enter', keyCode: 13 });
|
|
96
|
+
await waitFor(() =>
|
|
97
|
+
expect(screen.getByTestId('config').textContent).toContain(value),
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
let config = JSON.parse(screen.getByTestId('config').textContent!);
|
|
101
|
+
expect(config.operationRules).toEqual(
|
|
102
|
+
expect.arrayContaining([
|
|
103
|
+
expect.objectContaining({
|
|
104
|
+
operation: 'INSERT',
|
|
105
|
+
match: expect.objectContaining({
|
|
106
|
+
conditions: [
|
|
107
|
+
expect.objectContaining({
|
|
108
|
+
left: { source: 'WORKFLOW', path: 'after.status' },
|
|
109
|
+
right: expect.objectContaining({ value: 'PENDING' }),
|
|
110
|
+
}),
|
|
111
|
+
],
|
|
112
|
+
}),
|
|
113
|
+
}),
|
|
114
|
+
expect.objectContaining({
|
|
115
|
+
operation: 'UPDATE',
|
|
116
|
+
match: expect.objectContaining({
|
|
117
|
+
conditions: [
|
|
118
|
+
expect.objectContaining({
|
|
119
|
+
operator: 'IN',
|
|
120
|
+
left: { source: 'WORKFLOW', path: 'before.status' },
|
|
121
|
+
right: expect.objectContaining({
|
|
122
|
+
value: '["DRAFT","REJECTED"]',
|
|
123
|
+
}),
|
|
124
|
+
}),
|
|
125
|
+
],
|
|
126
|
+
}),
|
|
127
|
+
}),
|
|
128
|
+
]),
|
|
129
|
+
);
|
|
130
|
+
fireEvent.click(screen.getByText('新增时'));
|
|
131
|
+
expect(screen.getByDisplayValue('PENDING')).toBeInTheDocument();
|
|
132
|
+
fireEvent.click(screen.getByText('不启动'));
|
|
133
|
+
config = JSON.parse(screen.getByTestId('config').textContent!);
|
|
134
|
+
expect(
|
|
135
|
+
config.operationRules.map(
|
|
136
|
+
(rule: { operation: string }) => rule.operation,
|
|
137
|
+
),
|
|
138
|
+
).toEqual(['UPDATE']);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
@@ -14,17 +14,24 @@ import {
|
|
|
14
14
|
import { useEffect, useMemo, useState } from 'react';
|
|
15
15
|
import { useTranslation } from 'react-i18next';
|
|
16
16
|
import type { WidgetProps } from '../schema-form/widget-registry';
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
eventCatalogApi,
|
|
19
|
+
type BusinessEventOption,
|
|
20
|
+
type BusinessEventOperation,
|
|
21
|
+
} from './event-catalog.api';
|
|
18
22
|
|
|
19
23
|
type EventTriggerConfig = {
|
|
20
24
|
registrationCode: string;
|
|
21
|
-
|
|
25
|
+
operationRules: Array<{
|
|
26
|
+
operation: BusinessEventOperation;
|
|
27
|
+
match?: EventMatch;
|
|
28
|
+
}>;
|
|
22
29
|
processingMode: 'PARALLEL' | 'SERIAL_BY_KEY';
|
|
23
30
|
concurrencyKeyPath?: string;
|
|
24
31
|
maxConcurrency?: number | null;
|
|
25
32
|
};
|
|
26
33
|
|
|
27
|
-
type EventMatchOperator = 'EQ' | 'NEQ' | 'EMPTY' | 'NOT_EMPTY';
|
|
34
|
+
type EventMatchOperator = 'EQ' | 'NEQ' | 'IN' | 'EMPTY' | 'NOT_EMPTY';
|
|
28
35
|
type EventMatchCondition = {
|
|
29
36
|
id: string;
|
|
30
37
|
left: { source: 'WORKFLOW'; path: string };
|
|
@@ -42,6 +49,7 @@ type EventMatch = {
|
|
|
42
49
|
|
|
43
50
|
const DEFAULT_CONFIG: EventTriggerConfig = {
|
|
44
51
|
registrationCode: '',
|
|
52
|
+
operationRules: [],
|
|
45
53
|
processingMode: 'PARALLEL',
|
|
46
54
|
};
|
|
47
55
|
|
|
@@ -81,6 +89,8 @@ export function EventTriggerConfigEditor({
|
|
|
81
89
|
const [loading, setLoading] = useState(true);
|
|
82
90
|
const [loadFailed, setLoadFailed] = useState(false);
|
|
83
91
|
const config = { ...DEFAULT_CONFIG, ...value };
|
|
92
|
+
const [activeOperation, setActiveOperation] =
|
|
93
|
+
useState<BusinessEventOperation>('INSERT');
|
|
84
94
|
const selectedEvent = useMemo(
|
|
85
95
|
() =>
|
|
86
96
|
events.find(
|
|
@@ -88,6 +98,24 @@ export function EventTriggerConfigEditor({
|
|
|
88
98
|
),
|
|
89
99
|
[config.registrationCode, events],
|
|
90
100
|
);
|
|
101
|
+
const operation = selectedEvent?.operations.includes(activeOperation)
|
|
102
|
+
? activeOperation
|
|
103
|
+
: selectedEvent?.operations[0];
|
|
104
|
+
const rule = config.operationRules.find(
|
|
105
|
+
(item) => item.operation === operation,
|
|
106
|
+
);
|
|
107
|
+
const match = rule?.match;
|
|
108
|
+
|
|
109
|
+
function changeMatch(next?: EventMatch, enabled = true) {
|
|
110
|
+
if (!operation) return;
|
|
111
|
+
onChange({
|
|
112
|
+
...config,
|
|
113
|
+
operationRules: [
|
|
114
|
+
...config.operationRules.filter((item) => item.operation !== operation),
|
|
115
|
+
...(enabled ? [{ operation, ...(next ? { match: next } : {}) }] : []),
|
|
116
|
+
],
|
|
117
|
+
});
|
|
118
|
+
}
|
|
91
119
|
const serialKeyFields = useMemo(
|
|
92
120
|
() =>
|
|
93
121
|
(selectedEvent?.fields ?? []).filter(
|
|
@@ -99,10 +127,13 @@ export function EventTriggerConfigEditor({
|
|
|
99
127
|
);
|
|
100
128
|
const matchFields = useMemo(
|
|
101
129
|
() =>
|
|
102
|
-
(selectedEvent?.fields ?? []).filter(
|
|
103
|
-
|
|
130
|
+
(selectedEvent?.fields ?? []).filter(
|
|
131
|
+
(field) =>
|
|
132
|
+
['string', 'number', 'boolean'].includes(field.type) &&
|
|
133
|
+
!(operation === 'INSERT' && field.path.startsWith('before.')) &&
|
|
134
|
+
!(operation === 'DELETE' && field.path.startsWith('after.')),
|
|
104
135
|
),
|
|
105
|
-
[selectedEvent],
|
|
136
|
+
[selectedEvent, operation],
|
|
106
137
|
);
|
|
107
138
|
const defaultMatchField =
|
|
108
139
|
matchFields.find((field) => !field.primaryKey) ?? matchFields[0];
|
|
@@ -145,6 +176,9 @@ export function EventTriggerConfigEditor({
|
|
|
145
176
|
|
|
146
177
|
onChange({
|
|
147
178
|
registrationCode,
|
|
179
|
+
operationRules: (event?.operations ?? []).map((operation) => ({
|
|
180
|
+
operation,
|
|
181
|
+
})),
|
|
148
182
|
processingMode: config.processingMode,
|
|
149
183
|
...(config.processingMode === 'SERIAL_BY_KEY'
|
|
150
184
|
? {
|
|
@@ -161,7 +195,7 @@ export function EventTriggerConfigEditor({
|
|
|
161
195
|
if (processingMode === 'PARALLEL') {
|
|
162
196
|
onChange({
|
|
163
197
|
registrationCode: config.registrationCode,
|
|
164
|
-
|
|
198
|
+
operationRules: config.operationRules,
|
|
165
199
|
processingMode,
|
|
166
200
|
});
|
|
167
201
|
return;
|
|
@@ -180,31 +214,24 @@ export function EventTriggerConfigEditor({
|
|
|
180
214
|
});
|
|
181
215
|
}
|
|
182
216
|
|
|
183
|
-
function changeMatchMode(mode: 'ALWAYS' | 'CONDITIONAL') {
|
|
184
|
-
if (mode
|
|
185
|
-
|
|
186
|
-
onChange(next);
|
|
217
|
+
function changeMatchMode(mode: 'NEVER' | 'ALWAYS' | 'CONDITIONAL') {
|
|
218
|
+
if (mode !== 'CONDITIONAL') {
|
|
219
|
+
changeMatch(undefined, mode === 'ALWAYS');
|
|
187
220
|
return;
|
|
188
221
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
combineOperation: 'AND',
|
|
193
|
-
conditions: [newMatchCondition(defaultMatchField)],
|
|
194
|
-
},
|
|
222
|
+
changeMatch({
|
|
223
|
+
combineOperation: 'AND',
|
|
224
|
+
conditions: [newMatchCondition(defaultMatchField)],
|
|
195
225
|
});
|
|
196
226
|
}
|
|
197
227
|
|
|
198
228
|
function updateMatchCondition(index: number, next: EventMatchCondition) {
|
|
199
|
-
if (!
|
|
200
|
-
|
|
201
|
-
...
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
current === index ? next : condition,
|
|
206
|
-
),
|
|
207
|
-
},
|
|
229
|
+
if (!match) return;
|
|
230
|
+
changeMatch({
|
|
231
|
+
...match,
|
|
232
|
+
conditions: match.conditions.map((condition, current) =>
|
|
233
|
+
current === index ? next : condition,
|
|
234
|
+
),
|
|
208
235
|
});
|
|
209
236
|
}
|
|
210
237
|
|
|
@@ -276,11 +303,23 @@ export function EventTriggerConfigEditor({
|
|
|
276
303
|
|
|
277
304
|
<Card size="small" title={t('nodeEditors.event.startCondition')}>
|
|
278
305
|
<Radio.Group
|
|
279
|
-
value={
|
|
306
|
+
value={operation}
|
|
307
|
+
onChange={(event) => setActiveOperation(event.target.value)}
|
|
308
|
+
style={{ display: 'block', marginBottom: 16 }}
|
|
309
|
+
>
|
|
310
|
+
{(selectedEvent?.operations ?? []).map((item) => (
|
|
311
|
+
<Radio.Button key={item} value={item}>
|
|
312
|
+
{t(`nodeEditors.event.operations.${item}`)}
|
|
313
|
+
</Radio.Button>
|
|
314
|
+
))}
|
|
315
|
+
</Radio.Group>
|
|
316
|
+
<Radio.Group
|
|
317
|
+
value={!rule ? 'NEVER' : match ? 'CONDITIONAL' : 'ALWAYS'}
|
|
280
318
|
disabled={disabled || !selectedEvent}
|
|
281
319
|
onChange={(event) => changeMatchMode(event.target.value)}
|
|
282
320
|
>
|
|
283
321
|
<Space direction="vertical" size={8}>
|
|
322
|
+
<Radio value="NEVER">{t('nodeEditors.event.neverStart')}</Radio>
|
|
284
323
|
<Radio value="ALWAYS">{t('nodeEditors.event.alwaysStart')}</Radio>
|
|
285
324
|
<Radio value="CONDITIONAL">
|
|
286
325
|
{t('nodeEditors.event.conditionalStart')}
|
|
@@ -288,23 +327,20 @@ export function EventTriggerConfigEditor({
|
|
|
288
327
|
</Space>
|
|
289
328
|
</Radio.Group>
|
|
290
329
|
|
|
291
|
-
{
|
|
330
|
+
{match && (
|
|
292
331
|
<Space
|
|
293
332
|
direction="vertical"
|
|
294
333
|
size={10}
|
|
295
334
|
style={{ width: '100%', marginTop: 14 }}
|
|
296
335
|
>
|
|
297
|
-
{
|
|
336
|
+
{match.conditions.length > 1 && (
|
|
298
337
|
<Radio.Group
|
|
299
|
-
value={
|
|
338
|
+
value={match.combineOperation}
|
|
300
339
|
disabled={disabled}
|
|
301
340
|
onChange={(event) =>
|
|
302
|
-
|
|
303
|
-
...
|
|
304
|
-
|
|
305
|
-
...config.match!,
|
|
306
|
-
combineOperation: event.target.value,
|
|
307
|
-
},
|
|
341
|
+
changeMatch({
|
|
342
|
+
...match!,
|
|
343
|
+
combineOperation: event.target.value,
|
|
308
344
|
})
|
|
309
345
|
}
|
|
310
346
|
>
|
|
@@ -317,7 +353,7 @@ export function EventTriggerConfigEditor({
|
|
|
317
353
|
</Radio.Group>
|
|
318
354
|
)}
|
|
319
355
|
|
|
320
|
-
{
|
|
356
|
+
{match.conditions.map((condition, index) => {
|
|
321
357
|
const selectedField = matchFields.find(
|
|
322
358
|
(field) => field.path === condition.left.path,
|
|
323
359
|
);
|
|
@@ -355,7 +391,8 @@ export function EventTriggerConfigEditor({
|
|
|
355
391
|
: {
|
|
356
392
|
source: 'FIXED',
|
|
357
393
|
valueType: fixedValueType(field),
|
|
358
|
-
value:
|
|
394
|
+
value:
|
|
395
|
+
condition.operator === 'IN' ? '[]' : '',
|
|
359
396
|
},
|
|
360
397
|
});
|
|
361
398
|
}}
|
|
@@ -368,7 +405,7 @@ export function EventTriggerConfigEditor({
|
|
|
368
405
|
value={condition.operator}
|
|
369
406
|
disabled={disabled}
|
|
370
407
|
options={(
|
|
371
|
-
['EQ', 'NEQ', 'EMPTY', 'NOT_EMPTY'] as const
|
|
408
|
+
['EQ', 'NEQ', 'IN', 'EMPTY', 'NOT_EMPTY'] as const
|
|
372
409
|
).map((operator) => ({
|
|
373
410
|
value: operator,
|
|
374
411
|
label: t(
|
|
@@ -381,17 +418,38 @@ export function EventTriggerConfigEditor({
|
|
|
381
418
|
operator,
|
|
382
419
|
right: UNARY_MATCH_OPERATORS.has(operator)
|
|
383
420
|
? undefined
|
|
384
|
-
:
|
|
421
|
+
: {
|
|
385
422
|
source: 'FIXED',
|
|
386
423
|
valueType: fixedValueType(selectedField),
|
|
387
|
-
value: '',
|
|
388
|
-
}
|
|
424
|
+
value: operator === 'IN' ? '[]' : '',
|
|
425
|
+
},
|
|
389
426
|
})
|
|
390
427
|
}
|
|
391
428
|
style={{ width: '30%' }}
|
|
392
429
|
/>
|
|
393
430
|
{!UNARY_MATCH_OPERATORS.has(condition.operator) &&
|
|
394
|
-
(
|
|
431
|
+
(condition.operator === 'IN' ? (
|
|
432
|
+
<Select
|
|
433
|
+
mode="tags"
|
|
434
|
+
aria-label={t('nodeEditors.event.conditionValue', {
|
|
435
|
+
number: index + 1,
|
|
436
|
+
})}
|
|
437
|
+
value={parseValueList(condition.right?.value)}
|
|
438
|
+
disabled={disabled}
|
|
439
|
+
placeholder={t('nodeEditors.event.valueListHint')}
|
|
440
|
+
onChange={(values) =>
|
|
441
|
+
updateMatchCondition(index, {
|
|
442
|
+
...condition,
|
|
443
|
+
right: {
|
|
444
|
+
source: 'FIXED',
|
|
445
|
+
valueType: fixedValueType(selectedField),
|
|
446
|
+
value: JSON.stringify(values),
|
|
447
|
+
},
|
|
448
|
+
})
|
|
449
|
+
}
|
|
450
|
+
style={{ width: '25%' }}
|
|
451
|
+
/>
|
|
452
|
+
) : selectedField?.type === 'boolean' ? (
|
|
395
453
|
<Select
|
|
396
454
|
aria-label={t('nodeEditors.event.conditionValue', {
|
|
397
455
|
number: index + 1,
|
|
@@ -473,14 +531,11 @@ export function EventTriggerConfigEditor({
|
|
|
473
531
|
})}
|
|
474
532
|
disabled={disabled}
|
|
475
533
|
onClick={() =>
|
|
476
|
-
|
|
477
|
-
...
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
(_, current) => current !== index,
|
|
482
|
-
),
|
|
483
|
-
},
|
|
534
|
+
changeMatch({
|
|
535
|
+
...match!,
|
|
536
|
+
conditions: match!.conditions.filter(
|
|
537
|
+
(_, current) => current !== index,
|
|
538
|
+
),
|
|
484
539
|
})
|
|
485
540
|
}
|
|
486
541
|
>
|
|
@@ -499,15 +554,12 @@ export function EventTriggerConfigEditor({
|
|
|
499
554
|
icon={<PlusOutlined />}
|
|
500
555
|
disabled={disabled}
|
|
501
556
|
onClick={() =>
|
|
502
|
-
|
|
503
|
-
...
|
|
504
|
-
|
|
505
|
-
...
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
newMatchCondition(defaultMatchField),
|
|
509
|
-
],
|
|
510
|
-
},
|
|
557
|
+
changeMatch({
|
|
558
|
+
...match!,
|
|
559
|
+
conditions: [
|
|
560
|
+
...match!.conditions,
|
|
561
|
+
newMatchCondition(defaultMatchField),
|
|
562
|
+
],
|
|
511
563
|
})
|
|
512
564
|
}
|
|
513
565
|
>
|
|
@@ -617,3 +669,14 @@ export function EventTriggerConfigEditor({
|
|
|
617
669
|
</Space>
|
|
618
670
|
);
|
|
619
671
|
}
|
|
672
|
+
|
|
673
|
+
function parseValueList(value?: string): string[] {
|
|
674
|
+
try {
|
|
675
|
+
const parsed: unknown = JSON.parse(value ?? '[]');
|
|
676
|
+
return Array.isArray(parsed)
|
|
677
|
+
? parsed.filter((item): item is string => typeof item === 'string')
|
|
678
|
+
: [];
|
|
679
|
+
} catch {
|
|
680
|
+
return [];
|
|
681
|
+
}
|
|
682
|
+
}
|
|
@@ -39,7 +39,7 @@ vi.mock('./event-catalog.api', () => ({
|
|
|
39
39
|
label: '用户创建',
|
|
40
40
|
schemaName: 'public',
|
|
41
41
|
tableName: 't_user',
|
|
42
|
-
|
|
42
|
+
operations: ['INSERT'],
|
|
43
43
|
watchedFields: ['id'],
|
|
44
44
|
primaryKeyFields: ['id'],
|
|
45
45
|
isEnabled: true,
|
|
@@ -68,7 +68,7 @@ vi.mock('./event-catalog.api', () => ({
|
|
|
68
68
|
label: '硬件资产报废申请',
|
|
69
69
|
schemaName: 'public',
|
|
70
70
|
tableName: 't_hardware_asset',
|
|
71
|
-
|
|
71
|
+
operations: ['UPDATE'],
|
|
72
72
|
watchedFields: ['status'],
|
|
73
73
|
primaryKeyFields: ['id'],
|
|
74
74
|
isEnabled: true,
|
|
@@ -412,7 +412,11 @@ describe('simplified workflow node editors', () => {
|
|
|
412
412
|
const onChange = vi.fn();
|
|
413
413
|
render(
|
|
414
414
|
<EventTriggerConfigEditor
|
|
415
|
-
value={{
|
|
415
|
+
value={{
|
|
416
|
+
registrationCode: '',
|
|
417
|
+
operationRules: [],
|
|
418
|
+
processingMode: 'PARALLEL',
|
|
419
|
+
}}
|
|
416
420
|
onChange={onChange}
|
|
417
421
|
schema={schema}
|
|
418
422
|
/>,
|
|
@@ -425,6 +429,7 @@ describe('simplified workflow node editors', () => {
|
|
|
425
429
|
|
|
426
430
|
expect(onChange).toHaveBeenCalledWith({
|
|
427
431
|
registrationCode: 'user-account-created',
|
|
432
|
+
operationRules: [{ operation: 'INSERT' }],
|
|
428
433
|
processingMode: 'PARALLEL',
|
|
429
434
|
});
|
|
430
435
|
});
|
|
@@ -434,7 +439,11 @@ describe('simplified workflow node editors', () => {
|
|
|
434
439
|
const onChange = vi.fn();
|
|
435
440
|
render(
|
|
436
441
|
<EventTriggerConfigEditor
|
|
437
|
-
value={{
|
|
442
|
+
value={{
|
|
443
|
+
registrationCode: '',
|
|
444
|
+
operationRules: [],
|
|
445
|
+
processingMode: 'PARALLEL',
|
|
446
|
+
}}
|
|
438
447
|
onChange={onChange}
|
|
439
448
|
schema={schema}
|
|
440
449
|
/>,
|
|
@@ -447,6 +456,7 @@ describe('simplified workflow node editors', () => {
|
|
|
447
456
|
|
|
448
457
|
expect(onChange).toHaveBeenCalledWith({
|
|
449
458
|
registrationCode: 'asset-disposal-submitted',
|
|
459
|
+
operationRules: [{ operation: 'UPDATE' }],
|
|
450
460
|
processingMode: 'PARALLEL',
|
|
451
461
|
});
|
|
452
462
|
});
|
|
@@ -457,6 +467,7 @@ describe('simplified workflow node editors', () => {
|
|
|
457
467
|
<EventTriggerConfigEditor
|
|
458
468
|
value={{
|
|
459
469
|
registrationCode: 'asset-disposal-submitted',
|
|
470
|
+
operationRules: [{ operation: 'UPDATE' }],
|
|
460
471
|
processingMode: 'PARALLEL',
|
|
461
472
|
}}
|
|
462
473
|
onChange={vi.fn()}
|
|
@@ -479,6 +490,7 @@ describe('simplified workflow node editors', () => {
|
|
|
479
490
|
<EventTriggerConfigEditor
|
|
480
491
|
value={{
|
|
481
492
|
registrationCode: 'asset-disposal-submitted',
|
|
493
|
+
operationRules: [{ operation: 'UPDATE' }],
|
|
482
494
|
processingMode: 'PARALLEL',
|
|
483
495
|
}}
|
|
484
496
|
onChange={onChange}
|
|
@@ -491,17 +503,22 @@ describe('simplified workflow node editors', () => {
|
|
|
491
503
|
expect(onChange).toHaveBeenLastCalledWith({
|
|
492
504
|
registrationCode: 'asset-disposal-submitted',
|
|
493
505
|
processingMode: 'PARALLEL',
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
{
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
506
|
+
operationRules: [
|
|
507
|
+
{
|
|
508
|
+
operation: 'UPDATE',
|
|
509
|
+
match: {
|
|
510
|
+
combineOperation: 'AND',
|
|
511
|
+
conditions: [
|
|
512
|
+
{
|
|
513
|
+
id: expect.stringMatching(/^condition-/),
|
|
514
|
+
left: { source: 'WORKFLOW', path: 'after.status' },
|
|
515
|
+
operator: 'EQ',
|
|
516
|
+
right: { source: 'FIXED', valueType: 'STRING', value: '' },
|
|
517
|
+
},
|
|
518
|
+
],
|
|
502
519
|
},
|
|
503
|
-
|
|
504
|
-
|
|
520
|
+
},
|
|
521
|
+
],
|
|
505
522
|
});
|
|
506
523
|
});
|
|
507
524
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { customRequest } from 'helpers/http';
|
|
2
2
|
|
|
3
|
+
export type BusinessEventOperation = 'INSERT' | 'UPDATE' | 'DELETE';
|
|
4
|
+
|
|
3
5
|
export interface BusinessEventField {
|
|
4
6
|
path: string;
|
|
5
7
|
label: string;
|
|
@@ -15,7 +17,7 @@ export interface BusinessEventOption {
|
|
|
15
17
|
label: string;
|
|
16
18
|
schemaName: string;
|
|
17
19
|
tableName: string;
|
|
18
|
-
|
|
20
|
+
operations: BusinessEventOperation[];
|
|
19
21
|
watchedFields: string[];
|
|
20
22
|
primaryKeyFields: string[];
|
|
21
23
|
isEnabled: boolean;
|
|
@@ -52,7 +54,7 @@ export interface BusinessEventInstanceItem {
|
|
|
52
54
|
export interface BusinessEventRegistrationInput {
|
|
53
55
|
schemaName: string;
|
|
54
56
|
tableName: string;
|
|
55
|
-
|
|
57
|
+
operations: BusinessEventOperation[];
|
|
56
58
|
watchedFields: string[];
|
|
57
59
|
}
|
|
58
60
|
|