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:
- 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

View File

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

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
// it should be possible to search for a paticular call activity.
bpmnModeler.on('spiff.callactivity.search', (event) => {

View File

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

View File

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

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

View File

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

View File

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

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 {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}
/>;

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 {
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,

View File

@ -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 (

View File

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

View File

@ -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 => {

View File

@ -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,

View File

@ -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) => {

View File

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

View File

@ -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 => {

View File

@ -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 <TextFieldEntry

View File

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

View File

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

View File

@ -8,8 +8,8 @@
"build:watch": "webpack --watch",
"dev": "run-p build:watch serve",
"serve": "sirv public --dev",
"lint": "./node_modules/.bin/eslint src *.js",
"lint:fix": "./node_modules/.bin/eslint --fix src *.js",
"lint": "./node_modules/.bin/eslint app *.js",
"lint:fix": "./node_modules/.bin/eslint --fix app *.js",
"start": "run-s build serve",
"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

@ -10,7 +10,7 @@ import {
idToHumanReadableName,
} from '../../app/spiffworkflow/DataObject/DataObjectHelpers';
describe('DataObject Interceptor', function() {
describe('DataObject Interceptor', function () {
let xml = require('./bpmn/empty_diagram.bpmn').default;
@ -23,7 +23,7 @@ describe('DataObject Interceptor', function() {
]
}));
it('New Data Object References should create a data object if none exist.', inject(function(canvas, modeling) {
it('New Data Object References should create a data object if none exist.', inject(function (canvas, modeling) {
// IF - a new dataObjectReference is created
let rootShape = canvas.getRootElement();
@ -37,7 +37,7 @@ describe('DataObject Interceptor', function() {
}));
it('New Data Object References should connect to the first available data Object if it exists', inject(function(canvas, modeling) {
it('New Data Object References should connect to the first available data Object if it exists', inject(function (canvas, modeling) {
// IF - two dataObjectReferences are created
let rootShape = canvas.getRootElement();
@ -54,7 +54,7 @@ describe('DataObject Interceptor', function() {
}));
it('Deleting a data object reference does not delete the data object, unless it is the last reference', inject(function(canvas, modeling) {
it('Deleting a data object reference does not delete the data object, unless it is the last reference', inject(function (canvas, modeling) {
// IF - two dataObjectReferences are created
let rootShape = canvas.getRootElement();
@ -71,7 +71,7 @@ describe('DataObject Interceptor', function() {
expect(dataObjects.length).to.equal(1);
}));
it('Deleting all the data references will also delete the data object', inject(function(canvas, modeling) {
it('Deleting all the data references will also delete the data object', inject(function (canvas, modeling) {
// IF - two dataObjectReferences are created
let rootShape = canvas.getRootElement();
@ -89,7 +89,7 @@ describe('DataObject Interceptor', function() {
expect(dataObjects.length).to.equal(0);
}));
it('Creating a new Reference will update the name to match the DataObject', inject(function(canvas, modeling) {
it('Creating a new Reference will update the name to match the DataObject', inject(function (canvas, modeling) {
// IF - a Data Reference Exists
let rootShape = canvas.getRootElement();
@ -101,7 +101,7 @@ describe('DataObject Interceptor', function() {
expect(dataObjectRefShape1.businessObject.name).to.equal(human_readable_name);
}));
it('should allow you to add a data object to a subprocess', inject(function(canvas, modeling, elementRegistry) {
it('should allow you to add a data object to a subprocess', inject(function (canvas, modeling, elementRegistry) {
// IF - A data object reference is added to a sup-process
let subProcessShape = elementRegistry.get('my_subprocess');
@ -117,7 +117,7 @@ describe('DataObject Interceptor', function() {
expect(dataObjects.length).to.equal(1);
}));
it('Data objects in a process should be visible in a subprocess', inject(function(canvas, modeling, elementRegistry) {
it('Data objects in a process should be visible in a subprocess', inject(function (canvas, modeling, elementRegistry) {
let subProcessShape = elementRegistry.get('my_subprocess');
let subProcess = subProcessShape.businessObject;
@ -132,7 +132,7 @@ describe('DataObject Interceptor', function() {
expect(dataObjects.length).to.equal(1);
}));
it('Data objects in a subprocess should not be visible in a process', inject(function(canvas, modeling, elementRegistry) {
it('Data objects in a subprocess should not be visible in a process', inject(function (canvas, modeling, elementRegistry) {
let subProcessShape = elementRegistry.get('my_subprocess');
let subProcess = subProcessShape.businessObject;
@ -147,7 +147,7 @@ describe('DataObject Interceptor', function() {
expect(dataObjects.length).to.equal(0);
}));
it('References inside subprocesses should be visible in a process', inject(function(canvas, modeling, elementRegistry) {
it('References inside subprocesses should be visible in a process', inject(function (canvas, modeling, elementRegistry) {
let rootShape = canvas.getRootElement();
const refOne = modeling.createShape({ type: 'bpmn:DataObjectReference' },

View File

@ -1,18 +1,30 @@
import {
bootstrapPropertiesPanel, changeInput,
bootstrapPropertiesPanel,
changeInput,
expectSelected,
findEntry, findInput, findSelect,
findEntry,
findInput,
findSelect
} 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 TestContainer from 'mocha-test-container-support';
import DataObject from '../../app/spiffworkflow/DataObject';
describe('Properties Panel for Data Objects', function() {
describe('Properties Panel for Data Objects', function () {
let xml = require('./bpmn/diagram.bpmn').default;
let container;
beforeEach(function() {
beforeEach(function () {
container = TestContainer.get(this);
});
@ -29,7 +41,7 @@ describe('Properties Panel for Data Objects', function() {
},
}));
it('should allow you to see a list of data objects', async function() {
it('should allow you to see a list of data objects', async function () {
// IF - a data object reference is selected
let my_data_ref_1 = await expectSelected('my_data_ref_1');
@ -45,8 +57,7 @@ describe('Properties Panel for Data Objects', function() {
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
let my_data_ref_1 = await expectSelected('my_data_ref_1');
@ -62,20 +73,23 @@ describe('Properties Panel for Data Objects', function() {
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.
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');
// // 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).to.equal('My Nifty New Name');
});
// // 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).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 () {
// IF - a process is selected, and the name of a data object is changed.
let entry = findEntry('ProcessTest-dataObj-2-id', container);
@ -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.
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', {
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!');

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:scriptTask>
<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:sequenceFlow id="Flow_132laxn" sourceRef="task_confirm" targetRef="business_rule_task" />
<bpmn:userTask id="task_confirm" name="confirm contentment">
@ -61,6 +61,11 @@
</camunda:properties>
</bpmn:extensionElements>
</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:sequenceFlow id="Flow_1lu1qyz" sourceRef="business_rule_task" targetRef="Event_14wzv4j" />
<bpmn:businessRuleTask id="business_rule_task">
@ -119,6 +124,12 @@
<dc:Bounds x="451" y="322" width="78" height="14" />
</bpmndi:BPMNLabel>
</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">
<dc:Bounds x="172" y="85" width="36" height="50" />
<bpmndi:BPMNLabel>