Squashed 'bpmn-js-spiffworkflow/' changes from 0a9db509a..9dcca6c80

9dcca6c80 Merge pull request #39 from sartography/message_fixes
9de4d9a2e update github action for tests.
84183ffd3 we weren't setting the property when updating a start event.
66a26cc85 does adding a new check prevent an error that only seems to happen whenthe frontend is engaged
877424a55 Merge pull request #37 from sartography/bugfix/bugfixes-for-mi-and-payloads
afb071d01 apparently didn't finish search and replace when creating the escalation panels
c8040aab5 remove unused MI attributes from XML
1bc43155d Merge pull request #34 from sartography/dependabot/github_actions/dependabot/fetch-metadata-1.6.0
a645c08f5 Merge pull request #36 from sartography/feature/events-with-payloads
8e0f84fbe Merge pull request #35 from sartography/bug/data_objects_in_pools
4b732edd3 add events with payloads
3247a197c update event select to include code field
91e012582 add generic event selector
021f53bb5 add generic event list
b19c69080 Assure we delete reference objects when the visible entity is removed. And remove all those console.logs.
d46741ffd A few more fixes to prevent bugs from showing up later ... * Deleting a pool was erroring out when it contained a list of data objects, now it works ok. * We were getting duplicate DataObjectReferences in the XML when doing a copy paste operation.  Duplicates are no longer generated.
f40cecc05 * Assure that Data object in pools can be changed to reference other data objects within the same pool. * In the runnable demo, add the keyboard bindings to copy/paste/delete etc... work. * Added a test for data objects in pools.
2f835fc7f Bump dependabot/fetch-metadata from 1.4.0 to 1.6.0
f6a79440e Merge pull request #33 from sartography/bugfix/restore-references-without-breaking-messages
2556a4599 better method for fixing references
5c49d665f Merge pull request #32 from sartography/bugfix/add-mi-to-subprocess
e138c4c26 add mi panel to subprocesses
462a5e777 Merge pull request #27 from sartography/feature/multi-instance-task-panel
63dc415fc add MI for call activities
61f2e5db3 add custom importer to handle loop input/output
e504af9bb add multi instance configuration panel

git-subtree-dir: bpmn-js-spiffworkflow
git-subtree-split: 9dcca6c80b8ab8ed0d79658456047b90e8483541
This commit is contained in:
burnettk 2023-08-09 16:14:32 -04:00
parent a1c7cecf59
commit 7db152f010
29 changed files with 4190 additions and 28 deletions

View File

@ -15,7 +15,7 @@ jobs:
steps:
- name: Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@v1.4.0
uses: dependabot/fetch-metadata@v1.6.0
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
- name: Enable auto-merge for Dependabot PRs

View File

@ -20,7 +20,7 @@ jobs:
steps:
- uses: actions/checkout@v3 #Checkout Repo
- uses: actions/setup-node@v3 #Setup Node
- uses: nanasess/setup-chromedriver@v1
- uses: nanasess/setup-chromedriver@v2 #Setup ChromeDriver
with:
node-version: '18'
- name: Run Karma Tests

View File

