Skip to content

Allow setting a messages json schema to a local file reference.#150

Open
danfunk wants to merge 1 commit into
mainfrom
feature/messages_with_schema_files
Open

Allow setting a messages json schema to a local file reference.#150
danfunk wants to merge 1 commit into
mainfrom
feature/messages_with_schema_files

Conversation

@danfunk

@danfunk danfunk commented Apr 10, 2026

Copy link
Copy Markdown
Contributor

This allows us to connect a json schema file to a bpmn:Message. This will allow the BPMN file to contain everything about how a message works, and provides a way to collaborate on a message definition between Ed and Arena such that everything is included in the BPMN file and no additional configuration files are required - working much the same way user tasks currently reference locally json files.

We will still have to deal with the problem of the schema up-search in Arena.

So it is possible to update and retrieve a link to a json schema that is connected directy to a bpmn:message element, as shown below.

 <bpmn:message id="remote_update" name="remote_update">
     <bpmn:extensionElements>
         <spiffworkflow:property name="formJsonSchemaFilename" value="mission-parameters-schema.json" />
       </spiffworkflow:properties>
 </bpmn:message>

So it is possible to update set and retrieve a link to a json schema, as shown below.
 ```xml
  <bpmn:message id="remote_update" name="remote_update">
      <bpmn:extensionElements>
          <spiffworkflow:property name="formJsonSchemaFilename" value="mission-parameters-schema.json" />
        </spiffworkflow:properties>
  </bpmn:message>
```
@coderabbitai

coderabbitai Bot commented Apr 10, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

The changes introduce two new helper functions for managing message schema files (getMessageSchemaFile and setMessageSchemaFile), refactor correlation property cleanup logic in MessageHelpers, and update message selection UI components to use the new helpers. Tests are added to validate schema file persistence in BPMN exports.

Changes

Cohort / File(s) Summary
Message Schema File Helpers
app/spiffworkflow/messages/MessageHelpers.js
Rewrote syncCorrelationProperties correlation cleanup logic to iterate and validate correlationPropertyRetrievalExpression entries, removing unresolvable message references and deleting unused correlation properties. Added getMessageSchemaFile() and setMessageSchemaFile() exported helpers to manage formJsonSchemaFilename via BPMN extension elements.
Message UI Integration
app/spiffworkflow/messages/propertiesPanel/elementLevelProvider/MessageJsonSchemaSelect.js, MessageSelect.jsx
Updated UI components to use new getMessageSchemaFile and setMessageSchemaFile helpers. MessageSelect now handles SPIFF_ADD_MESSAGE_RETURNED_EVENT with schema file attachment via setMessageSchemaFile() when schema file is present in event payload.
Minor Cleanup
app/spiffworkflow/messages/propertiesPanel/elementLevelProvider/MessageLaunchEditorButton.js
Removed commented-out dead code; no functional changes to control flow.
Test Coverage
test/spec/MessagesSpec.js
Added three new integration tests validating schema file creation, update, and absence scenarios with BPMN XML persistence verification. Removed debug logging from existing test.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • burnettk
  • essweine

Poem

🐰 A message finds its form and file,
Schema helpers make us smile!
Correlations cleaned with care,
Helpers shared everywhere—hop on! 🐇

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main feature: enabling JSON schema file associations with BPMN messages via extension properties.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/messages_with_schema_files

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@danfunk danfunk requested a review from burnettk April 10, 2026 20:25

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
test/spec/MessagesSpec.js (1)

315-343: Add the missing clear-schema regression.

