Skip to content

Provide variables used in feel expressions#74

Open
Buckwich wants to merge 12 commits intomainfrom
5639_variable-input-requirements
Open

Provide variables used in feel expressions#74
Buckwich wants to merge 12 commits intomainfrom
5639_variable-input-requirements

Conversation

@Buckwich
Copy link
Member

@Buckwich Buckwich commented Feb 18, 2026

Related to camunda/camunda-modeler#5639

Proposed Changes

  • getVariables now also includec scopeless (=> read) variables
  • getVariablesForElement can now take an optional filter object (defaults to true for all) to reuse common filtering tasks to differentiate between read/written and local/external variables

different consumers:

npx @bpmn-io/sr camunda/task-testing#update-variable-resolver-v3 -l bpmn-io/variable-resolver#5639_variable-input-requirements

npx @bpmn-io/sr bpmn-io/variable-outline#update-variable-resolver-v3 -l bpmn-io/variable-resolver#5639_variable-input-requirements

follow up PRs:

Checklist

Ensure you provide everything we need to review your contribution:

  • Contribution meets our definition of done
  • Pull request establishes context
    • Link to related issue(s), i.e. Closes {LINK_TO_ISSUE} or Related to {LINK_TO_ISSUE}
    • Brief textual description of the changes
    • Screenshots or short videos showing UI/UX changes
    • Steps to try out, i.e. using the @bpmn-io/sr tool

@bpmn-io-tasks bpmn-io-tasks bot added the in progress Currently worked on label Feb 18, 2026
@Buckwich Buckwich requested a review from Copilot February 18, 2026 23:16
@Buckwich Buckwich changed the base branch from simon-wip to main February 18, 2026 23:16
@Buckwich Buckwich changed the title feat: provide variables used in feel expressions Provide variables used in feel expressions Feb 18, 2026
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request implements functionality to extract and track variables used in FEEL expressions within BPMN process definitions. The feature analyzes input mappings and script task expressions to identify which variables are required as inputs, creating "input requirement" entries that track dependencies between variables.

Changes:

  • Added FEEL expression analysis using @bpmn-io/feel-analyzer to extract input variables from expressions
  • Introduced usedBy property on ProcessVariable objects to track which target variables depend on each input
  • Added getInputRequirementsForElement API method to retrieve input requirements for specific elements

Reviewed changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated no comments.

Show a summary per file
File Description
package.json Added dependencies for @bpmn-io/feel-analyzer and @camunda/feel-builtins to enable FEEL expression analysis
package-lock.json Updated dependency tree with new packages and peer dependency resolutions
lib/zeebe/util/feelUtility.js Core implementation: analyzes expressions with FeelAnalyzer, builds input requirements with deduplication and entry merging, filters scoped variables
lib/zeebe/VariableResolver.js Integrates input requirements into the variable resolution pipeline
lib/base/VariableResolver.js Adds getInputRequirementsForElement method, updates scope filtering to exclude input requirements, fixes async return type documentation
test/spec/zeebe/Mappings.spec.js Comprehensive tests for input requirement extraction covering simple/nested expressions, deduplication, merging, scoping, and script tasks
test/fixtures/zeebe/mappings/input-requirements.bpmn Test fixture with various input mapping scenarios
test/fixtures/zeebe/mappings/script-task-inputs.bpmn Test fixture for script task input extraction
test/fixtures/zeebe/mappings/script-task-with-input-mappings.bpmn Test fixture combining script tasks with input mappings
Comments suppressed due to low confidence (3)

lib/base/VariableResolver.js:320

  • The new public method getInputRequirementsForElement has no test coverage. Consider adding tests to verify that it correctly filters input requirements by element and handles edge cases like elements with no input requirements, multiple input requirements, and both diagram-js elements and moddle elements as parameters.
  async getInputRequirementsForElement(element) {
    const allVariablesByRoot = await this.getVariables()
      .catch(() => {
        return {};
      });

    const allVariables = Object.values(allVariablesByRoot).flat();

    return allVariables.filter(v =>
      v.usedBy && v.usedBy.length > 0
      && v.origin.length === 1 && v.origin[0].id === element.id
    );
  }

