diff --git a/lib/base/VariableResolver.js b/lib/base/VariableResolver.js index 83a9dd1..53611bc 100644 --- a/lib/base/VariableResolver.js +++ b/lib/base/VariableResolver.js @@ -1,6 +1,7 @@ import { getBusinessObject, is } from 'bpmn-js/lib/util/ModelUtil'; import CachedValue from './util/CachedValue'; import { getParents, getScope } from './util/scopeUtil'; +import { uniqueBy } from 'min-dash'; /** * @typedef {Object} AdditionalVariable @@ -257,15 +258,18 @@ export class BaseVariableResolver { const root = getRootElement(bo); const allVariables = await this.getProcessVariables(root); + // keep only unique variables based on name property + const uniqueVariables = uniqueBy('name', allVariables.reverse()); + // (1) get variables for given scope - var scopeVariables = allVariables.filter(function(variable) { + var scopeVariables = uniqueVariables.filter(function(variable) { return variable.scope.id === bo.id; }); // (2) get variables for parent scopes var parents = getParents(bo); - var parentsScopeVariables = allVariables.filter(function(variable) { + var parentsScopeVariables = uniqueVariables.filter(function(variable) { return parents.find(function(parent) { return parent.id === variable.scope.id; }); diff --git a/lib/zeebe/VariableResolver.js b/lib/zeebe/VariableResolver.js index 996c9fe..5a032a6 100644 --- a/lib/zeebe/VariableResolver.js +++ b/lib/zeebe/VariableResolver.js @@ -1,6 +1,9 @@ import { getProcessVariables } from '@bpmn-io/extract-process-variables/zeebe'; import { BaseVariableResolver } from '../base/VariableResolver'; -import { parseVariables } from './util/feelUtility'; +import { + parseVariables, + getElementNamesToRemove +} from './util/feelUtility'; import { getBusinessObject, is @@ -36,42 +39,7 @@ export default class ZeebeVariableResolver extends BaseVariableResolver { return variables; } - const namesToFilter = []; - - // Input: remove all inputs defined after the current input definition - if (is(moddleElement, 'zeebe:Input')) { - const allInputs = inputOutput.inputParameters; - - const inputsToFilter = - allInputs - .slice(allInputs.indexOf(moddleElement)) - .map(o => o.target); - - namesToFilter.push(...inputsToFilter); - } - - const allOutputs = inputOutput.outputParameters; - - // Output: remove all outputs defined after the current output definition - if (is(moddleElement, 'zeebe:Output')) { - - // Get all output mappings defined after the current element, including own name - const outputsToFilter = allOutputs - .slice(allOutputs.indexOf(moddleElement)) - .map(o => o.target); - - namesToFilter.push(...outputsToFilter); - } - - // Input or general property: remove all outputs - else if (allOutputs) { - - // Input or execution-related element, remove all outputs - const outputsToFilter = allOutputs - .map(o => o.target); - - namesToFilter.push(...outputsToFilter); - } + const namesToFilter = getElementNamesToRemove(moddleElement, inputOutput); return variables.filter(v => { diff --git a/lib/zeebe/util/feelUtility.js b/lib/zeebe/util/feelUtility.js index 57c89b5..697341f 100644 --- a/lib/zeebe/util/feelUtility.js +++ b/lib/zeebe/util/feelUtility.js @@ -6,6 +6,7 @@ import { import { EntriesContext } from './VariableContext'; import { getExtensionElementsList } from '../../base/util/ExtensionElementsUtil'; import { getParents } from '../../base/util/scopeUtil'; +import { is } from 'bpmn-js/lib/util/ModelUtil'; export function parseVariables(variables) { @@ -158,7 +159,12 @@ export function getResultContext(expression, variables = {}) { * @returns {{ expression: String, unresolved: Array }}} */ function getExpressionDetails(variable, origin) { - const expression = getIoExpression(variable, origin) || getScriptExpression(variable, origin); + + // if variable scope is !== origin (global), prioritize IoExpression over ScriptExpression + // if variable scope is === origin (local), prioritize ScriptExpression over IoExpression + const expression = variable.scope !== origin + ? getIoExpression(variable, origin) || getScriptExpression(variable, origin) + : getScriptExpression(variable, origin) || getIoExpression(variable, origin); if (!expression) { return; @@ -166,7 +172,7 @@ function getExpressionDetails(variable, origin) { const result = getResultContext(expression); - const unresolved = findUnresolvedVariables(result) ; + const unresolved = findUnresolvedVariables(result); return { expression, unresolved }; } @@ -197,7 +203,7 @@ function getIoExpression(variable, origin) { return; } - const mapping = mappings.find(mapping => mapping.target === variable.name); + const mapping = mappings.slice().reverse().find(mapping => mapping.target === variable.name); if (!mapping || !mapping.source) { return; @@ -374,4 +380,48 @@ function filterForScope(context, variable) { } return scopedResults; +} + +/** + * Remove input/output element name after current definition + */ +export function getElementNamesToRemove(moddleElement, inputOutput) { + const namesToFilter = []; + + // Input: remove all inputs defined after the current input definition + if (is(moddleElement, 'zeebe:Input')) { + const allInputs = inputOutput.inputParameters; + + const inputsToFilter = + allInputs + .slice(allInputs.indexOf(moddleElement)) + .map(o => o.target); + + namesToFilter.push(...inputsToFilter); + } + + const allOutputs = inputOutput.outputParameters; + + // Output: remove all outputs defined after the current output definition + if (is(moddleElement, 'zeebe:Output')) { + + // Get all output mappings defined after the current element, including own name + const outputsToFilter = allOutputs + .slice(allOutputs.indexOf(moddleElement)) + .map(o => o.target); + + namesToFilter.push(...outputsToFilter); + } + + // Input or general property: remove all outputs + else if (allOutputs) { + + // Input or execution-related element, remove all outputs + const outputsToFilter = allOutputs + .map(o => o.target); + + namesToFilter.push(...outputsToFilter); + } + + return namesToFilter; } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 25c5682..d99ccf6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.3.2", "license": "MIT", "dependencies": { - "@bpmn-io/extract-process-variables": "^1.0.0", + "@bpmn-io/extract-process-variables": "^1.0.1", "@lezer/common": "^1.2.3", "lezer-feel": "^1.4.0", "min-dash": "^4.2.2" @@ -411,9 +411,10 @@ } }, "node_modules/@bpmn-io/extract-process-variables": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@bpmn-io/extract-process-variables/-/extract-process-variables-1.0.0.tgz", - "integrity": "sha512-4EpxKwsV1h1ttlFywo8Af+JBhGzmmv63C8x2Fu5traH8z/4VJbPa2AncIU2GFHj7LYeZlygEDYWPB0QRYoQWKg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@bpmn-io/extract-process-variables/-/extract-process-variables-1.0.1.tgz", + "integrity": "sha512-bisd1kL38HIjB+nmvtw3mpktH0Il3y55eiH/F7kKJs00jf+eksRBoUdr8q4Ge7w5uWxEKVszrasZCee5LTcYEw==", + "license": "MIT", "dependencies": { "min-dash": "^4.0.0" } @@ -8834,9 +8835,9 @@ } }, "@bpmn-io/extract-process-variables": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@bpmn-io/extract-process-variables/-/extract-process-variables-1.0.0.tgz", - "integrity": "sha512-4EpxKwsV1h1ttlFywo8Af+JBhGzmmv63C8x2Fu5traH8z/4VJbPa2AncIU2GFHj7LYeZlygEDYWPB0QRYoQWKg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@bpmn-io/extract-process-variables/-/extract-process-variables-1.0.1.tgz", + "integrity": "sha512-bisd1kL38HIjB+nmvtw3mpktH0Il3y55eiH/F7kKJs00jf+eksRBoUdr8q4Ge7w5uWxEKVszrasZCee5LTcYEw==", "requires": { "min-dash": "^4.0.0" } diff --git a/package.json b/package.json index 11f0982..30460c1 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "author": "bpmn.io contributors", "license": "MIT", "dependencies": { - "@bpmn-io/extract-process-variables": "^1.0.0", + "@bpmn-io/extract-process-variables": "^1.0.1", "@lezer/common": "^1.2.3", "lezer-feel": "^1.4.0", "min-dash": "^4.2.2" diff --git a/test/fixtures/zeebe/mappings/scope.bpmn b/test/fixtures/zeebe/mappings/scope.bpmn index 1445541..6a6fa38 100644 --- a/test/fixtures/zeebe/mappings/scope.bpmn +++ b/test/fixtures/zeebe/mappings/scope.bpmn @@ -1,30 +1,151 @@ - + + + + + + + + + + - + - - + + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/spec/zeebe/Mappings.spec.js b/test/spec/zeebe/Mappings.spec.js index 9d88f81..cfc6ebf 100644 --- a/test/spec/zeebe/Mappings.spec.js +++ b/test/spec/zeebe/Mappings.spec.js @@ -3,6 +3,7 @@ import TestContainer from 'mocha-test-container-support'; import ZeebeModdle from 'zeebe-bpmn-moddle/resources/zeebe'; import { is } from 'bpmn-js/lib/util/ModelUtil'; +import { getInputOutput } from '../../../lib/base/util/ExtensionElementsUtil'; import { bootstrapModeler, inject } from 'test/TestHelper'; @@ -283,12 +284,12 @@ describe('ZeebeVariableResolver - Variable Mappings', function() { it('should only resolve variables in scope', inject(async function(variableResolver, elementRegistry) { // given - const root = elementRegistry.get('Process_1'); + const root = elementRegistry.get('Participant_1'); const initialVariables = [ { name: 'globalVariable', - type: 'TestVariable', - info: 'TestInfo', + type: 'String', + info: '1', entries: [ ] } ]; @@ -298,24 +299,144 @@ describe('ZeebeVariableResolver - Variable Mappings', function() { }); // when - const variables = await variableResolver.getVariablesForElement(root.businessObject); + const variables = await variableResolver.getVariablesForElement(root.businessObject.processRef); // then expect(variables).to.variableEqual([ ...initialVariables, { - name: 'validMapping', - type: 'TestVariable', - info: 'TestInfo' + name: 'fooOutputVariable', + type: 'String', + info: '1' }, { - name: 'invalidMapping', + name: 'barOutputVariable', type: '', info: '' } ]); })); + + it('should resolve result variable if no outputs exist', inject(async function(variableResolver, elementRegistry) { + + // given + const root = elementRegistry.get('Participant_4'); + + // when + const variables = await variableResolver.getVariablesForElement(root.businessObject.processRef); + + // then + expect(variables).to.variableEqual([ + { + name: 'resultVariable', + type: 'String', + info: '1' + } + ]); + })); + + + it('should resolve output instead of result variable if outputs exist', inject(async function(variableResolver, elementRegistry) { + + // given + const root = elementRegistry.get('Participant_3'); + + // when + const variables = await variableResolver.getVariablesForElement(root.businessObject.processRef); + + // then + expect(variables).to.variableEqual([ + { + name: 'output', + type: 'String', + info: '2' + } + ]); + })); + + + it('should only resolve result variable if input and result variable with same name exist', inject(async function(variableResolver, elementRegistry) { + + // given + const root = elementRegistry.get('ScriptTask_1'); + const bo = root.businessObject; + const output = getInputOutput(bo).outputParameters[0]; + + // when + const variables = await variableResolver.getVariablesForElement(root.businessObject, output); + + // then + expect(variables).to.variableEqual([ + { + name: 'foo', + type: 'String', + info: '1' + } + ]); + })); + + + it('should only resolve the output variable if result variable and output with same name exist', inject(async function(variableResolver, elementRegistry) { + + // given + const root = elementRegistry.get('ScriptTask_4'); + const bo = root.businessObject; + const output = getInputOutput(bo).outputParameters[1]; + + // when + const variables = await variableResolver.getVariablesForElement(root.businessObject, output); + + // then + expect(variables).to.variableEqual([ + { + name: 'foo', + type: 'String', + info: '2' + } + ]); + })); + + + it('should only resolve the output variable if input and output with same name exist', inject(async function(variableResolver, elementRegistry) { + + // given + const root = elementRegistry.get('ServiceTask_3'); + const bo = root.businessObject; + const output = getInputOutput(bo).outputParameters[1]; + + // when + const variables = await variableResolver.getVariablesForElement(root.businessObject, output); + + // then + expect(variables).to.variableEqual([ + { + name: 'foo', + type: 'String', + info: '2' + } + ]); + })); + + + it('should only resolve the latest variable if inputs with same name exist', inject(async function(variableResolver, elementRegistry) { + + // given + const root = elementRegistry.get('ServiceTask_4'); + + // when + const variables = await variableResolver.getVariablesForElement(root.businessObject); + + // then + expect(variables).to.variableEqual([ + { + name: 'foo', + type: 'String', + info: '2' + } + ]); + })); + });