Adding a Launch Button for Call Activity

Json files are now a selection list, not a text field -- see app.json for usage.
New SpiffExtensionSelect can be used to add select boxes anywhere you want.
This commit is contained in:
Dan 2022-10-31 16:20:05 -04:00
parent fbcda3f672
commit 6467e967b8
8 changed files with 324 additions and 93 deletions

View File

@ -126,6 +126,44 @@ saveMarkdownBtn.addEventListener('click', (_event) => {
document.getElementById('markdown_overlay').style.display = 'none';
});
/**
* Also can be good to launch an editor for a call activity.
* Not implemented here but imagine opening up a new browser tab
* and showing a different process.
*/
bpmnModeler.on('callactivity.editor.launch', (newEvent) => {
console.log(
'Open new window with editor for call activity: ',
newEvent.processId
);
});
/**
* Also handy to get a list of available files that can be used in a given
* context, say json files for a form, or a DMN file for a BusinessRuleTask
*/
bpmnModeler.on('spiff.options.requested', (event) => {
console.log('Requested!', event);
if (event.optionType === 'json') {
console.log("Firing the json")
event.eventBus.fire('spiff.options.returned.json', {
options: [
{ label: 'pizza_form.json', value: 'pizza_form.json' },
{ label: 'credit_card_form.json', value: 'credit_card_form.json' },
],
});
} else if (event.optionType === 'dmn') {
console.log("Firing the dmn")
event.eventBus.fire('spiff.options.returned.dmn', {
options: [
{ label: 'Pizza Special Prices', value: 'pizza_prices' },
{ label: 'Topping Prices', value: 'topping_prices' },
],
});
}
});
// 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
// create and save files, so keeping it outside the example.

View File

@ -1,5 +1,5 @@
import { is } from 'bpmn-js/lib/util/ModelUtil';
import { TextFieldEntry } from '@bpmn-io/properties-panel';
import { HeaderButton, TextFieldEntry } from '@bpmn-io/properties-panel';
import { useService } from 'bpmn-js-properties-panel';
const LOW_PRIORITY = 500;
@ -45,21 +45,33 @@ function createCalledElementGroup(element, translate, moddle, commandStack) {
commandStack,
translate,
},
{
id: `called_element_launch_button`,
element,
component: LaunchEditorButton,
moddle,
commandStack,
translate,
},
],
};
}
function getCalledElementValue(element) {
const { calledElement } = element.businessObject;
if (calledElement) {
return calledElement;
}
return '';
}
function CalledElementTextField(props) {
const { element } = props;
const { translate } = props;
const debounce = useService('debounceInput');
const getValue = () => {
const { calledElement } = element.businessObject;
if (calledElement) {
return calledElement;
}
return '';
return getCalledElementValue(element);
};
const setValue = (value) => {
@ -75,3 +87,20 @@ function CalledElementTextField(props) {
debounce,
});
}
function LaunchEditorButton(props) {
const { element } = props;
const eventBus = useService('eventBus');
return HeaderButton({
id: 'spiffworkflow-open-call-activity-button',
class: 'spiffworkflow-properties-panel-button',
onClick: () => {
const processId = getCalledElementValue(element);
eventBus.fire('callactivity.editor.launch', {
element,
processId,
});
},
children: 'Launch Editor',
});
}

View File

@ -0,0 +1,57 @@
const SPIFF_PARENT_PROP = 'spiffworkflow:properties';
const SPIFF_PROP = 'spiffworkflow:property';
export function getExtensionProperties(element) {
const bizObj = element.businessObject;
if (!bizObj.extensionElements) {
return null;
}
const extensionElements = bizObj.extensionElements.get('values');
return extensionElements.filter(function (extensionElement) {
if (extensionElement.$instanceOf(SPIFF_PARENT_PROP)) {
return extensionElement;
}
return null;
})[0];
}
export function getExtensionProperty(element, name) {
const parentElement = getExtensionProperties(element);
if (parentElement) {
return parentElement.get('properties').filter(function (propertyElement) {
return (
propertyElement.$instanceOf(SPIFF_PROP) && propertyElement.name === name
);
})[0];
}
return null;
}
export function setExtensionProperty(element, name, value, moddle, commandStack) {
let properties = getExtensionProperties(element);
let property = getExtensionProperty(element, name);
const { businessObject } = element;
let extensions = businessObject.extensionElements;
if (!extensions) {
extensions = moddle.create('bpmn:ExtensionElements');
}
if (!properties) {
properties = moddle.create(SPIFF_PARENT_PROP);
extensions.get('values').push(properties);
}
if (!property) {
property = moddle.create(SPIFF_PROP);
properties.get('properties').push(property);
}
property.value = value;
property.name = name;
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: businessObject,
properties: {
extensionElements: extensions,
},
});
}