lib/base/VariableResolver.js:305

  • The parameter type annotation for element should be {ModdleElement} to be consistent with other methods in this class like getProcessVariables and getVariablesForElement which use the same parameter type.
   * @param {Object} element

lib/base/VariableResolver.js:318

  • The method compares v.origin[0].id with element.id directly, but the method might be called with a diagram-js element (which has a .businessObject property) or a moddle element directly. Consider using getBusinessObject(element) like getVariablesForElement does on line 256 to handle both cases correctly.
      && v.origin.length === 1 && v.origin[0].id === element.id

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Buckwich Buckwich marked this pull request as ready for review February 19, 2026 10:33
@bpmn-io-tasks bpmn-io-tasks bot added needs review Review pending and removed in progress Currently worked on labels Feb 19, 2026
@Buckwich Buckwich requested review from a team, barmac and jarekdanielak and removed request for a team February 19, 2026 10:33
@barmac
Copy link
Member

barmac commented Feb 19, 2026

One problem which I noticed is that we don't take into account the order in which inputs are defined. This is already a problem with existing variable resolver as we suggest the local variables even before they are declared. You can consider this to be an edge case, but it may be confusing for the users (potential follow-up).

On the example, abc is not defined when input1 is evaluated, so the script result is null in the consequence:

image

In this PR, this manifests by not prefiling abc:

image

@Buckwich
Copy link
Member Author

@barmac I've adjusted the behavior to follow the input mapping chain

@barmac
Copy link
Member

barmac commented Feb 20, 2026

@barmac I've adjusted the behavior to follow the input mapping chain

Hmm it looks that we still remove the variable which is required for the execution:

Screen.Recording.2026-02-20.at.14.07.03.mov

@Buckwich
Copy link
Member Author

Sorry I didn't update the task testing PR yet. you need to test with (updated description)

npx @bpmn-io/sr camunda/task-testing#5640_prefill-tasktesting -l bpmn-io/variable-resolver#5639_variable-input-requirements
Screen.Recording.2026-02-20.at.14.20.03.mov

@barmac
Copy link
Member

barmac commented Feb 20, 2026

Do we want to handle script and business rule tasks correctly in this PR? Asking because there are many places where a variable can be used, and not necessarily defined.

image image

@barmac
Copy link
Member

barmac commented Feb 20, 2026

Variables ordering in script:

  1. inputs from the top to the bottom
  2. script FEEL expression

So the script can use the inputs contrary to what the screenshot indicates.

@Buckwich
Copy link
Member Author

ATM it only looks at io mappings and script expressions. I can either just include all feel expressions (in the sense of any value starting with =, might be to broad) or I need to come up with a list of potential feel expressions

@barmac
Copy link
Member

barmac commented Feb 20, 2026

Gotcha. In that case, I think it's fine to move the complete support to a separate issue, but let's either fix or remove the script part.

@barmac
Copy link
Member

barmac commented Feb 20, 2026

We don't want to check all of the values starting with =, cf. camunda/bpmnlint-plugin-camunda-compat#53

@Buckwich Buckwich force-pushed the 5639_variable-input-requirements branch from ccce04e to 3120e8a Compare February 23, 2026 14:06
Copy link
Member

@nikku nikku left a comment

Choose a reason for hiding this comment

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

Let's discuss the things mentioned:

  • Performance - are we now "fast enough" with all built-ins used?
  • Parsing - we parse the AST twice (this library, and feel-analyzer) - does it matter, if so, how?
  • inputRequrements, is that the name we want to use?
  • output mappings may establish dependencies

@nikku
Copy link
Member

nikku commented Mar 6, 2026

Via #74 (review) I wanted us to show the E2E scope of this change - ensuring we add "used variables" in an integrated way. I went ahead and sketched how that integration could look like in this document.

Summary

