@pbinitiative/zenbpm-js-properties-panel 0.2.0 → 0.3.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.
- package/README.md +1 -0
- package/dist/index.cjs +291 -40
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +291 -40
- package/dist/index.mjs.map +1 -1
- package/dist/types/provider/zenbpm/parts/CorrelationKeyProps.d.ts +7 -0
- package/dist/types/provider/zenbpm/parts/ZenFormProps.d.ts +1 -0
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -3,15 +3,14 @@ import { createElement } from '@bpmn-io/properties-panel/preact';
|
|
|
3
3
|
import { useService } from 'bpmn-js-properties-panel';
|
|
4
4
|
|
|
5
5
|
function ZenFormProps(element) {
|
|
6
|
-
if (element.type !== 'bpmn:UserTask')
|
|
6
|
+
if (element.type !== 'bpmn:UserTask')
|
|
7
7
|
return [];
|
|
8
|
-
}
|
|
9
8
|
return [
|
|
10
9
|
{
|
|
11
10
|
id: 'zenFormDesignButton',
|
|
12
11
|
component: ZenFormDesignButtonEntry,
|
|
13
12
|
isEdited: () => false,
|
|
14
|
-
}
|
|
13
|
+
},
|
|
15
14
|
];
|
|
16
15
|
}
|
|
17
16
|
function getZenFormValue(element) {
|
|
@@ -25,7 +24,6 @@ function getZenFormValue(element) {
|
|
|
25
24
|
const input = (ioMapping.inputParameters || []).find((p) => p.target === 'ZEN_FORM');
|
|
26
25
|
if (!input?.source)
|
|
27
26
|
return '';
|
|
28
|
-
// Parse FEEL string literal: ="..." → raw JSON
|
|
29
27
|
const src = input.source;
|
|
30
28
|
if (src.startsWith('="') && src.endsWith('"')) {
|
|
31
29
|
return src.slice(2, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
|
|
@@ -49,6 +47,137 @@ function ZenFormDesignButtonEntry(props) {
|
|
|
49
47
|
'font-size: 13px; font-weight: 500;',
|
|
50
48
|
}, translate('Design Form')));
|
|
51
49
|
}
|
|
50
|
+
// ─── Form variable scanning ──────────────────────────────────────────────────
|
|
51
|
+
function extractFormKeys(components) {
|
|
52
|
+
const keys = [];
|
|
53
|
+
for (const comp of components || []) {
|
|
54
|
+
if (comp.key)
|
|
55
|
+
keys.push(comp.key);
|
|
56
|
+
if (comp.components)
|
|
57
|
+
keys.push(...extractFormKeys(comp.components));
|
|
58
|
+
if (comp.rows) {
|
|
59
|
+
for (const row of comp.rows) {
|
|
60
|
+
if (Array.isArray(row))
|
|
61
|
+
keys.push(...extractFormKeys(row));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (comp.columns) {
|
|
65
|
+
for (const col of comp.columns) {
|
|
66
|
+
if (col.components)
|
|
67
|
+
keys.push(...extractFormKeys(col.components));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return keys;
|
|
72
|
+
}
|
|
73
|
+
function scanFormVariables(formJson) {
|
|
74
|
+
try {
|
|
75
|
+
const schema = JSON.parse(formJson);
|
|
76
|
+
return extractFormKeys(schema.components || []);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
console.warn('[ZenBPM] Failed to parse form JSON for variable scanning');
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Sync output mappings with current form fields.
|
|
85
|
+
* - Form fields without an existing output get a default one.
|
|
86
|
+
* - Existing outputs with the same source are kept, preserving user's target.
|
|
87
|
+
* - Outputs for removed form fields are dropped.
|
|
88
|
+
*/
|
|
89
|
+
function syncOutputMappings(element, injector, variableKeys) {
|
|
90
|
+
const commandStack = injector.get('commandStack');
|
|
91
|
+
const bpmnFactory = injector.get('bpmnFactory');
|
|
92
|
+
const bo = element.businessObject;
|
|
93
|
+
let extensionElements = bo.extensionElements;
|
|
94
|
+
const commands = [];
|
|
95
|
+
if (!extensionElements) {
|
|
96
|
+
extensionElements = bpmnFactory.create('bpmn:ExtensionElements', {
|
|
97
|
+
values: [],
|
|
98
|
+
});
|
|
99
|
+
extensionElements.$parent = bo;
|
|
100
|
+
commands.push({
|
|
101
|
+
cmd: 'element.updateModdleProperties',
|
|
102
|
+
context: {
|
|
103
|
+
element,
|
|
104
|
+
moddleElement: bo,
|
|
105
|
+
properties: { extensionElements },
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
let ioMapping = (extensionElements.values || []).find((e) => e.$instanceOf('zenbpm:IoMapping'));
|
|
110
|
+
if (!ioMapping) {
|
|
111
|
+
ioMapping = bpmnFactory.create('zenbpm:IoMapping', {
|
|
112
|
+
inputParameters: [],
|
|
113
|
+
outputParameters: [],
|
|
114
|
+
});
|
|
115
|
+
ioMapping.$parent = extensionElements;
|
|
116
|
+
commands.push({
|
|
117
|
+
cmd: 'element.updateModdleProperties',
|
|
118
|
+
context: {
|
|
119
|
+
element,
|
|
120
|
+
moddleElement: extensionElements,
|
|
121
|
+
properties: {
|
|
122
|
+
values: [...(extensionElements.values || []), ioMapping],
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
// Index existing outputs by source
|
|
128
|
+
const existingBySource = new Map((ioMapping.outputParameters || []).map((o) => [o.source, o]));
|
|
129
|
+
// For each form field, produce an output — reusing existing one if available
|
|
130
|
+
const outputs = variableKeys.map((key) => {
|
|
131
|
+
const source = `=${key}`;
|
|
132
|
+
const existing = existingBySource.get(source);
|
|
133
|
+
if (existing)
|
|
134
|
+
return existing;
|
|
135
|
+
const output = bpmnFactory.create('zenbpm:Output', {
|
|
136
|
+
source,
|
|
137
|
+
target: key,
|
|
138
|
+
});
|
|
139
|
+
output.$parent = ioMapping;
|
|
140
|
+
return output;
|
|
141
|
+
});
|
|
142
|
+
commands.push({
|
|
143
|
+
cmd: 'element.updateModdleProperties',
|
|
144
|
+
context: {
|
|
145
|
+
element,
|
|
146
|
+
moddleElement: ioMapping,
|
|
147
|
+
properties: { outputParameters: outputs },
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
commandStack.execute('properties-panel.multi-command-executor', commands);
|
|
151
|
+
}
|
|
152
|
+
// ─── Form save handler ───────────────────────────────────────────────────────
|
|
153
|
+
const lastFormValueByElement = new Map();
|
|
154
|
+
function setupFormSaveHandler(injector) {
|
|
155
|
+
const eventBus = injector.get('eventBus');
|
|
156
|
+
eventBus.on('commandStack.element.updateModdleProperties.executed', (event) => {
|
|
157
|
+
const { context } = event;
|
|
158
|
+
if (!context)
|
|
159
|
+
return;
|
|
160
|
+
const { moddleElement, properties, element } = context;
|
|
161
|
+
if (moddleElement?.$type !== 'zenbpm:Input' ||
|
|
162
|
+
moddleElement.target !== 'ZEN_FORM' ||
|
|
163
|
+
properties?.source === undefined) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (!element || element.type !== 'bpmn:UserTask')
|
|
167
|
+
return;
|
|
168
|
+
// Defer to avoid nested commandStack.execute() while stack is mid-execution
|
|
169
|
+
setTimeout(() => {
|
|
170
|
+
const formJson = getZenFormValue(element);
|
|
171
|
+
if (!formJson)
|
|
172
|
+
return;
|
|
173
|
+
if (lastFormValueByElement.get(element.id) === formJson)
|
|
174
|
+
return;
|
|
175
|
+
lastFormValueByElement.set(element.id, formJson);
|
|
176
|
+
const variableKeys = scanFormVariables(formJson);
|
|
177
|
+
syncOutputMappings(element, injector, variableKeys);
|
|
178
|
+
}, 0);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
52
181
|
|
|
53
182
|
/**
|
|
54
183
|
* Return the first extension element of `type` from the given business object,
|
|
@@ -331,11 +460,11 @@ function bindingEntries(idPrefix, bindingTypeComponent, versionTagComponent, ele
|
|
|
331
460
|
return entries;
|
|
332
461
|
}
|
|
333
462
|
|
|
334
|
-
const TYPE$
|
|
335
|
-
const ID$
|
|
463
|
+
const TYPE$3 = 'zenbpm:CalledElement';
|
|
464
|
+
const ID$2 = 'zenbpm-calledEl';
|
|
336
465
|
// Module-level component instances — stable references, never recreated on render.
|
|
337
|
-
const BindingTypeEntry$1 = makeBindingTypeEntry(ID$
|
|
338
|
-
const BindingVersionTagEntry$1 = makeVersionTagEntry(ID$
|
|
466
|
+
const BindingTypeEntry$1 = makeBindingTypeEntry(ID$2, TYPE$3);
|
|
467
|
+
const BindingVersionTagEntry$1 = makeVersionTagEntry(ID$2, TYPE$3);
|
|
339
468
|
// ─── entry components ────────────────────────────────────────────────────────
|
|
340
469
|
function ProcessIdEntry(props) {
|
|
341
470
|
const { element } = props;
|
|
@@ -344,9 +473,9 @@ function ProcessIdEntry(props) {
|
|
|
344
473
|
const translate = useService('translate');
|
|
345
474
|
const debounce = useService('debounceInput');
|
|
346
475
|
const bo = element.businessObject;
|
|
347
|
-
const getValue = () => getExtensionElement(bo, TYPE$
|
|
348
|
-
const setValue = (value) => updateExtensionElementProps(element, bo, TYPE$
|
|
349
|
-
return TextFieldEntry({ element, id: `${ID$
|
|
476
|
+
const getValue = () => getExtensionElement(bo, TYPE$3)?.processId ?? '';
|
|
477
|
+
const setValue = (value) => updateExtensionElementProps(element, bo, TYPE$3, { processId: value }, bpmnFactory, commandStack);
|
|
478
|
+
return TextFieldEntry({ element, id: `${ID$2}-processId`, label: translate('Process ID'), getValue, setValue, debounce });
|
|
350
479
|
}
|
|
351
480
|
function PropagateAllChildVarsEntry(props) {
|
|
352
481
|
const { element } = props;
|
|
@@ -354,9 +483,9 @@ function PropagateAllChildVarsEntry(props) {
|
|
|
354
483
|
const bpmnFactory = useService('bpmnFactory');
|
|
355
484
|
const translate = useService('translate');
|
|
356
485
|
const bo = element.businessObject;
|
|
357
|
-
const getValue = () => getExtensionElement(bo, TYPE$
|
|
358
|
-
const setValue = (value) => updateExtensionElementProps(element, bo, TYPE$
|
|
359
|
-
return ToggleSwitchEntry({ element, id: `${ID$
|
|
486
|
+
const getValue = () => getExtensionElement(bo, TYPE$3)?.propagateAllChildVariables ?? false;
|
|
487
|
+
const setValue = (value) => updateExtensionElementProps(element, bo, TYPE$3, { propagateAllChildVariables: value }, bpmnFactory, commandStack);
|
|
488
|
+
return ToggleSwitchEntry({ element, id: `${ID$2}-propagateAllChildVariables`, label: translate('Propagate all child variables'), getValue, setValue });
|
|
360
489
|
}
|
|
361
490
|
function PropagateAllParentVarsEntry(props) {
|
|
362
491
|
const { element } = props;
|
|
@@ -364,27 +493,27 @@ function PropagateAllParentVarsEntry(props) {
|
|
|
364
493
|
const bpmnFactory = useService('bpmnFactory');
|
|
365
494
|
const translate = useService('translate');
|
|
366
495
|
const bo = element.businessObject;
|
|
367
|
-
const getValue = () => getExtensionElement(bo, TYPE$
|
|
368
|
-
const setValue = (value) => updateExtensionElementProps(element, bo, TYPE$
|
|
369
|
-
return ToggleSwitchEntry({ element, id: `${ID$
|
|
496
|
+
const getValue = () => getExtensionElement(bo, TYPE$3)?.propagateAllParentVariables ?? true;
|
|
497
|
+
const setValue = (value) => updateExtensionElementProps(element, bo, TYPE$3, { propagateAllParentVariables: value }, bpmnFactory, commandStack);
|
|
498
|
+
return ToggleSwitchEntry({ element, id: `${ID$2}-propagateAllParentVariables`, label: translate('Propagate all parent variables'), getValue, setValue });
|
|
370
499
|
}
|
|
371
500
|
// ─── exported entry list ─────────────────────────────────────────────────────
|
|
372
501
|
function CalledElementProps(element) {
|
|
373
502
|
if (element.type !== 'bpmn:CallActivity')
|
|
374
503
|
return [];
|
|
375
504
|
return [
|
|
376
|
-
{ id: `${ID$
|
|
377
|
-
...bindingEntries(ID$
|
|
378
|
-
{ id: `${ID$
|
|
379
|
-
{ id: `${ID$
|
|
505
|
+
{ id: `${ID$2}-processId`, component: ProcessIdEntry, isEdited: isTextFieldEntryEdited },
|
|
506
|
+
...bindingEntries(ID$2, BindingTypeEntry$1, BindingVersionTagEntry$1, element, TYPE$3),
|
|
507
|
+
{ id: `${ID$2}-propagateAllChildVariables`, component: PropagateAllChildVarsEntry, isEdited: isToggleSwitchEntryEdited },
|
|
508
|
+
{ id: `${ID$2}-propagateAllParentVariables`, component: PropagateAllParentVarsEntry, isEdited: isToggleSwitchEntryEdited },
|
|
380
509
|
];
|
|
381
510
|
}
|
|
382
511
|
|
|
383
|
-
const TYPE$
|
|
384
|
-
const ID = 'zenbpm-calledDecision';
|
|
512
|
+
const TYPE$2 = 'zenbpm:CalledDecision';
|
|
513
|
+
const ID$1 = 'zenbpm-calledDecision';
|
|
385
514
|
// Module-level component instances — stable references, never recreated on render.
|
|
386
|
-
const BindingTypeEntry = makeBindingTypeEntry(ID, TYPE$
|
|
387
|
-
const BindingVersionTagEntry = makeVersionTagEntry(ID, TYPE$
|
|
515
|
+
const BindingTypeEntry = makeBindingTypeEntry(ID$1, TYPE$2);
|
|
516
|
+
const BindingVersionTagEntry = makeVersionTagEntry(ID$1, TYPE$2);
|
|
388
517
|
// ─── entry components ────────────────────────────────────────────────────────
|
|
389
518
|
function DecisionIdEntry(props) {
|
|
390
519
|
const { element } = props;
|
|
@@ -393,9 +522,9 @@ function DecisionIdEntry(props) {
|
|
|
393
522
|
const translate = useService('translate');
|
|
394
523
|
const debounce = useService('debounceInput');
|
|
395
524
|
const bo = element.businessObject;
|
|
396
|
-
const getValue = () => getExtensionElement(bo, TYPE$
|
|
397
|
-
const setValue = (value) => updateExtensionElementProps(element, bo, TYPE$
|
|
398
|
-
return TextFieldEntry({ element, id: `${ID}-decisionId`, label: translate('Decision ID'), getValue, setValue, debounce });
|
|
525
|
+
const getValue = () => getExtensionElement(bo, TYPE$2)?.decisionId ?? '';
|
|
526
|
+
const setValue = (value) => updateExtensionElementProps(element, bo, TYPE$2, { decisionId: value }, bpmnFactory, commandStack);
|
|
527
|
+
return TextFieldEntry({ element, id: `${ID$1}-decisionId`, label: translate('Decision ID'), getValue, setValue, debounce });
|
|
399
528
|
}
|
|
400
529
|
function ResultVariableEntry(props) {
|
|
401
530
|
const { element } = props;
|
|
@@ -404,18 +533,18 @@ function ResultVariableEntry(props) {
|
|
|
404
533
|
const translate = useService('translate');
|
|
405
534
|
const debounce = useService('debounceInput');
|
|
406
535
|
const bo = element.businessObject;
|
|
407
|
-
const getValue = () => getExtensionElement(bo, TYPE$
|
|
408
|
-
const setValue = (value) => updateExtensionElementProps(element, bo, TYPE$
|
|
409
|
-
return TextFieldEntry({ element, id: `${ID}-resultVariable`, label: translate('Result variable'), getValue, setValue, debounce });
|
|
536
|
+
const getValue = () => getExtensionElement(bo, TYPE$2)?.resultVariable ?? '';
|
|
537
|
+
const setValue = (value) => updateExtensionElementProps(element, bo, TYPE$2, { resultVariable: value }, bpmnFactory, commandStack);
|
|
538
|
+
return TextFieldEntry({ element, id: `${ID$1}-resultVariable`, label: translate('Result variable'), getValue, setValue, debounce });
|
|
410
539
|
}
|
|
411
540
|
// ─── exported entry list ─────────────────────────────────────────────────────
|
|
412
541
|
function CalledDecisionProps(element) {
|
|
413
542
|
if (element.type !== 'bpmn:BusinessRuleTask')
|
|
414
543
|
return [];
|
|
415
544
|
return [
|
|
416
|
-
{ id: `${ID}-decisionId`, component: DecisionIdEntry, isEdited: isTextFieldEntryEdited },
|
|
417
|
-
...bindingEntries(ID, BindingTypeEntry, BindingVersionTagEntry, element, TYPE$
|
|
418
|
-
{ id: `${ID}-resultVariable`, component: ResultVariableEntry, isEdited: isTextFieldEntryEdited },
|
|
545
|
+
{ id: `${ID$1}-decisionId`, component: DecisionIdEntry, isEdited: isTextFieldEntryEdited },
|
|
546
|
+
...bindingEntries(ID$1, BindingTypeEntry, BindingVersionTagEntry, element, TYPE$2),
|
|
547
|
+
{ id: `${ID$1}-resultVariable`, component: ResultVariableEntry, isEdited: isTextFieldEntryEdited },
|
|
419
548
|
];
|
|
420
549
|
}
|
|
421
550
|
|
|
@@ -499,7 +628,7 @@ function VersionTagProps(element) {
|
|
|
499
628
|
];
|
|
500
629
|
}
|
|
501
630
|
|
|
502
|
-
const TYPE = 'zenbpm:LoopCharacteristics';
|
|
631
|
+
const TYPE$1 = 'zenbpm:LoopCharacteristics';
|
|
503
632
|
// ─── helpers ─────────────────────────────────────────────────────────────────
|
|
504
633
|
/**
|
|
505
634
|
* Return the bpmn:MultiInstanceLoopCharacteristics of an element, or null.
|
|
@@ -512,7 +641,7 @@ function getMultiInstanceLoopCharacteristics(element) {
|
|
|
512
641
|
}
|
|
513
642
|
function getZenbpmLoopCharacteristics(element) {
|
|
514
643
|
const lc = getMultiInstanceLoopCharacteristics(element);
|
|
515
|
-
return lc ? getExtensionElement(lc, TYPE) : undefined;
|
|
644
|
+
return lc ? getExtensionElement(lc, TYPE$1) : undefined;
|
|
516
645
|
}
|
|
517
646
|
// ─── entry components ────────────────────────────────────────────────────────
|
|
518
647
|
/**
|
|
@@ -526,7 +655,7 @@ function InputCollectionEntry(props) {
|
|
|
526
655
|
const debounce = useService('debounceInput');
|
|
527
656
|
const lc = getMultiInstanceLoopCharacteristics(element);
|
|
528
657
|
const getValue = () => getZenbpmLoopCharacteristics(element)?.inputCollection ?? '';
|
|
529
|
-
const setValue = (value) => updateExtensionElementProps(element, lc, TYPE, { inputCollection: value }, bpmnFactory, commandStack);
|
|
658
|
+
const setValue = (value) => updateExtensionElementProps(element, lc, TYPE$1, { inputCollection: value }, bpmnFactory, commandStack);
|
|
530
659
|
return FeelEntry({
|
|
531
660
|
element,
|
|
532
661
|
id: 'zenbpm-multiInstance-inputCollection',
|
|
@@ -548,7 +677,7 @@ function InputElementEntry(props) {
|
|
|
548
677
|
const debounce = useService('debounceInput');
|
|
549
678
|
const lc = getMultiInstanceLoopCharacteristics(element);
|
|
550
679
|
const getValue = () => getZenbpmLoopCharacteristics(element)?.inputElement ?? '';
|
|
551
|
-
const setValue = (value) => updateExtensionElementProps(element, lc, TYPE, { inputElement: value }, bpmnFactory, commandStack);
|
|
680
|
+
const setValue = (value) => updateExtensionElementProps(element, lc, TYPE$1, { inputElement: value }, bpmnFactory, commandStack);
|
|
552
681
|
return TextFieldEntry({
|
|
553
682
|
element,
|
|
554
683
|
id: 'zenbpm-multiInstance-inputElement',
|
|
@@ -569,7 +698,7 @@ function OutputCollectionEntry(props) {
|
|
|
569
698
|
const debounce = useService('debounceInput');
|
|
570
699
|
const lc = getMultiInstanceLoopCharacteristics(element);
|
|
571
700
|
const getValue = () => getZenbpmLoopCharacteristics(element)?.outputCollection ?? '';
|
|
572
|
-
const setValue = (value) => updateExtensionElementProps(element, lc, TYPE, { outputCollection: value }, bpmnFactory, commandStack);
|
|
701
|
+
const setValue = (value) => updateExtensionElementProps(element, lc, TYPE$1, { outputCollection: value }, bpmnFactory, commandStack);
|
|
573
702
|
return TextFieldEntry({
|
|
574
703
|
element,
|
|
575
704
|
id: 'zenbpm-multiInstance-outputCollection',
|
|
@@ -590,7 +719,7 @@ function OutputElementEntry(props) {
|
|
|
590
719
|
const debounce = useService('debounceInput');
|
|
591
720
|
const lc = getMultiInstanceLoopCharacteristics(element);
|
|
592
721
|
const getValue = () => getZenbpmLoopCharacteristics(element)?.outputElement ?? '';
|
|
593
|
-
const setValue = (value) => updateExtensionElementProps(element, lc, TYPE, { outputElement: value }, bpmnFactory, commandStack);
|
|
722
|
+
const setValue = (value) => updateExtensionElementProps(element, lc, TYPE$1, { outputElement: value }, bpmnFactory, commandStack);
|
|
594
723
|
return FeelEntry({
|
|
595
724
|
element,
|
|
596
725
|
id: 'zenbpm-multiInstance-outputElement',
|
|
@@ -788,6 +917,107 @@ function ConditionExpressionProps(element) {
|
|
|
788
917
|
];
|
|
789
918
|
}
|
|
790
919
|
|
|
920
|
+
const TYPE = 'zenbpm:Subscription';
|
|
921
|
+
const ID = 'zenbpm-messageSubscriptionCorrelationKey';
|
|
922
|
+
// ─── helpers ─────────────────────────────────────────────────────────────────
|
|
923
|
+
/**
|
|
924
|
+
* Return the bpmn:Message associated with the given diagram element, or
|
|
925
|
+
* undefined if the element has no message (and therefore no subscription).
|
|
926
|
+
*
|
|
927
|
+
* ZenBPM only considers the following elements to be message subscribers:
|
|
928
|
+
* - bpmn:IntermediateCatchEvent (with bpmn:MessageEventDefinition)
|
|
929
|
+
* - bpmn:BoundaryEvent (with bpmn:MessageEventDefinition)
|
|
930
|
+
* - bpmn:StartEvent (only inside an event sub-process)
|
|
931
|
+
* ReceiveTask / EndEvent / IntermediateThrowEvent are not subscription points.
|
|
932
|
+
*/
|
|
933
|
+
function getMessage(element) {
|
|
934
|
+
const bo = element.businessObject;
|
|
935
|
+
if (!bo) {
|
|
936
|
+
return undefined;
|
|
937
|
+
}
|
|
938
|
+
const eventDefinitions = bo.eventDefinitions || [];
|
|
939
|
+
for (const def of eventDefinitions) {
|
|
940
|
+
if (def.$type === 'bpmn:MessageEventDefinition') {
|
|
941
|
+
return def.get('messageRef');
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
return undefined;
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* Eligibility for the subscription correlation key field, derived from how the
|
|
948
|
+
* ZenBPM engine actually consumes the value at runtime:
|
|
949
|
+
*
|
|
950
|
+
* - bpmn:IntermediateCatchEvent / bpmn:BoundaryEvent → yes
|
|
951
|
+
* Engine creates a TokenMessageSubscription that uses the key for matching.
|
|
952
|
+
*
|
|
953
|
+
* - bpmn:StartEvent inside an event sub-process only → yes
|
|
954
|
+
* Engine creates an InstanceMessageSubscription that uses the key.
|
|
955
|
+
*
|
|
956
|
+
* - bpmn:StartEvent at the process root → no
|
|
957
|
+
* Engine creates a DefinitionMessageSubscription that ignores the key.
|
|
958
|
+
*
|
|
959
|
+
* - bpmn:ReceiveTask → no
|
|
960
|
+
* Not supported by the ZenBPM engine (deployment error).
|
|
961
|
+
*
|
|
962
|
+
* - bpmn:EndEvent / bpmn:IntermediateThrowEvent → no
|
|
963
|
+
* Throw events are job-based, not subscription-based.
|
|
964
|
+
*/
|
|
965
|
+
function canHaveSubscriptionCorrelationKey(element) {
|
|
966
|
+
const bo = element.businessObject;
|
|
967
|
+
if (!bo) {
|
|
968
|
+
return false;
|
|
969
|
+
}
|
|
970
|
+
if (bo.$type === 'bpmn:IntermediateCatchEvent' || bo.$type === 'bpmn:BoundaryEvent') {
|
|
971
|
+
return !!getMessage(element);
|
|
972
|
+
}
|
|
973
|
+
if (bo.$type === 'bpmn:StartEvent') {
|
|
974
|
+
const parentBo = element.parent?.businessObject;
|
|
975
|
+
return !!parentBo && parentBo.$type === 'bpmn:SubProcess' && !!parentBo.triggeredByEvent;
|
|
976
|
+
}
|
|
977
|
+
return false;
|
|
978
|
+
}
|
|
979
|
+
// ─── entry component ────────────────────────────────────────────────────────
|
|
980
|
+
function MessageSubscriptionCorrelationKeyEntry(props) {
|
|
981
|
+
const { element } = props;
|
|
982
|
+
const commandStack = useService('commandStack');
|
|
983
|
+
const bpmnFactory = useService('bpmnFactory');
|
|
984
|
+
const translate = useService('translate');
|
|
985
|
+
const debounce = useService('debounceInput');
|
|
986
|
+
// The subscription lives on the referenced bpmn:Message, not on the
|
|
987
|
+
// diagram element itself — this matches the zeebe:Subscription behaviour.
|
|
988
|
+
// `message` can become undefined at render time if the user unlinks the
|
|
989
|
+
// message after the entry is already mounted, so guard every access.
|
|
990
|
+
const message = getMessage(element);
|
|
991
|
+
const getValue = () => message
|
|
992
|
+
? getFeelValue(getExtensionElement(message, TYPE)?.correlationKey)
|
|
993
|
+
: '';
|
|
994
|
+
const setValue = (value) => {
|
|
995
|
+
if (!message) {
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
updateExtensionElementProps(element, message, TYPE, { correlationKey: value }, bpmnFactory, commandStack);
|
|
999
|
+
};
|
|
1000
|
+
return FeelEntry({
|
|
1001
|
+
element,
|
|
1002
|
+
id: ID,
|
|
1003
|
+
label: translate('Subscription correlation key'),
|
|
1004
|
+
feel: 'required',
|
|
1005
|
+
getValue,
|
|
1006
|
+
setValue,
|
|
1007
|
+
debounce,
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
// ─── exported entry list ─────────────────────────────────────────────────────
|
|
1011
|
+
function CorrelationKeyProps(element) {
|
|
1012
|
+
if (!canHaveSubscriptionCorrelationKey(element))
|
|
1013
|
+
return [];
|
|
1014
|
+
if (!getMessage(element))
|
|
1015
|
+
return [];
|
|
1016
|
+
return [
|
|
1017
|
+
{ id: ID, component: MessageSubscriptionCorrelationKeyEntry, isEdited: isFeelEntryEdited },
|
|
1018
|
+
];
|
|
1019
|
+
}
|
|
1020
|
+
|
|
791
1021
|
const PROVIDER_PRIORITY = 500;
|
|
792
1022
|
class ZenBpmPropertiesProvider {
|
|
793
1023
|
static $inject = ['propertiesPanel', 'injector'];
|
|
@@ -795,6 +1025,9 @@ class ZenBpmPropertiesProvider {
|
|
|
795
1025
|
constructor(propertiesPanel, injector) {
|
|
796
1026
|
this._injector = injector;
|
|
797
1027
|
propertiesPanel.registerProvider(PROVIDER_PRIORITY, this);
|
|
1028
|
+
// When the Zen Form editor is submitted, scan form field variables
|
|
1029
|
+
// and automatically add them to the output mapping.
|
|
1030
|
+
setupFormSaveHandler(injector);
|
|
798
1031
|
}
|
|
799
1032
|
getGroups(element) {
|
|
800
1033
|
return (groups) => {
|
|
@@ -875,6 +1108,24 @@ class ZenBpmPropertiesProvider {
|
|
|
875
1108
|
});
|
|
876
1109
|
}
|
|
877
1110
|
}
|
|
1111
|
+
// ── Message subscription correlation key ────────────────────────────
|
|
1112
|
+
// Appended to the standard 'message' group (created by bpmn-js-properties-panel)
|
|
1113
|
+
// so it sits right under the message name, mirroring the zeebe:Subscription UX.
|
|
1114
|
+
const correlationKeyEntries = CorrelationKeyProps(element);
|
|
1115
|
+
if (correlationKeyEntries.length) {
|
|
1116
|
+
const messageGroup = groups.find((g) => g.id === 'message');
|
|
1117
|
+
if (messageGroup) {
|
|
1118
|
+
messageGroup.entries = [...messageGroup.entries, ...correlationKeyEntries];
|
|
1119
|
+
}
|
|
1120
|
+
else {
|
|
1121
|
+
groups.push({
|
|
1122
|
+
id: 'message',
|
|
1123
|
+
label: translate('Message'),
|
|
1124
|
+
entries: correlationKeyEntries,
|
|
1125
|
+
component: Group,
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
878
1129
|
// ── Condition expression ─────────────────────────────────────────────
|
|
879
1130
|
// The standard bpmn-js-properties-panel already adds a 'conditionExpression'
|
|
880
1131
|
// entry to the 'condition' group. We replace the entire group so that only
|