View File

@ -8,6 +8,7 @@ import {
ServiceTaskParameterArray,
ServiceTaskOperatorSelect, ServiceTaskResultTextInput,
} from './SpiffExtensionServiceProperties';
import {OPTION_TYPE, SpiffExtensionSelect} from './SpiffExtensionSelect';
const LOW_PRIORITY = 500;
@ -140,7 +141,8 @@ function createUserGroup(element, translate, moddle, commandStack) {
element,
moddle,
commandStack,
component: SpiffExtensionTextInput,
component: SpiffExtensionSelect,
optionType: OPTION_TYPE.json,
label: translate('JSON Schema Filename'),
description: translate('RJSF Json Data Structure Filename'),
name: 'formJsonSchemaFilename',
@ -149,7 +151,8 @@ function createUserGroup(element, translate, moddle, commandStack) {
element,
moddle,
commandStack,
component: SpiffExtensionTextInput,
component: SpiffExtensionSelect,
optionType: OPTION_TYPE.json,
label: translate('UI Schema Filename'),
description: translate('RJSF User Interface Filename'),
name: 'formUiSchemaFilename',

View File

@ -0,0 +1,100 @@
import { SelectEntry } from '@bpmn-io/properties-panel';
import { useService } from 'bpmn-js-properties-panel';
import {
getExtensionProperty,
setExtensionProperty,
} from '../extensionHelpers';
const spiffExtensionOptions = {};
export const OPTION_TYPE = {
json: 'json',
dmn: 'dmn',
};
/**
* Allow selecting an option from a list of available options, and setting
* the name and value of a SpiffWorkflow Property to the one selected in the
* dropdown list.
* The list of options must be provided by the containing library - by responding
* to a request passed to the eventBus.
* When needed, the event "spiff.options.requested" will be fired. The event will include
* a 'type' attribute that will be one of the following:
* * jsonFiles
* * dmnFiles
* The response should be sent to "spiff.options.returned.___" where the final
* section is the name requested, ie "spiff.options.returned.jsonFiles" The response
* event should include an 'options' attribute that is list of labels and values:
* [ { label: 'Product Prices DMN', value: 'Process_16xfaqc' } ]
*/
export function SpiffExtensionSelect(props) {
const { element } = props;
const { commandStack } = props;
const { moddle } = props;
const { label, description } = props;
const { name } = props;
const { optionType } = props;
const debounce = useService('debounceInput');
const eventBus = useService('eventBus');
const getValue = () => {
const property = getExtensionProperty(element, name);
if (property) {
return property.value;
}
return '';
};
const setValue = (value) => {
console.log(`Set Value called with ${ value}`);
setExtensionProperty(element, name, value, moddle, commandStack);
};
if (
!(optionType in spiffExtensionOptions) ||
spiffExtensionOptions[optionType].length === 0
) {
spiffExtensionOptions[optionType] = [];
requestOptions(eventBus, element, commandStack, optionType);
} else {
console.log("Getting here.", spiffExtensionOptions)
}
const getOptions = () => {
const optionList = [];
if (optionType in spiffExtensionOptions) {
spiffExtensionOptions[optionType].forEach((opt) => {
optionList.push({
label: opt.label,
value: opt.value,
});
});
}
return optionList;
};
return SelectEntry({
id: `extension_${name}`,
element,
label,
description,
getValue,
setValue,
getOptions,
debounce,
});
}
function requestOptions(eventBus, element, commandStack, optionType) {
// Little backwards, but you want to assure you are ready to catch, before you throw
// or you risk a race condition.
eventBus.once('spiff.options.returned.json', (event) => {
spiffExtensionOptions[optionType] = event.options;
commandStack.execute('element.updateProperties', {
element,
properties: {},
});
});
eventBus.fire('spiff.options.requested', { eventBus, optionType });
}

View File

@ -1,8 +1,12 @@
import {useService } from 'bpmn-js-properties-panel';
import { TextFieldEntry } from '@bpmn-io/properties-panel';
import {
addOrUpdateExtensionProperty,
getExtensionProperties,
getExtensionPropertiesObject,
getExtensionProperty, setExtensionProperty
} from '../extensionHelpers';
const SPIFF_PARENT_PROP = "spiffworkflow:properties"
const SPIFF_PROP = "spiffworkflow:property"
/**
* A generic properties' editor for text input.
@ -25,32 +29,8 @@ export function SpiffExtensionTextInput(props) {
const name = props.name, label = props.label, description = props.description;
const debounce = useService('debounceInput');
const getPropertiesObject = () => {
const bizObj = element.businessObject;
if (!bizObj.extensionElements) {
return null;
} else {
const extensionElements = bizObj.extensionElements.get("values");
return extensionElements.filter(function (extensionElement) {
if (extensionElement.$instanceOf(SPIFF_PARENT_PROP)) {
return extensionElement;
}
})[0];
}
}
const getPropertyObject = () => {
const parentElement = getPropertiesObject();
if (parentElement) {
return parentElement.get("properties").filter(function (propertyElement) {
return propertyElement.$instanceOf(SPIFF_PROP) && propertyElement.name === name;
})[0];
}
return null;
}
const getValue = () => {
const property = getPropertyObject()
const property = getExtensionProperty(element, name)
if (property) {
return property.value;
}
@ -58,32 +38,7 @@ export function SpiffExtensionTextInput(props) {
}
const setValue = value => {
let properties = getPropertiesObject()
let property = getPropertyObject()
let businessObject = element.businessObject;
let extensions = businessObject.extensionElements;
if (!extensions) {
extensions = moddle.create('bpmn:ExtensionElements');
}
if (!properties) {
properties = moddle.create(SPIFF_PARENT_PROP);
extensions.get('values').push(properties);
}
if (!property) {
property = moddle.create(SPIFF_PROP);
properties.get('properties').push(property);
}
property.value = value;
property.name = name;
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: businessObject,
properties: {
"extensionElements": extensions
}
});
setExtensionProperty(element, name, value, moddle, commandStack)
};
return <TextFieldEntry

View File

@ -5,15 +5,17 @@ import {
} from 'bpmn-js-properties-panel';
import { query as domQuery } from 'min-dom';
import { getBusinessObject } from 'bpmn-js/lib/util/ModelUtil';
import { inject } from 'bpmn-js/test/helper';
import {
bootstrapPropertiesPanel,
changeInput,
expectSelected,
findGroupEntry,
expectSelected, findButton,
findGroupEntry, pressButton,
} from './helpers';
import spiffModdleExtension from '../../app/spiffworkflow/moddle/spiffworkflow.json';
import callActivity from '../../app/spiffworkflow/callActivity';
describe('Call Activities should work', function () {
const xml = require('./bpmn/call_activity.bpmn').default;
let container;
@ -57,4 +59,23 @@ describe('Call Activities should work', function () {
changeInput(textInput, 'newProcessId');
expect(businessObject.get('calledElement')).to.equal('newProcessId');
});
it('should issue an event to the event bus if user clicks the edit button', inject(
async function(eventBus) {
const shapeElement = await expectSelected('the_call_activity');
expect(shapeElement, "Can't find Call Activity").to.exist;
const businessObject = getBusinessObject(shapeElement);
expect(businessObject.get('calledElement')).to.equal('ProcessIdTBD1');
const entry = findGroupEntry('called_element', container);
const button = findButton('spiffworkflow-open-call-activity-button', entry);
expect(button).to.exist;
let launchEvent;
eventBus.on('callactivity.editor.launch', function (event) {
launchEvent = event;
});
await pressButton(button);
expect(launchEvent.processId).to.exist;
}));
});

View File

@ -1,24 +1,47 @@
import {
bootstrapPropertiesPanel, changeInput,
expectSelected,
findEntry, findGroupEntry, findInput
} from './helpers';
import spiffModdleExtension from '../../app/spiffworkflow/moddle/spiffworkflow.json';
import { BpmnPropertiesPanelModule, BpmnPropertiesProviderModule } from 'bpmn-js-properties-panel';
BpmnPropertiesPanelModule,
BpmnPropertiesProviderModule,
} from 'bpmn-js-properties-panel';
import { getBpmnJS } from 'bpmn-js/test/helper';
import { getBusinessObject } from 'bpmn-js/lib/util/ModelUtil';
import TestContainer from 'mocha-test-container-support';
import spiffModdleExtension from '../../app/spiffworkflow/moddle/spiffworkflow.json';
import {
bootstrapPropertiesPanel,
changeInput,
expectSelected,
findEntry,
findGroupEntry,
findInput,
findSelect,
} from './helpers';
import extensions from '../../app/spiffworkflow/extensions';
describe('Properties Panel for User Tasks', function() {
describe('Properties Panel for User Tasks', function () {
const user_form_xml = require('./bpmn/user_form.bpmn').default;
const diagram_xml = require('./bpmn/diagram.bpmn').default;
let container;
beforeEach(function() {
beforeEach(function () {
container = TestContainer.get(this);
});
function addOptionsToEventBus(bpmnModeler) {
bpmnModeler.on('spiff.options.requested', (event) => {
if (event.optionType === 'json') {
event.eventBus.fire('spiff.options.returned.json', {
options: [
{ label: 'pizza_form.json', value: 'pizza_form.json' },
{ label: 'credit_card_form.json', value: 'credit_card_form.json' },
{ label: 'give_me_a_number_form.json', value: 'give_me_a_number_form.json' },
{ label: 'number_form_schema.json', value: 'number_form_schema.json' },
],
});
}
});
}
function preparePropertiesPanelWithXml(xml) {
return bootstrapPropertiesPanel(xml, {
container,
@ -29,12 +52,12 @@ describe('Properties Panel for User Tasks', function() {
BpmnPropertiesProviderModule,
],
moddleExtensions: {
spiffworkflow: spiffModdleExtension
spiffworkflow: spiffModdleExtension,
},
});
}
it('should display a panel for setting the web form properties', async function() {
it('should display a panel for setting the web form properties', async function () {
await preparePropertiesPanelWithXml(user_form_xml)();
// IF - you select a user task
@ -42,44 +65,49 @@ describe('Properties Panel for User Tasks', function() {
expect(userTask).to.exist;
// THEN - a property panel exists with a section for editing web forms
let group = findGroupEntry('user_task_properties', container);
const group = findGroupEntry('user_task_properties', container);
expect(group).to.exist;
});
it('should allow you to edit a web form property.', async function() {
it('should allow you to select a json file.', async function () {
await preparePropertiesPanelWithXml(user_form_xml)();
const modeler = getBpmnJS();
addOptionsToEventBus(modeler);
// IF - you select a user task and change the formJsonSchemaFilename text field
const userTask = await expectSelected('my_user_task');
let group = findGroupEntry('user_task_properties', container);
let entry = findEntry('extension_formJsonSchemaFilename', group);
let input = findInput('text', entry);
expect(input).to.exist;
changeInput(input, 'my_filename.json');
const group = findGroupEntry('user_task_properties', container);
const entry = findEntry('extension_formJsonSchemaFilename', group);
const selectList = findSelect(entry);
expect(selectList).to.exist;
expect(selectList.options.length).to.equal(4);
expect(selectList.options[0].label).to.equal('pizza_form.json');
expect(selectList.options[1].label).to.equal('credit_card_form.json');
changeInput(selectList, 'pizza_form.json');
// THEN - the input is updated.
let businessObject = getBusinessObject(userTask);
const businessObject = getBusinessObject(userTask);
expect(businessObject.extensionElements).to.exist;
let properties = businessObject.extensionElements.values[1];
const properties = businessObject.extensionElements.values[1];
expect(properties.properties).to.exist;
const property = properties.properties[0];
expect(property.value).to.equal('my_filename.json');
expect(property.value).to.equal('pizza_form.json');
expect(property.name).to.equal('formJsonSchemaFilename');
});
it('should parse the spiffworkflow:properties tag when you open an existing file', async function() {
it('should parse the spiffworkflow:properties tag when you open an existing file', async function () {
await preparePropertiesPanelWithXml(diagram_xml)();
const modeler = getBpmnJS();
addOptionsToEventBus(modeler);
// IF - a script tag is selected, and you change the script in the properties panel
await expectSelected('task_confirm');
let group = findGroupEntry('user_task_properties', container);
let formJsonSchemaFilenameEntry = findEntry('extension_formJsonSchemaFilename', group);
let formJsonSchemaFilenameInput = findInput('text', formJsonSchemaFilenameEntry);
const group = findGroupEntry('user_task_properties', container);
const formJsonSchemaFilenameEntry = findEntry('extension_formJsonSchemaFilename', group);
const formJsonSchemaFilenameInput = findSelect(formJsonSchemaFilenameEntry);
expect(formJsonSchemaFilenameInput.value).to.equal('give_me_a_number_form.json');
let formUiSchemaFilenameEntry = findEntry('extension_formUiSchemaFilename', group);
let formUiSchemaFilenameInput = findInput('text', formUiSchemaFilenameEntry);
const formUiSchemaFilenameEntry = findEntry('extension_formUiSchemaFilename', group);
const formUiSchemaFilenameInput = findSelect(formUiSchemaFilenameEntry);
expect(formUiSchemaFilenameInput.value).to.equal('number_form_schema.json');
});
});