To summarize the main takeaways:

  • scope and origin become optional - there can be variables that don't have an origin, that don’t have a scope. usedBy tracks diagram elements that use a particular variable
  • usedBy is a ModdleElement[] - if an element with id=Task_1 is in the usedBy list of variable[name=foo], then it means Task_1 uses foo
  • We need to merge / attache usedBy to the correct scopes (test: verify usedBy attaches to correct scope #92)
  • We have to handle dual use (read and write) scenarios (test: validate dual use of read and written variable #91)
  • We have to be able to handle scenarios where no variables are written (from the model) (test: verify used variables in different scenarios #90)
  • We release the change as major - variables may now be unscoped, and key APIs return read and written variables
  • We don't need to introduce a new API - but can add basic filters to the existing API

Proposed addition to existing API

// return only read variables, excluding local ones
const consumedVariables = variableResolver.getVariablesForElement(element, { read: true, local: false });

// return all written variables
const allWrittenVariables = variableResolver.getVariablesForElement(element, { written: true });

// return all variables (default)
const allVariables = variableResolver.getVariablesForElement(element);

@Buckwich Buckwich force-pushed the 5639_variable-input-requirements branch from d873ddf to dd46c34 Compare March 6, 2026 16:57
@Buckwich Buckwich marked this pull request as ready for review March 6, 2026 16:59
@bpmn-io-tasks bpmn-io-tasks bot added needs review Review pending and removed in progress Currently worked on labels Mar 6, 2026
@nikku nikku force-pushed the 5639_variable-input-requirements branch from dd46c34 to 6b96a34 Compare March 9, 2026 10:48
@nikku nikku added the in progress Currently worked on label Mar 9, 2026 — with bpmn-io-tasks
@nikku nikku removed the needs review Review pending label Mar 9, 2026
@Buckwich
Copy link
Member Author

Buckwich commented Mar 9, 2026

i've adjusted the tests to match (dc02ab2) , note the read:true got a bit more complicated {written:false, local:false, external:false}) but we got rid of the undefined state of local with the new external one

Details

Old

and combination of defined filters

Given

name scope origin usedBy
localResult Task_1 Task_1 Task_1
subFoo SubProcess_1 SubProcess_1 Task_1
taskFoo Task_1 Task_1 Task_1
taskResult Process_1 Task_1

Read

{read:true, written:false, local:undefined}

name scope origin usedBy result
localResult x x
subFoo x x
taskFoo x x
taskResult

Read / local

{read:true, written:false, local:true}

name scope origin usedBy result
localResult x x x
subFoo x
taskFoo x x x
taskResult

Read / global

{read:true, written:false, local:false}

name scope origin usedBy result
localResult x
subFoo x x x
taskFoo x
taskResult x

New

And combination of true filters

Given

name scope origin usedBy
localResult Task_1 Task_1 Task_1
subFoo SubProcess_1 SubProcess_1 Task_1
taskFoo Task_1 Task_1 Task_1
taskResult Process_1 Task_1

Read

{read:true, written:false, local:false, external:false}

name scope origin usedBy result
localResult x x
subFoo x x
taskFoo x x
taskResult

Read/local

{read:true, written:false, local:true, external:false}

name scope origin usedBy result
localResult x x x
subFoo x
taskFoo x x x
taskResult

Read / global

{read:true, written:false, local:false, external:true}

name scope origin usedBy result
localResult x
subFoo x x x
taskFoo x
taskResult x

@Buckwich Buckwich marked this pull request as draft March 9, 2026 16:03
@Buckwich Buckwich force-pushed the 5639_variable-input-requirements branch from dc02ab2 to ef354f4 Compare March 9, 2026 16:19
@nikku
Copy link
Member

nikku commented Mar 10, 2026

@Buckwich I don't understand this:

note the read:true got a bit more complicated {written:false, local:false, external:false})

==> read: false, written: false what results will it yield? All variables that this element does not interact with
==> local: false, external: false will always yield an empty list

@Buckwich Buckwich force-pushed the 5639_variable-input-requirements branch from ef354f4 to 10ec2a1 Compare March 10, 2026 12:16
@Buckwich Buckwich force-pushed the 5639_variable-input-requirements branch from 10ec2a1 to d9e8707 Compare March 11, 2026 15:02
@Buckwich Buckwich marked this pull request as ready for review March 11, 2026 15:15
@bpmn-io-tasks bpmn-io-tasks bot added needs review Review pending and removed in progress Currently worked on labels Mar 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs review Review pending

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants