Standardize all Event Names, and document in README.

Fix the SpiffExtensions so they work consistently with both nested Extension Properties, and as single extensions depending on the namespace.
Add a Spiff Extension Text Area.
Everything that is within the body of tag can be referenced with ".value" -- there was a lot of pointless inconsistency in the moddle json file.
This commit is contained in:
Dan 2022-11-01 20:24:28 -04:00
parent aeeaf1596e
commit 1e75ff7b53
18 changed files with 322 additions and 410 deletions

View File

@ -13,9 +13,6 @@ This extension creates a BPMN editor with all the capabilities of [BPMN.js](http
* Ability to insert BPMN's Data Input and Data Output Objects.
* A SpiffWorkflow centric Properties Panel for specifying scripts to run before and after a task, and for defining documentation, and Mark-up content for displaying in user and manual tasks. Among other things.
# Future Plans
* We look forward to integrating a real time Python execution environment for live script development.
# Data Input and Data Output Element
This extension will allow you to drag BPMN Data Input and Data Output elements onto the diagram and give them appropriate labels. This will generate valid BPMN elements in the underlying XML file - connecting them to the IO Specification of the process, as shown below:
```xml
@ -68,5 +65,38 @@ To build the example into the `public` folder execute
npm run all
```
# Integration Points
You can use the EventBus to communicate with this UI, sending and receiving messages to change
the behavior of the editor making it easier for your end users. There are many examples of
this in the app.js file.
Below is a table of all the events that are sent and accepted:
| Event Name | Description | Fired or Acceped | Parameters | Description |
| ------------------------------ | ---------------------------------------------------------------------------- | ---------------- | -------------------- | ------------------------------------------------------------------------ |
| spiff.service\_tasks.requested | Request a list of available services that can be called from a service task. | Fired | \- | |
| spiff.service\_tasks.returned | Provides a list of services. | Recieved | serviceTaskOperators | ex: \[{id:'Chuck Facts', parameters\[{id:'category', type:'string'}\]}\] |
| spiff.script.edit | Request to edit a python script in some sort of facy editor. | Fired | scriptType | one of: script, preScript, postScript |
| | | | script | The actual python script |
| | | | element | The element that needs updating |
| | | | eventBus | Used by receiver to fire back an event |
| spiff.script.update | Update a python script to a new value. | Recieved | scriptType | one of: script, preScript, postScript |
| | | | script | The updated script |
| | | | element | The element that needs updating |
| spiff.markdown.edit | Request to edit markdown in a fancy editor. | Fired | element | The element that needs updating |
| | | | markdown | The current markdown content |
| spiff.markdown.update | Update Markdown content for a paticular elements 'instructions'. | Recieved | element | The element that needs updating |
| | | | markdown | Tne updated Markdown content |
| spiff.callactivity.edit | Requst to edit a call activity by process id. | Fired | processId | The Process the users wants to edit |
| spiff.file.edit | request to edit a file, but file name. | Fired | value | The file name the user wants to edit |
| spiff.dmn.edit | request to edit a dmn by process id. | Fired | value | The DMN id the user wants to edit |
| spiff.json\_files.requested | request a list of local json files. | Fired | optionType | The type of options required ('json' or 'dmn') |
| spff.dmn\_files.requested | request of list of local dmn files. | | | |
| spiff.json\_files.returned | Return a list of available json files | Recieved | options | \[{lable:'My Label', value:'1'}\] |
| spff.dmn\_files.returned | Return a list of available dmn files. | Recieved | options | \[{lable:'My Label', value:'1'}\] |
## License
MIT

View File

@ -89,32 +89,37 @@ const myCodeMirror = CodeMirror(document.getElementById('code_editor'), {
const saveCodeBtn = document.getElementById('saveCode');
let launchCodeEvent = null;
bpmnModeler.on('script.editor.launch', (newEvent) => {
bpmnModeler.on('spiff.script.edit', (newEvent) => {
launchCodeEvent = newEvent;
myCodeMirror.setValue(launchCodeEvent.script);
setTimeout(function() {
setTimeout(function () {
myCodeMirror.refresh();
},1); // We have to wait a moment before calling refresh.
}, 1); // We have to wait a moment before calling refresh.
document.getElementById('code_overlay').style.display = 'block';
document.getElementById('code_editor').focus();
});
saveCodeBtn.addEventListener('click', (_event) => {
const { scriptType, element } = launchCodeEvent;
launchCodeEvent.eventBus.fire('script.editor.update', { element, scriptType, script: myCodeMirror.getValue()} )
launchCodeEvent.eventBus.fire('spiff.script.update', {
element,
scriptType,
script: myCodeMirror.getValue(),
});
document.getElementById('code_overlay').style.display = 'none';
});
/**
* Like Python Script Editing, it can be nice to edit your Markdown in a
* good editor as well.
*/
var simplemde = new SimpleMDE({ element: document.getElementById("markdown_textarea") });
const simplemde = new SimpleMDE({
element: document.getElementById('markdown_textarea'),
});
let launchMarkdownEvent = null;
bpmnModeler.on('markdown.editor.launch', (newEvent) => {
bpmnModeler.on('spiff.markdown.edit', (newEvent) => {
launchMarkdownEvent = newEvent;
simplemde.value(launchMarkdownEvent.markdown);
simplemde.value(launchMarkdownEvent.value);
document.getElementById('markdown_overlay').style.display = 'block';
document.getElementById('markdown_editor').focus();
});
@ -122,7 +127,10 @@ bpmnModeler.on('markdown.editor.launch', (newEvent) => {
const saveMarkdownBtn = document.getElementById('saveMarkdown');
saveMarkdownBtn.addEventListener('click', (_event) => {
const { element } = launchMarkdownEvent;
launchMarkdownEvent.eventBus.fire('markdown.editor.update', { element, markdown:simplemde.value() });
launchMarkdownEvent.eventBus.fire('spiff.markdown.update', {
element,
value: simplemde.value(),
});
document.getElementById('markdown_overlay').style.display = 'none';
});
@ -131,7 +139,7 @@ saveMarkdownBtn.addEventListener('click', (_event) => {
* Not implemented here but imagine opening up a new browser tab
* and showing a different process or completly different file editor.
*/
bpmnModeler.on('callactivity.editor.launch', (newEvent) => {
bpmnModeler.on('spiff.callactivity.edit', (newEvent) => {
console.log(
'Open new window with editor for call activity: ',
newEvent.processId
@ -143,46 +151,35 @@ bpmnModeler.on('callactivity.editor.launch', (newEvent) => {
* Not implemented here but imagine opening up a new browser tab
* and showing a different process.
*/
bpmnModeler.on('file.editor.launch', (newEvent) => {
console.log(
'Open new window to edit file: ',
newEvent.value
);
bpmnModeler.on('spiff.file.edit', (newEvent) => {
console.log('Open new window to edit file: ', newEvent.value);
});
bpmnModeler.on('dmn.editor.launch', (newEvent) => {
console.log(
'Open new window to edit DMN table: ',
newEvent.value
);
bpmnModeler.on('spiff.dmn.edit', (newEvent) => {
console.log('Open new window to edit DMN table: ', newEvent.value);
});
/**
* 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' },
{ label: 'Test Decision', value: 'test_decision' },
],
});
}
bpmnModeler.on('spiff.json_files.requested', (event) => {
event.eventBus.fire('spiff.json_files.returned', {
options: [
{ label: 'pizza_form.json', value: 'pizza_form.json' },
{ label: 'credit_card_form.json', value: 'credit_card_form.json' },
],
});
});
bpmnModeler.on('spiff.dmn_files.requested', (event) => {
event.eventBus.fire('spiff.dmn_files.returned', {
options: [
{ label: 'Pizza Special Prices', value: 'pizza_prices' },
{ label: 'Topping Prices', value: 'topping_prices' },
{ label: 'Test Decision', value: 'test_decision' },
],
});
});
// 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

@ -96,7 +96,7 @@ function LaunchEditorButton(props) {
class: 'spiffworkflow-properties-panel-button',
onClick: () => {
const processId = getCalledElementValue(element);
eventBus.fire('callactivity.editor.launch', {
eventBus.fire('spiff.callactivity.edit', {
element,
processId,
});

View File

@ -1,64 +1,85 @@
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;
}
const PREFIX = 'spiffworkflow:';
/**
* Returns the string value of the extension properties value attribute, or ""
*
* Spiff Extensions can show up in two distinct ways. The useProperties toggles between them
*
* 1. They might be a top level extension, such as a buisness rule, for example:
*
* <bpmn:extensionElements>
* <spiffworkflow:calledDecisionId>my_id</spiffworkflow:calledDecisionId>
* </bpmn:extensionElements>
*
* 2. Or the extension value may exist in a name/value pair inside a Spiffworkflow Properties extension. You would
* do this if you wanted to hide the values from the SpiffWorkflow enginge completely, and pass these values
* through unaltered to your actual application. For Example:
*
* <bpmn:extensionElements>
* <spiffworkflow:properties>
* <spiffworkflow:property name="formJsonSchemaFilename" value="json_schema.json" />
* </spiffworkflow:properties>
* </bpmn:extensionElements>
*
*
*/
/**
* Returns the string value of the spiff extension with the given name on the provided element. ""
* @param useProperties if set to true, will look inside extensions/spiffworkflow:properties otherwise, just
* looks for a spiffworkflow:[name] and returns that value inside of it.
* @param element
* @param name
*/
export function getExtensionValue(element, name) {
const property = getExtensionProperty(element, name);
if (property) {
return property.value;
const useProperties = !name.startsWith(PREFIX);
let extension;
if (useProperties) {
extension = getExtensionProperty(element, name);
} else {
extension = getExtension(element, name);
}
if (extension) {
return extension.value;
}
return '';
}
export function setExtensionProperty(element, name, value, moddle, commandStack) {
let properties = getExtensionProperties(element);
let property = getExtensionProperty(element, name);
const { businessObject } = element;
let extensions = businessObject.extensionElements;
export function setExtensionValue(element, name, value, moddle, commandStack) {
const useProperties = !name.startsWith(PREFIX)
const { businessObject } = element;
// Assure we have extensions
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 (useProperties) {
let properties = getExtension(element, SPIFF_PARENT_PROP);
let property = getExtensionProperty(element, name);
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;
} else {
let extension = getExtension(element, name);
if (!extension) {
extension = moddle.create(name);
extensions.get('values').push(extension)
}
extension.value = value;
}
if (!property) {
property = moddle.create(SPIFF_PROP);
properties.get('properties').push(property);
}
property.value = value;
property.name = name;
commandStack.execute('element.updateModdleProperties', {
element,
@ -68,3 +89,29 @@ export function setExtensionProperty(element, name, value, moddle, commandStack)
},
});
}
function getExtension(element, name) {
const bizObj = element.businessObject;
if (!bizObj.extensionElements) {
return null;
}
const extensionElements = bizObj.extensionElements.get('values');
return extensionElements.filter(function (extensionElement) {
if (extensionElement.$instanceOf(name)) {
return true;
}
})[0];
}
function getExtensionProperty(element, name) {
const parentElement = getExtension(element, SPIFF_PARENT_PROP);
if (parentElement) {
return parentElement.get('properties').filter(function (propertyElement) {
return (
propertyElement.$instanceOf(SPIFF_PROP) && propertyElement.name === name
);
})[0];
}
return null;
}

View File

@ -1,15 +1,13 @@
import { ListGroup } from '@bpmn-io/properties-panel';
import { is, isAny } from 'bpmn-js/lib/util/ModelUtil';
import scriptGroup, { SCRIPT_TYPE } from './SpiffScriptGroup';
import { SpiffExtensionTextInput } from './SpiffExtensionTextInput';
import instructionsGroup from './SpiffExtensionInstructionsForEndUser';
import {
ServiceTaskParameterArray,
ServiceTaskOperatorSelect, ServiceTaskResultTextInput,
} from './SpiffExtensionServiceProperties';
import {OPTION_TYPE, SpiffExtensionSelect} from './SpiffExtensionSelect';
import {SpiffExtensionLaunchButton} from './SpiffExtensionLaunchButton';
import {SiffExtensionCalledDecision} from './SpiffExtensionCalledDecision';
import {SpiffExtensionTextArea} from './SpiffExtensionTextArea';
const LOW_PRIORITY = 500;
@ -144,16 +142,16 @@ function createUserGroup(element, translate, moddle, commandStack) {
commandStack,
component: SpiffExtensionSelect,
optionType: OPTION_TYPE.json,
name: 'formJsonSchemaFilename',
label: translate('JSON Schema Filename'),
description: translate('Form Description (RSJF)'),
name: 'formJsonSchemaFilename',
},
{
component: SpiffExtensionLaunchButton,
element,
name: 'formJsonSchemaFilename',
label: translate('Launch Editor'),
event: 'file.editor.launch',
event: 'spiff.file.edit',
description: translate('Edit the form description'),
},
{
@ -163,7 +161,7 @@ function createUserGroup(element, translate, moddle, commandStack) {
component: SpiffExtensionSelect,
optionType: OPTION_TYPE.json,
label: translate('UI Schema Filename'),
event: 'file.editor.launch',
event: 'spiff.file.edit',
description: translate('Rules for displaying the form. (RSJF Schema)'),
name: 'formUiSchemaFilename',
},
@ -172,7 +170,7 @@ function createUserGroup(element, translate, moddle, commandStack) {
element,
name: 'formUiSchemaFilename',
label: translate('Launch Editor'),
event: 'file.editor.launch',
event: 'spiff.file.edit',
description: translate('Edit the form schema'),
},
],
@ -180,11 +178,13 @@ function createUserGroup(element, translate, moddle, commandStack) {
}
/**
* Create a group on the main panel with a text box (for choosing the dmn to connect)
* Select and launch for Business Rules
*
* @param element
* @param translate
* @param moddle
* @returns entries
* @param commandStack
* @returns {{entries: [{moddle, component: ((function(*): preact.VNode<any>)|*), name: string, description, label, commandStack, element},{component: ((function(*): preact.VNode<any>)|*), name: string, description, label, event: string, element}], id: string, label}}
*/
function createBusinessRuleGroup(element, translate, moddle, commandStack) {
return {
@ -195,20 +195,18 @@ function createBusinessRuleGroup(element, translate, moddle, commandStack) {
element,
moddle,
commandStack,
name: 'calledDecisionId',
component: SpiffExtensionSelect,
optionType: OPTION_TYPE.dmn,
component: SiffExtensionCalledDecision,
name: 'spiffworkflow:calledDecisionId',
label: translate('Select Decision Table'),
description: translate(
'Select an existing decision table from the list'
),
description: translate('Select a decision table from the list'),
},
{
component: SpiffExtensionLaunchButton,
element,
name: 'calledDecisionId',
component: SpiffExtensionLaunchButton,
name: 'spiffworkflow:calledDecisionId',
label: translate('Launch Editor'),
event: 'dmn.editor.launch',
event: 'spiff.dmn.edit',
description: translate('Modify the Decision Table'),
},
],
@ -232,14 +230,26 @@ function createUserInstructionsGroup (
id: 'instructions',
label: translate('Instructions'),
entries: [
...instructionsGroup({
{
element,
moddle,
commandStack,
translate,
component: SpiffExtensionTextArea,
name: 'spiffworkflow:instructionsForEndUser',
label: 'Instructions',
description: 'The instructions to display when completing this task.',
}),
},
{
element,
moddle,
commandStack,
component: SpiffExtensionLaunchButton,
name: 'spiffworkflow:instructionsForEndUser',
label: translate('Launch Editor'),
event: 'spiff.markdown.edit',
listenEvent: 'spiff.markdown.update',
description: translate('Edit the form schema'),
}
],
};
}

View File

@ -1,107 +0,0 @@
import { useService } from 'bpmn-js-properties-panel';
import { SelectEntry } from '@bpmn-io/properties-panel';
const SPIFF_PROP = 'spiffworkflow:calledDecisionId';
let DMN_OPTIONS = [];
/**
* Allow selecting a DMN Table from a list of known tables provided through the Event bux.
<bpmn:businessRuleTask id="Activity_0t218za">
<bpmn:extensionElements>
<spiffworkflow:calledDecisionId>my_id</spiffworkflow:calledDecisionId>
</bpmn:extensionElements>
</bpmn:businessRuleTask>
*/
export function SiffExtensionCalledDecision(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 getPropertyObject = () => {
const bizObj = element.businessObject;
if (!bizObj.extensionElements) {
return null;
}
return bizObj.extensionElements.get('values').filter(function (e) {
return e.$instanceOf(SPIFF_PROP);
})[0];
};
const getValue = () => {
const property = getPropertyObject();
if (property) {
return property.calledDecisionId;
}
return '';
};
const setValue = (value) => {
let property = getPropertyObject();
const { businessObject } = element;
let extensions = businessObject.extensionElements;
if (!property) {
property = moddle.create(SPIFF_PROP);
if (!extensions) {
extensions = moddle.create('bpmn:ExtensionElements');
}
extensions.get('values').push(property);
}
property.calledDecisionId = value;
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: businessObject,
properties: {
extensionElements: extensions,
},
});
};
if (DMN_OPTIONS.length === 0) {
requestDmnOptions(eventBus, element, commandStack, optionType);
}
const getOptions = () => {
const optionList = [];
DMN_OPTIONS.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 requestDmnOptions(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.dmn`, (event) => {
DMN_OPTIONS = event.options;
commandStack.execute('element.updateProperties', {
element,
properties: {},
});
});
eventBus.fire('spiff.options.requested', { eventBus, optionType });
}

View File

@ -1,136 +0,0 @@
import { useService } from 'bpmn-js-properties-panel';
import {
HeaderButton,
isTextFieldEntryEdited,
TextAreaEntry,
} from '@bpmn-io/properties-panel';
const SPIFF_PROP = 'spiffworkflow:instructionsForEndUser';
/**
* A generic properties' editor for text input.
* Allows you to provide additional SpiffWorkflow extension properties. Just
* uses whatever name is provide on the property, and adds or updates it as
* needed.
*
*
*
* @returns {string|null|*}
*/
function SpiffExtensionInstructionsForEndUser(props) {
const { element, commandStack, moddle, label, description } = props;
const debounce = useService('debounceInput');
const getValue = () => {
return getPropertyValue(element);
};
const setValue = (value) => {
setProperty(commandStack, moddle, element, value);
};
return TextAreaEntry({
id: 'extension_instruction_for_end_user',
element,
description,
label,
getValue,
setValue,
debounce,
});
}
function getPropertyObject(element) {
const bizObj = element.businessObject;
if (!bizObj.extensionElements) {
return null;
}
return bizObj.extensionElements.get('values').filter(function (e) {
return e.$instanceOf(SPIFF_PROP);
})[0];
}
function getPropertyValue(element) {
const property = getPropertyObject(element);
if (property) {
return property.instructionsForEndUser;
}
return '';
}
function setProperty(commandStack, moddle, element, value) {
let property = getPropertyObject(element);
const { businessObject } = element;
let extensions = businessObject.extensionElements;
if (!property) {
property = moddle.create(SPIFF_PROP);
if (!extensions) {
extensions = moddle.create('bpmn:ExtensionElements');
}
extensions.get('values').push(property);
}
property.instructionsForEndUser = value;
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: businessObject,
properties: {
extensionElements: extensions,
},
});
}
function LaunchMarkdownEditorButton(props) {
const { element, moddle, commandStack } = props;
const eventBus = useService('eventBus');
return HeaderButton({
className: 'spiffworkflow-properties-panel-button',
onClick: () => {
const markdown = getPropertyValue(element);
eventBus.fire('markdown.editor.launch', {
element,
markdown,
eventBus,
});
// Listen for a response, to update the script.
eventBus.once('markdown.editor.update', (event) => {
console.log("Markdown update!!!")
setProperty(commandStack, moddle, event.element, event.markdown);
});
},
children: 'Launch Editor',
});
}
/**
* Generates a text box and button for editing markdown.
* @param element The element that should get the markdown.
* @param moddle For updating the underlying xml document when needed.
* @returns {[{component: (function(*)), isEdited: *, id: string, element},{component: (function(*)), isEdited: *, id: string, element}]}
*/
export default function getEntries(props) {
const { element, moddle, label, description, translate, commandStack } =
props;
return [
{
id: `edit_markdown`,
element,
component: SpiffExtensionInstructionsForEndUser,
isEdited: isTextFieldEntryEdited,
moddle,
commandStack,
label,
description,
},
{
id: `launchMarkdownEditor`,
element,
component: LaunchMarkdownEditorButton,
isEdited: isTextFieldEntryEdited,
moddle,
commandStack,
},
];
}

View File

@ -1,22 +1,38 @@
import { HeaderButton } from '@bpmn-io/properties-panel';
import { useService } from 'bpmn-js-properties-panel';
import { getExtensionValue } from '../extensionHelpers';
import {getExtensionValue, setExtensionValue} from '../extensionHelpers';
/**
* Sends a notification to the host application saying the user
* would like to edit an external file. Hosting application
* would need to handle saving the file.
* would like to edit something. Hosting application can then
* update the value and send it back.
*/
export function SpiffExtensionLaunchButton(props) {
const { element, name, event } = props;
const { element, name, event, listenEvent } = props;
const eventBus = useService('eventBus');
return HeaderButton({
className: 'spiffworkflow-properties-panel-button',
id: `launch_editor_button_${name}`,
onClick: () => {
const value = getExtensionValue(element, name);
eventBus.fire(event, {
value,
eventBus,
listenEvent,
});
// Listen for a respose if the listenEvent is provided, and
// set the value to the response
// Optional additional arguments if we should listen for a reponse.
if (listenEvent) {
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);
});
}
},
children: 'Launch Editor',
});

View File

@ -2,7 +2,7 @@ import { SelectEntry } from '@bpmn-io/properties-panel';
import { useService } from 'bpmn-js-properties-panel';
import {
getExtensionValue,
setExtensionProperty,
setExtensionValue,
} from '../extensionHelpers';
const spiffExtensionOptions = {};
@ -45,7 +45,7 @@ export function SpiffExtensionSelect(props) {
const setValue = (value) => {
console.log(`Set Value called with ${ value}`);
setExtensionProperty(element, name, value, moddle, commandStack);
setExtensionValue(element, name, value, moddle, commandStack);
};
if (
@ -83,12 +83,12 @@ export function SpiffExtensionSelect(props) {
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.${optionType}`, (event) => {
eventBus.once(`spiff.${optionType}_files.returned`, (event) => {
spiffExtensionOptions[optionType] = event.options;
commandStack.execute('element.updateProperties', {
element,
properties: {},
});
});
eventBus.fire('spiff.options.requested', { eventBus, optionType });
eventBus.fire(`spiff.${optionType}_files.requested`, { eventBus });
}

View File

@ -0,0 +1,35 @@
import {useService } from 'bpmn-js-properties-panel';
import {TextAreaEntry, TextFieldEntry} from '@bpmn-io/properties-panel';
import {
getExtensionValue, setExtensionValue
} from '../extensionHelpers';
/**
* A generic properties' editor for text area.
*/
export function SpiffExtensionTextArea(props) {
const element = props.element;
const commandStack = props.commandStack, moddle = props.moddle;
const name = props.name, label = props.label, description = props.description;
const debounce = useService('debounceInput');
const getValue = () => {
return getExtensionValue(element, name)
}
const setValue = value => {
setExtensionValue(element, name, value, moddle, commandStack)
};
return <TextAreaEntry
id={'extension_' + name}
element={element}
description={description}
label={label}
getValue={getValue}
setValue={setValue}
debounce={debounce}
/>;
}

View File

@ -1,7 +1,7 @@
import {useService } from 'bpmn-js-properties-panel';
import { TextFieldEntry } from '@bpmn-io/properties-panel';
import {
getExtensionValue, setExtensionProperty
getExtensionValue, setExtensionValue
} from '../extensionHelpers';
@ -31,7 +31,7 @@ export function SpiffExtensionTextInput(props) {
}
const setValue = value => {
setExtensionProperty(element, name, value, moddle, commandStack)
setExtensionValue(element, name, value, moddle, commandStack)
};
return <TextFieldEntry

View File

@ -49,14 +49,14 @@ function LaunchEditorButton(props) {
className: 'spiffworkflow-properties-panel-button',
onClick: () => {
const script = getScriptString(element, type);
eventBus.fire('script.editor.launch', {
eventBus.fire('spiff.script.edit', {
element,
scriptType: type,
script,
eventBus,
});
// Listen for a response, to update the script.
eventBus.once('script.editor.update', (event) => {
eventBus.once('spiff.script.update', (event) => {
updateScript(
commandStack,
moddle,
@ -136,8 +136,8 @@ function updateScript(commandStack, moddle, element, scriptType, newValue) {
function getScriptString(element, scriptType) {
const scriptObj = getScriptObject(element, scriptType);
if (scriptObj && scriptObj.script) {
return scriptObj.script;
if (scriptObj && scriptObj.value) {
return scriptObj.value;
}
return '';
}

View File

@ -28,7 +28,7 @@ export function MessagePayload(props) {
const getValue = () => {
const messagePayloadObject = getMessagePayloadObject();
if (messagePayloadObject) {
return messagePayloadObject.messagePayload;
return messagePayloadObject.value;
}
return '';
};
@ -46,7 +46,7 @@ export function MessagePayload(props) {
}
messageElement.extensionElements.get('values').push(messagePayloadObject);
}
messagePayloadObject.messagePayload = value;
messagePayloadObject.value = value;
};
return (

View File

@ -28,7 +28,7 @@ export function MessageVariable(props) {
const getValue = () => {
const messageVariableObject = getMessageVariableObject();
if (messageVariableObject) {
return messageVariableObject.messageVariable;
return messageVariableObject.value;
}
return '';
};
@ -48,7 +48,7 @@ export function MessageVariable(props) {
.get('values')
.push(messageVariableObject);
}
messageVariableObject.messageVariable = value;
messageVariableObject.value = value;
};
return (

View File

@ -9,7 +9,7 @@
"superClass": [ "Element" ],
"properties": [
{
"name": "script",
"name": "value",
"isBody": true,
"type": "String"
}
@ -20,7 +20,7 @@
"superClass": [ "Element" ],
"properties": [
{
"name": "script",
"name": "value",
"isBody": true,
"type": "String"
}
@ -31,7 +31,7 @@
"superClass": [ "Element" ],
"properties": [
{
"name": "messagePayload",
"name": "value",
"isBody": true,
"type": "String"
}
@ -42,7 +42,7 @@
"superClass": [ "Element" ],
"properties": [
{
"name": "messageVariable",
"name": "value",
"isBody": true,
"type": "String"
}
@ -53,7 +53,7 @@
"superClass": [ "Element" ],
"properties": [
{
"name": "calledDecisionId",
"name": "value",
"isBody": true,
"type": "String"
}
@ -64,7 +64,7 @@
"superClass": [ "Element" ],
"properties": [
{
"name": "instructionsForEndUser",
"name": "value",
"isBody": true,
"type": "String"
}

View File

@ -1,4 +1,3 @@
import { query as domQuery } from 'min-dom';
import { getBpmnJS } from 'bpmn-js/test/helper';
import {
@ -24,9 +23,9 @@ describe('Business Rule Properties Panel', function () {
bootstrapPropertiesPanel(xml, {
debounceInput: false,
additionalModules: [
extensions,
BpmnPropertiesPanelModule,
BpmnPropertiesProviderModule,
extensions,
],
moddleExtensions: {
spiffworkflow: spiffModdleExtension,
@ -35,16 +34,14 @@ describe('Business Rule Properties Panel', function () {
);
function addOptionsToEventBus(bpmnModeler) {
bpmnModeler.on('spiff.options.requested', (event) => {
if (event.optionType === 'dmn') {
event.eventBus.fire('spiff.options.returned.dmn', {
options: [
{ label: 'Calculate Pizza Price', value: 'Decision_Pizza_Price' },
{ label: 'Viking Availability', value: 'Decision_Vikings' },
{ label: 'Test Decision', value: 'test_decision' },
],
});
}
bpmnModeler.on('spiff.dmn_files.requested', (event) => {
event.eventBus.fire('spiff.dmn_files.returned', {
options: [
{ label: 'Calculate Pizza Price', value: 'Decision_Pizza_Price' },
{ label: 'Viking Availability', value: 'Decision_Vikings' },
{ label: 'Test Decision', value: 'test_decision' },
],
});
});
}
@ -54,10 +51,10 @@ describe('Business Rule Properties Panel', function () {
expectSelected('business_rule_task');
// THEN - a properties panel exists with a section for editing that script
const entry = findEntry('extension_calledDecisionId', getPropertiesPanel());
expect(entry).to.exist;
const entry = findEntry('extension_spiffworkflow:calledDecisionId', getPropertiesPanel());
expect(entry, 'No Entry').to.exist;
const selectList = findSelect(entry);
expect(selectList).to.exist;
expect(selectList, 'No Select').to.exist;
});
it('should update the spiffworkflow:calledDecisionId tag when you modify the called decision select box', async function () {
@ -73,19 +70,19 @@ describe('Business Rule Properties Panel', function () {
const businessObject = getBusinessObject(businessRuleTask);
expect(businessObject.extensionElements).to.exist;
const element = businessObject.extensionElements.values[0];
expect(element.calledDecisionId).to.equal('Decision_Pizza_Price');
expect(element.value).to.equal('Decision_Pizza_Price');
});
it('should load up the xml and the value for the called decision should match the xml', async function () {
const businessRuleTask = await expectSelected('business_rule_task');
const entry = findEntry('extension_calledDecisionId', getPropertiesPanel());
const selectList = findSelect(entry);
expect(selectList.value).to.equal('test_decision');
expect(selectList.value, "initial value is wrong").to.equal('test_decision');
// THEN - the script tag in the BPMN Business object / XML is updated as well.
const businessObject = getBusinessObject(businessRuleTask);
expect(businessObject.extensionElements).to.exist;
const element = businessObject.extensionElements.values[0];
expect(element.calledDecisionId).to.equal('test_decision');
expect(element.value).to.equal('test_decision');
});
});

View File

@ -72,7 +72,7 @@ describe('Call Activities should work', function () {
expect(button).to.exist;
let launchEvent;
eventBus.on('callactivity.editor.launch', function (event) {
eventBus.on('spiff.callactivity.edit', function (event) {
launchEvent = event;
});
await pressButton(button);

View File

@ -6,16 +6,19 @@ 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 {getExtensionProperty, getExtensionValue} from '../../app/spiffworkflow/extensions/extensionHelpers';
import {
bootstrapPropertiesPanel,
changeInput,
expectSelected,
expectSelected, findButton,
findEntry,
findGroupEntry,
findInput,
findSelect,
findSelect, pressButton,
} from './helpers';
import extensions from '../../app/spiffworkflow/extensions';
import {query as domQuery} from 'min-dom';
import {default as diagram_xml} from './bpmn/diagram.bpmn';
describe('Properties Panel for User Tasks', function () {
const user_form_xml = require('./bpmn/user_form.bpmn').default;
@ -27,18 +30,15 @@ describe('Properties Panel for User Tasks', function () {
});
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' },
],
});
}
bpmnModeler.on('spiff.json_files.requested', (event) => {
event.eventBus.fire('spiff.json_files.returned', {
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' },
],
});
});
}
@ -100,7 +100,7 @@ describe('Properties Panel for User Tasks', function () {
const modeler = getBpmnJS();
addOptionsToEventBus(modeler);
// IF - a script tag is selected, and you change the script in the properties panel
// IF - a user tag is selected, and you change the script in the properties panel
await expectSelected('task_confirm');
const group = findGroupEntry('user_task_properties', container);
const formJsonSchemaFilenameEntry = findEntry('extension_formJsonSchemaFilename', group);
@ -110,4 +110,27 @@ describe('Properties Panel for User Tasks', function () {
const formUiSchemaFilenameInput = findSelect(formUiSchemaFilenameEntry);
expect(formUiSchemaFilenameInput.value).to.equal('number_form_schema.json');
});
it('should allow you to change the instructions to the end user', async function () {
// If a user task is selected
await preparePropertiesPanelWithXml(diagram_xml)();
const modeler = getBpmnJS();
addOptionsToEventBus(modeler);
// AND the value of the instructions is changed
const userElement = await expectSelected('task_confirm');
const group = findGroupEntry('instructions', container);
const input = domQuery('textarea', group);
changeInput(input, '#Hello!');
// THEN - the script tag in the BPMN Business object / XML is updated as well.
const businessObject = getBusinessObject(userElement);
// The change is reflected in the business object
let instructions = getExtensionValue(
userElement,
'spiffworkflow:instructionsForEndUser'
);
expect(instructions).to.equal('#Hello!');
});
});