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
This commit is contained in:
burnettk 2023-12-13 10:19:59 -05:00
parent 392c9a1b44
commit 9273e6425b
36 changed files with 1223 additions and 114 deletions

View File

@ -13,12 +13,12 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 #Checkout Repo - uses: actions/checkout@v4 #Checkout Repo
- uses: actions/setup-node@v3 #Setup Node - uses: actions/setup-node@v4 #Setup Node
with: with:
node-version: 18 node-version: 18
- run: npm install - run: npm install
- run: npm test - run: npm test
- uses: JS-DevTools/npm-publish@v2 - uses: JS-DevTools/npm-publish@v3
with: with:
token: ${{ secrets.NPM_TOKEN }} token: ${{ secrets.NPM_TOKEN }}
access: public access: public

View File

@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 #Checkout Repo - 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 - uses: nanasess/setup-chromedriver@v2 #Setup ChromeDriver
with: with:
node-version: '18' node-version: '18'

View File

@ -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 // As call activites might refernce processes across the system
// it should be possible to search for a paticular call activity. // it should be possible to search for a paticular call activity.
bpmnModeler.on('spiff.callactivity.search', (event) => { bpmnModeler.on('spiff.callactivity.search', (event) => {

View File

@ -68,3 +68,19 @@ export function idToHumanReadableName(id) {
return word.charAt(0).toUpperCase() + word.substring(1); 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],
});
}
}

View File

@ -87,6 +87,7 @@ export default class DataObjectInterceptor extends CommandInterceptor {
dataObject = existingDataObjects[0]; dataObject = existingDataObjects[0];
} else { } else {
dataObject = bpmnFactory.create('bpmn:DataObject'); dataObject = bpmnFactory.create('bpmn:DataObject');
dataObject.name = idToHumanReadableName(dataObject.id);
} }
// set the reference to the DataObject // set the reference to the DataObject
shape.businessObject.dataObjectRef = dataObject; shape.businessObject.dataObjectRef = dataObject;
@ -107,7 +108,7 @@ export default class DataObjectInterceptor extends CommandInterceptor {
element: shape, element: shape,
moddleElement: shape.businessObject, moddleElement: shape.businessObject,
properties: { properties: {
name: idToHumanReadableName(shape.businessObject.dataObjectRef.id), name: shape.businessObject.dataObjectRef.name,
}, },
}); });
} }
@ -137,6 +138,7 @@ export default class DataObjectInterceptor extends CommandInterceptor {
} }
} }
}); });
} }
} }

View File

@ -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'
];

View File

@ -3,17 +3,18 @@ import DataObjectRules from './DataObjectRules';
import RulesModule from 'diagram-js/lib/features/rules'; import RulesModule from 'diagram-js/lib/features/rules';
import DataObjectRenderer from './DataObjectRenderer'; import DataObjectRenderer from './DataObjectRenderer';
import DataObjectPropertiesProvider from './propertiesPanel/DataObjectPropertiesProvider'; import DataObjectPropertiesProvider from './propertiesPanel/DataObjectPropertiesProvider';
import DataObjectLabelEditingProvider from './DataObjectLabelEditingProvider';
export default { export default {
__depends__: [ __depends__: [
RulesModule RulesModule
], ],
__init__: [ 'dataInterceptor', 'dataObjectRules', 'dataObjectRenderer', 'dataObjectPropertiesProvider' ], __init__: [ 'dataInterceptor', 'dataObjectRules', 'dataObjectRenderer', 'dataObjectPropertiesProvider', 'dataObjectLabelEditingProvider' ],
dataInterceptor: [ 'type', DataObjectInterceptor ], dataInterceptor: [ 'type', DataObjectInterceptor ],
dataObjectRules: [ 'type', DataObjectRules ], dataObjectRules: [ 'type', DataObjectRules ],
dataObjectRenderer: [ 'type', DataObjectRenderer ], dataObjectRenderer: [ 'type', DataObjectRenderer ],
dataObjectPropertiesProvider: [ 'type', DataObjectPropertiesProvider ] dataObjectPropertiesProvider: [ 'type', DataObjectPropertiesProvider ],
dataObjectLabelEditingProvider: [ 'type', DataObjectLabelEditingProvider ]
}; };

View File