This only proves that a message with no prior schema stays empty. It will still pass if an existing formJsonSchemaFilename survives an update. Please seed love_letter with an old schema first, then fire the event with the chosen clear contract and assert the extension is removed.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/spiffworkflow/messages/MessageHelpers.js`:
- Around line 920-941: The helper setMessageSchemaFile currently mutates
messageBo.extensionElements directly which bypasses the model command stack;
update it to perform the same changes via the modeling/command stack instead.
Specifically, change setMessageSchemaFile to accept (or obtain) the commandStack
or modeling service and wrap the creation/assignment of extensionElements,
Properties, Property and the prop.value change inside a commandStack.execute (or
modeling.updateModdleProperties / equivalent modeling API) call so the mutation
is executed through the command stack and supports undo/redo and modeling
listeners; reference the existing symbols setMessageSchemaFile, messageBo,
moddle, extensionElements, spiffworkflow:Properties and formJsonSchemaFilename
when locating where to apply this change.
- Around line 835-884: The code in syncCorrelationProperties wrongly requires
msgObject to be present to mark a correlation property as used, causing
properties referenced by other messages to be deleted when msgObject is
undefined; update the usage check inside the loop over
cProperty.correlationPropertyRetrievalExpression to mark isUsed true whenever
msgRef exists and either msgObject is not provided or the msgRef id differs from
msgObject.identifier (e.g. isUsed = msgRef && (!msgObject ||
cpExpression.messageRef.id !== msgObject.identifier) ? true : isUsed), and
additionally tighten the deletion guard for correlation properties by checking
existing messages in definitions.rootElements for any message element whose
correlation_properties reference cProperty.id (use correlation_properties and
correlationPropertyRetrievalExpression identifiers) before removing cProperty.

In
`@app/spiffworkflow/messages/propertiesPanel/elementLevelProvider/MessageSelect.jsx`:
- Around line 139-145: The code only updates the BPMN message when
event.schema_file is truthy, so an explicit empty string never clears an
existing formJsonSchemaFilename; modify the branch in MessageSelect.jsx that
calls getRoot/findMessageById/setMessageSchemaFile so it treats an explicit ''
as a clear action: if event.schema_file === '' call
setMessageSchemaFile(bpmnMessage, '', moddle) (or remove/undefined depending on
setMessageSchemaFile's contract) to clear the existing value, otherwise when
event.schema_file is defined and non-empty call setMessageSchemaFile as before;
keep using getRoot and findMessageById to locate bpmnMessage and always pass
moddle.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 12d49d65-05db-44fe-bd24-f22a2c80c123

📥 Commits

Reviewing files that changed from the base of the PR and between ce90492 and 0aac6e9.

📒 Files selected for processing (5)
  • app/spiffworkflow/messages/MessageHelpers.js
  • app/spiffworkflow/messages/propertiesPanel/elementLevelProvider/MessageJsonSchemaSelect.js
  • app/spiffworkflow/messages/propertiesPanel/elementLevelProvider/MessageLaunchEditorButton.js
  • app/spiffworkflow/messages/propertiesPanel/elementLevelProvider/MessageSelect.jsx
  • test/spec/MessagesSpec.js
💤 Files with no reviewable changes (1)
  • app/spiffworkflow/messages/propertiesPanel/elementLevelProvider/MessageLaunchEditorButton.js

Comment on lines +835 to 884
for (let cProperty of correlationProps) {
let isUsed = false;
const expressionsToDelete = [];
for (const cpExpression of cProperty.correlationPropertyRetrievalExpression) {
const msgRef = cpExpression.messageRef
? findMessageElement(
businessObject,
cpExpression.messageRef.id,
definitions
)
: undefined;
isUsed =
msgRef &&
msgObject &&
cpExpression.messageRef.id !== msgObject.identifier
? true
: isUsed;
// if unused false, delete retrival expression
if (!msgRef) {
return false;
expressionsToDelete.push(cpExpression);
}
}

if (messageId && cpExpression.messageRef.id === messageId) {
return activePropertyIds.has(cProperty.id);
}

return true;
});

cProperty.correlationPropertyRetrievalExpression = nextExpressions;

const hasOtherMessageReferences = nextExpressions.some(
(cpExpression) => cpExpression.messageRef?.id !== messageId
);
const keepProperty =
activePropertyIds.has(cProperty.id) || hasOtherMessageReferences;
const cPropertyIndex = definitions.get('rootElements').indexOf(cProperty);

if (!keepProperty) {
if (cPropertyIndex > -1) {
definitions.rootElements.splice(cPropertyIndex, 1);
// Delete the retrieval expressions that are not used
for (const expression of expressionsToDelete) {
const index =
cProperty.correlationPropertyRetrievalExpression.indexOf(expression);
if (index > -1) {
cProperty.correlationPropertyRetrievalExpression.splice(index, 1);
const cPropertyIndex = definitions
.get('rootElements')
.indexOf(cProperty);
definitions.rootElements.splice(cPropertyIndex, 1, cProperty);
}
continue;
}

if (cPropertyIndex > -1) {
definitions.rootElements.splice(cPropertyIndex, 1, cProperty);
// If Unused, delete the correlation property
const propertyToBeDeleted =
isUsed ||
(msgObject &&
msgObject.correlation_properties &&
msgObject.correlation_properties.some(
(obj) => obj.identifier === cProperty.id
));
if (!propertyToBeDeleted) {
const index = definitions.get('rootElements').indexOf(cProperty);
if (index > -1) {
definitions.rootElements.splice(index, 1);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

This deletes unrelated correlation properties during message removal.

syncCorrelationProperties is also called from app/spiffworkflow/messages/MessageInterceptor.js:41-56 without msgObject. In that path, isUsed can never become true because of the msgObject && ... guard, so correlation properties still referenced by other messages are dropped as soon as one message is deleted.

Safer direction
+  const currentMessageId = msgObject?.identifier;
   for (let cProperty of correlationProps) {
     let isUsed = false;
     const expressionsToDelete = [];
-    for (const cpExpression of cProperty.correlationPropertyRetrievalExpression) {
-      const msgRef = cpExpression.messageRef
-        ? findMessageElement(
-            businessObject,
-            cpExpression.messageRef.id,
-            definitions
-          )
-        : undefined;
-      isUsed =
-        msgRef &&
-        msgObject &&
-        cpExpression.messageRef.id !== msgObject.identifier
-          ? true
-          : isUsed;
+    for (const cpExpression of cProperty.correlationPropertyRetrievalExpression || []) {
+      const referencedId = cpExpression.messageRef?.id;
+      const msgRef = definitions.rootElements?.find(
+        (rootElement) =>
+          rootElement.$type === 'bpmn:Message' && rootElement.id === referencedId
+      );
+      if (msgRef && (!currentMessageId || referencedId !== currentMessageId)) {
+        isUsed = true;
+      }
       if (!msgRef) {
         expressionsToDelete.push(cpExpression);
       }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/spiffworkflow/messages/MessageHelpers.js` around lines 835 - 884, The
