From 760ba19b17e893c30f1c1e8162a4e2ea4b13ee68 Mon Sep 17 00:00:00 2001 From: Abdul Ahad Date: Mon, 26 Aug 2024 12:04:58 +0200 Subject: [PATCH] fix: variable name changes when element name/label changes Closes #863 --- .../src/features/modeling/index.js | 5 +- .../behavior/NameChangeBehaviorSpec.js | 40 +++++++++ .../behavior/name-change-behavior.dmn | 31 +++++++ .../src/features/modeling/behavior/index.js | 5 +- .../modeling/cmd/UpdatePropertiesHandler.js | 18 ++-- .../src/features/replace/DrdReplace.js | 5 +- .../behavior/NameChangeBehaviorSpec.js | 37 ++++++++ .../behavior/name-change-behavior.dmn | 28 ++++++ .../src/features/modeling/Modeling.js | 9 ++ .../src/features/modeling/index.js | 6 +- .../behavior/NameChangeBehaviorSpec.js | 37 ++++++++ .../modeling/behavior/NameChangeBehavior.js | 87 +++++++++++++++++++ 12 files changed, 293 insertions(+), 15 deletions(-) create mode 100644 packages/dmn-js-decision-table/test/spec/features/modeling/behavior/NameChangeBehaviorSpec.js create mode 100644 packages/dmn-js-decision-table/test/spec/features/modeling/behavior/name-change-behavior.dmn create mode 100644 packages/dmn-js-drd/test/spec/features/modeling/behavior/NameChangeBehaviorSpec.js create mode 100644 packages/dmn-js-drd/test/spec/features/modeling/behavior/name-change-behavior.dmn create mode 100644 packages/dmn-js-literal-expression/test/spec/features/modeling/behavior/NameChangeBehaviorSpec.js create mode 100644 packages/dmn-js-shared/src/features/modeling/behavior/NameChangeBehavior.js diff --git a/packages/dmn-js-decision-table/src/features/modeling/index.js b/packages/dmn-js-decision-table/src/features/modeling/index.js index 7ad1bc6b3..a52913f00 100644 --- a/packages/dmn-js-decision-table/src/features/modeling/index.js +++ b/packages/dmn-js-decision-table/src/features/modeling/index.js @@ -4,15 +4,18 @@ import DmnFactory from './DmnFactory'; import ElementFactory from './ElementFactory'; import IdChangeBehavior from 'dmn-js-shared/lib/features/modeling/behavior/IdChangeBehavior'; +import NameChangeBehavior from + 'dmn-js-shared/lib/features/modeling/behavior/NameChangeBehavior'; import Modeling from './Modeling'; import Behavior from './behavior'; export default { - __init__: [ 'dmnUpdater', 'idChangeBehavior', 'modeling' ], + __init__: [ 'dmnUpdater', 'idChangeBehavior', 'nameChangeBehavior', 'modeling' ], __depends__: [ Behavior, CommandStack ], dmnUpdater: [ 'type', DmnUpdater ], dmnFactory: [ 'type', DmnFactory ], elementFactory: [ 'type', ElementFactory ], idChangeBehavior: [ 'type', IdChangeBehavior ], + nameChangeBehavior: [ 'type', NameChangeBehavior ], modeling: [ 'type', Modeling ] }; \ No newline at end of file diff --git a/packages/dmn-js-decision-table/test/spec/features/modeling/behavior/NameChangeBehaviorSpec.js b/packages/dmn-js-decision-table/test/spec/features/modeling/behavior/NameChangeBehaviorSpec.js new file mode 100644 index 000000000..8bd5be6a4 --- /dev/null +++ b/packages/dmn-js-decision-table/test/spec/features/modeling/behavior/NameChangeBehaviorSpec.js @@ -0,0 +1,40 @@ +import { bootstrapModeler, inject } from 'test/helper'; + +import decisionTableXML from './name-change-behavior.dmn'; + +import CoreModule from 'src/core'; +import Modeling from 'src/features/modeling'; + + +describe('NameChangeBehavior', function() { + + describe('with existing variable', function() { + + beforeEach(bootstrapModeler(decisionTableXML, { + modules: [ + CoreModule, + Modeling + ], + })); + + + it('should update variable name when element name is changed', inject( + function(modeling, sheet) { + + // given + const root = sheet.getRoot(), + decisionTable = root.businessObject; + + const decision = decisionTable.$parent; + + // when + modeling.editDecisionTableName('foo'); + + // then + const variable = decision.get('variable'); + + expect(variable.get('name')).to.equal('foo'); + } + )); + }); +}); diff --git a/packages/dmn-js-decision-table/test/spec/features/modeling/behavior/name-change-behavior.dmn b/packages/dmn-js-decision-table/test/spec/features/modeling/behavior/name-change-behavior.dmn new file mode 100644 index 000000000..a8a4d73da --- /dev/null +++ b/packages/dmn-js-decision-table/test/spec/features/modeling/behavior/name-change-behavior.dmn @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/dmn-js-drd/src/features/modeling/behavior/index.js b/packages/dmn-js-drd/src/features/modeling/behavior/index.js index 5857aba71..40de7c033 100644 --- a/packages/dmn-js-drd/src/features/modeling/behavior/index.js +++ b/packages/dmn-js-drd/src/features/modeling/behavior/index.js @@ -4,17 +4,20 @@ import ReplaceConnectionBehavior from './ReplaceConnectionBehavior'; import ReplaceElementBehavior from './ReplaceElementBehavior'; import IdChangeBehavior from 'dmn-js-shared/lib/features/modeling/behavior/IdChangeBehavior'; - +import NameChangeBehavior from + 'dmn-js-shared/lib/features/modeling/behavior/NameChangeBehavior'; export default { __init__: [ 'createConnectionBehavior', 'idChangeBehavior', + 'nameChangeBehavior', 'layoutConnectionBehavior', 'replaceConnectionBehavior', 'replaceElementBehavior' ], createConnectionBehavior: [ 'type', CreateConnectionBehavior ], idChangeBehavior: [ 'type', IdChangeBehavior ], + nameChangeBehavior: [ 'type', NameChangeBehavior ], layoutConnectionBehavior: [ 'type', LayoutConnectionBehavior ], replaceConnectionBehavior: [ 'type', ReplaceConnectionBehavior ], replaceElementBehavior: [ 'type', ReplaceElementBehavior ] diff --git a/packages/dmn-js-drd/src/features/modeling/cmd/UpdatePropertiesHandler.js b/packages/dmn-js-drd/src/features/modeling/cmd/UpdatePropertiesHandler.js index 70ea76711..c6df8bb54 100644 --- a/packages/dmn-js-drd/src/features/modeling/cmd/UpdatePropertiesHandler.js +++ b/packages/dmn-js-drd/src/features/modeling/cmd/UpdatePropertiesHandler.js @@ -4,6 +4,8 @@ import { forEach } from 'min-dash'; +import { getBusinessObject } from 'dmn-js-shared/lib/util/ModelUtil'; + var NAME = 'name', ID = 'id'; @@ -37,8 +39,8 @@ UpdatePropertiesHandler.$inject = [ 'elementRegistry', 'moddle' ]; */ UpdatePropertiesHandler.prototype.execute = function(context) { - var element = context.element, - changed = [ element ]; + const { element, properties } = context, + changed = [ element ]; if (!element) { throw new Error('element required'); @@ -47,8 +49,7 @@ UpdatePropertiesHandler.prototype.execute = function(context) { var elementRegistry = this._elementRegistry, ids = this._moddle.ids; - var businessObject = element.businessObject, - properties = context.properties, + var businessObject = getBusinessObject(element), oldProperties = ( context.oldProperties || getProperties(businessObject, keys(properties)) @@ -86,12 +87,9 @@ UpdatePropertiesHandler.prototype.execute = function(context) { * @return {djs.model.Base} the updated element */ UpdatePropertiesHandler.prototype.revert = function(context) { - - var element = context.element, - properties = context.properties, - oldProperties = context.oldProperties, - businessObject = element.businessObject, - elementRegistry = this._elementRegistry, + const { element, properties, oldProperties } = context; + var businessObject = getBusinessObject(element); + var elementRegistry = this._elementRegistry, ids = this._moddle.ids; // update properties diff --git a/packages/dmn-js-drd/src/features/replace/DrdReplace.js b/packages/dmn-js-drd/src/features/replace/DrdReplace.js index 0540db471..e1e8bc67e 100644 --- a/packages/dmn-js-drd/src/features/replace/DrdReplace.js +++ b/packages/dmn-js-drd/src/features/replace/DrdReplace.js @@ -59,8 +59,11 @@ export default function DrdReplace(drdFactory, replace, selection, modeling) { } if (target.expression) { + + // variable set to element name var literalExpression = drdFactory.create('dmn:LiteralExpression'), - variable = drdFactory.create('dmn:InformationItem'); + variable = drdFactory.create('dmn:InformationItem', + { name: oldBusinessObject.name }); setBoxedExpression(newBusinessObject, literalExpression, drdFactory, variable); } diff --git a/packages/dmn-js-drd/test/spec/features/modeling/behavior/NameChangeBehaviorSpec.js b/packages/dmn-js-drd/test/spec/features/modeling/behavior/NameChangeBehaviorSpec.js new file mode 100644 index 000000000..1da32df3e --- /dev/null +++ b/packages/dmn-js-drd/test/spec/features/modeling/behavior/NameChangeBehaviorSpec.js @@ -0,0 +1,37 @@ +import { bootstrapModeler, inject } from 'test/helper'; + +import simpleStringEditXML from './name-change-behavior.dmn'; + +import CoreModule from 'src/core'; +import Modeling from 'src/features/modeling'; + + +describe('NameChangeBehavior', function() { + + describe('with label change', function() { + + beforeEach(bootstrapModeler(simpleStringEditXML, { + modules: [ + CoreModule, + Modeling + ], + })); + + + it('should update variable name when label is changed', inject( + function(modeling, elementRegistry) { + + // given + const decision = elementRegistry.get('season'), + bo = decision.businessObject, + variable = bo.variable; + + // when + modeling.updateLabel(decision,'foo'); + + // then + expect(variable.get('name')).to.equal('foo'); + } + )); + }); +}); diff --git a/packages/dmn-js-drd/test/spec/features/modeling/behavior/name-change-behavior.dmn b/packages/dmn-js-drd/test/spec/features/modeling/behavior/name-change-behavior.dmn new file mode 100644 index 000000000..bdf8de402 --- /dev/null +++ b/packages/dmn-js-drd/test/spec/features/modeling/behavior/name-change-behavior.dmn @@ -0,0 +1,28 @@ + + + + + + + + + calendar.getSeason(date) + + + + + + + + + + + + + + + + + + + diff --git a/packages/dmn-js-literal-expression/src/features/modeling/Modeling.js b/packages/dmn-js-literal-expression/src/features/modeling/Modeling.js index 58d922f86..519ca5b33 100644 --- a/packages/dmn-js-literal-expression/src/features/modeling/Modeling.js +++ b/packages/dmn-js-literal-expression/src/features/modeling/Modeling.js @@ -115,6 +115,15 @@ export default class Modeling { this._commandStack.execute('element.updateProperties', context); } + + updateProperties(el, props) { + const context = { + element: el, + properties: props + }; + + this._commandStack.execute('element.updateProperties', context); + } } Modeling.$inject = [ 'commandStack', 'viewer', 'eventBus' ]; diff --git a/packages/dmn-js-literal-expression/src/features/modeling/index.js b/packages/dmn-js-literal-expression/src/features/modeling/index.js index dec3ff4ce..959fe535c 100644 --- a/packages/dmn-js-literal-expression/src/features/modeling/index.js +++ b/packages/dmn-js-literal-expression/src/features/modeling/index.js @@ -1,12 +1,14 @@ import CommandStack from 'diagram-js/lib/command/CommandStack'; import IdChangeBehavior from 'dmn-js-shared/lib/features/modeling/behavior/IdChangeBehavior'; - +import NameChangeBehavior from + 'dmn-js-shared/lib/features/modeling/behavior/NameChangeBehavior'; import Modeling from './Modeling'; export default { - __init__: [ 'idChangeBehavior', 'modeling' ], + __init__: [ 'idChangeBehavior', 'nameChangeBehavior', 'modeling' ], commandStack: [ 'type', CommandStack ], idChangeBehavior: [ 'type', IdChangeBehavior ], + nameChangeBehavior: [ 'type', NameChangeBehavior ], modeling: [ 'type', Modeling ] }; \ No newline at end of file diff --git a/packages/dmn-js-literal-expression/test/spec/features/modeling/behavior/NameChangeBehaviorSpec.js b/packages/dmn-js-literal-expression/test/spec/features/modeling/behavior/NameChangeBehaviorSpec.js new file mode 100644 index 000000000..0d0126a25 --- /dev/null +++ b/packages/dmn-js-literal-expression/test/spec/features/modeling/behavior/NameChangeBehaviorSpec.js @@ -0,0 +1,37 @@ +import { bootstrapModeler, inject } from 'test/helper'; + +import simpleStringEditXML from '../../../literal-expression.dmn'; + +import CoreModule from 'src/core'; +import Modeling from 'src/features/modeling'; + + +describe('NameChangeBehavior', function() { + + describe('with existing variable', function() { + + beforeEach(bootstrapModeler(simpleStringEditXML, { + modules: [ + CoreModule, + Modeling + ], + })); + + + it('should update variable name when element name is changed', inject( + function(modeling, viewer) { + + // given + const decision = viewer.getDecision(); + + // when + modeling.editDecisionName('foo'); + + // then + const variable = decision.get('variable'); + + expect(variable.get('name')).to.equal('foo'); + } + )); + }); +}); diff --git a/packages/dmn-js-shared/src/features/modeling/behavior/NameChangeBehavior.js b/packages/dmn-js-shared/src/features/modeling/behavior/NameChangeBehavior.js new file mode 100644 index 000000000..b1a277b1c --- /dev/null +++ b/packages/dmn-js-shared/src/features/modeling/behavior/NameChangeBehavior.js @@ -0,0 +1,87 @@ +import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor'; + +import { + getBusinessObject, + is +} from 'dmn-js-shared/lib/util/ModelUtil'; + + +export default class NameChangeBehavior extends CommandInterceptor { + + static $inject = [ 'eventBus', 'modeling' ]; + + constructor(eventBus, modeling) { + super(eventBus); + + this._modeling = modeling; + + this.postExecuted('element.updateProperties', this.updateVariableFromElement); + this.postExecuted('element.updateLabel', this.updateVariableFromLabel); + } + + updateVariableFromLabel = ({ context }) => { + const { element, newLabel } = context; + const bo = getBusinessObject(element), + variable = bo.variable; + + if (!variable) { + return; + } + + this._modeling.updateProperties(variable, { name: newLabel }); + }; + + updateVariableFromElement = ({ context }) => { + const { element, properties } = context; + const bo = getBusinessObject(element); + + if (!bo.variable) { + return; + } + + if (!(is(element, 'dmn:Decision') || is(element, 'dmn:BusinessKnowledgeModel'))) { + return; + } + + if (!this.isNameChanged(properties)) { + return; + } + + if (this.isVariable(element)) { + return; + } + + else if (!this.isElementVariable(element)) { + this.syncElementVariableChange(bo); + } + }; + + isNameChanged(properties) { + return 'name' in properties; + } + + isVariable(element) { + const parent = getParent(element); + return ( + is(element, 'dmn:InformationItem') && + parent && parent.get('variable') === element + ); + } + + isElementVariable(element) { + const variable = element.get('variable'); + return variable && (element.name === variable.name); + } + + syncElementVariableChange(businessObject) { + const name = businessObject.get('name'); + const variable = businessObject.variable; + this._modeling.updateProperties(variable, { name }); + } +} + +// helpers ////////////////////// + +function getParent(element) { + return element.$parent; +} \ No newline at end of file