@ -20,6 +20,7 @@ let bpmnModeler;
try {
bpmnModeler = new BpmnModeler({
container: modelerEl,
keyboard: { bindTo: document },
propertiesPanel: {
parent: panelEl,
},
@ -41,9 +42,6 @@ try {
throw error;
}
// import XML
bpmnModeler.importXML(diagramXML).then(() => {});
/**
* It is possible to populate certain components using API calls to
* a backend. Here we mock out the API call, but this gives you
@ -191,6 +189,23 @@ bpmnModeler.on('spiff.callactivity.search', (event) => {
});
});
/* This restores unresolved references that camunda removes */
bpmnModeler.on('import.parse.complete', event => {
const refs = event.references.filter(r => r.property === 'bpmn:loopDataInputRef' || r.property === 'bpmn:loopDataOutputRef');
const desc = bpmnModeler._moddle.registry.getEffectiveDescriptor('bpmn:ItemAwareElement');
refs.forEach(ref => {
const props = {
id: ref.id,
name: ref.id ? typeof(ref.name) === 'undefined': ref.name,
};
let elem = bpmnModeler._moddle.create(desc, props);
elem.$parent = ref.element;
ref.element.set(ref.property, elem);
});
});
bpmnModeler.importXML(diagramXML).then(() => {});
// This handles the download and upload buttons - it isn't specific to
// the BPMN modeler or these extensions, just a quick way to allow you to

View File

@ -12,6 +12,7 @@ export default function setupFileOperations(bpmnModeler) {
* Just a quick bit of code so we can save the XML that is output.
* Helps for debugging against other libraries (like SpiffWorkflow)
*/
const btn = document.getElementById('downloadButton');
btn.addEventListener('click', (_event) => {
saveXML();
@ -29,12 +30,8 @@ export default function setupFileOperations(bpmnModeler) {
*/
const uploadBtn = document.getElementById('uploadButton');
uploadBtn.addEventListener('click', (_event) => {
openFile(displayFile);
openFile(bpmnModeler);
});
function displayFile(contents) {
bpmnModeler.importXML(contents).then(() => {});
}
}
function clickElem(elem) {
@ -59,7 +56,7 @@ function clickElem(elem) {
elem.dispatchEvent(eventMouse);
}
export function openFile(func) {
export function openFile(bpmnModeler) {
const readFile = function readFileCallback(e) {
const file = e.target.files[0];
if (!file) {
@ -68,7 +65,7 @@ export function openFile(func) {
const reader = new FileReader();
reader.onload = function onloadCallback(onloadEvent) {
const contents = onloadEvent.target.result;
fileInput.func(contents);
bpmnModeler.importXML(contents);
document.body.removeChild(fileInput);
};
reader.readAsText(file);
@ -77,7 +74,6 @@ export function openFile(func) {
fileInput.type = 'file';
fileInput.style.display = 'none';
fileInput.onchange = readFile;
fileInput.func = func;
document.body.appendChild(fileInput);
clickElem(fileInput);
}

View File

@ -36,6 +36,9 @@ export function findDataObject(process, id) {
}
export function findDataObjectReferences(children, dataObjectId) {
if (children == null) {
return [];
}
return children.flatMap((child) => {
if (child.$type == 'bpmn:DataObjectReference' && child.dataObjectRef.id == dataObjectId)
return [child];
@ -58,7 +61,7 @@ export function findDataObjectReferenceShapes(children, dataObjectId) {
}
export function idToHumanReadableName(id) {
const words = id.match(/[A-Za-z][a-z]*/g) || [id];
const words = id.match(/[A-Za-z][a-z]*|[0-9]+/g) || [id];
return words.map(capitalize).join(' ');
function capitalize(word) {

View File

@ -42,7 +42,10 @@ export default class DataObjectInterceptor extends CommandInterceptor {
// when the shape is deleted, but not interested in refactoring at the moment.
if (realParent != null) {
const flowElements = realParent.get('flowElements');
flowElements.push(businessObject);
const existingElement = flowElements.find(i => i.id === 1);
if (!existingElement) {
flowElements.push(businessObject);
}
}
} else if (is(businessObject, 'bpmn:DataObject')) {
// For data objects, only update the flowElements for new data objects, and set the parent so it doesn't get moved.
@ -120,12 +123,17 @@ export default class DataObjectInterceptor extends CommandInterceptor {
const { shape } = context;
if (is(shape, 'bpmn:DataObjectReference') && shape.type !== 'label') {
const dataObject = shape.businessObject.dataObjectRef;
let flowElements = shape.businessObject.$parent.get('flowElements');
let parent = shape.businessObject.$parent;
if (parent.processRef) {
// Our immediate parent may be a pool, so we need to get the process
parent = parent.processRef;
}
const flowElements = parent.get('flowElements');
collectionRemove(flowElements, shape.businessObject);
let references = findDataObjectReferences(flowElements, dataObject.id);
const references = findDataObjectReferences(flowElements, dataObject.id);
if (references.length === 0) {
let flowElements = dataObject.$parent.get('flowElements');
collectionRemove(flowElements, dataObject);
const dataFlowElements = dataObject.$parent.get('flowElements');
collectionRemove(dataFlowElements, dataObject);
}
}
});

View File

@ -31,7 +31,8 @@ export function DataObjectSelect(props) {
const setValue = value => {
const businessObject = element.businessObject;
for (const flowElem of businessObject.$parent.flowElements) {
const dataObjects = findDataObjects(businessObject.$parent)
for (const flowElem of dataObjects) {
if (flowElem.$type === 'bpmn:DataObject' && flowElem.id === value) {
commandStack.execute('element.updateModdleProperties', {
element,

View File

@ -0,0 +1,6 @@
import ErrorPropertiesProvider from './propertiesPanel/ErrorPropertiesProvider';
export default {
__init__: ['errorPropertiesProvider'],
errorPropertiesProvider: ['type', ErrorPropertiesProvider],
}

View File

@ -0,0 +1,49 @@
import { is } from 'bpmn-js/lib/util/ModelUtil';
import { getRoot } from '../../helpers';
import { getArrayForType, getListGroupForType } from '../../eventList.js';
import { hasEventType,
replaceGroup,
getSelectorForType,
getConfigureGroupForType
} from '../../eventSelect.js';
const LOW_PRIORITY = 500;
const eventDetails = {
'eventType': 'bpmn:Error',
'eventDefType': 'bpmn:ErrorEventDefinition',
'referenceType': 'errorRef',
'idPrefix': 'error',
};
export default function ErrorPropertiesProvider(
propertiesPanel,
translate,
moddle,
commandStack,
) {
this.getGroups = function (element) {
return function (groups) {
if (is(element, 'bpmn:Process') || is(element, 'bpmn:Collaboration')) {
const getErrorArray = getArrayForType('bpmn:Error', 'errorRef', 'Error');
const errorGroup = getListGroupForType('errors', 'Errors', getErrorArray);
groups.push(errorGroup({ element, translate, moddle, commandStack }));
} else if (hasEventType(element, 'bpmn:ErrorEventDefinition')) {
const getErrorSelector = getSelectorForType(eventDetails);
const errorGroup = getConfigureGroupForType(eventDetails, 'Error', true, getErrorSelector);
const group = errorGroup({ element, translate, moddle, commandStack });
replaceGroup('error', groups, group);
}
return groups;
};
};
propertiesPanel.registerProvider(LOW_PRIORITY, this);
}
ErrorPropertiesProvider.$inject = [
'propertiesPanel',
'translate',
'moddle',
'commandStack',
];

View File

@ -0,0 +1,6 @@
import EscalationPropertiesProvider from './propertiesPanel/EscalationPropertiesProvider';
export default {
__init__: ['escalationPropertiesProvider'],
escalationrrorPropertiesProvider: ['type', EscalationPropertiesProvider],
}

View File

@ -0,0 +1,49 @@
import { is } from 'bpmn-js/lib/util/ModelUtil';
import { getRoot } from '../../helpers';
import { getArrayForType, getListGroupForType } from '../../eventList.js';
import { hasEventType,
replaceGroup,
getSelectorForType,
getConfigureGroupForType
} from '../../eventSelect.js';
const LOW_PRIORITY = 500;
const eventDetails = {
'eventType': 'bpmn:Escalation',
'eventDefType': 'bpmn:EscalationEventDefinition',
'referenceType': 'escalationRef',
'idPrefix': 'escalation',
};
export default function EscalationPropertiesProvider(
propertiesPanel,
translate,
moddle,
commandStack,
) {
this.getGroups = function (element) {
return function (groups) {
if (is(element, 'bpmn:Process') || is(element, 'bpmn:Collaboration')) {
const getEscalationArray = getArrayForType('bpmn:Escalation', 'escalationRef', 'Escalation');
const escalationGroup = getListGroupForType('escalations', 'Escalations', getEscalationArray);
groups.push(escalationGroup({ element, translate, moddle, commandStack }));
} else if (hasEventType(element, 'bpmn:EscalationEventDefinition')) {
const getEscalationSelector = getSelectorForType(eventDetails);
const escalationGroup = getConfigureGroupForType(eventDetails, 'Escalation', true, getEscalationSelector);
const group = escalationGroup({ element, translate, moddle, commandStack });
replaceGroup('escalation', groups, group);
}
return groups;
};
};
propertiesPanel.registerProvider(LOW_PRIORITY, this);
}
EscalationPropertiesProvider.$inject = [
'propertiesPanel',
'translate',
'moddle',
'commandStack',
];

View File

@ -0,0 +1,163 @@
import { is } from 'bpmn-js/lib/util/ModelUtil';
import { useService } from 'bpmn-js-properties-panel';
import {
ListGroup,
TextFieldEntry,
isTextFieldEntryEdited
} from '@bpmn-io/properties-panel';
import { getRoot } from './helpers';
/* This function creates a list of a particular event type at the process level using the item list
* and add function provided by `getArray`.
*
* Usage:
* const getArray = getArrayForType('bpmn:Signal', 'signalRef', 'Signal');
* const signalGroup = createGroupForType('signals', 'Signals', getArray);
*/
function getListGroupForType(groupId, label, getArray) {
return function (props) {
const { element, translate, moddle, commandStack } = props;
const eventArray = {
id: groupId,
element,
label: label,
component: ListGroup,
...getArray({ element, moddle, commandStack, translate }),
};
if (eventArray.items) {
return eventArray;
}
}
}
function getArrayForType(itemType, referenceType, prefix) {
return function (props) {
const { element, moddle, commandStack, translate } = props;
const root = getRoot(element.businessObject);
const matching = root.rootElements ? root.rootElements.filter(elem => elem.$type === itemType) : [];
function removeModelReferences(flowElements, match) {
flowElements.map(elem => {
if (elem.eventDefinitions)
elem.eventDefinitions = elem.eventDefinitions.filter(def => def.get(referenceType) != match);
else if (elem.flowElements)
removeModelReferences(elem.flowElements, match);
});
}
function removeElementReferences(children, match) {
children.map(child => {
if (child.businessObject.eventDefinitions) {
const bo = child.businessObject;
bo.eventDefinitions = bo.eventDefinitions.filter(def => def.get(referenceType) != match);
commandStack.execute('element.updateProperties', {
element: child,
moddleElement: bo,
properties: {}
});
}
if (child.children)
removeElementReferences(child.children, match);
});
}
function removeFactory(item) {
return function (event) {
event.stopPropagation();
if (root.rootElements) {
root.rootElements = root.rootElements.filter(elem => elem != item);
// This updates visible elements
removeElementReferences(element.children, item);
// This handles everything else (eg collapsed subprocesses) but does not update the shapes
// I can't figure out how to do that
root.rootElements.filter(elem => elem.$type === 'bpmn:Process').map(
process => removeModelReferences(process.flowElements, item)
);
commandStack.execute('element.updateProperties', {
element,
properties: {},
});
}
}
}
const items = matching.map((item, idx) => {
const itemId = `${prefix}-${idx}`;
return {
id: itemId,
label: item.name,
entries: getItemEditor({
itemId,
element,
item,
commandStack,
translate,
}),
autoFocusEntry: itemId,
remove: removeFactory(item),
};
});
function add(event) {
event.stopPropagation();
const item = moddle.create(itemType);
item.id = moddle.ids.nextPrefixed(`${prefix}_`);
item.name = item.id;
if (root.rootElements)
root.rootElements.push(item);
commandStack.execute('element.updateProperties', {
element,
properties: {},
});
};
return { items, add };
}
}
function getItemEditor(props) {
const { itemId, element, item, commandStack, translate } = props;
return [
{
id: `${itemId}-name`,
component: ItemTextField,
item,
commandStack,
translate,
},
];
}
function ItemTextField(props) {
const { itemId, element, item, commandStack, translate } = props;
const debounce = useService('debounceInput');
const setValue = (value) => {
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: item,
properties: {
id: value,
name: value,
},
});
};
const getValue = () => { return item.id; }
return TextFieldEntry({
element,
id: `${itemId}-id-textField`,
label: translate('ID'),
getValue,
setValue,
debounce,
});
}
export { getArrayForType, getListGroupForType };

View File

@ -0,0 +1,251 @@
import { is, isAny } from 'bpmn-js/lib/util/ModelUtil';
import { useService } from 'bpmn-js-properties-panel';
import {
ListGroup,
TextFieldEntry,
TextAreaEntry,
SelectEntry,
isTextFieldEntryEdited
} from '@bpmn-io/properties-panel';
import { getRoot } from './helpers';
function hasEventType(element, eventType) {
const events = element.businessObject.eventDefinitions;
return events && events.filter(item => is(item, eventType)).length > 0;
}
function replaceGroup(groupId, groups, group) {
const idx = groups.map(g => g.id).indexOf(groupId);
if (idx > -1)
groups.splice(idx, 1, group);
else
groups.push(group);
group.shouldOpen = true;
}
function isCatchingEvent(element) {
return isAny(element, ['bpmn:StartEvent', 'bpmn:IntermediateCatchEvent', 'bpmn:BoundaryEvent']);
}
function isThrowingEvent(element) {
return isAny(element, ['bpmn:EndEvent', 'bpmn:IntermediateThrowEvent']);
}
function getConfigureGroupForType(eventDetails, label, includeCode, getSelect) {
const { eventType, eventDefType, referenceType, idPrefix } = eventDetails;
return function (props) {
const { element, translate, moddle, commandStack } = props;
const variableName = getTextFieldForExtension(eventDetails, 'Variable Name', 'The name of the variable to store the payload in', true);
const payloadDefinition = getTextFieldForExtension(eventDetails, 'Payload', 'The expression to create the payload with', false);
const entries = [
{
id: `${idPrefix}-select`,
element,
component: getSelect,
isEdited: isTextFieldEntryEdited,
moddle,
commandStack,
},
];
if (includeCode) {
const codeField = getCodeTextField(eventDetails, `${label} Code`);
entries.push({
id: `${idPrefix}-code`,
element,
component: codeField,
isEdited: isTextFieldEntryEdited,
moddle,
commandStack,
});
}
if (isCatchingEvent(element)) {
entries.push({
id: `${idPrefix}-variable`,
element,
component: variableName,
isEdited: isTextFieldEntryEdited,
moddle,
commandStack,
});
} else if (isThrowingEvent(element)) {
entries.push({
id: `${idPrefix}-payload`,
element,
component: payloadDefinition,
isEdited: isTextFieldEntryEdited,
moddle,
commandStack,
});
};
return {
id: `${idPrefix}-group`,
label: label,
entries,
}
}
}
function getSelectorForType(eventDetails) {
const { eventType, eventDefType, referenceType, idPrefix } = eventDetails;
return function (props) {
const { element, translate, moddle, commandStack } = props;
const debounce = useService('debounceInput');
const root = getRoot(element.businessObject);
const getValue = () => {
const eventDef = element.businessObject.eventDefinitions.find(v => v.$type == eventDefType);
return (eventDef && eventDef.get(referenceType)) ? eventDef.get(referenceType).id : '';
};
const setValue = (value) => {
const bpmnEvent = root.rootElements.find(e => e.id == value);
// not sure how to handle multiple event definitions
const eventDef = element.businessObject.eventDefinitions.find(v => v.$type == eventDefType);
// really not sure what to do here if one of these can't be found either
if (bpmnEvent && eventDef)
eventDef.set(referenceType, bpmnEvent);
commandStack.execute('element.updateProperties', {
element,
moddleElement: element.businessObject,
properties: {},
});
};
const getOptions = (val) => {
const matching = root.rootElements ? root.rootElements.filter(elem => elem.$type === eventType) : [];
const options = [];
matching.map(option => options.push({label: option.name, value: option.id}));
return options;
}
return SelectEntry({
id: `${idPrefix}-select`,
element,
description: 'Select item',
getValue,
setValue,
getOptions,
debounce,
});
}
}
function getTextFieldForExtension(eventDetails, label, description, catching) {
const { eventType, eventDefType, referenceType, idPrefix } = eventDetails;
return function (props) {
const { element, moddle, commandStack } = props;
const debounce = useService('debounceInput');
const translate = useService('translate');
const root = getRoot(element.businessObject);
const extensionName = (catching) ? 'spiffworkflow:variableName' : 'spiffworkflow:payloadExpression';
const getEvent = () => {
const eventDef = element.businessObject.eventDefinitions.find(v => v.$type == eventDefType);
const bpmnEvent = eventDef.get(referenceType);
return bpmnEvent;
};
const getValue = () => {
// I've put the variable name (and payload) on the event for consistency with messages.
// However, when I think about this, I wonder if it shouldn't be on the event definition.
// I think that's something we should address in the future.
// Creating a payload and defining access to it are both process-specific, and that's an argument for leaving
// it in the event definition
const bpmnEvent = getEvent();
if (bpmnEvent && bpmnEvent.extensionElements) {
const extension = bpmnEvent.extensionElements.get('values').find(ext => ext.$instanceOf(extensionName));
return (extension) ? extension.value : null;
}
}
const setValue = (value) => {
const bpmnEvent = getEvent();
if (bpmnEvent) {
if (!bpmnEvent.extensionElements)
bpmnEvent.extensionElements = moddle.create('bpmn:ExtensionElements');
const extensions = bpmnEvent.extensionElements.get('values');
const extension = extensions.find(ext => ext.$instanceOf(extensionName));
if (!extension) {
const newExt = moddle.create(extensionName);
newExt.value = value;
extensions.push(newExt);
} else
extension.value = value;
} // not sure what to do if the event hasn't been set
};
if (catching) {
return TextFieldEntry({
element,
id: `${idPrefix}-variable-name`,
description: description,
label: translate(label),
getValue,
setValue,
debounce,
});
} else {
return TextAreaEntry({
element,
id: `${idPrefix}-payload-expression`,
description: description,
label: translate(label),
getValue,
setValue,
debounce,
});
}
}
}
function getCodeTextField(eventDetails, label) {
const { eventType, eventDefType, referenceType, idPrefix } = eventDetails;
return function (props) {
const { element, moddle, commandStack } = props;
const translate = useService('translate');
const debounce = useService('debounceInput');
const attrName = `${idPrefix}Code`;
const getEvent = () => {
const eventDef = element.businessObject.eventDefinitions.find(v => v.$type == eventDefType);
const bpmnEvent = eventDef.get(referenceType);
return bpmnEvent;
};
const getValue = () => {
const bpmnEvent = getEvent();
return (bpmnEvent) ? bpmnEvent.get(attrName) : null;
};
const setValue = (value) => {
const bpmnEvent = getEvent();
if (bpmnEvent)
bpmnEvent.set(attrName, value);
};
return TextFieldEntry({
element,
id: `${idPrefix}-code-value`,
label: translate(label),
getValue,
setValue,
debounce,
});
}
}
export { hasEventType, getSelectorForType, getConfigureGroupForType, replaceGroup };

View File

@ -28,7 +28,6 @@ export function SpiffExtensionLaunchButton(props) {
const { commandStack, moddle } = props;
// Listen for a response, to update the script.
eventBus.once(listenEvent, (response) => {
console.log("Calling Update!")
setExtensionValue(element, name, response.value, moddle, commandStack);
});
}

View File

@ -40,7 +40,6 @@ export function SpiffExtensionSelect(props) {
};
const setValue = (value) => {
console.log(`Set Value called with ${ value}`);
setExtensionValue(element, name, value, moddle, commandStack);
};

View File

@ -12,3 +12,27 @@ export function removeExtensionElementsIfEmpty(moddleElement) {
moddleElement.extensionElements = null;
}
}
/**
* loops up until it can find the root.
* @param element
*/
export function getRoot(businessObject, moddle) {
// HACK: get the root element. need a more formal way to do this
if (moddle) {
for (const elementId in moddle.ids._seed.hats) {
if (elementId.startsWith('Definitions_')) {
return moddle.ids._seed.hats[elementId];
}
}
} else {
// todo: Do we want businessObject to be a shape or moddle object?
if (businessObject.$type === 'bpmn:Definitions') {
return businessObject;
}
if (typeof businessObject.$parent !== 'undefined') {
return getRoot(businessObject.$parent);
}
}
return businessObject;
}

View File

@ -9,7 +9,12 @@ import DataObjectPropertiesProvider from './DataObject/propertiesPanel/DataObjec
import ConditionsPropertiesProvider from './conditions/propertiesPanel/ConditionsPropertiesProvider';
import ExtensionsPropertiesProvider from './extensions/propertiesPanel/ExtensionsPropertiesProvider';
import MessagesPropertiesProvider from './messages/propertiesPanel/MessagesPropertiesProvider';
import SignalPropertiesProvider from './signals/propertiesPanel/SignalPropertiesProvider';
import ErrorPropertiesProvider from './errors/propertiesPanel/ErrorPropertiesProvider';
import EscalationPropertiesProvider from './escalations/propertiesPanel/EscalationPropertiesProvider';
import CallActivityPropertiesProvider from './callActivity/propertiesPanel/CallActivityPropertiesProvider';
import StandardLoopPropertiesProvider from './loops/propertiesPanel/StandardLoopPropertiesProvider';
import MultiInstancePropertiesProvider from './loops/propertiesPanel/MultiInstancePropertiesProvider';
export default {
__depends__: [RulesModule],
@ -20,11 +25,16 @@ export default {
'conditionsPropertiesProvider',
'extensionsPropertiesProvider',
'messagesPropertiesProvider',
'signalPropertiesProvider',
'errorPropertiesProvider',
'escalationPropertiesProvider',
'callActivityPropertiesProvider',
'ioPalette',
'ioRules',
'ioInterceptor',
'dataObjectRenderer',
'multiInstancePropertiesProvider',
'standardLoopPropertiesProvider',
],
dataObjectInterceptor: ['type', DataObjectInterceptor],
dataObjectRules: ['type', DataObjectRules],
@ -32,9 +42,14 @@ export default {
dataObjectPropertiesProvider: ['type', DataObjectPropertiesProvider],
conditionsPropertiesProvider: ['type', ConditionsPropertiesProvider],
extensionsPropertiesProvider: ['type', ExtensionsPropertiesProvider],
signalPropertiesProvider: ['type', SignalPropertiesProvider],
errorPropertiesProvider: ['type', ErrorPropertiesProvider],
escalationPropertiesProvider: ['type', EscalationPropertiesProvider],
messagesPropertiesProvider: ['type', MessagesPropertiesProvider],
callActivityPropertiesProvider: ['type', CallActivityPropertiesProvider],
ioPalette: ['type', IoPalette],
ioRules: ['type', IoRules],
ioInterceptor: ['type', IoInterceptor],
multiInstancePropertiesProvider: ['type', MultiInstancePropertiesProvider],
standardLoopPropertiesProvider: ['type', StandardLoopPropertiesProvider],
};

View File

@ -0,0 +1,30 @@
export function getLoopProperty(element, propertyName) {
const loopCharacteristics = element.businessObject.loopCharacteristics;
const prop = loopCharacteristics.get(propertyName);
let value = '';
if (typeof(prop) !== 'object') {
value = prop;
} else if (typeof(prop) !== 'undefined') {
if (prop.$type === 'bpmn:FormalExpression')
value = prop.get('body');
else
value = prop.get('id');
}
return value;
}
export function setLoopProperty(element, propertyName, value, commandStack) {
const loopCharacteristics = element.businessObject.loopCharacteristics;
if (typeof(value) === 'object')
value.$parent = loopCharacteristics;
let properties = { [propertyName]: value };
if (propertyName === 'loopCardinality') properties['loopDataInputRef'] = undefined;
if (propertyName === 'loopDataInputRef') properties['loopCardinality'] = undefined;
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: loopCharacteristics,
properties: properties,
});
}

View File

@ -0,0 +1,221 @@
import { is } from 'bpmn-js/lib/util/ModelUtil';
import { useService } from 'bpmn-js-properties-panel';
import { TextFieldEntry, isTextFieldEntryEdited } from '@bpmn-io/properties-panel';
import { getLoopProperty, setLoopProperty } from './LoopProperty';
const LOW_PRIORITY = 500;
export default function MultiInstancePropertiesProvider(propertiesPanel) {
this.getGroups = function getGroupsCallback(element) {
return function pushGroup(groups) {
if (is(element, 'bpmn:Task') || is(element, 'bpmn:CallActivity') || is(element, 'bpmn:SubProcess')) {
let group = groups.filter(g => g.id == 'multiInstance');
if (group.length == 1)
updateMultiInstanceGroup(element, group[0]);
}
return groups;
};
};
propertiesPanel.registerProvider(LOW_PRIORITY, this);
}
MultiInstancePropertiesProvider.$inject = ['propertiesPanel'];
function updateMultiInstanceGroup(element, group) {
group.entries = MultiInstanceProps({element});
group.shouldOpen = true;
}
function MultiInstanceProps(props) {
const { element } = props;
const entries = [{
id: 'loopCardinality',
component: LoopCardinality,
isEdited: isTextFieldEntryEdited
}, {
id: 'loopDataInputRef',
component: InputCollection,
isEdited: isTextFieldEntryEdited
}, {
id: 'dataInputItem',
component: InputItem,
isEdited: isTextFieldEntryEdited
}, {
id: 'loopDataOutputRef',
component: OutputCollection,
isEdited: isTextFieldEntryEdited
}, {
id: 'dataOutputItem',
component: OutputItem,
isEdited: isTextFieldEntryEdited
}, {
id: 'completionCondition',
component: CompletionCondition,
isEdited: isTextFieldEntryEdited
}];
return entries;
}
function LoopCardinality(props) {
const { element } = props;
const debounce = useService('debounceInput');
const translate = useService('translate');
const commandStack = useService('commandStack');
const bpmnFactory = useService('bpmnFactory');
const getValue = () => {
return getLoopProperty(element, 'loopCardinality');
};
const setValue = value => {
const loopCardinality = bpmnFactory.create('bpmn:FormalExpression', {body: value})
setLoopProperty(element, 'loopCardinality', loopCardinality, commandStack);
};
return TextFieldEntry({
element,
id: 'loopCardinality',
label: translate('Loop Cardinality'),
getValue,
setValue,
debounce,
description: 'Explicitly set the number of instances'
});
}
function InputCollection(props) {
const { element } = props;
const debounce = useService('debounceInput');
const translate = useService('translate');
const commandStack = useService('commandStack');
const bpmnFactory = useService('bpmnFactory');
const getValue = () => {
return getLoopProperty(element, 'loopDataInputRef');
};
const setValue = value => {
const collection = bpmnFactory.create('bpmn:ItemAwareElement', {id: value});
setLoopProperty(element, 'loopDataInputRef', collection, commandStack);
};
return TextFieldEntry({
element,
id: 'loopDataInputRef',
label: translate('Input Collection'),
getValue,
setValue,
debounce,
description: 'Create an instance for each item in this collection'
});
}
function InputItem(props) {
const { element } = props;
const debounce = useService('debounceInput');
const translate = useService('translate');
const commandStack = useService('commandStack');
const bpmnFactory = useService('bpmnFactory');
const getValue = () => {
return getLoopProperty(element, 'inputDataItem');
};
const setValue = value => {
const item = (typeof(value) !== 'undefined') ? bpmnFactory.create('bpmn:DataInput', {id: value, name: value}) : undefined;
setLoopProperty(element, 'inputDataItem', item, commandStack);
};
return TextFieldEntry({
element,
id: 'inputDataItem',
label: translate('Input Element'),
getValue,
setValue,
debounce,
description: 'Each item in the collection will be copied to this variable'
});
}
function OutputCollection(props) {
const { element } = props;
const debounce = useService('debounceInput');
const translate = useService('translate');
const commandStack = useService('commandStack');
const bpmnFactory = useService('bpmnFactory');
const getValue = () => {
return getLoopProperty(element, 'loopDataOutputRef');
};
const setValue = value => {
const collection = bpmnFactory.create('bpmn:ItemAwareElement', {id: value});
setLoopProperty(element, 'loopDataOutputRef', collection, commandStack);
};
return TextFieldEntry({
element,
id: 'loopDataOutputRef',
label: translate('Output Collection'),
getValue,
setValue,
debounce,
description: 'Create or update this collection with the instance results'
});
}
function OutputItem(props) {
const { element } = props;
const debounce = useService('debounceInput');
const translate = useService('translate');
const commandStack = useService('commandStack');
const bpmnFactory = useService('bpmnFactory');
const getValue = () => {
return getLoopProperty(element, 'outputDataItem');
};
const setValue = value => {
const item = (typeof(value) !== 'undefined') ? bpmnFactory.create('bpmn:DataOutput', {id: value, name: value}) : undefined;
setLoopProperty(element, 'outputDataItem', item, commandStack);
};
return TextFieldEntry({
element,
id: 'outputDataItem',
label: translate('Output Element'),
getValue,
setValue,
debounce,
description: 'The value of this variable will be added to the output collection'
});
}
function CompletionCondition(props) {
const { element } = props;
const debounce = useService('debounceInput');
const translate = useService('translate');
const commandStack = useService('commandStack');
const bpmnFactory = useService('bpmnFactory');
const getValue = () => {
return getLoopProperty(element, 'completionCondition');
};
const setValue = value => {
const completionCondition = bpmnFactory.create('bpmn:FormalExpression', {body: value})
setLoopProperty(element, 'completionCondition', completionCondition, commandStack);
};
return TextFieldEntry({
element,
id: 'completionCondition',
label: translate('Completion Condition'),
getValue,
setValue,
debounce,
description: 'Stop executing this task when this condition is met'
});
}

View File

@ -0,0 +1,133 @@
import { is } from 'bpmn-js/lib/util/ModelUtil';
import { useService } from 'bpmn-js-properties-panel';
import {
Group,
TextFieldEntry,
isTextFieldEntryEdited,
CheckboxEntry,
isCheckboxEntryEdited,
} from '@bpmn-io/properties-panel';
import { getLoopProperty, setLoopProperty } from './LoopProperty';
const LOW_PRIORITY = 500;
export default function StandardLoopPropertiesProvider(propertiesPanel) {
this.getGroups = function getGroupsCallback(element) {
return function pushGroup(groups) {
if (
(is(element, 'bpmn:Task') || is(element, 'bpmn:CallActivity') || is(element, 'bpmn:SubProcess')) &&
typeof(element.businessObject.loopCharacteristics) !== 'undefined' &&
element.businessObject.loopCharacteristics.$type === 'bpmn:StandardLoopCharacteristics'
) {
const group = {
id: 'standardLoopCharacteristics',
component: Group,
label: 'Standard Loop',
entries: StandardLoopProps(element),
shouldOpen: true,
};
if (groups.length < 3)
groups.push(group);
else
groups.splice(2, 0, group);
}
return groups;
};
};
propertiesPanel.registerProvider(LOW_PRIORITY, this);
}
StandardLoopPropertiesProvider.$inject = ['propertiesPanel'];
function StandardLoopProps(props) {
const { element } = props;
return [{
id: 'loopMaximum',
component: LoopMaximum,
isEdited: isTextFieldEntryEdited
}, {
id: 'loopCondition',
component: LoopCondition,
isEdited: isTextFieldEntryEdited
}, {
id: 'testBefore',
component: TestBefore,
isEdited: isCheckboxEntryEdited
}];
}
function LoopMaximum(props) {
const { element } = props;
const debounce = useService('debounceInput');
const translate = useService('translate');
const commandStack = useService('commandStack');
const bpmnFactory = useService('bpmnFactory');
const getValue = () => {
return getLoopProperty(element, 'loopMaximum');
};
const setValue = value => {
setLoopProperty(element, 'loopMaximum', value, commandStack);
};
return TextFieldEntry({
element,
id: 'loopMaximum',
label: translate('Loop Maximum'),
getValue,
setValue,
debounce
});
}
function TestBefore(props) {
const { element } = props;
const debounce = useService('debounceInput');
const translate = useService('translate');
const commandStack = useService('commandStack');
const bpmnFactory = useService('bpmnFactory');
const getValue = () => {
return getLoopProperty(element, 'testBefore');
};
const setValue = value => {
setLoopProperty(element, 'testBefore', value, commandStack);
};
return CheckboxEntry({
element,
id: 'testBefore',
label: translate('Test Before'),
getValue,
setValue,
});
}
function LoopCondition(props) {
const { element } = props;
const debounce = useService('debounceInput');
const translate = useService('translate');
const commandStack = useService('commandStack');
const bpmnFactory = useService('bpmnFactory');
const getValue = () => {
return getLoopProperty(element, 'loopCondition');
};
const setValue = value => {
const loopCondition = bpmnFactory.create('bpmn:FormalExpression', {body: value})
setLoopProperty(element, 'loopCondition', loopCondition, commandStack);
};
return TextFieldEntry({
element,
id: 'loopCondition',
label: translate('Loop Condition'),
getValue,
setValue,
debounce
});
}

View File

@ -148,14 +148,24 @@ function getRetrievalExpressionFromCorrelationProperty(
export function findCorrelationProperties(businessObject, moddle) {
const root = getRoot(businessObject, moddle);
const correlationProperties = [];
for (const rootElement of root.rootElements) {
if (rootElement.$type === 'bpmn:CorrelationProperty') {
correlationProperties.push(rootElement);
if (isIterable(root.rootElements)) {
for (const rootElement of root.rootElements) {
if (rootElement.$type === 'bpmn:CorrelationProperty') {
correlationProperties.push(rootElement);
}
}
}
return correlationProperties;
}
function isIterable(obj) {
// checks for null and undefined
if (obj == null) {
return false;
}
return typeof obj[Symbol.iterator] === 'function';
}
export function findCorrelationKeys(businessObject, moddle) {
const root = getRoot(businessObject, moddle);
const correlationKeys = [];

View File

@ -35,6 +35,9 @@ export function MessageSelect(props) {
commandStack.execute('element.updateModdleProperties', {
element: shapeElement,
moddleElement: businessObject,
properties: {
messageRef: message,
},
});
} else if (
businessObject.$type === 'bpmn:ReceiveTask' ||

View File

@ -219,6 +219,28 @@
"type": "string"
}
]
},
{
"name": "payloadExpression",
"superClass": [ "Element" ],
"properties": [
{
"name": "value",
"isBody": true,
"type": "String"
}
]
},
{
"name": "variableName",
"superClass": [ "Element" ],
"properties": [
{
"name": "value",
"isBody": true,
"type": "String"
}
]
}
]
}

View File

@ -0,0 +1,6 @@
import SignalPropertiesProvider from './propertiesPanel/SignalPropertiesProvider';
export default {
__init__: ['signalPropertiesProvider'],
signalPropertiesProvider: ['type', SignalPropertiesProvider],
}

View File

@ -0,0 +1,49 @@
import { is } from 'bpmn-js/lib/util/ModelUtil';
import { getRoot } from '../../helpers';
import { getArrayForType, getListGroupForType } from '../../eventList.js';
import { hasEventType,
replaceGroup,
getSelectorForType,
getConfigureGroupForType
} from '../../eventSelect.js';
const LOW_PRIORITY = 500;
const eventDetails = {
'eventType': 'bpmn:Signal',
'eventDefType': 'bpmn:SignalEventDefinition',
'referenceType': 'signalRef',
'idPrefix': 'signal',
};
export default function SignalPropertiesProvider(
propertiesPanel,
translate,
moddle,
commandStack,
) {
this.getGroups = function (element) {
return function (groups) {
if (is(element, 'bpmn:Process') || is(element, 'bpmn:Collaboration')) {
const getSignalArray = getArrayForType('bpmn:Signal', 'signalRef', 'Signal');
const signalGroup = getListGroupForType('signals', 'Signals', getSignalArray);
groups.push(signalGroup({ element, translate, moddle, commandStack }));
} else if (hasEventType(element, 'bpmn:SignalEventDefinition')) {
const getSignalSelector = getSelectorForType(eventDetails);
const signalGroup = getConfigureGroupForType(eventDetails, 'Signal', false, getSignalSelector);
const group = signalGroup({ element, translate, moddle, commandStack });
replaceGroup('signal', groups, group);
}
return groups;
};
};
propertiesPanel.registerProvider(LOW_PRIORITY, this);
}
SignalPropertiesProvider.$inject = [
'propertiesPanel',
'translate',
'moddle',
'commandStack',
];

View File

@ -0,0 +1,66 @@
import {
BpmnPropertiesPanelModule,
BpmnPropertiesProviderModule,
} from 'bpmn-js-properties-panel';
import TestContainer from 'mocha-test-container-support';
import {
bootstrapPropertiesPanel,
changeInput,
expectSelected,
findEntry,
findSelect,
} from './helpers';
import spiffModdleExtension from '../../app/spiffworkflow/moddle/spiffworkflow.json';
import DataObject from '../../app/spiffworkflow/DataObject';
describe('Properties Panel for Data Objects', function () {
const xml = require('./bpmn/data_objects_in_pools.bpmn').default;
let container;
beforeEach(function () {
container = TestContainer.get(this);
});
beforeEach(
bootstrapPropertiesPanel(xml, {
container,
debounceInput: false,
additionalModules: [
DataObject,
BpmnPropertiesPanelModule,
BpmnPropertiesProviderModule,
],
moddleExtensions: {
spiffworkflow: spiffModdleExtension,
},
})
);
it('should allow you to select other data objects within the same participant', async function () {
// IF - a data object reference is selected
const doREF = await expectSelected('pool1Do1_REF');
expect(doREF).to.exist;
// THEN - a select Data Object section should appear in the properties panel
const entry = findEntry('selectDataObject', container);
const selector = findSelect(entry);
changeInput(selector, 'pool1Do2');
// then this data reference object now references that data object.
const { businessObject } = doREF;
expect(businessObject.get('dataObjectRef').id).to.equal('pool1Do2');
});
it('should NOT allow you to select data objects within other participants', async function () {
// IF - a data object reference is selected
const doREF = await expectSelected('pool1Do1_REF');
expect(doREF).to.exist;
// THEN - a select Data Object section should appear in the properties panel but pool2Do1 should not be an option
const entry = findEntry('selectDataObject', container);
const selector = findSelect(entry);
expect(selector.length).to.equal(2);
expect(selector[0].value === 'pool1Do2');
expect(selector[1].value === 'pool1Do1');
});
});

View File

@ -6,9 +6,6 @@ import {
import { BpmnPropertiesPanelModule, BpmnPropertiesProviderModule } from 'bpmn-js-properties-panel';
import spiffModdleExtension from '../../app/spiffworkflow/moddle/spiffworkflow.json';
import TestContainer from 'mocha-test-container-support';
import DataObjectPropertiesProvider
from '../../app/spiffworkflow/DataObject/propertiesPanel/DataObjectPropertiesProvider';
import spiffworkflow from '../../app/spiffworkflow';
import DataObject from '../../app/spiffworkflow/DataObject';
describe('Properties Panel for Data Objects', function() {
@ -78,4 +75,17 @@ describe('Properties Panel for Data Objects', function() {
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() {
// 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, 'MyObject1');
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('MyObject1');
expect(my_data_ref_1.businessObject.name).to.equal('My Object 1');
});
});

View File

@ -0,0 +1,44 @@
<?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:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_19o7vxg" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.0.0" modeler:executionPlatform="Camunda Platform" modeler:executionPlatformVersion="7.17.0">
<bpmn:collaboration id="Collaboration_0jmlu5k">
<bpmn:participant id="Participant_11j98s8" processRef="ProcessTest" />
<bpmn:participant id="Participant_1d4d2vr" processRef="Process_1y9rx5q" />
</bpmn:collaboration>
<bpmn:process id="ProcessTest" name="Process Test" isExecutable="true">
<bpmn:dataObjectReference id="pool1Do2_REF" name="Pool 1 Do 2" dataObjectRef="pool1Do2" />
<bpmn:dataObject id="pool1Do2" />
<bpmn:dataObject id="pool1Do1" />
<bpmn:dataObjectReference id="pool1Do2_REF" name="Pool 1 Do 2" dataObjectRef="pool1Do2" />
<bpmn:dataObjectReference id="pool1Do1_REF" name="Pool 1 Do 1" dataObjectRef="pool1Do1" />
</bpmn:process>
<bpmn:process id="Process_1y9rx5q">
<bpmn:dataObject id="Pool2do" />
<bpmn:dataObjectReference id="pool2Do2" name="Pool 2 Do" dataObjectRef="Pool2do" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Collaboration_0jmlu5k">
<bpmndi:BPMNShape id="Participant_11j98s8_di" bpmnElement="Participant_11j98s8" isHorizontal="true">
<dc:Bounds x="290" y="270" width="300" height="140" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="pool1Do2_REF_di" bpmnElement="pool1Do2_REF">
<dc:Bounds x="462" y="295" width="36" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="453" y="352" width="58" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="pool1Do1_REF_di" bpmnElement="pool1Do1_REF">
<dc:Bounds x="372" y="295" width="36" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="365" y="352" width="58" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Participant_1d4d2vr_di" bpmnElement="Participant_1d4d2vr" isHorizontal="true">
<dc:Bounds x="290" y="430" width="390" height="130" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="DataObjectReference_18xfjvh_di" bpmnElement="DataObjectReference_18xfjvh">
<dc:Bounds x="372" y="465" width="36" height="50" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

File diff suppressed because it is too large Load Diff