Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ All notable changes to [@bpmn-io/variable-resolver](https://github.com/bpmn-io/v

___Note:__ Yet to be released changes appear here._

* `FIX`: show FEEL expression for unresolvable function invocations ([#104](https://github.com/bpmn-io/variable-resolver/pull/104), [camunda/camunda-modeler#5744](https://github.com/camunda/camunda-modeler/issues/5744))
* `DEPS`: update to `@bpmn-io/lezer-feel@2.4.0`

## 3.0.0

* `FEAT`: expose both read and written variables. `getVariablesForElement` can be used to retrieve variables for a specific element ([#74](https://github.com/bpmn-io/variable-resolver/pull/74))
Expand Down
38 changes: 36 additions & 2 deletions lib/zeebe/util/feelUtility.js
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,14 @@ function resolveReferences(variablesToResolve, allVariables) {
computedResult.value.info = expression;
}

// If the result is still unresolvable (type "Any") and no info was set,
// preserve the original FEEL expression as info so users can see the
// expression itself (e.g. "=abs(-22)") instead of a blank value.
// cf. https://github.com/camunda/camunda-modeler/issues/5744
if (getType(computedResult.value) === 'Any' && !computedResult.value.info) {
computedResult.value.info = expression;
}

// Ensure we don't copy the scope from the mapped variable
computedResult.scope = variable.scope;

Expand Down Expand Up @@ -656,9 +664,25 @@ export function toUnifiedFormat(variables) {
continue;
}

const annotated = annotate(variable);
const entries = toUnifiedFormat(variable.entries);

if (isNullishValue(annotated, entries)) {

// FEEL has no "unknown" concept. So we normalize to Null.
result.push({
name: key,
type: 'Null',
info: 'null',
scope: variable.scope
});

continue;
}

result.push({
...annotate(variable),
entries: toUnifiedFormat(variable.entries),
...annotated,
entries,
name: key,
scope: variable.scope
});
Expand All @@ -668,6 +692,16 @@ export function toUnifiedFormat(variables) {
}


/**
* Values with unknown type, no info, and no sub-entries cannot be
* determined at design time (e.g. nested results of unresolvable
* function invocations).
*/
function isNullishValue(annotated, entries) {
return annotated.type === 'Any' && !annotated.info &&
(!entries || !entries.length);
}

function annotate(variable) {
return {
...variable,
Expand Down
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"dependencies": {
"@bpmn-io/extract-process-variables": "^2.2.1",
"@bpmn-io/feel-analyzer": "^0.1.0",
"@bpmn-io/lezer-feel": "^2.3.1",
"@bpmn-io/lezer-feel": "^2.4.0",
"@camunda/feel-builtins": "^1.0.0",
"@lezer/common": "^1.5.1",
"min-dash": "^5.0.0"
Expand Down
22 changes: 22 additions & 0 deletions test/fixtures/zeebe/function-invocation.bpmn
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:zeebe="http://camunda.org/schema/zeebe/1.0" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_1" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.42.0" modeler:executionPlatform="Camunda Cloud" modeler:executionPlatformVersion="8.9.0">
<bpmn:process id="Process_functionInvocation" isExecutable="true">
<bpmn:serviceTask id="functionInvocationTask" name="Function Invocation">
<bpmn:extensionElements>
<zeebe:ioMapping>
<zeebe:output source="=abs(-22)" target="outAbs" />
<zeebe:output source="=substring(&#34;foobar&#34;, 3)" target="outSubstring" />
<zeebe:output source="=now()" target="outNow" />
<zeebe:output source="={ a: function() { nested: abs(x) }, b: a() }.b" target="outContextFunc" />
</zeebe:ioMapping>
</bpmn:extensionElements>
</bpmn:serviceTask>
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_functionInvocation">
<bpmndi:BPMNShape id="functionInvocationTask_di" bpmnElement="functionInvocationTask">
<dc:Bounds x="160" y="80" width="100" height="80" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>
70 changes: 70 additions & 0 deletions test/spec/zeebe/ZeebeVariableResolver.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import subprocessNoOutputMappingXML from 'test/fixtures/zeebe/sub-process.no-out
import longBrokenExpressionXML from 'test/fixtures/zeebe/long-broken-expression.bpmn';
import immediatelyBrokenExpressionXML from 'test/fixtures/zeebe/immediately-broken-expression.bpmn';
import typeResolutionXML from 'test/fixtures/zeebe/type-resolution.bpmn';
import functionInvocationXML from 'test/fixtures/zeebe/function-invocation.bpmn';
import usedVariablesXML from 'test/fixtures/zeebe/used-variables.bpmn';
import usedVariablesScopesXML from 'test/fixtures/zeebe/used-variables.scopes.bpmn';
import readWriteXML from 'test/fixtures/zeebe/read-write.bpmn';
Expand Down Expand Up @@ -2668,6 +2669,75 @@ describe('ZeebeVariableResolver', function() {
});


describe('function invocation resolution', function() {

beforeEach(bootstrapModeler(functionInvocationXML, {
additionalModules: [
ZeebeVariableResolverModule
],
moddleExtensions: {
zeebe: ZeebeModdle
}
}));

it('should show expression for unresolvable function calls', inject(async function(elementRegistry, variableResolver) {

// given
const task = elementRegistry.get('functionInvocationTask');

// when
const variables = await variableResolver.getVariablesForElement(task);

// then
expect(variables).to.variableInclude([
{
name: 'outAbs',
type: 'Any',
info: '=abs(-22)',
scope: 'Process_functionInvocation'
},
{
name: 'outSubstring',
type: 'Any',
info: '=substring("foobar", 3)',
scope: 'Process_functionInvocation'
},
{
name: 'outNow',
type: 'Any',
info: '=now()',
scope: 'Process_functionInvocation'
}
]);
}));


it('should preserve nested entries in context-defined function results', inject(async function(elementRegistry, variableResolver) {

// given
const task = elementRegistry.get('functionInvocationTask');

// when
const variables = await variableResolver.getVariablesForElement(task);

// then
expect(variables).to.variableInclude({
name: 'outContextFunc',
type: 'Context',
scope: 'Process_functionInvocation',
entries: [
{
name: 'nested',
type: 'Null',
info: 'null'
}
]
});
}));

});


describe('used variables - scopes', function() {

beforeEach(bootstrapModeler(usedVariablesScopesXML, {
Expand Down