code in syncCorrelationProperties wrongly requires msgObject to be present to
mark a correlation property as used, causing properties referenced by other
messages to be deleted when msgObject is undefined; update the usage check
inside the loop over cProperty.correlationPropertyRetrievalExpression to mark
isUsed true whenever msgRef exists and either msgObject is not provided or the
msgRef id differs from msgObject.identifier (e.g. isUsed = msgRef && (!msgObject
|| cpExpression.messageRef.id !== msgObject.identifier) ? true : isUsed), and
additionally tighten the deletion guard for correlation properties by checking
existing messages in definitions.rootElements for any message element whose
correlation_properties reference cProperty.id (use correlation_properties and
correlationPropertyRetrievalExpression identifiers) before removing cProperty.

Comment on lines +920 to +941
export function setMessageSchemaFile(messageBo, schemaFile, moddle) {
if (!messageBo.extensionElements) {
messageBo.extensionElements = moddle.create('bpmn:ExtensionElements');
}
const exts = messageBo.extensionElements;
let props = exts
.get('values')
.find((e) => e.$instanceOf('spiffworkflow:Properties'));
if (!props) {
props = moddle.create('spiffworkflow:Properties');
exts.get('values').push(props);
}
let prop = props
.get('properties')
.find((p) => p.name === 'formJsonSchemaFilename');
if (!prop) {
prop = moddle.create('spiffworkflow:Property');
props.get('properties').push(prop);
}
prop.name = 'formJsonSchemaFilename';
prop.value = schemaFile;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Run schema-file mutations through the command stack.

This helper mutates extensionElements in place. The new callers in the properties panel and add-message event flow use it without an element.update* command, so schema edits bypass undo/redo and other modeling listeners.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/spiffworkflow/messages/MessageHelpers.js` around lines 920 - 941, The
helper setMessageSchemaFile currently mutates messageBo.extensionElements
directly which bypasses the model command stack; update it to perform the same
changes via the modeling/command stack instead. Specifically, change
setMessageSchemaFile to accept (or obtain) the commandStack or modeling service
and wrap the creation/assignment of extensionElements, Properties, Property and
the prop.value change inside a commandStack.execute (or
modeling.updateModdleProperties / equivalent modeling API) call so the mutation
is executed through the command stack and supports undo/redo and modeling
listeners; reference the existing symbols setMessageSchemaFile, messageBo,
moddle, extensionElements, spiffworkflow:Properties and formJsonSchemaFilename
when locating where to apply this change.

Comment on lines +139 to +145
if (event.schema_file) {
const defs = getRoot(element.businessObject);
const bpmnMessage = findMessageById(defs, event.name);
if (bpmnMessage) {
setMessageSchemaFile(bpmnMessage, event.schema_file, moddle);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Handle empty schema_file as a clear, not a no-op.

This branch only runs for truthy values, so updating an existing message with schema_file: '' leaves the old formJsonSchemaFilename behind. That makes schema removal impossible from this event path.

Possible fix
-    if (event.schema_file) {
+    if ('schema_file' in event) {
       const defs = getRoot(element.businessObject);
       const bpmnMessage = findMessageById(defs, event.name);
       if (bpmnMessage) {
-        setMessageSchemaFile(bpmnMessage, event.schema_file, moddle);
+        setMessageSchemaFile(bpmnMessage, event.schema_file ?? '', moddle);
       }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (event.schema_file) {
const defs = getRoot(element.businessObject);
const bpmnMessage = findMessageById(defs, event.name);
if (bpmnMessage) {
setMessageSchemaFile(bpmnMessage, event.schema_file, moddle);
}
}
if ('schema_file' in event) {
const defs = getRoot(element.businessObject);
const bpmnMessage = findMessageById(defs, event.name);
if (bpmnMessage) {
setMessageSchemaFile(bpmnMessage, event.schema_file ?? '', moddle);
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/spiffworkflow/messages/propertiesPanel/elementLevelProvider/MessageSelect.jsx`
around lines 139 - 145, The code only updates the BPMN message when
event.schema_file is truthy, so an explicit empty string never clears an
existing formJsonSchemaFilename; modify the branch in MessageSelect.jsx that
calls getRoot/findMessageById/setMessageSchemaFile so it treats an explicit ''
as a clear action: if event.schema_file === '' call
setMessageSchemaFile(bpmnMessage, '', moddle) (or remove/undefined depending on
setMessageSchemaFile's contract) to clear the existing value, otherwise when
event.schema_file is defined and non-empty call setMessageSchemaFile as before;
keep using getRoot and findMessageById to locate bpmnMessage and always pass
moddle.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant