Squashed 'bpmn-js-spiffworkflow/' changes from a547888ef..c4843c17b

c4843c17b Merge pull request #26 from sartography/feature/inherited-data-objects
9e2a8f384 minor tweak for adding data objects to lanes/participants.
ada919e59 add a few tests on data object visibility
627e771d4 allow subprocesses to inherit data objects
887f318f7 Minor cleanup of display in bpmn-js

git-subtree-dir: bpmn-js-spiffworkflow
git-subtree-split: c4843c17b869d1c730494dc10ddb31d761e2ac40
This commit is contained in:
Dan 2023-03-03 14:31:23 -05:00
parent 3add069267
commit 1be006811b
8 changed files with 139 additions and 61 deletions

View File

@ -3,7 +3,7 @@ import {
BpmnPropertiesPanelModule, BpmnPropertiesPanelModule,
BpmnPropertiesProviderModule, BpmnPropertiesProviderModule,
} from 'bpmn-js-properties-panel'; } from 'bpmn-js-properties-panel';
import diagramXML from '../test/spec/bpmn/basic_message.bpmn'; import diagramXML from '../test/spec/bpmn/empty_diagram.bpmn';
import spiffworkflow from './spiffworkflow'; import spiffworkflow from './spiffworkflow';
import setupFileOperations from './fileOperations'; import setupFileOperations from './fileOperations';

View File

