Merge pull request #26 from sartography/feature/inherited-data-objects

Feature/inherited data objects
This commit is contained in:
Dan Funk 2023-03-03 14:30:00 -05:00 committed by GitHub
commit c4843c17b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 137 additions and 59 deletions

View File

@ -3,7 +3,7 @@ import {
BpmnPropertiesPanelModule,
BpmnPropertiesProviderModule,
} 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 setupFileOperations from './fileOperations';

View File

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

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 {
findDataObjects,
findDataReferenceShapes,
findDataObjectReferences,
idToHumanReadableName,
} from './DataObjectHelpers';
const HIGH_PRIORITY = 1500;
/**
* This Command Interceptor functions like the BpmnUpdator in BPMN.js - It hooks into events
* 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.
*/
export default class DataObjectInterceptor extends CommandInterceptor {
constructor(eventBus, bpmnFactory, commandStack) {
constructor(eventBus, bpmnFactory, commandStack, bpmnUpdater) {
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 ...
* Prevent this from calling the CreateDataObjectBehavior in BPMN-js, as it will
@ -52,9 +84,9 @@ export default class DataObjectInterceptor extends CommandInterceptor {
} else {
dataObject = bpmnFactory.create('bpmn:DataObject');
}
// set the reference to the 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) {
const { context } = event;
const { shape, oldParent } = context;
const { shape } = context;
if (is(shape, 'bpmn:DataObjectReference') && shape.type !== 'label') {
const references = findDataReferenceShapes(
oldParent,
shape.businessObject.dataObjectRef.id
);
const dataObject = shape.businessObject.dataObjectRef;
let flowElements = shape.businessObject.$parent.get('flowElements');
collectionRemove(flowElements, shape.businessObject);
let references = findDataObjectReferences(flowElements, dataObject.id);
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';
import { without } from 'min-dash';
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.
@ -20,7 +24,7 @@ export function DataObjectArray(props) {
let 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;
} else if (element.businessObject.processRef) {
process = element.businessObject.processRef;
@ -53,6 +57,7 @@ export function DataObjectArray(props) {
const newDataObject = moddle.create('bpmn:DataObject');
const newElements = process.get('flowElements');
newDataObject.id = moddle.ids.nextPrefixed('DataObject_');
newDataObject.$parent = process;
newElements.push(newDataObject);
commandStack.execute('element.updateModdleProperties', {
element,
@ -79,7 +84,7 @@ function removeFactory(props) {
},
});
// 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) {
commandStack.execute('shape.delete', { shape: ref });
}
@ -116,7 +121,7 @@ function DataObjectTextField(props) {
});
// 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) {
commandStack.execute('element.updateProperties', {
element: ref,

View File

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

View File

@ -4,7 +4,11 @@ import { BpmnPropertiesPanelModule, BpmnPropertiesProviderModule } from 'bpmn-js
import {
inject,
} 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() {
@ -113,4 +117,52 @@ 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) {
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);
}));
});