From 9273e6425b5eeee40365f2d20a502ee966c7b973 Mon Sep 17 00:00:00 2001 From: burnettk Date: Wed, 13 Dec 2023 10:19:59 -0500 Subject: [PATCH] Squashed 'bpmn-js-spiffworkflow/' changes from 6007a770a..9762eb631 9762eb631 updated data object category to just category 2169b54d3 Merge pull request #58 from sartography/feature/data-object-category 1816c7f08 fixed tests w/ burnettk d888c37b0 added support to add categories to data objects in extensions w/ burnettk b00abf00c Merge pull request #57 from sartography/feature/dataObject-naming-with-dataState ae4bd8154 Fix Overrided Label editing provider bug bfbfb0106 Merge pull request #56 from sartography/enhancements/condition-expression 3b4f06b8c Switch from TextField to textAreaFieal ec81cfa0b Done with basic unic testing 9c1d9afac Implement new tests 123037d8e Before implementing new tests 84a0667ec Remove Consoles 511a9c9c3 Done with Fixing Broken Tests 37e71bdef Handle Broken Tests 5 7aa03cd39 Handle Broken Test 4 80dcc92d2 Handle Broken Tests 3 d3a1f3825 Handle Broken Tests 2 e598f86fe Broken Tests 5289bd6ee Add Rename references to dataObjectHelpers cc2482ad4 Handle Manual Changes f3d5a94c9 Add LabelEditing Provider ab311a29f NPM RUN LINT 39d0cf616 Before Implement Manual Change 84d6e20ff OnChange Events e35578061 Remove Name Input ee68f07f2 Update reference name on change dataObj Name 845c3edc4 INNIT eae58f559 Merge pull request #53 from sartography/feature/data-store-props aa6640e38 Ready To Deploy 2778c764f Last a368d0cec Feature is implemented 3d757eca9 Add Interceptors d1fe1d2b2 Remove duplicate group f0e98ceea Merging cd6546d3e Add bpmn:dataStore in the level of process definition 2a4c1ffc6 linting. 71cf495df Implement Tests c1dbb1599 Merge pull request #51 from sartography/dependabot/github_actions/JS-DevTools/npm-publish-3 5460f57fd Merge pull request #52 from sartography/dependabot/github_actions/actions/setup-node-4 eedab6e82 Bump actions/setup-node from 3 to 4 04186b903 Merge pull request #48 from sartography/feature/add-conditional-events 10bcfce4f No param sort (#50) 025f428cd Bump JS-DevTools/npm-publish from 2 to 3 4d160b8cb display condition panel for conditional events and inclusive gateway sources git-subtree-dir: bpmn-js-spiffworkflow git-subtree-split: 9762eb631de107aac584fce1c056070cdaed171e --- .github/workflows/publish.yml | 4 +- .github/workflows/tests.yml | 2 +- app/app.js | 9 + .../DataObject/DataObjectHelpers.js | 20 ++- .../DataObject/DataObjectInterceptor.js | 4 +- .../DataObjectLabelEditingProvider.js | 62 +++++++ app/spiffworkflow/DataObject/index.js | 7 +- .../propertiesPanel/DataObjectArray.js | 90 ++++++++-- .../DataObjectPropertiesProvider.js | 85 +++++++++- .../propertiesPanel/DataObjectSelect.js | 31 ++-- .../DataStoreReference/DataStoreHelpers.js | 18 ++ .../DataStoreInterceptor.js | 78 +++++++++ app/spiffworkflow/DataStoreReference/index.js | 12 ++ .../DataStorePropertiesProvider.js | 74 ++++++++ .../propertiesPanel/DataStoreSelect.js | 116 +++++++++++++ .../ConditionsPropertiesProvider.js | 30 +++- .../extensions/extensionHelpers.js | 35 ++-- .../ExtensionsPropertiesProvider.js | 1 + .../SpiffExtensionCheckboxEntry.js | 2 +- .../SpiffExtensionLaunchButton.js | 2 +- .../propertiesPanel/SpiffExtensionSelect.js | 2 +- .../SpiffExtensionServiceProperties.js | 6 +- .../propertiesPanel/SpiffExtensionTextArea.js | 2 +- .../SpiffExtensionTextInput.js | 8 +- app/spiffworkflow/index.js | 9 + app/spiffworkflow/moddle/spiffworkflow.json | 11 ++ package.json | 4 +- test/spec/ConditionSpec.js | 40 +++++ test/spec/DataObjectInterceptorSpec.js | 20 +-- test/spec/DataObjectPropsSpec.js | 144 +++++++++++++--- test/spec/DataStoreInterceptorSpec.js | 50 ++++++ test/spec/DataStoreReferenceSpec.js | 158 ++++++++++++++++++ test/spec/UserTaskPropsSpec.js | 6 +- test/spec/bpmn/conditional_event.bpmn | 119 +++++++++++++ test/spec/bpmn/data_store.bpmn | 63 +++++++ test/spec/bpmn/diagram.bpmn | 13 +- 36 files changed, 1223 insertions(+), 114 deletions(-) create mode 100644 app/spiffworkflow/DataObject/DataObjectLabelEditingProvider.js create mode 100644 app/spiffworkflow/DataStoreReference/DataStoreHelpers.js create mode 100644 app/spiffworkflow/DataStoreReference/DataStoreInterceptor.js create mode 100644 app/spiffworkflow/DataStoreReference/index.js create mode 100644 app/spiffworkflow/DataStoreReference/propertiesPanel/DataStorePropertiesProvider.js create mode 100644 app/spiffworkflow/DataStoreReference/propertiesPanel/DataStoreSelect.js create mode 100644 test/spec/ConditionSpec.js create mode 100644 test/spec/DataStoreInterceptorSpec.js create mode 100644 test/spec/DataStoreReferenceSpec.js create mode 100644 test/spec/bpmn/conditional_event.bpmn create mode 100644 test/spec/bpmn/data_store.bpmn diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index cb67de8f..9313dd2c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,12 +13,12 @@ jobs: steps: - uses: actions/checkout@v4 #Checkout Repo - - uses: actions/setup-node@v3 #Setup Node + - uses: actions/setup-node@v4 #Setup Node with: node-version: 18 - run: npm install - run: npm test - - uses: JS-DevTools/npm-publish@v2 + - uses: JS-DevTools/npm-publish@v3 with: token: ${{ secrets.NPM_TOKEN }} access: public diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 412d4bc5..9ba47794 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 #Checkout Repo - - uses: actions/setup-node@v3 #Setup Node + - uses: actions/setup-node@v4 #Setup Node - uses: nanasess/setup-chromedriver@v2 #Setup ChromeDriver with: node-version: '18' diff --git a/app/app.js b/app/app.js index 783a5c6a..edfa6fae 100644 --- a/app/app.js +++ b/app/app.js @@ -179,6 +179,15 @@ bpmnModeler.on('spiff.dmn_files.requested', (event) => { }); }); +bpmnModeler.on('spiff.data_stores.requested', (event) => { + event.eventBus.fire('spiff.data_stores.returned', { + options: [ + { type: 'typeahead', name: 'countries' }, + { type: 'kkv', name: 'foods' } + ], + }); +}); + // As call activites might refernce processes across the system // it should be possible to search for a paticular call activity. bpmnModeler.on('spiff.callactivity.search', (event) => { diff --git a/app/spiffworkflow/DataObject/DataObjectHelpers.js b/app/spiffworkflow/DataObject/DataObjectHelpers.js index dd343789..a2548161 100644 --- a/app/spiffworkflow/DataObject/DataObjectHelpers.js +++ b/app/spiffworkflow/DataObject/DataObjectHelpers.js @@ -5,7 +5,7 @@ */ export function findDataObjects(parent, dataObjects) { - if (typeof(dataObjects) === 'undefined') + if (typeof (dataObjects) === 'undefined') dataObjects = []; let process; if (!parent) { @@ -18,7 +18,7 @@ export function findDataObjects(parent, dataObjects) { if (process.$type === 'bpmn:SubProcess') findDataObjects(process.$parent, dataObjects); } - if (typeof(process.flowElements) !== 'undefined') { + if (typeof (process.flowElements) !== 'undefined') { for (const element of process.flowElements) { if (element.$type === 'bpmn:DataObject') dataObjects.push(element); @@ -68,3 +68,19 @@ export function idToHumanReadableName(id) { return word.charAt(0).toUpperCase() + word.substring(1); } } + +export function updateDataObjectReferencesName(parent, nameValue, dataObjectId, commandStack) { + const references = findDataObjectReferenceShapes(parent.children, dataObjectId); + for (const ref of references) { + const stateName = ref.businessObject.dataState && ref.businessObject.dataState.name ? ref.businessObject.dataState.name : ''; + const newName = stateName ? `${nameValue} [${stateName}]` : nameValue; + commandStack.execute('element.updateProperties', { + element: ref, + moddleElement: ref.businessObject, + properties: { + name: newName, + }, + changed: [ref], + }); + } +} diff --git a/app/spiffworkflow/DataObject/DataObjectInterceptor.js b/app/spiffworkflow/DataObject/DataObjectInterceptor.js index a8eb3f7d..862f970c 100644 --- a/app/spiffworkflow/DataObject/DataObjectInterceptor.js +++ b/app/spiffworkflow/DataObject/DataObjectInterceptor.js @@ -87,6 +87,7 @@ export default class DataObjectInterceptor extends CommandInterceptor { dataObject = existingDataObjects[0]; } else { dataObject = bpmnFactory.create('bpmn:DataObject'); + dataObject.name = idToHumanReadableName(dataObject.id); } // set the reference to the DataObject shape.businessObject.dataObjectRef = dataObject; @@ -107,7 +108,7 @@ export default class DataObjectInterceptor extends CommandInterceptor { element: shape, moddleElement: shape.businessObject, properties: { - name: idToHumanReadableName(shape.businessObject.dataObjectRef.id), + name: shape.businessObject.dataObjectRef.name, }, }); } @@ -137,6 +138,7 @@ export default class DataObjectInterceptor extends CommandInterceptor { } } }); + } } diff --git a/app/spiffworkflow/DataObject/DataObjectLabelEditingProvider.js b/app/spiffworkflow/DataObject/DataObjectLabelEditingProvider.js new file mode 100644 index 00000000..2ab145a8 --- /dev/null +++ b/app/spiffworkflow/DataObject/DataObjectLabelEditingProvider.js @@ -0,0 +1,62 @@ +import { is } from 'bpmn-js/lib/util/ModelUtil'; +import { findDataObject, updateDataObjectReferencesName } from './DataObjectHelpers'; + +export default function DataObjectLabelEditingProvider(eventBus, directEditing, commandStack, modeling) { + + let el; + + // listen to dblclick on non-root elements + eventBus.on('element.dblclick', function (event) { + const { element } = event; + if (is(element.businessObject, 'bpmn:DataObjectReference')) { + let label = element.businessObject.name; + label = label.replace(/\s*\[.*?\]\s*$/, ''); + modeling.updateLabel(element, label); + directEditing.activate(element); + el = element; + } + }); + + eventBus.on('directEditing.complete', function (event) { + + const element = el; + + if (element && is(element.businessObject, 'bpmn:DataObjectReference')) { + + setTimeout(() => { + const process = element.parent.businessObject; + const dataObject = findDataObject(process, element.businessObject.dataObjectRef.id); + const dataState = element.businessObject.dataState && element.businessObject.dataState.name; + + let newLabel = element.businessObject.name; + + commandStack.execute('element.updateModdleProperties', { + element, + moddleElement: dataObject, + properties: { + name: newLabel, + }, + }); + + // Update references name + updateDataObjectReferencesName(element.parent, newLabel, dataObject.id, commandStack); + + // Append the data state if it exists + if (dataState) { + newLabel += ` [${dataState}]`; + } + + // Update the label with the data state + modeling.updateLabel(element, newLabel); + el = undefined; + }, 100); + } + }); +} + +DataObjectLabelEditingProvider.$inject = [ + 'eventBus', + 'directEditing', + 'commandStack', + 'modeling' +]; \ No newline at end of file diff --git a/app/spiffworkflow/DataObject/index.js b/app/spiffworkflow/DataObject/index.js index 8489aec9..0c0ab140 100644 --- a/app/spiffworkflow/DataObject/index.js +++ b/app/spiffworkflow/DataObject/index.js @@ -3,17 +3,18 @@ import DataObjectRules from './DataObjectRules'; import RulesModule from 'diagram-js/lib/features/rules'; import DataObjectRenderer from './DataObjectRenderer'; import DataObjectPropertiesProvider from './propertiesPanel/DataObjectPropertiesProvider'; - +import DataObjectLabelEditingProvider from './DataObjectLabelEditingProvider'; export default { __depends__: [ RulesModule ], - __init__: [ 'dataInterceptor', 'dataObjectRules', 'dataObjectRenderer', 'dataObjectPropertiesProvider' ], + __init__: [ 'dataInterceptor', 'dataObjectRules', 'dataObjectRenderer', 'dataObjectPropertiesProvider', 'dataObjectLabelEditingProvider' ], dataInterceptor: [ 'type', DataObjectInterceptor ], dataObjectRules: [ 'type', DataObjectRules ], dataObjectRenderer: [ 'type', DataObjectRenderer ], - dataObjectPropertiesProvider: [ 'type', DataObjectPropertiesProvider ] + dataObjectPropertiesProvider: [ 'type', DataObjectPropertiesProvider ], + dataObjectLabelEditingProvider: [ 'type', DataObjectLabelEditingProvider ] }; diff --git a/app/spiffworkflow/DataObject/propertiesPanel/DataObjectArray.js b/app/spiffworkflow/DataObject/propertiesPanel/DataObjectArray.js index 70e5f416..cdaa7047 100644 --- a/app/spiffworkflow/DataObject/propertiesPanel/DataObjectArray.js +++ b/app/spiffworkflow/DataObject/propertiesPanel/DataObjectArray.js @@ -1,4 +1,5 @@ import { useService } from 'bpmn-js-properties-panel'; +import {SpiffExtensionTextInput} from '../../extensions/propertiesPanel/SpiffExtensionTextInput'; import { isTextFieldEntryEdited, TextFieldEntry, @@ -8,7 +9,9 @@ import { is } from 'bpmn-js/lib/util/ModelUtil'; import { findDataObjects, findDataObjectReferenceShapes, + updateDataObjectReferencesName, idToHumanReadableName, + findDataObject, } from '../DataObjectHelpers'; /** @@ -40,6 +43,8 @@ export function DataObjectArray(props) { idPrefix: id, element, dataObject, + commandStack, + moddle, }), autoFocusEntry: `${id}-dataObject`, remove: removeFactory({ @@ -57,6 +62,7 @@ export function DataObjectArray(props) { const newDataObject = moddle.create('bpmn:DataObject'); const newElements = process.get('flowElements'); newDataObject.id = moddle.ids.nextPrefixed('DataObject_'); + newDataObject.name = idToHumanReadableName(newDataObject.id); newDataObject.$parent = process; newElements.push(newDataObject); commandStack.execute('element.updateModdleProperties', { @@ -92,7 +98,7 @@ function removeFactory(props) { } function DataObjectGroup(props) { - const { idPrefix, dataObject } = props; + const { idPrefix, dataObject, element, moddle, commandStack } = props; return [ { @@ -102,6 +108,22 @@ function DataObjectGroup(props) { idPrefix, dataObject, }, + { + id: `${idPrefix}-dataObjectName`, + component: DataObjectNameTextField, + isEdited: isTextFieldEntryEdited, + idPrefix, + dataObject, + }, + { + businessObject: dataObject, + commandStack: commandStack, + moddle: moddle, + component: SpiffExtensionTextInput, + name: 'spiffworkflow:Category', + label: 'Data Object Category', + description: 'Useful for setting permissions on groups of data objects.', + }, ]; } @@ -112,25 +134,26 @@ function DataObjectTextField(props) { const debounce = useService('debounceInput'); const setValue = (value) => { - commandStack.execute('element.updateModdleProperties', { - element, - moddleElement: dataObject, - properties: { - id: value, - }, - }); + try { + // Check if new dataObject Id is not unique + if(findDataObject(element.businessObject, value) !== undefined){ + alert('Data Object ID Should be unique'); + return; + } - // Also update the label of all the references - const references = findDataObjectReferenceShapes(element.children, dataObject.id); - for (const ref of references) { - commandStack.execute('element.updateProperties', { - element: ref, - moddleElement: ref.businessObject, + // let doName = idToHumanReadableName(value); + commandStack.execute('element.updateModdleProperties', { + element, + moddleElement: dataObject, properties: { - name: idToHumanReadableName(value), + id: value, + // name: doName }, - changed: [ref], // everything is already marked as changed, don't recalculate. }); + // Update references name + // updateDataObjectReferencesName(element, doName, value, commandStack); + } catch (error) { + console.log('Set Value Error : ', error); } }; @@ -147,3 +170,38 @@ function DataObjectTextField(props) { debounce, }); } + +function DataObjectNameTextField(props) { + const { idPrefix, element, parameter, dataObject } = props; + + const commandStack = useService('commandStack'); + const debounce = useService('debounceInput'); + + const setValue = (value) => { + + // Update references name + updateDataObjectReferencesName(element, value, dataObject.id, commandStack); + + // Update dataObject name + commandStack.execute('element.updateModdleProperties', { + element, + moddleElement: dataObject, + properties: { + name: value, + }, + }); + }; + + const getValue = () => { + return dataObject.name; + }; + + return TextFieldEntry({ + element: parameter, + id: `${idPrefix}-name`, + label: 'Data Object Name', + getValue, + setValue, + debounce, + }); +} diff --git a/app/spiffworkflow/DataObject/propertiesPanel/DataObjectPropertiesProvider.js b/app/spiffworkflow/DataObject/propertiesPanel/DataObjectPropertiesProvider.js index 6af14266..79afcf69 100644 --- a/app/spiffworkflow/DataObject/propertiesPanel/DataObjectPropertiesProvider.js +++ b/app/spiffworkflow/DataObject/propertiesPanel/DataObjectPropertiesProvider.js @@ -1,7 +1,8 @@ import { is, isAny } from 'bpmn-js/lib/util/ModelUtil'; -import { ListGroup, isTextFieldEntryEdited } from '@bpmn-io/properties-panel'; +import { ListGroup, isTextFieldEntryEdited, TextFieldEntry } from '@bpmn-io/properties-panel'; import { DataObjectSelect } from './DataObjectSelect'; import { DataObjectArray } from './DataObjectArray'; +import { useService } from 'bpmn-js-properties-panel'; const LOW_PRIORITY = 500; @@ -10,13 +11,20 @@ export default function DataObjectPropertiesProvider( translate, moddle, commandStack, - elementRegistry + elementRegistry, + modeling, + bpmnFactory ) { this.getGroups = function (element) { return function (groups) { if (is(element, 'bpmn:DataObjectReference')) { + const generalGroup = groups.find(group => group.id === 'general'); + if (generalGroup) { + generalGroup.entries = generalGroup.entries.filter(entry => entry.id !== 'name'); + } + groups.push( - createDataObjectSelector(element, translate, moddle, commandStack) + createDataObjectSelector(element, translate, moddle, commandStack, modeling, bpmnFactory) ); } if ( @@ -45,6 +53,8 @@ DataObjectPropertiesProvider.$inject = [ 'moddle', 'commandStack', 'elementRegistry', + 'modeling', + 'bpmnFactory' ]; /** @@ -54,7 +64,7 @@ DataObjectPropertiesProvider.$inject = [ * @param moddle * @returns entries */ -function createDataObjectSelector(element, translate, moddle, commandStack) { +function createDataObjectSelector(element, translate, moddle, commandStack, modeling, bpmnFactory) { return { id: 'data_object_properties', label: translate('Data Object Properties'), @@ -67,6 +77,15 @@ function createDataObjectSelector(element, translate, moddle, commandStack) { moddle, commandStack, }, + { + id: 'selectDataState', + element, + component: createDataStateTextField, + moddle, + commandStack, + modeling, + bpmnFactory + } ], }; } @@ -98,3 +117,61 @@ function createDataObjectEditor( return dataObjectArray; } } + +function createDataStateTextField(props) { + const { id, element, commandStack, modeling, bpmnFactory } = props; + + const debounce = useService('debounceInput'); + + const setValue = (value) => { + const businessObject = element.businessObject; + + // Check if the element is a DataObjectReference + if (!is(businessObject, 'bpmn:DataObjectReference')) { + console.error('The element is not a DataObjectReference.'); + return; + } + + // Create a new DataState or update the existing one + let dataState = businessObject.dataState; + if (!dataState) { + dataState = bpmnFactory.create('bpmn:DataState', { + id: 'DataState_' + businessObject.id, + name: value + }); + } else { + dataState.name = value; + } + + // Update the DataObjectReference with new or updated DataState + modeling.updateProperties(element, { + dataState: dataState + }); + + // Extract the original name + const originalName = businessObject.name.split(' [')[0]; + + // Update the label of the DataObjectReference + const newName = (value) ? originalName + ' [' + value + ']' : originalName; + + modeling.updateProperties(element, { + name: newName + }); + }; + + const getValue = () => { + const businessObject = element.businessObject; + return businessObject.dataState ? businessObject.dataState.name : ''; + }; + + return TextFieldEntry({ + element, + id: `${id}-textField`, + name: 'spiffworkflow:DataStateLabel', + label: 'What is the state of this reference?', + description: 'Enter the Data State for this reference.', + getValue, + setValue, + debounce, + }); +} diff --git a/app/spiffworkflow/DataObject/propertiesPanel/DataObjectSelect.js b/app/spiffworkflow/DataObject/propertiesPanel/DataObjectSelect.js index 2804355f..2db1a390 100644 --- a/app/spiffworkflow/DataObject/propertiesPanel/DataObjectSelect.js +++ b/app/spiffworkflow/DataObject/propertiesPanel/DataObjectSelect.js @@ -1,6 +1,6 @@ -import {useService } from 'bpmn-js-properties-panel'; +import { useService } from 'bpmn-js-properties-panel'; import { SelectEntry } from '@bpmn-io/properties-panel'; -import {findDataObjects, idToHumanReadableName} from '../DataObjectHelpers'; +import { findDataObjects } from '../DataObjectHelpers'; /** * Finds the value of the given type within the extensionElements @@ -32,20 +32,25 @@ export function DataObjectSelect(props) { const setValue = value => { const businessObject = element.businessObject; const dataObjects = findDataObjects(businessObject.$parent) - for (const flowElem of dataObjects) { - if (flowElem.$type === 'bpmn:DataObject' && flowElem.id === value) { + for (const dataObject of dataObjects) { + if (dataObject.$type === 'bpmn:DataObject' && dataObject.id === value) { + commandStack.execute('element.updateModdleProperties', { - element, + element: element, moddleElement: businessObject, properties: { - dataObjectRef: flowElem + dataObjectRef: dataObject } }); + + // Construct the new name by : the dataObject name and the current state + const stateName = businessObject.dataState && businessObject.dataState.name ? businessObject.dataState.name : ''; + const newName = stateName ? `${dataObject.name} [${stateName}]` : dataObject.name; + // Update the name property of the DataObjectReference commandStack.execute('element.updateProperties', { - element, - moddleElement: businessObject, + element: element, properties: { - 'name': idToHumanReadableName(flowElem.id) + name: newName } }); } @@ -58,7 +63,7 @@ export function DataObjectSelect(props) { let dataObjects = findDataObjects(parent); let options = []; dataObjects.forEach(dataObj => { - options.push({label: dataObj.id, value: dataObj.id}) + options.push({ label: dataObj.id, value: dataObj.id }) }); return options; } @@ -68,9 +73,9 @@ export function DataObjectSelect(props) { element={element} description={"Select the Data Object this represents."} label={"Which Data Object does this reference?"} - getValue={ getValue } - setValue={ setValue } - getOptions={ getOptions } + getValue={getValue} + setValue={setValue} + getOptions={getOptions} debounce={debounce} />; diff --git a/app/spiffworkflow/DataStoreReference/DataStoreHelpers.js b/app/spiffworkflow/DataStoreReference/DataStoreHelpers.js new file mode 100644 index 00000000..9f2f2bea --- /dev/null +++ b/app/spiffworkflow/DataStoreReference/DataStoreHelpers.js @@ -0,0 +1,18 @@ +export function isDataStoreReferenced(process, dataStoreId) { + const status = process.get('flowElements').some(elem => + elem.$type === 'bpmn:DataStoreReference' && elem.dataStoreRef && elem.dataStoreRef.id === dataStoreId + ); + return status; +} + +export function isDataStoreReferencedV2(definitions, dataStoreId) { + return definitions.get('rootElements').some(elem => + elem.$type === 'bpmn:DataStoreReference' && elem.dataStoreRef && elem.dataStoreRef.id === dataStoreId + ); +} + +export function removeDataStore(definitions, dataStoreId) { + definitions.set('rootElements', definitions.get('rootElements').filter(elem => + !(elem.$type === 'bpmn:DataStore' && elem.id === dataStoreId) + )); +} \ No newline at end of file diff --git a/app/spiffworkflow/DataStoreReference/DataStoreInterceptor.js b/app/spiffworkflow/DataStoreReference/DataStoreInterceptor.js new file mode 100644 index 00000000..d4cc62ed --- /dev/null +++ b/app/spiffworkflow/DataStoreReference/DataStoreInterceptor.js @@ -0,0 +1,78 @@ +import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor'; +import { getDi, is } from 'bpmn-js/lib/util/ModelUtil'; +import { isDataStoreReferenced, removeDataStore } from './DataStoreHelpers'; + +const HIGH_PRIORITY = 1500; + +/** + * + */ +export default class DataStoreInterceptor extends CommandInterceptor { + + constructor(eventBus, bpmnFactory, commandStack, bpmnUpdater) { + super(eventBus); + + /* + * + */ + // bpmnUpdater.updateSemanticParent = (businessObject, parentBusinessObject) => { + // if (is(businessObject, 'bpmn:DataStoreReference')) { + // console.log('updateSemanticParent', businessObject, parentBusinessObject); + // bpmnUpdater.__proto__.updateSemanticParent.call(bpmnUpdater, businessObject, parentBusinessObject); + // } + // }; + + /** + * + */ + // this.preExecute(['shape.create'], HIGH_PRIORITY, function (event) { + // const { context } = event; + // const { shape } = context; + // if (is(shape, 'bpmn:DataStoreReference') && shape.type !== 'label') { + // // event.stopPropagation();* + // console.log('preExecute shape.create', shape, context); + // } + // }); + + /** + * + */ + // this.executed(['shape.create'], HIGH_PRIORITY, function (event) { + // const { context } = event; + // const { shape } = context; + // if (is(shape, 'bpmn:DataStoreReference') && shape.type !== 'label') { + // console.log('executed shape.create', shape, context); + // } + // }); + + /** + * + */ + // this.postExecuted(['shape.create'], HIGH_PRIORITY, function (event) { + // const { context } = event; + // const { shape } = context; + // if (is(shape, 'bpmn:DataStoreReference') && shape.type !== 'label') { + // console.log('postExecuted shape.create', shape, context); + // } + // }); + + /** + * + */ + this.postExecuted(['shape.delete'], HIGH_PRIORITY, function (event) { + const { context } = event; + const { shape } = context; + + if (is(shape, 'bpmn:DataStoreReference') && shape.type !== 'label') { + const definitions = context.oldParent.businessObject.$parent; + const dataStore = shape.businessObject.dataStoreRef; + if (dataStore && !isDataStoreReferenced(context.oldParent.businessObject, dataStore.id)) { + // Remove datastore if it's not linked with another datastore ref + removeDataStore(definitions, dataStore.id); + } + } + }); + } +} + +DataStoreInterceptor.$inject = ['eventBus', 'bpmnFactory', 'commandStack', 'bpmnUpdater']; diff --git a/app/spiffworkflow/DataStoreReference/index.js b/app/spiffworkflow/DataStoreReference/index.js new file mode 100644 index 00000000..6d46e11f --- /dev/null +++ b/app/spiffworkflow/DataStoreReference/index.js @@ -0,0 +1,12 @@ +import RulesModule from 'diagram-js/lib/features/rules'; +import DataStorePropertiesProvider from './propertiesPanel/DataStorePropertiesProvider'; +import DataStoreInterceptor from './DataStoreInterceptor'; + +export default { + __depends__: [ + RulesModule + ], + __init__: [ 'dataStoreInterceptor', 'dataStorePropertiesProvider' ], + dataStoreInterceptor: [ 'type', DataStoreInterceptor ], + dataStorePropertiesProvider: [ 'type', DataStorePropertiesProvider ] +}; diff --git a/app/spiffworkflow/DataStoreReference/propertiesPanel/DataStorePropertiesProvider.js b/app/spiffworkflow/DataStoreReference/propertiesPanel/DataStorePropertiesProvider.js new file mode 100644 index 00000000..ce227d16 --- /dev/null +++ b/app/spiffworkflow/DataStoreReference/propertiesPanel/DataStorePropertiesProvider.js @@ -0,0 +1,74 @@ +import { is } from 'bpmn-js/lib/util/ModelUtil'; +import { DataStoreSelect, OPTION_TYPE } from './DataStoreSelect'; + +const LOW_PRIORITY = 500; + +export default function DataStorePropertiesProvider( + modeling, + propertiesPanel, + translate, + moddle, + commandStack, + bpmnFactory, +) { + this.getGroups = function (element) { + return function (groups) { + if (is(element, 'bpmn:DataStoreReference')) { + groups.push( + createCustomDataStoreGroup( + modeling, + element, + translate, + moddle, + commandStack, + bpmnFactory + ) + ); + } + return groups; + }; + }; + propertiesPanel.registerProvider(LOW_PRIORITY, this); +} + +DataStorePropertiesProvider.$inject = [ + 'modeling', + 'propertiesPanel', + 'translate', + 'moddle', + 'commandStack', + 'bpmnFactory', +]; + +function createCustomDataStoreGroup( + modeling, + element, + translate, + moddle, + commandStack, + bpmnFactory +) { + const group = { + label: translate('Custom Data Store Properties'), + id: 'custom-datastore-properties', + entries: [], + }; + + // other custom properties as needed + group.entries.push({ + id: 'selectDataStore', + element, + component: DataStoreSelect, + optionType: OPTION_TYPE.data_stores, + moddle, + commandStack, + translate, + name: 'dataStoreRef', + label: translate('Select DataSource'), + description: translate('Select a datasource from the list'), + modeling, + bpmnFactory, + }); + + return group; +} diff --git a/app/spiffworkflow/DataStoreReference/propertiesPanel/DataStoreSelect.js b/app/spiffworkflow/DataStoreReference/propertiesPanel/DataStoreSelect.js new file mode 100644 index 00000000..607b45ce --- /dev/null +++ b/app/spiffworkflow/DataStoreReference/propertiesPanel/DataStoreSelect.js @@ -0,0 +1,116 @@ +import { useService } from 'bpmn-js-properties-panel'; +import { SelectEntry } from '@bpmn-io/properties-panel'; +import { isDataStoreReferenced, removeDataStore } from '../DataStoreHelpers'; + +export const OPTION_TYPE = { + data_stores: 'data_stores', +}; + +export const spiffExtensionOptions = {}; + +export function DataStoreSelect(props) { + + const { id, label, description, optionType } = props; + + const { element } = props; + const { commandStack } = props; + const { modeling } = props; + + const debounce = useService('debounceInput'); + const eventBus = useService('eventBus'); + const bpmnFactory = useService('bpmnFactory'); + + const getValue = () => { + return element.businessObject.dataStoreRef + ? element.businessObject.dataStoreRef.id + : ''; + }; + + const setValue = (value) => { + if (!value || value == '') { + modeling.updateProperties(element, { + dataStoreRef: null, + }); + return; + } + + // Add DataStore to the BPMN model + const process = element.businessObject.$parent; + const definitions = process.$parent; + if (!definitions.get('rootElements')) { + definitions.set('rootElements', []); + } + + // Persist Current DataStore Ref + const currentDataStoreRef = element.businessObject.dataStoreRef; + + // Create DataStore + let dataStore = definitions.get('rootElements').find(element => + element.$type === 'bpmn:DataStore' && element.id === value + ); + + // If the DataStore doesn't exist, create new one + if (!dataStore) { + dataStore = bpmnFactory.create('bpmn:DataStore', { + id: value, + name: 'DataStore_' + value + }); + definitions.get('rootElements').push(dataStore); + } + + modeling.updateProperties(element, { + dataStoreRef: dataStore, + }); + + // Remove the old DataStore if it's no longer referenced + if (currentDataStoreRef && !isDataStoreReferenced(process, currentDataStoreRef.id)) { + removeDataStore(definitions, currentDataStoreRef.id); + } + }; + + if ( + !(optionType in spiffExtensionOptions) || + spiffExtensionOptions[optionType] === null + ) { + spiffExtensionOptions[optionType] = null; + requestOptions(eventBus, element, commandStack, optionType); + } + + const getOptions = () => { + const optionList = []; + optionList.push({ + label: '', + value: '', + }); + if ( + optionType in spiffExtensionOptions && + spiffExtensionOptions[optionType] !== null + ) { + spiffExtensionOptions[optionType].forEach((opt) => { + optionList.push({ + label: opt.name, + value: opt.name, + }); + }); + } + return optionList; + }; + + return SelectEntry({ + id, + element, + label, + description, + getValue, + setValue, + getOptions, + debounce, + }); +} + +function requestOptions(eventBus, element, commandStack, optionType) { + eventBus.on(`spiff.${optionType}.returned`, (event) => { + spiffExtensionOptions[optionType] = event.options; + }); + eventBus.fire(`spiff.${optionType}.requested`, { eventBus }); +} diff --git a/app/spiffworkflow/conditions/propertiesPanel/ConditionsPropertiesProvider.js b/app/spiffworkflow/conditions/propertiesPanel/ConditionsPropertiesProvider.js index f473de14..05de8ff1 100644 --- a/app/spiffworkflow/conditions/propertiesPanel/ConditionsPropertiesProvider.js +++ b/app/spiffworkflow/conditions/propertiesPanel/ConditionsPropertiesProvider.js @@ -1,7 +1,6 @@ import { is } from 'bpmn-js/lib/util/ModelUtil'; import { - isTextFieldEntryEdited, - TextFieldEntry, + TextAreaEntry } from '@bpmn-io/properties-panel'; import { useService } from 'bpmn-js-properties-panel'; @@ -18,7 +17,14 @@ export default function ConditionsPropertiesProvider( return function pushGroup(groups) { if (is(element, 'bpmn:SequenceFlow')) { const { source } = element; - if (is(source, 'bpmn:ExclusiveGateway')) { + if (is(source, 'bpmn:ExclusiveGateway') || is(source, 'bpmn:InclusiveGateway')) { + groups.push( + createConditionsGroup(element, translate, moddle, commandStack) + ); + } + } else if (is(element, 'bpmn:Event')) { + const eventDefinitions = element.businessObject.eventDefinitions; + if (eventDefinitions.filter(ev => is(ev, 'bpmn:ConditionalEventDefinition')).length > 0) { groups.push( createConditionsGroup(element, translate, moddle, commandStack) ); @@ -73,7 +79,13 @@ function ConditionExpressionTextField(props) { const debounce = useService('debounceInput'); const getValue = () => { - const { conditionExpression } = element.businessObject; + let conditionExpression; + if (is(element, 'bpmn:SequenceFlow')) { + conditionExpression = element.businessObject.conditionExpression; + } else if (is(element, 'bpmn:Event')) { + const eventDef = element.businessObject.eventDefinitions.find(ev => is(ev, 'bpmn:ConditionalEventDefinition')); + conditionExpression = eventDef.condition; + } if (conditionExpression) { return conditionExpression.body; } @@ -86,11 +98,15 @@ function ConditionExpressionTextField(props) { conditionExpressionModdleElement = moddle.create('bpmn:Expression'); } conditionExpressionModdleElement.body = value; - element.businessObject.conditionExpression = - conditionExpressionModdleElement; + if (is(element, 'bpmn:SequenceFlow')) { + element.businessObject.conditionExpression = conditionExpressionModdleElement; + } else if (is(element, 'bpmn:Event')) { + const eventDef = element.businessObject.eventDefinitions.find(ev => is(ev, 'bpmn:ConditionalEventDefinition')); + eventDef.condition = conditionExpressionModdleElement; + } }; - return TextFieldEntry({ + return TextAreaEntry({ element, id: `the-id`, label, diff --git a/app/spiffworkflow/extensions/extensionHelpers.js b/app/spiffworkflow/extensions/extensionHelpers.js index 342b8f06..0d9c8742 100644 --- a/app/spiffworkflow/extensions/extensionHelpers.js +++ b/app/spiffworkflow/extensions/extensionHelpers.js @@ -33,14 +33,14 @@ const PREFIX = 'spiffworkflow:'; * @param element * @param name */ -export function getExtensionValue(element, name) { +export function getExtensionValue(businessObject, name) { const useProperties = !name.startsWith(PREFIX); let extension; if (useProperties) { - extension = getExtensionProperty(element, name); + extension = getExtensionProperty(businessObject, name); } else { - extension = getExtension(element, name); + extension = getExtension(businessObject, name); } if (extension) { return extension.value; @@ -48,20 +48,24 @@ export function getExtensionValue(element, name) { return ''; } -export function setExtensionValue(element, name, value, moddle, commandStack) { +export function setExtensionValue(element, name, value, moddle, commandStack, businessObject) { const useProperties = !name.startsWith(PREFIX) - const { businessObject } = element; + + let businessObjectToUse = businessObject + if (!businessObjectToUse) { + businessObjectToUse = element.businessObject; + } // Assure we have extensions - let extensions = businessObject.extensionElements; + let extensions = businessObjectToUse.extensionElements; if (!extensions) { extensions = moddle.create('bpmn:ExtensionElements'); } if (useProperties) { - let properties = getExtension(element, SPIFF_PARENT_PROP); - let property = getExtensionProperty(element, name); + let properties = getExtension(businessObjectToUse, SPIFF_PARENT_PROP); + let property = getExtensionProperty(businessObjectToUse, name); if (!properties) { properties = moddle.create(SPIFF_PARENT_PROP); extensions.get('values').push(properties); @@ -73,7 +77,7 @@ export function setExtensionValue(element, name, value, moddle, commandStack) { property.value = value; property.name = name; } else { - let extension = getExtension(element, name); + let extension = getExtension(businessObjectToUse, name); if (!extension) { extension = moddle.create(name); extensions.get('values').push(extension) @@ -83,19 +87,18 @@ export function setExtensionValue(element, name, value, moddle, commandStack) { commandStack.execute('element.updateModdleProperties', { element, - moddleElement: businessObject, + moddleElement: businessObjectToUse, properties: { extensionElements: extensions, }, }); } -function getExtension(element, name) { - const bizObj = element.businessObject; - if (!bizObj.extensionElements) { +function getExtension(businessObject, name) { + if (!businessObject.extensionElements) { return null; } - const extensionElements = bizObj.extensionElements.get('values'); + const extensionElements = businessObject.extensionElements.get('values'); return extensionElements.filter(function (extensionElement) { if (extensionElement.$instanceOf(name)) { return true; @@ -104,8 +107,8 @@ function getExtension(element, name) { } -function getExtensionProperty(element, name) { - const parentElement = getExtension(element, SPIFF_PARENT_PROP); +function getExtensionProperty(businessObject, name) { + const parentElement = getExtension(businessObject, SPIFF_PARENT_PROP); if (parentElement) { return parentElement.get('properties').filter(function (propertyElement) { return ( diff --git a/app/spiffworkflow/extensions/propertiesPanel/ExtensionsPropertiesProvider.js b/app/spiffworkflow/extensions/propertiesPanel/ExtensionsPropertiesProvider.js index 8aeb7c2d..d1d7a11e 100644 --- a/app/spiffworkflow/extensions/propertiesPanel/ExtensionsPropertiesProvider.js +++ b/app/spiffworkflow/extensions/propertiesPanel/ExtensionsPropertiesProvider.js @@ -436,6 +436,7 @@ function createServiceGroup(element, translate, moddle, commandStack) { id: 'serviceTaskParameters', label: translate('Parameters'), component: ListGroup, + shouldSort: false, ...ServiceTaskParameterArray({ element, moddle, diff --git a/app/spiffworkflow/extensions/propertiesPanel/SpiffExtensionCheckboxEntry.js b/app/spiffworkflow/extensions/propertiesPanel/SpiffExtensionCheckboxEntry.js index 0a40be55..d1065c87 100644 --- a/app/spiffworkflow/extensions/propertiesPanel/SpiffExtensionCheckboxEntry.js +++ b/app/spiffworkflow/extensions/propertiesPanel/SpiffExtensionCheckboxEntry.js @@ -15,7 +15,7 @@ export function SpiffExtensionCheckboxEntry(props) { const debounce = useService('debounceInput'); const getValue = () => { - return getExtensionValue(element, name) + return getExtensionValue(element.businessObject, name) } const setValue = value => { diff --git a/app/spiffworkflow/extensions/propertiesPanel/SpiffExtensionLaunchButton.js b/app/spiffworkflow/extensions/propertiesPanel/SpiffExtensionLaunchButton.js index 16f3cf8e..a983b611 100644 --- a/app/spiffworkflow/extensions/propertiesPanel/SpiffExtensionLaunchButton.js +++ b/app/spiffworkflow/extensions/propertiesPanel/SpiffExtensionLaunchButton.js @@ -14,7 +14,7 @@ export function SpiffExtensionLaunchButton(props) { className: 'spiffworkflow-properties-panel-button', id: `launch_editor_button_${name}`, onClick: () => { - const value = getExtensionValue(element, name); + const value = getExtensionValue(element.businessObject, name); eventBus.fire(event, { value, eventBus, diff --git a/app/spiffworkflow/extensions/propertiesPanel/SpiffExtensionSelect.js b/app/spiffworkflow/extensions/propertiesPanel/SpiffExtensionSelect.js index 77f513cf..3bd7a290 100644 --- a/app/spiffworkflow/extensions/propertiesPanel/SpiffExtensionSelect.js +++ b/app/spiffworkflow/extensions/propertiesPanel/SpiffExtensionSelect.js @@ -36,7 +36,7 @@ export function SpiffExtensionSelect(props) { const eventBus = useService('eventBus'); const getValue = () => { - return getExtensionValue(element, name); + return getExtensionValue(element.businessObject, name); }; const setValue = (value) => { diff --git a/app/spiffworkflow/extensions/propertiesPanel/SpiffExtensionServiceProperties.js b/app/spiffworkflow/extensions/propertiesPanel/SpiffExtensionServiceProperties.js index bb5e0b09..fa18051e 100644 --- a/app/spiffworkflow/extensions/propertiesPanel/SpiffExtensionServiceProperties.js +++ b/app/spiffworkflow/extensions/propertiesPanel/SpiffExtensionServiceProperties.js @@ -44,9 +44,7 @@ function requestServiceTaskOperators(eventBus, element, commandStack) { eventBus.fire('spiff.service_tasks.requested', { eventBus }); eventBus.on('spiff.service_tasks.returned', (event) => { if (event.serviceTaskOperators.length > 0) { - serviceTaskOperators = event.serviceTaskOperators.sort((a, b) => - a.id.localeCompare(b.id) - ); + serviceTaskOperators = event.serviceTaskOperators; } }); } @@ -69,7 +67,7 @@ function getServiceTaskParameterModdleElements(shapeElement) { if (serviceTaskOperatorModdleElement) { const { parameterList } = serviceTaskOperatorModdleElement; if (parameterList) { - return parameterList.parameters.sort((a, b) => a.id.localeCompare(b.id)); + return parameterList.parameters; } } return []; diff --git a/app/spiffworkflow/extensions/propertiesPanel/SpiffExtensionTextArea.js b/app/spiffworkflow/extensions/propertiesPanel/SpiffExtensionTextArea.js index bc54d263..7b720275 100644 --- a/app/spiffworkflow/extensions/propertiesPanel/SpiffExtensionTextArea.js +++ b/app/spiffworkflow/extensions/propertiesPanel/SpiffExtensionTextArea.js @@ -15,7 +15,7 @@ export function SpiffExtensionTextArea(props) { const debounce = useService('debounceInput'); const getValue = () => { - return getExtensionValue(element, name) + return getExtensionValue(element.businessObject, name) } const setValue = value => { diff --git a/app/spiffworkflow/extensions/propertiesPanel/SpiffExtensionTextInput.js b/app/spiffworkflow/extensions/propertiesPanel/SpiffExtensionTextInput.js index a72ad461..dbe765dd 100644 --- a/app/spiffworkflow/extensions/propertiesPanel/SpiffExtensionTextInput.js +++ b/app/spiffworkflow/extensions/propertiesPanel/SpiffExtensionTextInput.js @@ -21,17 +21,15 @@ import { * @returns {string|null|*} */ export function SpiffExtensionTextInput(props) { - const element = props.element; - const commandStack = props.commandStack, moddle = props.moddle; - const name = props.name, label = props.label, description = props.description; + const { element, commandStack, moddle, name, label, description, businessObject } = props; const debounce = useService('debounceInput'); const getValue = () => { - return getExtensionValue(element, name) + return getExtensionValue(businessObject, name) } const setValue = value => { - setExtensionValue(element, name, value, moddle, commandStack) + setExtensionValue(element, name, value, moddle, commandStack, businessObject) }; return + element.$type === 'bpmn:DataStore' && element.id === 'countries' + ); + expect(dataStoreExists, "DataStore 'countries' should be added at the root level").to.be.true; + + // Remove dataStoreReference + await modeler.get('modeling').removeShape(shapeElement); + const nwshapeElement = await expectSelected('DataStoreReference_0eqeh4p'); + expect(nwshapeElement, "I can't find DataStoreReference element").not.to.exist; + + // Check that DataStore foods is removed from the root of the process + definitions = await modeler.getDefinitions(); + dataStoreExists = definitions.get('rootElements').some(element => + element.$type === 'bpmn:DataStore' && element.id === 'countries' + ); + expect(dataStoreExists, "DataStore 'countries' should be removed from the root level").not.to.be.true; + })); + +}); diff --git a/test/spec/DataStoreReferenceSpec.js b/test/spec/DataStoreReferenceSpec.js new file mode 100644 index 00000000..12834cad --- /dev/null +++ b/test/spec/DataStoreReferenceSpec.js @@ -0,0 +1,158 @@ +import TestContainer from 'mocha-test-container-support'; +import { + BpmnPropertiesPanelModule, + BpmnPropertiesProviderModule, +} from 'bpmn-js-properties-panel'; +import { getBusinessObject } from 'bpmn-js/lib/util/ModelUtil'; +import { + bootstrapPropertiesPanel, + changeInput, + expectSelected, + findGroupEntry, + findEntry, + findSelect, + getPropertiesPanel +} from './helpers'; + +import { getBpmnJS, inject } from 'bpmn-js/test/helper'; + +import spiffModdleExtension from '../../app/spiffworkflow/moddle/spiffworkflow.json'; +import DataStoreReference from '../../app/spiffworkflow/DataStoreReference'; +import DataStoreInterceptor from '../../app/spiffworkflow/DataStoreReference/DataStoreInterceptor'; + +const return_datastores = (event) => { + event.eventBus.fire('spiff.data_stores.returned', { + options: [ + { type: 'typeahead', name: 'countries' }, + { type: 'kkv', name: 'foods' } + ], + }); +} + +describe('Data Source Reference Test cases', function () { + const xml = require('./bpmn/data_store.bpmn').default; + let container; + + beforeEach(function () { + container = TestContainer.get(this); + }); + + beforeEach( + bootstrapPropertiesPanel(xml, { + container, + debounceInput: false, + additionalModules: [ + DataStoreReference, + DataStoreInterceptor, + BpmnPropertiesPanelModule, + BpmnPropertiesProviderModule, + ], + moddleExtensions: { + spiffworkflow: spiffModdleExtension, + }, + }) + ); + + it('should display the custom data store properties group - DataStoreReference element', async function () { + // We Select a DataStoreReference element + const shapeElement = await expectSelected('DataStoreReference_0eqeh4p'); + expect(shapeElement, "I can't find DataStoreReference element").to.exist; + + // Lets Check if the custom properties group is displayed + const customGroup = findGroupEntry('custom-datastore-properties', container); + expect(customGroup).to.exist; + const entry = findEntry('selectDataStore', container); + expect(entry).to.exist; + }); + + it('should list data sources from aa api response and append it to select - DataStoreReference element', async function () { + const modeler = getBpmnJS(); + modeler.get('eventBus').once('spiff.data_stores.requested', return_datastores); + + // We Select a DataStoreReference element + const shapeElement = await expectSelected('DataStoreReference_0eqeh4p'); + expect(shapeElement, "I can't find DataStoreReference element").to.exist; + + // Interact with the DataStoreSelect component + const selectGroup = findGroupEntry('custom-datastore-properties', container) + expect(selectGroup).to.exist; + + const entry = findEntry('selectDataStore', getPropertiesPanel()); + expect(entry).to.exist; + + // Verification if the dataStoreRef attribute is updated + let selector = findSelect(entry); + expect(selector.length).to.equal(3); + expect(selector[1].value === 'countries'); + expect(selector[2].value === 'foods'); + }); + + it('should update dataStoreRef after a select event && should add new DataState in the level of process definition - DataStoreReference element', async function () { + const modeler = getBpmnJS(); + modeler.get('eventBus').once('spiff.data_stores.requested', return_datastores); + + // We Select a DataStoreReference element + const shapeElement = await expectSelected('DataStoreReference_0eqeh4p'); + expect(shapeElement, "I can't find DataStoreReference element").to.exist; + + // Interact with the DataStoreSelect component + const selectGroup = findGroupEntry('custom-datastore-properties', container) + expect(selectGroup).to.exist; + + const entry = findEntry('selectDataStore', getPropertiesPanel()); + expect(entry).to.exist; + + // Verification if the dataStoreRef attribute is updated + let selector = findSelect(entry); + changeInput(selector, 'foods'); + const nwbusinessObject = getBusinessObject(shapeElement); + expect(nwbusinessObject.get('dataStoreRef').id).to.equal('foods'); + + // Check if the DataStore is added at the root level + const definitions = modeler.getDefinitions(); + const dataStoreExists = definitions.get('rootElements').some(element => + element.$type === 'bpmn:DataStore' && element.id === 'foods' + ); + expect(dataStoreExists, "DataStore 'foods' should be added at the root level").to.be.true; + + }); + + it('should delete dataStore if dataStorRef is updated - DataStoreReference element', async function () { + const modeler = getBpmnJS(); + modeler.get('eventBus').once('spiff.data_stores.requested', return_datastores); + + // We Select a DataStoreReference element + const shapeElement = await expectSelected('DataStoreReference_0eqeh4p'); + expect(shapeElement, "I can't find DataStoreReference element").to.exist; + + // Interact with the DataStoreSelect component + const selectGroup = findGroupEntry('custom-datastore-properties', container) + expect(selectGroup).to.exist; + + const entry = findEntry('selectDataStore', getPropertiesPanel()); + expect(entry).to.exist; + + // Verification if the dataStoreRef attribute is updated + let selector = findSelect(entry); + changeInput(selector, 'foods'); + let nwbusinessObject = getBusinessObject(shapeElement); + expect(nwbusinessObject.get('dataStoreRef').id).to.equal('foods'); + // Then choose new dataStore + changeInput(selector, 'countries'); + nwbusinessObject = getBusinessObject(shapeElement); + expect(nwbusinessObject.get('dataStoreRef').id).to.equal('countries'); + + // Check if the DataStore is added at the root level with the updated dataStore + const definitions = modeler.getDefinitions(); + const countriesDataStoreExists = definitions.get('rootElements').some(element => + element.$type === 'bpmn:DataStore' && element.id === 'countries' + ); + expect(countriesDataStoreExists, "DataStore 'countries' should be added at the root level").to.be.true; + const foodsDataStoreExists = definitions.get('rootElements').some(element => + element.$type === 'bpmn:DataStore' && element.id === 'foods' + ); + expect(foodsDataStoreExists, "DataStore 'countries' should be removed from the root level").not.to.be.true; + + }); + +}); diff --git a/test/spec/UserTaskPropsSpec.js b/test/spec/UserTaskPropsSpec.js index aeeb0234..ab412c62 100644 --- a/test/spec/UserTaskPropsSpec.js +++ b/test/spec/UserTaskPropsSpec.js @@ -131,8 +131,8 @@ describe('Properties Panel for User Tasks', function () { eventBus.fire('spiff.jsonSchema.update', { value: 'new-schema.json', }); - const jsonFile = getExtensionValue(userElement, 'formJsonSchemaFilename'); - const uiFile = getExtensionValue(userElement, 'formUiSchemaFilename'); + const jsonFile = getExtensionValue(userElement.businessObject, 'formJsonSchemaFilename'); + const uiFile = getExtensionValue(userElement.businessObject, 'formUiSchemaFilename'); expect(jsonFile).to.equal('new-schema.json'); expect(uiFile).to.equal('new-uischema.json'); @@ -155,7 +155,7 @@ describe('Properties Panel for User Tasks', function () { const businessObject = getBusinessObject(userElement); // The change is reflected in the business object let instructions = getExtensionValue( - userElement, + businessObject, 'spiffworkflow:InstructionsForEndUser' ); expect(instructions).to.equal('#Hello!'); diff --git a/test/spec/bpmn/conditional_event.bpmn b/test/spec/bpmn/conditional_event.bpmn new file mode 100644 index 00000000..192998f9 --- /dev/null +++ b/test/spec/bpmn/conditional_event.bpmn @@ -0,0 +1,119 @@ + + + + + Flow_0n934tk + + + + Flow_0ghdmcg + Flow_1kyhah1 + Flow_1texcs9 + + + Flow_1kyhah1 + Flow_16kfh39 + + + + + Flow_16kfh39 + Flow_1u8w68b + Flow_1wczxjh + + + Flow_1wczxjh + + + + Flow_1ybk8c2 + + cancel_task_2 + + + + Flow_1ybk8c2 + + + + Flow_1texcs9 + Flow_1u8w68b + + + + + Flow_0n934tk + Flow_0ghdmcg + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/spec/bpmn/data_store.bpmn b/test/spec/bpmn/data_store.bpmn new file mode 100644 index 00000000..8374a7ea --- /dev/null +++ b/test/spec/bpmn/data_store.bpmn @@ -0,0 +1,63 @@ + + + + + Flow_0vt1twq + + + Flow_1udyjxo + + + + + + + + + + + + + + + + + Flow_0vt1twq + Flow_1udyjxo + + DataStoreReference_0eqeh4p + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/spec/bpmn/diagram.bpmn b/test/spec/bpmn/diagram.bpmn index 9104624d..de82f6ef 100644 --- a/test/spec/bpmn/diagram.bpmn +++ b/test/spec/bpmn/diagram.bpmn @@ -38,7 +38,7 @@ elizabeth="awesome" - + @@ -61,6 +61,11 @@ + + + + + @@ -119,6 +124,12 @@ + + + + + +