@ -4,9 +4,9 @@
* @param container * @param container
*/ */
export function findDataObjects(parent, dataObjects) {
export function findDataObjects(parent) { if (typeof(dataObjects) === 'undefined')
let dataObjects = []; dataObjects = [];
let process; let process;
if (!parent) { if (!parent) {
return []; return [];
@ -15,15 +15,12 @@ export function findDataObjects(parent) {
process = parent.processRef; process = parent.processRef;
} else { } else {
process = parent; process = parent;
if (process.$type === 'bpmn:SubProcess')
findDataObjects(process.$parent, dataObjects);
} }
if (!process.flowElements) { if (typeof(process.flowElements) !== 'undefined') {
return [];
}
for (const element of process.flowElements) { for (const element of process.flowElements) {
if ( if (element.$type === 'bpmn:DataObject')
element.$type === 'bpmn:DataObject' &&
dataObjects.indexOf(element) < 0
) {
dataObjects.push(element); dataObjects.push(element);
} }
} }
@ -38,16 +35,26 @@ export function findDataObject(process, id) {
} }
} }
export function findDataReferenceShapes(processShape, id) { export function findDataObjectReferences(children, dataObjectId) {
let refs = []; return children.flatMap((child) => {
for (const shape of processShape.children) { if (child.$type == 'bpmn:DataObjectReference' && child.dataObjectRef.id == dataObjectId)
if (shape.type === 'bpmn:DataObjectReference') { return [child];
if (shape.businessObject.dataObjectRef && shape.businessObject.dataObjectRef.id === id) { else if (child.$type == 'bpmn:SubProcess')
refs.push(shape); return findDataObjectReferences(child.get('flowElements'), dataObjectId);
else
return [];
});
} }
}
} export function findDataObjectReferenceShapes(children, dataObjectId) {
return refs; return children.flatMap((child) => {
if (child.type == 'bpmn:DataObjectReference' && child.businessObject.dataObjectRef.id == dataObjectId)
return [child];
else if (child.type == 'bpmn:SubProcess')
return findDataObjectReferenceShapes(child.children, dataObjectId);
else
return [];
});
} }
export function idToHumanReadableName(id) { export function idToHumanReadableName(id) {

View File

@ -3,11 +3,12 @@ import { getDi, is } from 'bpmn-js/lib/util/ModelUtil';
import { remove as collectionRemove } from 'diagram-js/lib/util/Collections'; import { remove as collectionRemove } from 'diagram-js/lib/util/Collections';
import { import {
findDataObjects, findDataObjects,
findDataReferenceShapes, findDataObjectReferences,
idToHumanReadableName, idToHumanReadableName,
} from './DataObjectHelpers'; } from './DataObjectHelpers';
const HIGH_PRIORITY = 1500; const HIGH_PRIORITY = 1500;
/** /**
* This Command Interceptor functions like the BpmnUpdator in BPMN.js - It hooks into events * This Command Interceptor functions like the BpmnUpdator in BPMN.js - It hooks into events
* from Diagram.js and updates the underlying BPMN model accordingly. * from Diagram.js and updates the underlying BPMN model accordingly.
@ -20,9 +21,40 @@ const HIGH_PRIORITY = 1500;
* 4) Don't allow someone to move a DataObjectReference from one process to another process. * 4) Don't allow someone to move a DataObjectReference from one process to another process.
*/ */
export default class DataObjectInterceptor extends CommandInterceptor { export default class DataObjectInterceptor extends CommandInterceptor {
constructor(eventBus, bpmnFactory, commandStack) {
constructor(eventBus, bpmnFactory, commandStack, bpmnUpdater) {
super(eventBus); super(eventBus);
/* The default behavior is to move the data object into whatever object the reference is being created in.
* If a data object already has a parent, don't change it.
*/
bpmnUpdater.updateSemanticParent = (businessObject, parentBusinessObject) => {
// Special case for participant - which is a valid place to drop a data object, but it needs to be added
// to the particpant's Process (which isn't directly accessible in BPMN.io
let realParent = parentBusinessObject;
if (is(realParent, 'bpmn:Participant')) {
realParent = realParent.processRef;
}
if (is(businessObject, 'bpmn:DataObjectReference')) {
// For data object references, always update the flowElements when a parent is provided
// The parent could be null if it's being deleted, and I could probably handle that here instead of
// when the shape is deleted, but not interested in refactoring at the moment.
if (realParent != null) {
const flowElements = realParent.get('flowElements');
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.
if (typeof(businessObject.$parent) === 'undefined') {
const flowElements = realParent.get('flowElements');
flowElements.push(businessObject);
businessObject.$parent = realParent;
}
} else
bpmnUpdater.__proto__.updateSemanticParent.call(this, businessObject, parentBusinessObject);
};
/** /**
* For DataObjectReferences only ... * For DataObjectReferences only ...
* Prevent this from calling the CreateDataObjectBehavior in BPMN-js, as it will * Prevent this from calling the CreateDataObjectBehavior in BPMN-js, as it will
@ -52,9 +84,9 @@ export default class DataObjectInterceptor extends CommandInterceptor {
} else { } else {
dataObject = bpmnFactory.create('bpmn:DataObject'); dataObject = bpmnFactory.create('bpmn:DataObject');
} }
// set the reference to the DataObject // set the reference to the DataObject
shape.businessObject.dataObjectRef = dataObject; shape.businessObject.dataObjectRef = dataObject;
shape.businessObject.$parent = process;
} }
}); });
@ -84,38 +116,19 @@ export default class DataObjectInterceptor extends CommandInterceptor {
*/ */
this.executed(['shape.delete'], HIGH_PRIORITY, function (event) { this.executed(['shape.delete'], HIGH_PRIORITY, function (event) {
const { context } = event; const { context } = event;
const { shape, oldParent } = context; const { shape } = context;
if (is(shape, 'bpmn:DataObjectReference') && shape.type !== 'label') { if (is(shape, 'bpmn:DataObjectReference') && shape.type !== 'label') {
const references = findDataReferenceShapes( const dataObject = shape.businessObject.dataObjectRef;
oldParent, let flowElements = shape.businessObject.$parent.get('flowElements');
shape.businessObject.dataObjectRef.id collectionRemove(flowElements, shape.businessObject);
); let references = findDataObjectReferences(flowElements, dataObject.id);
if (references.length === 0) { if (references.length === 0) {
return; // Use the default bahavior and delete the data object. let flowElements = dataObject.$parent.get('flowElements');
collectionRemove(flowElements, dataObject);
} }
// Remove the business Object
let containment = '';
const { businessObject } = shape;
if (is(businessObject, 'bpmn:DataOutputAssociation')) {
containment = 'dataOutputAssociations';
}
if (is(businessObject, 'bpmn:DataInputAssociation')) {
containment = 'dataInputAssociations';
}
const children = businessObject.$parent.get(containment);
collectionRemove(children, businessObject);
// Remove the visible element.
const di = getDi(shape);
const planeElements = di.$parent.get('planeElement');
collectionRemove(planeElements, di);
di.$parent = null;
// Stop the propogation.
event.stopPropagation();
} }
}); });
} }
} }
DataObjectInterceptor.$inject = ['eventBus', 'bpmnFactory', 'commandStack']; DataObjectInterceptor.$inject = ['eventBus', 'bpmnFactory', 'commandStack', 'bpmnUpdater'];

View File

@ -5,7 +5,11 @@ import {
} from '@bpmn-io/properties-panel'; } from '@bpmn-io/properties-panel';
import { without } from 'min-dash'; import { without } from 'min-dash';
import { is } from 'bpmn-js/lib/util/ModelUtil'; import { is } from 'bpmn-js/lib/util/ModelUtil';
import {findDataObjects, findDataReferenceShapes, idToHumanReadableName} from '../DataObjectHelpers'; import {
findDataObjects,
findDataObjectReferenceShapes,
idToHumanReadableName,
} from '../DataObjectHelpers';
/** /**
* Provides a list of data objects, and allows you to add / remove data objects, and change their ids. * Provides a list of data objects, and allows you to add / remove data objects, and change their ids.
@ -20,7 +24,7 @@ export function DataObjectArray(props) {
let process; let process;
// This element might be a process, or something that will reference a process. // This element might be a process, or something that will reference a process.
if (is(element.businessObject, 'bpmn:Process')) { if (is(element.businessObject, 'bpmn:Process') || is(element.businessObject, 'bpmn:SubProcess')) {
process = element.businessObject; process = element.businessObject;
} else if (element.businessObject.processRef) { } else if (element.businessObject.processRef) {
process = element.businessObject.processRef; process = element.businessObject.processRef;
@ -53,6 +57,7 @@ export function DataObjectArray(props) {
const newDataObject = moddle.create('bpmn:DataObject'); const newDataObject = moddle.create('bpmn:DataObject');
const newElements = process.get('flowElements'); const newElements = process.get('flowElements');
newDataObject.id = moddle.ids.nextPrefixed('DataObject_'); newDataObject.id = moddle.ids.nextPrefixed('DataObject_');
newDataObject.$parent = process;
newElements.push(newDataObject); newElements.push(newDataObject);
commandStack.execute('element.updateModdleProperties', { commandStack.execute('element.updateModdleProperties', {
element, element,
@ -79,7 +84,7 @@ function removeFactory(props) {
}, },
}); });
// When a data object is removed, remove all references as well. // When a data object is removed, remove all references as well.
const references = findDataReferenceShapes(element, dataObject.id); const references = findDataObjectReferenceShapes(element.children, dataObject.id);
for (const ref of references) { for (const ref of references) {
commandStack.execute('shape.delete', { shape: ref }); commandStack.execute('shape.delete', { shape: ref });
} }
@ -116,7 +121,7 @@ function DataObjectTextField(props) {
}); });
// Also update the label of all the references // Also update the label of all the references
const references = findDataReferenceShapes(element, dataObject.id); const references = findDataObjectReferenceShapes(element.children, dataObject.id);
for (const ref of references) { for (const ref of references) {
commandStack.execute('element.updateProperties', { commandStack.execute('element.updateProperties', {
element: ref, element: ref,

View File

@ -20,7 +20,8 @@ export default function DataObjectPropertiesProvider(
); );
} }
if ( if (
isAny(element, ['bpmn:Process', 'bpmn:SubProcess', 'bpmn:Participant']) isAny(element, ['bpmn:Process', 'bpmn:Participant']) ||
(is(element, 'bpmn:SubProcess') && !element.collapsed)
) { ) {
groups.push( groups.push(
createDataObjectEditor( createDataObjectEditor(

View File

@ -37,7 +37,7 @@ export function CorrelationPropertiesArray(props) {
}); });
return { return {
id, id,
label: correlationPropertyModdleElement.id, label: correlationPropertyModdleElement.name,
entries, entries,
autoFocusEntry: id, autoFocusEntry: id,
remove: removeFactory({ remove: removeFactory({

View File

@ -44,7 +44,7 @@ export function MessageCorrelationPropertiesArray(props) {
}); });
return { return {
id, id,
label: correlationPropertyModdleElement.id, label: correlationPropertyModdleElement.name,
entries, entries,
autoFocusEntry: id, autoFocusEntry: id,
remove: removeFactory({ remove: removeFactory({

View File

@ -4,7 +4,11 @@ import { BpmnPropertiesPanelModule, BpmnPropertiesProviderModule } from 'bpmn-js
import { import {
inject, inject,
} from 'bpmn-js/test/helper'; } from 'bpmn-js/test/helper';
import {findDataObjects, idToHumanReadableName} from '../../app/spiffworkflow/DataObject/DataObjectHelpers'; import {
findDataObjects,
findDataObjectReferenceShapes,
idToHumanReadableName,
} from '../../app/spiffworkflow/DataObject/DataObjectHelpers';
describe('DataObject Interceptor', function() { describe('DataObject Interceptor', function() {
@ -113,4 +117,52 @@ describe('DataObject Interceptor', function() {
expect(dataObjects.length).to.equal(1); expect(dataObjects.length).to.equal(1);
})); }));
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;
let dataObjects = findDataObjects(subProcess);
expect(dataObjects.length).to.equal(0);
let rootShape = canvas.getRootElement();
const dataObjectRefShape = modeling.createShape({ type: 'bpmn:DataObjectReference' },
{ x: 220, y: 220 }, rootShape);
dataObjects = findDataObjects(subProcess);
expect(dataObjects.length).to.equal(1);
}));
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;
const dataObjectRefShape = modeling.createShape({ type: 'bpmn:DataObjectReference' },
{ x: 220, y: 220 }, subProcessShape);
let dataObjects = findDataObjects(subProcess);
expect(dataObjects.length).to.equal(1);
let rootShape = canvas.getRootElement();
dataObjects = findDataObjects(rootShape);
expect(dataObjects.length).to.equal(0);
}));
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' },
{ x: 220, y: 220 }, rootShape);
let subProcessShape = elementRegistry.get('my_subprocess');
let subProcess = subProcessShape.businessObject;
const refTwo = modeling.createShape({ type: 'bpmn:DataObjectReference' },
{ x: 320, y: 220 }, subProcessShape);
let dataObjects = findDataObjects(subProcess);
expect(dataObjects.length).to.equal(1);
let references = findDataObjectReferenceShapes(rootShape.children, dataObjects[0].id);
expect(references.length).to.equal(2);
}));
}); });