@ -1,4 +1,5 @@
import { useService } from 'bpmn-js-properties-panel'; import { useService } from 'bpmn-js-properties-panel';
import {SpiffExtensionTextInput} from '../../extensions/propertiesPanel/SpiffExtensionTextInput';
import { import {
isTextFieldEntryEdited, isTextFieldEntryEdited,
TextFieldEntry, TextFieldEntry,
@ -8,7 +9,9 @@ import { is } from 'bpmn-js/lib/util/ModelUtil';
import { import {
findDataObjects, findDataObjects,
findDataObjectReferenceShapes, findDataObjectReferenceShapes,
updateDataObjectReferencesName,
idToHumanReadableName, idToHumanReadableName,
findDataObject,
} from '../DataObjectHelpers'; } from '../DataObjectHelpers';
/** /**
@ -40,6 +43,8 @@ export function DataObjectArray(props) {
idPrefix: id, idPrefix: id,
element, element,
dataObject, dataObject,
commandStack,
moddle,
}), }),
autoFocusEntry: `${id}-dataObject`, autoFocusEntry: `${id}-dataObject`,
remove: removeFactory({ remove: removeFactory({
@ -57,6 +62,7 @@ export function DataObjectArray(props) {
const newDataObject = moddle.create('bpmn:DataObject'); const newDataObject = moddle.create('bpmn:DataObject');
const newElements = process.get('flowElements'); const newElements = process.get('flowElements');
newDataObject.id = moddle.ids.nextPrefixed('DataObject_'); newDataObject.id = moddle.ids.nextPrefixed('DataObject_');
newDataObject.name = idToHumanReadableName(newDataObject.id);
newDataObject.$parent = process; newDataObject.$parent = process;
newElements.push(newDataObject); newElements.push(newDataObject);
commandStack.execute('element.updateModdleProperties', { commandStack.execute('element.updateModdleProperties', {
@ -92,7 +98,7 @@ function removeFactory(props) {
} }
function DataObjectGroup(props) { function DataObjectGroup(props) {
const { idPrefix, dataObject } = props; const { idPrefix, dataObject, element, moddle, commandStack } = props;
return [ return [
{ {
@ -102,6 +108,22 @@ function DataObjectGroup(props) {
idPrefix, idPrefix,
dataObject, 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 debounce = useService('debounceInput');
const setValue = (value) => { const setValue = (value) => {
try {
// Check if new dataObject Id is not unique
if(findDataObject(element.businessObject, value) !== undefined){
alert('Data Object ID Should be unique');
return;
}
// let doName = idToHumanReadableName(value);
commandStack.execute('element.updateModdleProperties', { commandStack.execute('element.updateModdleProperties', {
element, element,
moddleElement: dataObject, moddleElement: dataObject,
properties: { properties: {
id: value, id: value,
// name: doName
}, },
}); });
// Update references name
// Also update the label of all the references // updateDataObjectReferencesName(element, doName, value, commandStack);
const references = findDataObjectReferenceShapes(element.children, dataObject.id); } catch (error) {
for (const ref of references) { console.log('Set Value Error : ', error);
commandStack.execute('element.updateProperties', {
element: ref,
moddleElement: ref.businessObject,
properties: {
name: idToHumanReadableName(value),
},
changed: [ref], // everything is already marked as changed, don't recalculate.
});
} }
}; };
@ -147,3 +170,38 @@ function DataObjectTextField(props) {
debounce, 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,
});
}

View File

@ -1,7 +1,8 @@
import { is, isAny } from 'bpmn-js/lib/util/ModelUtil'; 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 { DataObjectSelect } from './DataObjectSelect';
import { DataObjectArray } from './DataObjectArray'; import { DataObjectArray } from './DataObjectArray';
import { useService } from 'bpmn-js-properties-panel';
const LOW_PRIORITY = 500; const LOW_PRIORITY = 500;
@ -10,13 +11,20 @@ export default function DataObjectPropertiesProvider(
translate, translate,
moddle, moddle,
commandStack, commandStack,
elementRegistry elementRegistry,
modeling,
bpmnFactory
) { ) {
this.getGroups = function (element) { this.getGroups = function (element) {
return function (groups) { return function (groups) {
if (is(element, 'bpmn:DataObjectReference')) { 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( groups.push(
createDataObjectSelector(element, translate, moddle, commandStack) createDataObjectSelector(element, translate, moddle, commandStack, modeling, bpmnFactory)
); );
} }
if ( if (
@ -45,6 +53,8 @@ DataObjectPropertiesProvider.$inject = [
'moddle', 'moddle',
'commandStack', 'commandStack',
'elementRegistry', 'elementRegistry',
'modeling',
'bpmnFactory'
]; ];
/** /**
@ -54,7 +64,7 @@ DataObjectPropertiesProvider.$inject = [
* @param moddle * @param moddle
* @returns entries * @returns entries
*/ */
function createDataObjectSelector(element, translate, moddle, commandStack) { function createDataObjectSelector(element, translate, moddle, commandStack, modeling, bpmnFactory) {
return { return {
id: 'data_object_properties', id: 'data_object_properties',
label: translate('Data Object Properties'), label: translate('Data Object Properties'),
@ -67,6 +77,15 @@ function createDataObjectSelector(element, translate, moddle, commandStack) {
moddle, moddle,
commandStack, commandStack,
}, },
{
id: 'selectDataState',
element,
component: createDataStateTextField,
moddle,
commandStack,
modeling,
bpmnFactory
}
], ],
}; };
} }
@ -98,3 +117,61 @@ function createDataObjectEditor(
return dataObjectArray; 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,
});
}

View File

@ -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 { 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 * Finds the value of the given type within the extensionElements
@ -32,20 +32,25 @@ export function DataObjectSelect(props) {
const setValue = value => { const setValue = value => {
const businessObject = element.businessObject; const businessObject = element.businessObject;
const dataObjects = findDataObjects(businessObject.$parent) const dataObjects = findDataObjects(businessObject.$parent)
for (const flowElem of dataObjects) { for (const dataObject of dataObjects) {
if (flowElem.$type === 'bpmn:DataObject' && flowElem.id === value) { if (dataObject.$type === 'bpmn:DataObject' && dataObject.id === value) {
commandStack.execute('element.updateModdleProperties', { commandStack.execute('element.updateModdleProperties', {
element, element: element,
moddleElement: businessObject, moddleElement: businessObject,
properties: { 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', { commandStack.execute('element.updateProperties', {
element, element: element,
moddleElement: businessObject,
properties: { properties: {
'name': idToHumanReadableName(flowElem.id) name: newName
} }
}); });
} }

View File

@ -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)
));
}

View File

@ -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'];

View File

@ -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 ]
};

View File

@ -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;
}

View File

@ -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 });
}

View File

