Skip to content

Commit 4a57e2c

Browse files
authored
handle correlation properties better on rename (#145)
Before the fix, when a message’s correlation properties changed, the reconciliation logic updated what was newly present, but it did not fully clean up what had become stale. That left three bad remnants behind when a property was renamed or removed: - the old bpmn:correlationProperty could stay in definitions - the old spiffworkflow:ProcessVariableCorrelation extension entry could stay on the message event - retrieval expressions or matching references could still point at the old property id That matters because a rename is really "remove old id, add new id," not just "edit a label." If the old id survives in XML, runtime matching can still see it and behave incorrectly. The fix makes syncCorrelationProperties(...) reconcile in both directions: - add or update the properties returned from the editor - remove correlation properties for this message that are no longer returned - remove stale ProcessVariableCorrelation extension elements tied to removed properties - remove stale retrieval-expression references for the removed property ids So the model is now treated as the editor response being the full source of truth for that message’s correlations, instead of only applying additive updates. The regression test in MessagesSpec.js:179 covers the real failure shape: start with a message using singer_name, make the matching condition reference it, then simulate the editor returning singer_full_name instead. The test asserts the old id is gone from both the in-memory BPMN model and the serialized XML. Co-authored-by: burnettk <burnettk@users.noreply.github.com>
1 parent d454ece commit 4a57e2c

2 files changed

Lines changed: 130 additions & 44 deletions

File tree

app/spiffworkflow/messages/MessageHelpers.js

Lines changed: 62 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -828,58 +828,76 @@ export function syncCorrelationProperties(
828828
) {
829829
const { businessObject } = element;
830830
const correlationProps = findCorrelationProperties(businessObject, moddle);
831-
const expressionsToDelete = [];
832-
833-
for (let cProperty of correlationProps) {
834-
let isUsed = false;
835-
for (const cpExpression of cProperty.correlationPropertyRetrievalExpression) {
836-
const msgRef = cpExpression.messageRef
837-
? findMessageElement(
838-
businessObject,
839-
cpExpression.messageRef.id,
840-
definitions
841-
)
842-
: undefined;
843-
isUsed =
844-
msgRef &&
845-
msgObject &&
846-
cpExpression.messageRef.id !== msgObject.identifier
847-
? true
848-
: isUsed;
849-
// if unused false, delete retrival expression
831+
const messageId = msgObject?.identifier;
832+
const activePropertyIds = new Set(
833+
(msgObject?.correlation_properties || []).map((obj) => obj.identifier)
834+
);
835+
836+
for (const cProperty of correlationProps) {
837+
const nextExpressions = (
838+
cProperty.correlationPropertyRetrievalExpression || []
839+
).filter((cpExpression) => {
840+
if (!cpExpression.messageRef?.id) {
841+
return false;
842+
}
843+
844+
const msgRef = findMessageElement(
845+
businessObject,
846+
cpExpression.messageRef.id,
847+
definitions
848+
);
849+
850850
if (!msgRef) {
851-
expressionsToDelete.push(cpExpression);
851+
return false;
852852
}
853-
}
854853

855-
// Delete the retrieval expressions that are not used
856-
for (const expression of expressionsToDelete) {
857-
const index =
858-
cProperty.correlationPropertyRetrievalExpression.indexOf(expression);
859-
if (index > -1) {
860-
cProperty.correlationPropertyRetrievalExpression.splice(index, 1);
861-
const cPropertyIndex = definitions
862-
.get('rootElements')
863-
.indexOf(cProperty);
864-
definitions.rootElements.splice(cPropertyIndex, 1, cProperty);
854+
if (messageId && cpExpression.messageRef.id === messageId) {
855+
return activePropertyIds.has(cProperty.id);
865856
}
866-
}
867857

868-
// If Unused, delete the correlation property
869-
const propertyToBeDeleted =
870-
isUsed ||
871-
(msgObject &&
872-
msgObject.correlation_properties &&
873-
msgObject.correlation_properties.some(
874-
(obj) => obj.identifier === cProperty.id
875-
));
876-
if (!propertyToBeDeleted) {
877-
const index = definitions.get('rootElements').indexOf(cProperty);
878-
if (index > -1) {
879-
definitions.rootElements.splice(index, 1);
858+
return true;
859+
});
860+
861+
cProperty.correlationPropertyRetrievalExpression = nextExpressions;
862+
863+
const hasOtherMessageReferences = nextExpressions.some(
864+
(cpExpression) => cpExpression.messageRef?.id !== messageId
865+
);
866+
const keepProperty =
867+
activePropertyIds.has(cProperty.id) || hasOtherMessageReferences;
868+
const cPropertyIndex = definitions.get('rootElements').indexOf(cProperty);
869+
870+
if (!keepProperty) {
871+
if (cPropertyIndex > -1) {
872+
definitions.rootElements.splice(cPropertyIndex, 1);
880873
}
874+
continue;
875+
}
876+
877+
if (cPropertyIndex > -1) {
878+
definitions.rootElements.splice(cPropertyIndex, 1, cProperty);
881879
}
882880
}
881+
882+
removeUnusedProcessVariableCorrelations(element, activePropertyIds);
883+
}
884+
885+
function removeUnusedProcessVariableCorrelations(element, activePropertyIds) {
886+
const extensionHost = isMessageEvent(element)
887+
? element.businessObject.eventDefinitions?.[0]
888+
: element.businessObject;
889+
const extensionElements = extensionHost?.get('extensionElements');
890+
891+
if (!extensionElements?.values) {
892+
return;
893+
}
894+
895+
extensionElements.values = extensionElements.values.filter((value) => {
896+
return (
897+
!value.$instanceOf('spiffworkflow:ProcessVariableCorrelation') ||
898+
activePropertyIds.has(value.propertyId)
899+
);
900+
});
883901
}
884902

885903
export function deleteMessage(definitions, messageId) {

test/spec/MessagesSpec.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import messages from '../../app/spiffworkflow/messages';
2121
import { fireEvent } from '@testing-library/preact';
2222
import { getBpmnJS, inject } from 'bpmn-js/test/helper';
2323
import { findCorrelationProperties, findMessageModdleElements } from '../../app/spiffworkflow/messages/MessageHelpers';
24+
import { SPIFF_ADD_MESSAGE_RETURNED_EVENT } from '../../app/spiffworkflow/constants';
2425

2526

2627
describe('Messages should work', function () {
@@ -175,6 +176,73 @@ describe('Messages should work', function () {
175176
expect(updatedSelector.options[2].value).to.equal('msgName');
176177
});
177178

179+
it('should remove stale correlation properties and matching conditions after a rename', inject(async function (canvas, commandStack, moddle) {
180+
const modeler = getBpmnJS();
181+
const eventBus = modeler.get('eventBus');
182+
const rootShape = canvas.getRootElement();
183+
184+
const receiveShape = await expectSelected('EventReceiveLetter');
185+
expect(receiveShape, "Can't find Receive Event").to.exist;
186+
187+
const messageEventDefinition = receiveShape.businessObject.eventDefinitions[0];
188+
const extensionElements = moddle.create('bpmn:ExtensionElements', {
189+
values: [],
190+
});
191+
192+
extensionElements.get('values').push(
193+
moddle.create('spiffworkflow:ProcessVariableCorrelation', {
194+
propertyId: 'singer_name',
195+
expression: 'payload.singer',
196+
})
197+
);
198+
199+
messageEventDefinition.set('extensionElements', extensionElements);
200+
201+
commandStack.execute('element.updateProperties', {
202+
element: receiveShape,
203+
properties: {
204+
'spiffworkflow:isMatchingCorrelation': true,
205+
},
206+
});
207+
208+
eventBus.fire(SPIFF_ADD_MESSAGE_RETURNED_EVENT, {
209+
elementId: receiveShape.id,
210+
name: 'love_letter_response',
211+
correlation_properties: {
212+
lover_instrument: { retrieval_expression: 'from.instrument' },
213+
lover_name: { retrieval_expression: 'from.name' },
214+
singer_full_name: { retrieval_expression: 'to.name' },
215+
},
216+
});
217+
218+
const updatedIds = findCorrelationProperties(rootShape.businessObject)
219+
.filter((property) =>
220+
property.correlationPropertyRetrievalExpression?.some(
221+
(expr) => expr.messageRef?.id === 'love_letter_response'
222+
)
223+
)
224+
.map((property) => property.id);
225+
226+
expect(updatedIds).to.include('singer_full_name');
227+
expect(updatedIds).not.to.include('singer_name');
228+
229+
const variableCorrelationIds = (
230+
messageEventDefinition.extensionElements?.values || []
231+
)
232+
.filter((value) =>
233+
value.$instanceOf('spiffworkflow:ProcessVariableCorrelation')
234+
)
235+
.map((value) => value.propertyId);
236+
237+
expect(variableCorrelationIds).not.to.include('singer_name');
238+
239+
const { xml: updatedXml } = await modeler.saveXML({ format: true });
240+
241+
expect(updatedXml).to.include('singer_full_name');
242+
expect(updatedXml).not.to.include('propertyId="singer_name"');
243+
expect(updatedXml).not.to.include('<bpmn:correlationProperty id="singer_name"');
244+
}));
245+
178246

179247

180248
// 🔶🔶 OLD Features

0 commit comments

Comments
 (0)