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