Ability to add and delete data objects (not just data object references)

Adding and removing these objects through the event bus / command stack to enable undo.
This commit is contained in:
Dan 2022-07-06 13:25:53 -04:00
parent 13a00d1762
commit a582fa6b55
8 changed files with 294 additions and 122 deletions

View File

@ -1,10 +1,12 @@
import scriptGroup, { SCRIPT_TYPE } from './parts/ScriptGroup';
import { is, isAny } from 'bpmn-js/lib/util/ModelUtil';
import dataReferenceGroup from './parts/DataReferenceGroup';
import { DataObjectSelect } from './parts/DataObjectSelect';
import { ListGroup, isTextFieldEntryEdited } from '@bpmn-io/properties-panel';
import {DataObjectArray} from './parts/DataObjectArray';
const LOW_PRIORITY = 500;
export default function SpiffWorkflowPropertiesProvider(propertiesPanel, translate, moddle) {
export default function SpiffWorkflowPropertiesProvider(propertiesPanel, translate, moddle, commandStack, elementRegistry) {
this.getGroups = function(element) {
return function(groups) {
if (is(element, 'bpmn:ScriptTask')) {
@ -13,8 +15,12 @@ export default function SpiffWorkflowPropertiesProvider(propertiesPanel, transla
groups.push(preScriptPostScriptGroup(element, translate, moddle));
}
if (is(element, 'bpmn:DataObjectReference')) {
groups.push(createDataObjectGroup(element, translate, moddle));
groups.push(createDataObjectSelector(element, translate, moddle));
}
if (is(element, 'bpmn:Process')) {
groups.push(createDataObjectEditor(element, translate, moddle, commandStack, elementRegistry));
}
return groups;
};
@ -22,7 +28,7 @@ export default function SpiffWorkflowPropertiesProvider(propertiesPanel, transla
propertiesPanel.registerProvider(LOW_PRIORITY, this);
}
SpiffWorkflowPropertiesProvider.$inject = [ 'propertiesPanel', 'translate', 'moddle' ];
SpiffWorkflowPropertiesProvider.$inject = [ 'propertiesPanel', 'translate', 'moddle', 'commandStack', 'elementRegistry' ];
/**
@ -31,7 +37,7 @@ SpiffWorkflowPropertiesProvider.$inject = [ 'propertiesPanel', 'translate', 'mod
* @param element
* @param translate
* @returns The components to add to the properties panel. */
function createScriptGroup(element, translate, moddle) {
function createScriptGroup(element, translate, moddle, commandStack) {
return {
id: 'spiff_script',
label: translate('SpiffWorkflow Properties'),
@ -66,6 +72,29 @@ function preScriptPostScriptGroup(element, translate, moddle) {
};
}
/**
* Create a group on the main panel with a select box (for choosing the Data Object to connect)
* @param element
* @param translate
* @param moddle
* @returns entries
*/
function createDataObjectSelector(element, translate, moddle) {
return {
id: 'data_object_properties',
label: translate('Data Object Properties'),
entries: [
{
id: 'selectDataObject',
element,
component: DataObjectSelect,
isEdited: isTextFieldEntryEdited,
moddle: moddle
}
]
};
}
/**
* Create a group on the main panel with a select box (for choosing the Data Object to connect) AND a
* full Data Object Array for modifying all the data objects.
@ -74,10 +103,16 @@ function preScriptPostScriptGroup(element, translate, moddle) {
* @param moddle
* @returns entries
*/
function createDataObjectGroup(element, translate, moddle) {
return {
id: 'data_object_properties',
label: translate('Data Object Properties'),
entries: dataReferenceGroup(element, moddle)
function createDataObjectEditor(element, translate, moddle, commandStack, elementRegistry) {
const dataObjectArray = {
id: 'editDataObjects',
element,
label: 'Data Objects',
component: ListGroup,
...DataObjectArray({ element, moddle, commandStack, elementRegistry })
};
if (dataObjectArray.items) {
return dataObjectArray;
}
}

View File

@ -1,6 +1,7 @@
import { useService } from 'bpmn-js-properties-panel';
import { getBusinessObject, is } from 'bpmn-js/lib/util/ModelUtil';
import { isTextFieldEntryEdited, TextFieldEntry } from '@bpmn-io/properties-panel';
import { remove as collectionRemove } from 'diagram-js/lib/util/Collections';
import { without } from 'min-dash';
/**
* Provides a list of data objects, and allows you to add / remove data objects, and change their ids.
@ -9,45 +10,87 @@ import { isTextFieldEntryEdited, TextFieldEntry } from '@bpmn-io/properties-pane
*/
export function DataObjectArray(props) {
const moddle = props.moddle;
const element = props.dataElement;
const process = getProcess(element);
const element = props.element;
const process = props.element.businessObject;
const commandStack = props.commandStack;
const elementRegistry = props.elementRegistry;
let dataObjects = findDataObjects(process);
const items = dataObjects.map((dataObject, index) => {
const id = process.id + '-dataObj-' + index;
return {
id: id,
label: dataObject.id,
entries:
DataObjectGroup({
idPrefix: id,
element,
dataObject
})
,
autoFocusEntry: id + '-dataObject',
remove: removeFactory({ element, dataObject, process, commandStack, elementRegistry })
};
});
function add(event) {
event.stopPropagation();
let newDo = moddle.create('bpmn:DataObject');
let newElements = process.get('flowElements');
newDo.id = moddle.ids.nextPrefixed('DataObject_');
newElements.push(newDo);
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: process,
properties: {
flowElements: newElements
}
});
}
return { items, add };
}
function removeFactory(props) {
const {
element,
dataObject,
process,
commandStack,
elementRegistry
} = props;
return function(event) {
event.stopPropagation();
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: process,
properties: {
flowElements: without(process.get('flowElements'), dataObject)
}
});
};
}
function findDataObjects(process) {
let dataObjects = [];
for (const element of process.flowElements) {
if (element.$type === 'bpmn:DataObject') {
dataObjects.push(element);
}
}
const items = dataObjects.map((dataObject, index) => {
const id = element.id + '-dataObjects-' + index;
return {
id: id,
label: dataObject.id,
entries: [
DataObjectGroup({
idPrefix: id,
element,
dataObject
})
],
autoFocusEntry: dataObject.id,
remove: remove
};
});
return dataObjects;
}
function add(event) {
// event.stopPropagation();
console.log('PLEASE ADD A NEW DATA OBJECT');
export function findDataObject(process, id) {
for (const dataObj of findDataObjects(process)) {
if (dataObj.id == id) {
return dataObj;
}
}
function remove(event) {
// event.stopPropagation();
console.log('PLEASE REMOVE A DATA OBJECT');
}
console.log("About to return the following items:", items);
return { items, add };
}
function DataObjectGroup(props) {
@ -57,54 +100,57 @@ function DataObjectGroup(props) {
dataObject
} = props;
let entries =
let entries = [
{
id: idPrefix + '-dataObject',
component: DataObjectTextField,
isEdited: isTextFieldEntryEdited,
idPrefix,
dataObject
};
console.log("Data Object Group", entries);
}
];
return entries;
}
function DataObjectTextField(props) {
console.log("The Text Field is Being Created");
const {
idPrefix,
element,
parameter
parameter,
dataObject
} = props;
const commandStack = useService('commandStack');
const translate = useService('translate');
const debounce = useService('debounceInput');
const setValue = (value) => {
console.log('set data object value ');
return commandStack.execute(
'element.updateModdleProperties',
{
element,
moddleElement: dataObject,
properties: {
'id': value
}
}
);
};
const getValue = (parameter) => {
console.log('get data object value ');
return dataObject.id;
};
return TextFieldEntry({
element: parameter,
id: idPrefix + '-name',
label: 'Data Object Name',
id: idPrefix + '-id',
label: 'Data Object Id',
getValue,
setValue,
debounce
});
}
function getProcess(element) {
let parent = element.parent;
return is(parent, 'bpmn:Process') ?
getBusinessObject(parent) :
getBusinessObject(parent).get('processRef');
}

View File

@ -1,35 +1,23 @@
import { ListGroup, isTextFieldEntryEdited } from '@bpmn-io/properties-panel';
import { DataObjectSelect } from './DataObjectSelect';
import { ListGroup } from '@bpmn-io/properties-panel';
import { DataObjectArray } from './DataObjectArray';
/**
* Allows you to associate the selected Data Reference to a
* data object. Many references can point to the same data object.
* Also allows you to select which Data Objects are available
* in the system overall.
* @param dataElement The selected Data Object Reference
* in the process element.
* @param element The selected process
* @param moddle For updating the underlying xml object
* @returns {[{component: (function(*)), isEdited: *, id: string, element},{component:
* (function(*)), isEdited: *, id: string, element}]}
*/
export default function(dataElement, moddle) {
export default function(element, moddle) {
const groupSections = [];
groupSections.push({
id: 'selectDataObject',
dataElement,
component: DataObjectSelect,
isEdited: isTextFieldEntryEdited,
moddle: moddle,
});
const dataObjectArray = {
id: 'editDataObjects',
dataElement,
element,
label: 'Available Data Objects',
component: ListGroup,
...DataObjectArray({ dataElement, moddle })
...DataObjectArray({ element, moddle })
};
console.log('The Data Objects Array is ', dataObjectArray);

View File

@ -1,6 +1,6 @@
<?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_19o7vxg" 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_1mzevep" isExecutable="true">
<bpmn:process id="ProcessTest" isExecutable="true">
<bpmn:ioSpecification>
<bpmn:dataInput id="num_dogs" name="Number of Dogs" />
<bpmn:dataOutput id="happy_index" name="Happiness Index" />
@ -9,7 +9,7 @@
<bpmn:outgoing>Flow_1mezzcx</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:endEvent id="Event_14wzv4j">
<bpmn:incoming>Flow_0q4oys2</bpmn:incoming>
<bpmn:incoming>Flow_0q4oys2</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_01jg677" sourceRef="Activity_15zz6ya" targetRef="my_script_task" />
<bpmn:sequenceFlow id="Flow_1mezzcx" sourceRef="StartEvent_1" targetRef="Activity_15zz6ya" />
@ -38,13 +38,7 @@
<bpmn:dataObjectReference id="my_data_ref_2" name="my_data_object" dataObjectRef="my_data_object" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1mzevep">
<bpmndi:BPMNShape id="dataInput_2" bpmnElement="happy_index">
<dc:Bounds x="602" y="85" width="36" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="580" y="142" width="83" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="ProcessTest">
<bpmndi:BPMNEdge id="Flow_0q4oys2_di" bpmnElement="Flow_0q4oys2">
<di:waypoint x="540" y="197" />
<di:waypoint x="602" y="197" />
@ -60,19 +54,13 @@
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="179" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_14wzv4j_di" bpmnElement="Event_14wzv4j">
<dc:Bounds x="602" y="179" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0t7iwfm_di" bpmnElement="Activity_15zz6ya">
<dc:Bounds x="280" y="157" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="dataInput_1" bpmnElement="num_dogs">
<dc:Bounds x="179" y="85" width="36" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="158" y="135" width="81" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_14wzv4j_di" bpmnElement="Event_14wzv4j">
<dc:Bounds x="602" y="179" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0h86vbv_di" bpmnElement="my_script_task">
<dc:Bounds x="440" y="157" width="100" height="80" />
</bpmndi:BPMNShape>
@ -88,6 +76,18 @@
<dc:Bounds x="464" y="332" width="33" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="dataInput_1" bpmnElement="num_dogs">
<dc:Bounds x="179" y="85" width="36" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="158" y="135" width="81" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="dataInput_2" bpmnElement="happy_index">
<dc:Bounds x="602" y="85" width="36" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="580" y="142" width="83" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="DataOutputAssociation_1uj5jzs_di" bpmnElement="DataOutputAssociation_1uj5jzs">
<di:waypoint x="329" y="237" />
<di:waypoint x="328" y="275" />
@ -98,4 +98,4 @@
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>
</bpmn:definitions>

View File

@ -9,7 +9,7 @@ import SpiffWorkflowPropertiesProvider from '../../app/spiffworkflow/PropertiesP
import spiffModdleExtension from '../../app/spiffworkflow/moddle/spiffworkflow.json';
import TestContainer from 'mocha-test-container-support';
describe('Properties Panel Script Tasks', function() {
describe('Properties Panel for Data Objects', function() {
let xml = require('./diagram.bpmn').default;
let container;
@ -46,22 +46,6 @@ describe('Properties Panel Script Tasks', function() {
expect(selector.length).to.equal(3);
});
it('should allow you to edit the data objects', async function() {
// IF - a data object reference is selected
let my_data_ref_1 = await expectSelected('my_data_ref_1');
// THEN - an edit Data Objects group section should appear in the properties panel
let entry = findGroupEntry('editDataObjects', container);
expect(entry).to.exist;
// And it should contain three items in the group.
});
it('selecting a data object should change the data model.', async function() {
// IF - a data object reference is selected

View File

@ -0,0 +1,95 @@
import {
query as domQuery,
} from 'min-dom';
import {
bootstrapPropertiesPanel, changeInput,
expectSelected, findDataObject, findDataObjects,
findEntry, findGroupEntry, findInput
} from './helpers';
import { BpmnPropertiesPanelModule, BpmnPropertiesProviderModule } from 'bpmn-js-properties-panel';
import SpiffWorkflowPropertiesProvider from '../../app/spiffworkflow/PropertiesPanel';
import spiffModdleExtension from '../../app/spiffworkflow/moddle/spiffworkflow.json';
import TestContainer from 'mocha-test-container-support';
import { fireEvent } from '@testing-library/preact';
describe('Properties Panel for a Process', function() {
let xml = require('./diagram.bpmn').default;
let container;
beforeEach(function() {
container = TestContainer.get(this);
});
beforeEach(bootstrapPropertiesPanel(xml, {
container,
debounceInput: false,
additionalModules: [
SpiffWorkflowPropertiesProvider,
BpmnPropertiesPanelModule,
BpmnPropertiesProviderModule,
],
moddleExtensions: {
spiffworkflow: spiffModdleExtension
},
}));
it('should allow you to edit the data objects', async function() {
// IF - a process is selected
await expectSelected('ProcessTest');
// THEN - there is a section where you can modify data objects.
let entry = findGroupEntry('editDataObjects', container);
expect(entry).to.exist;
});
it('should be possible to change a data objects id', async function() {
// IF - a process is selected and the id of a data object is changed
const process_svg = await expectSelected('ProcessTest');
let newId = 'a_brand_new_id';
// ID here is [process id]-dataObj-[data object index]-id
let myDataObjEntry = findEntry('ProcessTest-dataObj-0-id');
let textBox = findInput('text', myDataObjEntry);
changeInput(textBox, newId);
// THEN - there is a section where you can modify data objects.
let dataObject = findDataObject(process_svg.businessObject, newId);
expect(dataObject.id).to.equal(newId);
});
it('should be possible to remove a data object', async function() {
// IF - a process is selected and the delete button is clicked.
const process_svg = await expectSelected('ProcessTest');
const data_id = 'my_data_object';
let dataObject = findDataObject(process_svg.businessObject, data_id);
expect(dataObject.id).to.exist;
let myDataObjEntry = findEntry('ProcessTest-dataObj-my_data_object');
let deleteButton = domQuery('.bio-properties-panel-remove-entry', myDataObjEntry);
fireEvent.click(deleteButton);
// THEN - there should not be a 'my_data_object' anymore.
dataObject = findDataObject(process_svg.businessObject, data_id);
expect(dataObject).to.not.exist;
});
it('should be possible to add a data object', async function() {
// IF - a process is selected and the add button is clicked.
const process_svg = await expectSelected('ProcessTest');
let entry = findGroupEntry('editDataObjects', container);
let addButton = domQuery('.bio-properties-panel-add-entry', entry);
fireEvent.click(addButton);
// THEN - there should now be 4 data objects instead of just 3.
expect(findDataObjects(process_svg.businessObject).length).to.equal(4);
});
});

View File

@ -1,6 +1,6 @@
<?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_19o7vxg" 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_1mzevep" isExecutable="true">
<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_19o7vxg" 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="ProcessTest" name="Process Test" isExecutable="true">
<bpmn:ioSpecification>
<bpmn:dataInput id="num_dogs" name="Number of Dogs" />
<bpmn:dataOutput id="happy_index" name="Happiness Index" />
@ -23,6 +23,12 @@
</bpmn:manualTask>
<bpmn:sequenceFlow id="Flow_0q4oys2" sourceRef="my_script_task" targetRef="Event_14wzv4j" />
<bpmn:scriptTask id="my_script_task" name="calculate contentment">
<bpmn:extensionElements>
<camunda:inputOutput>
<camunda:inputParameter name="Input_2mpb4l8" />
<camunda:inputParameter name="Input_34evvjn" />
</camunda:inputOutput>
</bpmn:extensionElements>
<bpmn:incoming>Flow_01jg677</bpmn:incoming>
<bpmn:outgoing>Flow_0q4oys2</bpmn:outgoing>
<bpmn:property id="Property_1w1963p" name="__targetRef_placeholder" />
@ -39,7 +45,7 @@
<bpmn:dataObjectReference id="my_data_ref_2" name="my_data_object" dataObjectRef="my_data_object" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1mzevep">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="ProcessTest">
<bpmndi:BPMNEdge id="Flow_0q4oys2_di" bpmnElement="Flow_0q4oys2">
<di:waypoint x="540" y="197" />
<di:waypoint x="602" y="197" />

View File

@ -105,6 +105,10 @@ export function findInput(type, container) {
return domQuery(`input[type='${ type }']`, container);
}
export function findButton(id, container) {
return domQuery(`button[id='${ id }']`, container);
}
export function findSelect(container) {
return domQuery('select', container);
}
@ -113,12 +117,26 @@ export function changeInput(input, value) {
fireEvent.input(input, { target: { value } });
}
export function findDataObject(element, id) {
const root = getBusinessObject(element).$parent;
return rootElements.find((rootElement) => {
return is(rootElement, 'bpmn:Error')
&& rootElement.get('id').startsWith(`Error_${ errorRef }`);
});
export function pressButton(button) {
fireEvent.click(button);
}
export function findDataObjects(process) {
let dataObjects = [];
for (const element of process.flowElements) {
if (element.$type === 'bpmn:DataObject') {
dataObjects.push(element);
}
}
return dataObjects;
}
export function findDataObject(process, id) {
for (const dataObj of findDataObjects(process)) {
if (dataObj.id === id) {
return dataObj;
}
}
}