@ -1,7 +1,6 @@
import { is } from 'bpmn-js/lib/util/ModelUtil'; import { is } from 'bpmn-js/lib/util/ModelUtil';
import { import {
isTextFieldEntryEdited, TextAreaEntry
TextFieldEntry,
} from '@bpmn-io/properties-panel'; } from '@bpmn-io/properties-panel';
import { useService } from 'bpmn-js-properties-panel'; import { useService } from 'bpmn-js-properties-panel';
@ -18,7 +17,14 @@ export default function ConditionsPropertiesProvider(
return function pushGroup(groups) { return function pushGroup(groups) {
if (is(element, 'bpmn:SequenceFlow')) { if (is(element, 'bpmn:SequenceFlow')) {
const { source } = element; 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( groups.push(
createConditionsGroup(element, translate, moddle, commandStack) createConditionsGroup(element, translate, moddle, commandStack)
); );
@ -73,7 +79,13 @@ function ConditionExpressionTextField(props) {
const debounce = useService('debounceInput'); const debounce = useService('debounceInput');
const getValue = () => { 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) { if (conditionExpression) {
return conditionExpression.body; return conditionExpression.body;
} }
@ -86,11 +98,15 @@ function ConditionExpressionTextField(props) {
conditionExpressionModdleElement = moddle.create('bpmn:Expression'); conditionExpressionModdleElement = moddle.create('bpmn:Expression');
} }
conditionExpressionModdleElement.body = value; conditionExpressionModdleElement.body = value;
element.businessObject.conditionExpression = if (is(element, 'bpmn:SequenceFlow')) {
conditionExpressionModdleElement; 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, element,
id: `the-id`, id: `the-id`,
label, label,

View File

@ -33,14 +33,14 @@ const PREFIX = 'spiffworkflow:';
* @param element * @param element
* @param name * @param name
*/ */
export function getExtensionValue(element, name) { export function getExtensionValue(businessObject, name) {
const useProperties = !name.startsWith(PREFIX); const useProperties = !name.startsWith(PREFIX);
let extension; let extension;
if (useProperties) { if (useProperties) {
extension = getExtensionProperty(element, name); extension = getExtensionProperty(businessObject, name);
} else { } else {
extension = getExtension(element, name); extension = getExtension(businessObject, name);
} }
if (extension) { if (extension) {
return extension.value; return extension.value;
@ -48,20 +48,24 @@ export function getExtensionValue(element, name) {
return ''; return '';
} }
export function setExtensionValue(element, name, value, moddle, commandStack) { export function setExtensionValue(element, name, value, moddle, commandStack, businessObject) {
const useProperties = !name.startsWith(PREFIX) const useProperties = !name.startsWith(PREFIX)
const { businessObject } = element;
let businessObjectToUse = businessObject
if (!businessObjectToUse) {
businessObjectToUse = element.businessObject;
}
// Assure we have extensions // Assure we have extensions
let extensions = businessObject.extensionElements; let extensions = businessObjectToUse.extensionElements;
if (!extensions) { if (!extensions) {
extensions = moddle.create('bpmn:ExtensionElements'); extensions = moddle.create('bpmn:ExtensionElements');
} }
if (useProperties) { if (useProperties) {
let properties = getExtension(element, SPIFF_PARENT_PROP); let properties = getExtension(businessObjectToUse, SPIFF_PARENT_PROP);
let property = getExtensionProperty(element, name); let property = getExtensionProperty(businessObjectToUse, name);
if (!properties) { if (!properties) {
properties = moddle.create(SPIFF_PARENT_PROP); properties = moddle.create(SPIFF_PARENT_PROP);
extensions.get('values').push(properties); extensions.get('values').push(properties);
@ -73,7 +77,7 @@ export function setExtensionValue(element, name, value, moddle, commandStack) {
property.value = value; property.value = value;
property.name = name; property.name = name;
} else { } else {
let extension = getExtension(element, name); let extension = getExtension(businessObjectToUse, name);
if (!extension) { if (!extension) {
extension = moddle.create(name); extension = moddle.create(name);
extensions.get('values').push(extension) extensions.get('values').push(extension)
@ -83,19 +87,18 @@ export function setExtensionValue(element, name, value, moddle, commandStack) {
commandStack.execute('element.updateModdleProperties', { commandStack.execute('element.updateModdleProperties', {
element, element,
moddleElement: businessObject, moddleElement: businessObjectToUse,
properties: { properties: {
extensionElements: extensions, extensionElements: extensions,
}, },
}); });
} }
function getExtension(element, name) { function getExtension(businessObject, name) {
const bizObj = element.businessObject; if (!businessObject.extensionElements) {
if (!bizObj.extensionElements) {
return null; return null;
} }
const extensionElements = bizObj.extensionElements.get('values'); const extensionElements = businessObject.extensionElements.get('values');
return extensionElements.filter(function (extensionElement) { return extensionElements.filter(function (extensionElement) {
if (extensionElement.$instanceOf(name)) { if (extensionElement.$instanceOf(name)) {
return true; return true;
@ -104,8 +107,8 @@ function getExtension(element, name) {
} }
function getExtensionProperty(element, name) { function getExtensionProperty(businessObject, name) {
const parentElement = getExtension(element, SPIFF_PARENT_PROP); const parentElement = getExtension(businessObject, SPIFF_PARENT_PROP);
if (parentElement) { if (parentElement) {
return parentElement.get('properties').filter(function (propertyElement) { return parentElement.get('properties').filter(function (propertyElement) {
return ( return (

View File

@ -436,6 +436,7 @@ function createServiceGroup(element, translate, moddle, commandStack) {
id: 'serviceTaskParameters', id: 'serviceTaskParameters',
label: translate('Parameters'), label: translate('Parameters'),
component: ListGroup, component: ListGroup,
shouldSort: false,
...ServiceTaskParameterArray({ ...ServiceTaskParameterArray({
element, element,
moddle, moddle,

View File

@ -15,7 +15,7 @@ export function SpiffExtensionCheckboxEntry(props) {
const debounce = useService('debounceInput'); const debounce = useService('debounceInput');
const getValue = () => { const getValue = () => {
return getExtensionValue(element, name) return getExtensionValue(element.businessObject, name)
} }
const setValue = value => { const setValue = value => {

View File

@ -14,7 +14,7 @@ export function SpiffExtensionLaunchButton(props) {
className: 'spiffworkflow-properties-panel-button', className: 'spiffworkflow-properties-panel-button',
id: `launch_editor_button_${name}`, id: `launch_editor_button_${name}`,
onClick: () => { onClick: () => {
const value = getExtensionValue(element, name); const value = getExtensionValue(element.businessObject, name);
eventBus.fire(event, { eventBus.fire(event, {
value, value,
eventBus, eventBus,

View File

@ -36,7 +36,7 @@ export function SpiffExtensionSelect(props) {
const eventBus = useService('eventBus'); const eventBus = useService('eventBus');
const getValue = () => { const getValue = () => {
return getExtensionValue(element, name); return getExtensionValue(element.businessObject, name);
}; };
const setValue = (value) => { const setValue = (value) => {

View File

@ -44,9 +44,7 @@ function requestServiceTaskOperators(eventBus, element, commandStack) {
eventBus.fire('spiff.service_tasks.requested', { eventBus }); eventBus.fire('spiff.service_tasks.requested', { eventBus });
eventBus.on('spiff.service_tasks.returned', (event) => { eventBus.on('spiff.service_tasks.returned', (event) => {
if (event.serviceTaskOperators.length > 0) { if (event.serviceTaskOperators.length > 0) {
serviceTaskOperators = event.serviceTaskOperators.sort((a, b) => serviceTaskOperators = event.serviceTaskOperators;
a.id.localeCompare(b.id)
);
} }
}); });
} }
@ -69,7 +67,7 @@ function getServiceTaskParameterModdleElements(shapeElement) {
if (serviceTaskOperatorModdleElement) { if (serviceTaskOperatorModdleElement) {
const { parameterList } = serviceTaskOperatorModdleElement; const { parameterList } = serviceTaskOperatorModdleElement;
if (parameterList) { if (parameterList) {
return parameterList.parameters.sort((a, b) => a.id.localeCompare(b.id)); return parameterList.parameters;
} }
} }
return []; return [];

View File

@ -15,7 +15,7 @@ export function SpiffExtensionTextArea(props) {
const debounce = useService('debounceInput'); const debounce = useService('debounceInput');
const getValue = () => { const getValue = () => {
return getExtensionValue(element, name) return getExtensionValue(element.businessObject, name)
} }
const setValue = value => { const setValue = value => {

View File

@ -21,17 +21,15 @@ import {
* @returns {string|null|*} * @returns {string|null|*}
*/ */
export function SpiffExtensionTextInput(props) { export function SpiffExtensionTextInput(props) {
const element = props.element; const { element, commandStack, moddle, name, label, description, businessObject } = props;
const commandStack = props.commandStack, moddle = props.moddle;
const name = props.name, label = props.label, description = props.description;
const debounce = useService('debounceInput'); const debounce = useService('debounceInput');
const getValue = () => { const getValue = () => {
return getExtensionValue(element, name) return getExtensionValue(businessObject, name)
} }
const setValue = value => { const setValue = value => {
setExtensionValue(element, name, value, moddle, commandStack) setExtensionValue(element, name, value, moddle, commandStack, businessObject)
}; };
return <TextFieldEntry return <TextFieldEntry

View File

@ -6,6 +6,9 @@ import DataObjectInterceptor from './DataObject/DataObjectInterceptor';
import DataObjectRules from './DataObject/DataObjectRules'; import DataObjectRules from './DataObject/DataObjectRules';
import DataObjectRenderer from './DataObject/DataObjectRenderer'; import DataObjectRenderer from './DataObject/DataObjectRenderer';
import DataObjectPropertiesProvider from './DataObject/propertiesPanel/DataObjectPropertiesProvider'; import DataObjectPropertiesProvider from './DataObject/propertiesPanel/DataObjectPropertiesProvider';
import DataObjectLabelEditingProvider from './DataObject/DataObjectLabelEditingProvider';
import DataStorePropertiesProvider from './DataStoreReference/propertiesPanel/DataStorePropertiesProvider';
import DataStoreInterceptor from './DataStoreReference/DataStoreInterceptor';
import ConditionsPropertiesProvider from './conditions/propertiesPanel/ConditionsPropertiesProvider'; import ConditionsPropertiesProvider from './conditions/propertiesPanel/ConditionsPropertiesProvider';
import ExtensionsPropertiesProvider from './extensions/propertiesPanel/ExtensionsPropertiesProvider'; import ExtensionsPropertiesProvider from './extensions/propertiesPanel/ExtensionsPropertiesProvider';
import MessagesPropertiesProvider from './messages/propertiesPanel/MessagesPropertiesProvider'; import MessagesPropertiesProvider from './messages/propertiesPanel/MessagesPropertiesProvider';
@ -22,6 +25,9 @@ export default {
'dataObjectInterceptor', 'dataObjectInterceptor',
'dataObjectRules', 'dataObjectRules',
'dataObjectPropertiesProvider', 'dataObjectPropertiesProvider',
'dataObjectLabelEditingProvider',
'dataStoreInterceptor',
'dataStorePropertiesProvider',
'conditionsPropertiesProvider', 'conditionsPropertiesProvider',
'extensionsPropertiesProvider', 'extensionsPropertiesProvider',
'messagesPropertiesProvider', 'messagesPropertiesProvider',
@ -40,6 +46,9 @@ export default {
dataObjectRules: ['type', DataObjectRules], dataObjectRules: ['type', DataObjectRules],
dataObjectRenderer: ['type', DataObjectRenderer], dataObjectRenderer: ['type', DataObjectRenderer],
dataObjectPropertiesProvider: ['type', DataObjectPropertiesProvider], dataObjectPropertiesProvider: ['type', DataObjectPropertiesProvider],
dataObjectLabelEditingProvider: ['type', DataObjectLabelEditingProvider],
dataStoreInterceptor: ['type', DataStoreInterceptor],
dataStorePropertiesProvider: ['type', DataStorePropertiesProvider],
conditionsPropertiesProvider: ['type', ConditionsPropertiesProvider], conditionsPropertiesProvider: ['type', ConditionsPropertiesProvider],
extensionsPropertiesProvider: ['type', ExtensionsPropertiesProvider], extensionsPropertiesProvider: ['type', ExtensionsPropertiesProvider],
signalPropertiesProvider: ['type', SignalPropertiesProvider], signalPropertiesProvider: ['type', SignalPropertiesProvider],

View File

@ -93,6 +93,17 @@
} }
] ]
}, },
{
"name": "Category",
"superClass": [ "Element" ],
"properties": [
{
"name": "value",
"isBody": true,
"type": "String"
}
]
},
{ {
"name": "SignalButtonLabel", "name": "SignalButtonLabel",
"superClass": [ "Element" ], "superClass": [ "Element" ],

View File

@ -8,8 +8,8 @@
"build:watch": "webpack --watch", "build:watch": "webpack --watch",
"dev": "run-p build:watch serve", "dev": "run-p build:watch serve",
"serve": "sirv public --dev", "serve": "sirv public --dev",
"lint": "./node_modules/.bin/eslint src *.js", "lint": "./node_modules/.bin/eslint app *.js",
"lint:fix": "./node_modules/.bin/eslint --fix src *.js", "lint:fix": "./node_modules/.bin/eslint --fix app *.js",
"start": "run-s build serve", "start": "run-s build serve",
"test": "karma start karma.conf.js" "test": "karma start karma.conf.js"
}, },

View File

@ -0,0 +1,40 @@
import {
query as domQuery,
queryAll as domQueryAll
} from 'min-dom';
import {
bootstrapPropertiesPanel,
expectSelected,
findGroupEntry,
changeInput,
PROPERTIES_PANEL_CONTAINER,
} from './helpers';
import conditionsPanel from '../../app/spiffworkflow/conditions';
import { BpmnPropertiesPanelModule, BpmnPropertiesProviderModule } from 'bpmn-js-properties-panel';
import { getBusinessObject } from 'bpmn-js/lib/util/ModelUtil';
describe('BPMN Condition', function() {
let xml = require('./bpmn/conditional_event.bpmn').default;
beforeEach(bootstrapPropertiesPanel(xml, {
debounceInput: false,
additionalModules: [
conditionsPanel,
BpmnPropertiesPanelModule,
BpmnPropertiesProviderModule,
]
}));
it('should add a condition panel when Conditional Event is selected', async function() {
const shapeElement = await expectSelected('conditional_event');
const businessObject = getBusinessObject(shapeElement);
const conditions = findGroupEntry('conditions', PROPERTIES_PANEL_CONTAINER);
expect(conditions).to.exist;
const textInput = domQuery('textarea', conditions);
expect(textInput.value).to.equal('cancel_task_2');
changeInput(textInput, 'True');
});
});

View File

@ -1,9 +1,21 @@
import { import {
bootstrapPropertiesPanel, changeInput, bootstrapPropertiesPanel,
changeInput,
expectSelected, expectSelected,
findEntry, findInput, findSelect, findEntry,
findInput,
findSelect
} from './helpers'; } from './helpers';
import { BpmnPropertiesPanelModule, BpmnPropertiesProviderModule } from 'bpmn-js-properties-panel';
import {
inject,
} from 'bpmn-js/test/helper';
import {
BpmnPropertiesPanelModule,
BpmnPropertiesProviderModule
} from 'bpmn-js-properties-panel';
import spiffModdleExtension from '../../app/spiffworkflow/moddle/spiffworkflow.json'; import spiffModdleExtension from '../../app/spiffworkflow/moddle/spiffworkflow.json';
import TestContainer from 'mocha-test-container-support'; import TestContainer from 'mocha-test-container-support';
import DataObject from '../../app/spiffworkflow/DataObject'; import DataObject from '../../app/spiffworkflow/DataObject';
@ -45,7 +57,6 @@ describe('Properties Panel for Data Objects', function() {
expect(selector.length).to.equal(3); expect(selector.length).to.equal(3);
}); });
it('selecting a different data object should change the data model.', async function () { it('selecting a different data object should change the data model.', async function () {
// IF - a data object reference is selected // IF - a data object reference is selected
@ -62,18 +73,21 @@ describe('Properties Panel for Data Objects', function() {
expect(businessObject.get('dataObjectRef').id).to.equal('my_third_data_object'); expect(businessObject.get('dataObjectRef').id).to.equal('my_third_data_object');
}); });
it('renaming a data object, changes to the label of references', async function() { // Notice: Test Case for Data Object ID Changes No Longer Required
// With our new feature implementation, changing a Data Object ID is now independent of altering
// its Data Reference Name. This decoupling eliminates the need for the specific test case previously required for these changes.
// it('renaming a data object, changes to the label of references', async function() {
// IF - a process is selected, and the name of a data object is changed. // // IF - a process is selected, and the name of a data object is changed.
let entry = findEntry('ProcessTest-dataObj-2-id', container); // let entry = findEntry('ProcessTest-dataObj-2-id', container);
let textInput = findInput('text', entry); // let textInput = findInput('text', entry);
changeInput(textInput, 'my_nifty_new_name'); // changeInput(textInput, 'my_nifty_new_name');
let my_data_ref_1 = await expectSelected('my_data_ref_1'); // let my_data_ref_1 = await expectSelected('my_data_ref_1');
// THEN - both the data object itself, and the label of any references are updated. // // THEN - both the data object itself, and the label of any references are updated.
expect(my_data_ref_1.businessObject.dataObjectRef.id).to.equal('my_nifty_new_name'); // expect(my_data_ref_1.businessObject.dataObjectRef.id).to.equal('my_nifty_new_name');
expect(my_data_ref_1.businessObject.name).to.equal('My Nifty New Name'); // expect(my_data_ref_1.businessObject.name).to.equal('My Nifty New Name');
}); // });
it('renaming a data object creates a lable without losing the numbers', async function () { it('renaming a data object creates a lable without losing the numbers', async function () {
@ -85,7 +99,97 @@ describe('Properties Panel for Data Objects', function() {
// THEN - both the data object itself, and the label of any references are updated. // THEN - both the data object itself, and the label of any references are updated.
expect(my_data_ref_1.businessObject.dataObjectRef.id).to.equal('MyObject1'); expect(my_data_ref_1.businessObject.dataObjectRef.id).to.equal('MyObject1');
expect(my_data_ref_1.businessObject.name).to.equal('My Object 1'); // Notice: Test Case for Data Object ID Changes No Longer Required
// expect(my_data_ref_1.businessObject.name).to.equal('My Object 1');
}); });
it('renaming a data object ID, does not change the label of references', async function () {
// IF - a process is selected, and the name of a data object is changed.
let entry = findEntry('ProcessTest-dataObj-2-id', container);
let textInput = findInput('text', entry);
changeInput(textInput, 'my_nifty_new_name');
let my_data_ref_1 = await expectSelected('my_data_ref_1');
// THEN - both the data object itself, and the label of any references are updated.
expect(my_data_ref_1.businessObject.dataObjectRef.id).to.equal('my_nifty_new_name');
expect(my_data_ref_1.businessObject.name).not.to.equal('My Nifty New Name');
});
it('renaming a data object name, does change the label of references', async function () {
let entry = findEntry('ProcessTest-dataObj-2-name', container);
let textInput = findInput('text', entry);
let newDataObjectName = 'A New Data Object Name';
changeInput(textInput, newDataObjectName);
let my_data_ref_1 = await expectSelected('my_data_ref_1');
let my_data_ref_2 = await expectSelected('my_data_ref_2');
// THEN - the label of any references are updated.
expect(my_data_ref_1.businessObject.name).to.equal(newDataObjectName);
expect(my_data_ref_2.businessObject.name).to.equal(newDataObjectName);
// Test References with DataState
let my_data_ref_3 = await expectSelected('my_data_ref_3');
let my_data_ref_3_DataState = my_data_ref_3.businessObject.dataState.name;
expect(my_data_ref_3.businessObject.name).to.equal(`${newDataObjectName} [${my_data_ref_3_DataState}]`);
});
it('renaming a data object reference state, does change the label its reference', async function () {
let my_data_ref_1 = await expectSelected('my_data_ref_1');
let dtObjCurrentName = my_data_ref_1.businessObject.name;
let entry = findEntry('selectDataState-textField', container);
let idInput = findInput('text', entry);
let nwState = "New State";
// Change Data State
changeInput(idInput, nwState);
// Expect new DataObjectRef Name to be like 'DataObjectRefName [DataState]'
expect(my_data_ref_1.businessObject.name).to.equal(`${dtObjCurrentName} [${nwState}]`);
expect(my_data_ref_1.businessObject.name).not.to.equal(dtObjCurrentName);
});
it('selecting a different data object should not change the data object reference name.', async function () {
// IF - a data object reference is selected
let my_data_ref_1 = await expectSelected('my_data_ref_1');
let entry = findEntry('selectDataObject', container);
let selector = findSelect(entry);
let businessObject = my_data_ref_1.businessObject;
changeInput(selector, 'my_third_data_object');
expect(businessObject.get('dataObjectRef').id).to.equal('my_third_data_object');
expect(businessObject.name).to.equal('D3');
expect(businessObject.name).not.to.equal('my_data_object');
});
it('should not allow two dataObjects to have the same ID', inject(async function (canvas, modeling) {
// Creating the first dataObject
let rootShape = canvas.getRootElement();
const dataObject1 = modeling.createShape({ type: 'bpmn:DataObject' },
{ x: 100, y: 100 }, rootShape);
// Creating the second dataObject
const dataObject2 = modeling.createShape({ type: 'bpmn:DataObject' },
{ x: 150, y: 100 }, rootShape);
await expectSelected(dataObject2.id);
let entry = findEntry('dataObjectId', container);
let idInput = findInput('text', entry);
const duplicateId = dataObject1.businessObject.id;
changeInput(idInput, duplicateId);
// Check that the ID change is not successful
expect(dataObject2.businessObject.id).not.to.equal(duplicateId);
}));
}); });

View File

@ -0,0 +1,50 @@
import { bootstrapPropertiesPanel, expectSelected } from './helpers';
import { BpmnPropertiesPanelModule, BpmnPropertiesProviderModule } from 'bpmn-js-properties-panel';
import {
getBpmnJS,
inject
} from 'bpmn-js/test/helper';
import dataStoreInterceptor from '../../app/spiffworkflow/DataStoreReference';
describe('DataStore Interceptor', function () {
let xml = require('./bpmn/data_store.bpmn').default;
beforeEach(bootstrapPropertiesPanel(xml, {
debounceInput: false,
additionalModules: [
dataStoreInterceptor,
BpmnPropertiesPanelModule,
BpmnPropertiesProviderModule,
]
}));
it('should delete dataStore in case dataStoreRef is deleted - DataStoreReference element', inject(async function (modeling) {
const modeler = getBpmnJS();
// We Select a DataStoreReference element
const shapeElement = await expectSelected('DataStoreReference_0eqeh4p');
expect(shapeElement, "I can't find DataStoreReference element").to.exist;
let definitions = await modeler.getDefinitions();
let dataStoreExists = definitions.get('rootElements').some(element =>
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;
}));
});

View File

@ -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;
});
});

View File

@ -131,8 +131,8 @@ describe('Properties Panel for User Tasks', function () {
eventBus.fire('spiff.jsonSchema.update', { eventBus.fire('spiff.jsonSchema.update', {
value: 'new-schema.json', value: 'new-schema.json',
}); });
const jsonFile = getExtensionValue(userElement, 'formJsonSchemaFilename'); const jsonFile = getExtensionValue(userElement.businessObject, 'formJsonSchemaFilename');
const uiFile = getExtensionValue(userElement, 'formUiSchemaFilename'); const uiFile = getExtensionValue(userElement.businessObject, 'formUiSchemaFilename');
expect(jsonFile).to.equal('new-schema.json'); expect(jsonFile).to.equal('new-schema.json');
expect(uiFile).to.equal('new-uischema.json'); expect(uiFile).to.equal('new-uischema.json');
@ -155,7 +155,7 @@ describe('Properties Panel for User Tasks', function () {
const businessObject = getBusinessObject(userElement); const businessObject = getBusinessObject(userElement);
// The change is reflected in the business object // The change is reflected in the business object
let instructions = getExtensionValue( let instructions = getExtensionValue(
userElement, businessObject,
'spiffworkflow:InstructionsForEndUser' 'spiffworkflow:InstructionsForEndUser'
); );
expect(instructions).to.equal('#Hello!'); expect(instructions).to.equal('#Hello!');

View File

@ -0,0 +1,119 @@
<?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:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_1qnx3d3" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.0.0" modeler:executionPlatform="Camunda Platform" modeler:executionPlatformVersion="7.17.0">
<bpmn:process id="main" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_0n934tk</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_0n934tk" sourceRef="StartEvent_1" targetRef="setup" />
<bpmn:parallelGateway id="gateway_1">
<bpmn:incoming>Flow_0ghdmcg</bpmn:incoming>
<bpmn:outgoing>Flow_1kyhah1</bpmn:outgoing>
<bpmn:outgoing>Flow_1texcs9</bpmn:outgoing>
</bpmn:parallelGateway>
<bpmn:task id="task_1" name="Task 1">
<bpmn:incoming>Flow_1kyhah1</bpmn:incoming>
<bpmn:outgoing>Flow_16kfh39</bpmn:outgoing>
</bpmn:task>
<bpmn:sequenceFlow id="Flow_1kyhah1" sourceRef="gateway_1" targetRef="task_1" />
<bpmn:sequenceFlow id="Flow_16kfh39" sourceRef="task_1" targetRef="gateway_2" />
<bpmn:parallelGateway id="gateway_2">
<bpmn:incoming>Flow_16kfh39</bpmn:incoming>
<bpmn:incoming>Flow_1u8w68b</bpmn:incoming>
<bpmn:outgoing>Flow_1wczxjh</bpmn:outgoing>
</bpmn:parallelGateway>
<bpmn:endEvent id="normal_end">
<bpmn:incoming>Flow_1wczxjh</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_1wczxjh" sourceRef="gateway_2" targetRef="normal_end" />
<bpmn:boundaryEvent id="conditional_event" attachedToRef="task_2">
<bpmn:outgoing>Flow_1ybk8c2</bpmn:outgoing>
<bpmn:conditionalEventDefinition id="ConditionalEventDefinition_0q26i1s">
<bpmn:condition>cancel_task_2</bpmn:condition>
</bpmn:conditionalEventDefinition>
</bpmn:boundaryEvent>
<bpmn:endEvent id="conditional_end">
<bpmn:incoming>Flow_1ybk8c2</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_1ybk8c2" sourceRef="conditional_event" targetRef="conditional_end" />
<bpmn:task id="task_2" name="Task 2">
<bpmn:incoming>Flow_1texcs9</bpmn:incoming>
<bpmn:outgoing>Flow_1u8w68b</bpmn:outgoing>
</bpmn:task>
<bpmn:sequenceFlow id="Flow_1texcs9" sourceRef="gateway_1" targetRef="task_2" />
<bpmn:sequenceFlow id="Flow_1u8w68b" sourceRef="task_2" targetRef="gateway_2" />
<bpmn:task id="setup" name="Setup">
<bpmn:incoming>Flow_0n934tk</bpmn:incoming>
<bpmn:outgoing>Flow_0ghdmcg</bpmn:outgoing>
</bpmn:task>
<bpmn:sequenceFlow id="Flow_0ghdmcg" sourceRef="setup" targetRef="gateway_1" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_16xfaqc">
<bpmndi:BPMNShape id="Gateway_1k9pqw1_di" bpmnElement="gateway_1">
<dc:Bounds x="425" y="165" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0ilxk3l_di" bpmnElement="task_1">
<dc:Bounds x="530" y="150" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_1a3gl8a_di" bpmnElement="gateway_2">
<dc:Bounds x="685" y="165" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1y4buya_di" bpmnElement="normal_end">
<dc:Bounds x="792" y="172" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0f7n8iv_di" bpmnElement="task_2">
<dc:Bounds x="530" y="280" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1gkguhe_di" bpmnElement="conditional_end">
<dc:Bounds x="652" y="402" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1v7pfua_di" bpmnElement="setup">
<dc:Bounds x="280" y="150" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1vhin9h_di" bpmnElement="StartEvent_1">
<dc:Bounds x="192" y="172" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1vn14hl_di" bpmnElement="conditional_event">
<dc:Bounds x="562" y="342" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_0n934tk_di" bpmnElement="Flow_0n934tk">
<di:waypoint x="228" y="190" />
<di:waypoint x="280" y="190" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1kyhah1_di" bpmnElement="Flow_1kyhah1">
<di:waypoint x="475" y="190" />
<di:waypoint x="530" y="190" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_16kfh39_di" bpmnElement="Flow_16kfh39">
<di:waypoint x="630" y="190" />
<di:waypoint x="685" y="190" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1wczxjh_di" bpmnElement="Flow_1wczxjh">
<di:waypoint x="735" y="190" />
<di:waypoint x="792" y="190" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1ybk8c2_di" bpmnElement="Flow_1ybk8c2">
<di:waypoint x="580" y="378" />
<di:waypoint x="580" y="420" />
<di:waypoint x="652" y="420" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1texcs9_di" bpmnElement="Flow_1texcs9">
<di:waypoint x="450" y="215" />
<di:waypoint x="450" y="320" />
<di:waypoint x="530" y="320" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1u8w68b_di" bpmnElement="Flow_1u8w68b">
<di:waypoint x="630" y="320" />
<di:waypoint x="710" y="320" />
<di:waypoint x="710" y="215" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0ghdmcg_di" bpmnElement="Flow_0ghdmcg">
<di:waypoint x="380" y="190" />
<di:waypoint x="425" y="190" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -0,0 +1,63 @@
<?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:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_1qnx3d3" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.0.0" modeler:executionPlatform="Camunda Platform" modeler:executionPlatformVersion="7.17.0">
<bpmn:process id="Process_16xfaqc" isExecutable="true" camunda:versionTag="1">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_0vt1twq</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:endEvent id="Event_0yxpeto">
<bpmn:incoming>Flow_1udyjxo</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_0vt1twq" sourceRef="StartEvent_1" targetRef="my_user_task" />
<bpmn:sequenceFlow id="Flow_1udyjxo" sourceRef="my_user_task" targetRef="Event_0yxpeto" />
<bpmn:userTask id="my_user_task" name="Complete Web Form">
<bpmn:extensionElements>
<camunda:formData>
<camunda:formField type="boolean">
<camunda:properties>
<camunda:property />
</camunda:properties>
<camunda:validation>
<camunda:constraint />
</camunda:validation>
</camunda:formField>
</camunda:formData>
</bpmn:extensionElements>
<bpmn:incoming>Flow_0vt1twq</bpmn:incoming>
<bpmn:outgoing>Flow_1udyjxo</bpmn:outgoing>
<bpmn:dataOutputAssociation id="DataOutputAssociation_1nwrgyb">
<bpmn:targetRef>DataStoreReference_0eqeh4p</bpmn:targetRef>
</bpmn:dataOutputAssociation>
</bpmn:userTask>
<bpmn:dataStoreReference id="DataStoreReference_0eqeh4p" dataStoreRef="countries" />
</bpmn:process>
<bpmn:dataStore id="countries" name="DataStore_countries" />
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_16xfaqc">
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="142" y="82" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0yxpeto_di" bpmnElement="Event_0yxpeto">
<dc:Bounds x="422" y="82" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0096bsk_di" bpmnElement="my_user_task">
<dc:Bounds x="260" y="60" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="DataStoreReference_0eqeh4p_di" bpmnElement="DataStoreReference_0eqeh4p">
<dc:Bounds x="345" y="265" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_0vt1twq_di" bpmnElement="Flow_0vt1twq">
<di:waypoint x="178" y="100" />
<di:waypoint x="260" y="100" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1udyjxo_di" bpmnElement="Flow_1udyjxo">
<di:waypoint x="360" y="100" />
<di:waypoint x="422" y="100" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="DataOutputAssociation_1nwrgyb_di" bpmnElement="DataOutputAssociation_1nwrgyb">
<di:waypoint x="323" y="140" />
<di:waypoint x="364" y="265" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -38,7 +38,7 @@
<bpmn:script>elizabeth="awesome"</bpmn:script> <bpmn:script>elizabeth="awesome"</bpmn:script>
</bpmn:scriptTask> </bpmn:scriptTask>
<bpmn:dataObject id="my_other_data_object" /> <bpmn:dataObject id="my_other_data_object" />
<bpmn:dataObject id="my_third_data_object" /> <bpmn:dataObject id="my_third_data_object" name="D3" />
<bpmn:dataObjectReference id="my_data_ref_1" name="my_data_object" dataObjectRef="my_data_object" /> <bpmn:dataObjectReference id="my_data_ref_1" name="my_data_object" dataObjectRef="my_data_object" />
<bpmn:sequenceFlow id="Flow_132laxn" sourceRef="task_confirm" targetRef="business_rule_task" /> <bpmn:sequenceFlow id="Flow_132laxn" sourceRef="task_confirm" targetRef="business_rule_task" />
<bpmn:userTask id="task_confirm" name="confirm contentment"> <bpmn:userTask id="task_confirm" name="confirm contentment">
@ -61,6 +61,11 @@
</camunda:properties> </camunda:properties>
</bpmn:extensionElements> </bpmn:extensionElements>
</bpmn:dataObjectReference> </bpmn:dataObjectReference>
<bpmn:dataObjectReference id="my_data_ref_3" name="my_data_object" dataObjectRef="my_data_object">
<bpmn:dataState id="DataState_my_data_ref_3" name="OK" />
</bpmn:dataObjectReference>
<bpmn:dataObject id="my_data_object" /> <bpmn:dataObject id="my_data_object" />
<bpmn:sequenceFlow id="Flow_1lu1qyz" sourceRef="business_rule_task" targetRef="Event_14wzv4j" /> <bpmn:sequenceFlow id="Flow_1lu1qyz" sourceRef="business_rule_task" targetRef="Event_14wzv4j" />
<bpmn:businessRuleTask id="business_rule_task"> <bpmn:businessRuleTask id="business_rule_task">
@ -119,6 +124,12 @@
<dc:Bounds x="451" y="322" width="78" height="14" /> <dc:Bounds x="451" y="322" width="78" height="14" />
</bpmndi:BPMNLabel> </bpmndi:BPMNLabel>
</bpmndi:BPMNShape> </bpmndi:BPMNShape>
<bpmndi:BPMNShape id="DataObjectReference_08bm73g_di" bpmnElement="my_data_ref_3">
<dc:Bounds x="472" y="265" width="36" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="451" y="322" width="78" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="dataInput_1" bpmnElement="num_dogs"> <bpmndi:BPMNShape id="dataInput_1" bpmnElement="num_dogs">
<dc:Bounds x="172" y="85" width="36" height="50" /> <dc:Bounds x="172" y="85" width="36" height="50" />
<bpmndi:BPMNLabel> <bpmndi:BPMNLabel>