Squashed 'bpmn-js-spiffworkflow/' content from commit b3eef6e5

git-subtree-dir: bpmn-js-spiffworkflow
git-subtree-split: b3eef6e52cb91d10081f1586dc035701956f93a9
This commit is contained in:
Jon Herron 2022-10-12 10:21:19 -04:00
commit 1d3a492179
92 changed files with 63515 additions and 0 deletions

54
.eslintrc.js Normal file
View File

@ -0,0 +1,54 @@
module.exports = {
env: {
browser: true,
es2021: true,
},
extends: [
'airbnb',
'plugin:bpmn-io/es6',
'plugin:prettier/recommended',
'plugin:sonarjs/recommended',
'plugin:import/errors',
'plugin:import/warnings',
],
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 'latest',
sourceType: 'module',
},
rules: {
'jsx-a11y/no-autofocus': 'off',
'jsx-a11y/label-has-associated-control': 'off',
'no-console': 'off',
'no-unused-vars': [
'error',
{
destructuredArrayIgnorePattern: '^_',
varsIgnorePattern: '_',
argsIgnorePattern: '^_',
},
],
'import/extensions': [
'error',
'ignorePackages',
{
js: 'never',
jsx: 'never',
ts: 'never',
tsx: 'never',
},
],
// We could try turning these on at some point but do not want to force it now
'react/react-in-jsx-scope': 'off',
'react/prop-types': 'off',
'react/jsx-filename-extension': [1, { extensions: ['.js', '.jsx'] }],
'no-use-before-define': 0,
'func-names': 'off',
'react/destructuring-assignment': 'off',
'import/prefer-default-export': 'off',
'no-restricted-syntax': 'off',
},
};

18
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,18 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: daily
- package-ecosystem: pip
directory: "/.github/workflows"
schedule:
interval: daily
- package-ecosystem: pip
directory: "/docs"
schedule:
interval: daily
- package-ecosystem: pip
directory: "/"
schedule:
interval: daily

66
.github/labels.yml vendored Normal file
View File

@ -0,0 +1,66 @@
---
# Labels names are important as they are used by Release Drafter to decide
# regarding where to record them in changelog or if to skip them.
#
# The repository labels will be automatically configured using this file and
# the GitHub Action https://github.com/marketplace/actions/github-labeler.
- name: breaking
description: Breaking Changes
color: bfd4f2
- name: bug
description: Something isn't working
color: d73a4a
- name: build
description: Build System and Dependencies
color: bfdadc
- name: ci
description: Continuous Integration
color: 4a97d6
- name: dependencies
description: Pull requests that update a dependency file
color: 0366d6
- name: documentation
description: Improvements or additions to documentation
color: 0075ca
- name: duplicate
description: This issue or pull request already exists
color: cfd3d7
- name: enhancement
description: New feature or request
color: a2eeef
- name: github_actions
description: Pull requests that update Github_actions code
color: "000000"
- name: good first issue
description: Good for newcomers
color: 7057ff
- name: help wanted
description: Extra attention is needed
color: 008672
- name: invalid
description: This doesn't seem right
color: e4e669
- name: performance
description: Performance
color: "016175"
- name: python
description: Pull requests that update Python code
color: 2b67c6
- name: question
description: Further information is requested
color: d876e3
- name: refactoring
description: Refactoring
color: ef67c4
- name: removal
description: Removals and Deprecations
color: 9ae7ea
- name: style
description: Style
color: c120e5
- name: testing
description: Testing
color: b1fc6f
- name: wontfix
description: This will not be worked on
color: ffffff

29
.github/release-drafter.yml vendored Normal file
View File

@ -0,0 +1,29 @@
categories:
- title: ":boom: Breaking Changes"
label: "breaking"
- title: ":rocket: Features"
label: "enhancement"
- title: ":fire: Removals and Deprecations"
label: "removal"
- title: ":beetle: Fixes"
label: "bug"
- title: ":racehorse: Performance"
label: "performance"
- title: ":rotating_light: Testing"
label: "testing"
- title: ":construction_worker: Continuous Integration"
label: "ci"
- title: ":books: Documentation"
label: "documentation"
- title: ":hammer: Refactoring"
label: "refactoring"
- title: ":lipstick: Style"
label: "style"
- title: ":package: Dependencies"
labels:
- "dependencies"
- "build"
template: |
## Changes
$CHANGES

View File

@ -0,0 +1,28 @@
name: Dependabot auto-merge
on:
workflow_run:
workflows: ["Tests"]
types:
- completed
permissions:
contents: write
jobs:
dependabot:
runs-on: ubuntu-latest
if: ${{ github.actor == 'dependabot[bot]' && github.event.workflow_run.conclusion == 'success' && github.event_name == 'pull_request' }}
steps:
- name: Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@v1.3.4
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
- name: Enable auto-merge for Dependabot PRs
# if: ${{contains(steps.metadata.outputs.dependency-names, 'pytest') && steps.metadata.outputs.update-type == 'version-update:semver-patch'}}
# if: ${{contains(steps.metadata.outputs.dependency-names, 'pytest')}}
# ideally we auto-merge if all checks pass
run: gh pr merge --auto --merge "$PR_URL"
env:
PR_URL: ${{github.event.pull_request.html_url}}
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}

19
.github/workflows/labeler.yml vendored Normal file
View File

@ -0,0 +1,19 @@
name: Labeler
on:
push:
branches:
- main
- master
jobs:
labeler:
runs-on: ubuntu-latest
steps:
- name: Check out the repository
uses: actions/checkout@v3
- name: Run Labeler
uses: crazy-max/ghaction-github-labeler@v4.0.0
with:
skip-delete: true

27
.github/workflows/publish.yml vendored Normal file
View File

@ -0,0 +1,27 @@
name: Publish NPM
# On a published release, run tests and deploy to NPM
on:
workflow_dispatch:
release:
types: [published]
# Job Setup
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3 #Checkout Repo
- uses: actions/setup-node@v3 #Setup Node
with:
node-version: 18
- run: npm install
- run: npm test
- uses: JS-DevTools/npm-publish@v1
with:
token: ${{ secrets.NPM_TOKEN }}
access: public
- if: steps.publish.outputs.type != 'none'
run: |
echo "Version changed: ${{ steps.publish.outputs.old-version }} => ${{ steps.publish.outputs.version }}"

30
.github/workflows/tests.yml vendored Normal file
View File

@ -0,0 +1,30 @@
name: Tests
# Run on Pull Requests and pushes to main
on:
workflow_dispatch:
pull_request:
push:
branches:
- main
# This allows a subsequently queued workflow run to interrupt previous runs
concurrency:
group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}'
cancel-in-progress: true
# Job Setup
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3 #Checkout Repo
- uses: actions/setup-node@v3 #Setup Node
- uses: nanasess/setup-chromedriver@v1
with:
node-version: '18'
- name: Run Karma Tests
run: |
npm ci
npm run test

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
node_modules/
public/
.idea/

3
.prettierrc.js Normal file
View File

@ -0,0 +1,3 @@
module.exports = {
singleQuote: true,
};

1
.tool-versions Normal file
View File

@ -0,0 +1 @@
nodejs 18.3.0

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Sartography
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

72
README.md Normal file
View File

@ -0,0 +1,72 @@
![Tests](https://github.com/sartography/bpmn-js-spiffworkflow/actions/workflows/tests.yml/badge.svg?branch=main)
# SpiffWorkflow Extensions for BPMN.js
This package provides extensions that can be applied to BPMN.js that will enable some important features of [SpiffWorkflow](https://github.com/sartography/SpiffWorkflow) - the Python BPMN Library for executing business processes. See below for more information.
**IMPORTANT**: This is a work in progress, and is not yet released.
# About
This extension creates a BPMN editor with all the capabilities of [BPMN.js](https://github.com/bpmn-io/bpmn-js) and the following additions / modifications:
* 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
<bpmn:process id="my_delightful_process" isExecutable="true">
<bpmn:ioSpecification>
<bpmn:dataInput id="DataInput-745019423-1" name="num_dogs" />
<bpmn:dataOutput id="DataOutput-711207596-1" name="happy_index" />
</bpmn:ioSpecification>
...
```
![Screenshot](docs/io.png)
Using these data input and outputs will allow you to create processes designed to be used as Call Activities. SpiffWorkflow (in a soon-to-be released version) will pick up this information, and enforce it. So that you must provide these input variables to execute, and only the variables mentioned in the output will be passed back to the calling process.
## Usage
```javascript
import BpmnModeler from 'bpmn-js/lib/Modeler';
import spiffworkflow from 'bpmn-js-spiffworkflow/app/spiffworkflow';
var bpmnJS = new BpmnModeler({
additionalModules: [
spiffworkflow
],
moddleExtensions: {
spiffworkflowModdle: spiffModdleExtension
}
});
```
## Run the Example
You need a [NodeJS](http://nodejs.org) development stack with [npm](https://npmjs.org) installed to build the project.
To install all project dependencies execute
```sh
npm install
```
To start the example execute
```sh
npm start
```
To build the example into the `public` folder execute
```sh
npm run all
```
## License
MIT

15
RELEASE_PROCESS.md Normal file
View File

@ -0,0 +1,15 @@
## Releases
Be sure to edit the package.json, and update the version. Releases won't create
a new NPM package unless the version was updated.
A good way to do go about this is with npm version. Which will increment the version in package.json and create a new commit and tag. Here are few examples that you might use, but
there is more information on [NPM Version](https://docs.npmjs.com/cli/v8/commands/npm-version).
For doing a patch release, you can do
```bash
npm version patch -m "Upgrade to %s for reasons"
```
aside from patch, you can use the keywords `minor`, and `major` (there are some others).
Once this is complete, log into GitHub and do an offical release of the package. A published release will result in a new published version on NPM (via a GitHub Action)

81
app/app.js Normal file
View File

@ -0,0 +1,81 @@
import BpmnModeler from 'bpmn-js/lib/Modeler';
import {
BpmnPropertiesPanelModule,
BpmnPropertiesProviderModule,
} from 'bpmn-js-properties-panel';
import diagramXML from '../test/spec/bpmn/script_task.bpmn';
import spiffworkflow from './spiffworkflow';
import setupFileOperations from './fileOperations';
const modelerEl = document.getElementById('modeler');
const panelEl = document.getElementById('panel');
const spiffModdleExtension = require('./spiffworkflow/moddle/spiffworkflow.json');
let bpmnModeler;
/**
* This provides an example of how to instantiate a BPMN Modeler configured with
* all the extensions and modifications in this application.
*/
try {
bpmnModeler = new BpmnModeler({
container: modelerEl,
propertiesPanel: {
parent: panelEl,
},
additionalModules: [
spiffworkflow,
BpmnPropertiesPanelModule,
BpmnPropertiesProviderModule,
],
moddleExtensions: {
spiffworkflowModdle: spiffModdleExtension,
},
});
} catch (error) {
if (error.constructor.name === 'AggregateError') {
console.log(error.message);
console.log(error.name);
console.log(error.errors);
}
throw error;
}
// import XML
bpmnModeler.importXML(diagramXML).then(() => {});
/**
* It is possible to poplulate certain components using API calls to
* a backend. Here we mock out the API call, but this gives you
* a sense of how things might work.
*
*/
bpmnModeler.on('spiff.service_tasks.requested', (event) => {
event.eventBus.fire('spiff.service_tasks.returned', {
serviceTaskOperators: [
{
id: 'Chuck Norris Fact Service',
parameters: [
{
id: 'category',
type: 'string',
},
],
},
{
id: 'Fact about a Number',
parameters: [
{
id: 'number',
type: 'integer',
},
],
},
],
});
});
// 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.
setupFileOperations(bpmnModeler);

59
app/css/app.css Normal file
View File

@ -0,0 +1,59 @@
* {
box-sizing: border-box;
margin: 0;
outline: none;
padding: 0;
}
html, body {
height: 100%;
}
.hidden {
display: none;
}
#container {
display: flex;
width: 100%;
height: 100%;
}
#modeler {
flex-grow: 1;
}
#panel {
background-color: #fafafa;
border: solid 1px #ccc;
border-radius: 2px;
font-family: 'Arial', sans-serif;
padding: 10px;
min-width: 400px;
}
.djs-label {
font-family: 'Arial', sans-serif;
}
.spiffworkflow-properties-panel-button {
margin: 2px 32px 6px 12px;
padding-left: 2px;
padding-right: 2px;
}
/* Style buttons */
.bpmn-js-spiffworkflow-btn {
background-color: DodgerBlue;
border: none;
color: white;
padding: 8px 15px;
cursor: pointer;
font-size: 16px;
margin: 12px;
}
/* Darker background on mouse-over */
.bpmn-js-spiffworkflow-btn:hover {
background-color: RoyalBlue;
}

83
app/fileOperations.js Normal file
View File

@ -0,0 +1,83 @@
// FileSaver isn't really a dependency, we use it here to provide an example.
// eslint-disable-next-line import/no-extraneous-dependencies
import FileSaver from 'file-saver';
/** ****************************************
* Below are a few helper methods so we can upload and download files
* easily from the editor for testing purposes.
* -----------------------------------------
*/
export default function setupFileOperations(bpmnModeler) {
/**
* Just a quick bit of code so we can save the XML that is output.
* Helps for debugging against other libraries (like SpiffWorkflow)
*/
const btn = document.getElementById('downloadButton');
btn.addEventListener('click', (_event) => {
saveXML();
});
async function saveXML() {
const { xml } = await bpmnModeler.saveXML({ format: true });
const blob = new Blob([xml], { type: 'text/xml' });
FileSaver.saveAs(blob, 'diagram.bpmn');
}
/**
* Just a quick bit of code so we can open a local XML file
* Helps for debugging against other libraries (like SpiffWorkflow)
*/
const uploadBtn = document.getElementById('uploadButton');
uploadBtn.addEventListener('click', (_event) => {
openFile(displayFile);
});
function displayFile(contents) {
bpmnModeler.importXML(contents).then(() => {});
}
}
function clickElem(elem) {
const eventMouse = document.createEvent('MouseEvents');
eventMouse.initMouseEvent(
'click',
true,
false,
window,
0,
0,
0,
0,
0,
false,
false,
false,
false,
0,
null
);
elem.dispatchEvent(eventMouse);
}
export function openFile(func) {
const readFile = function readFileCallback(e) {
const file = e.target.files[0];
if (!file) {
return;
}
const reader = new FileReader();
reader.onload = function onloadCallback(onloadEvent) {
const contents = onloadEvent.target.result;
fileInput.func(contents);
document.body.removeChild(fileInput);
};
reader.readAsText(file);
};
let fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.style.display = 'none';
fileInput.onchange = readFile;
fileInput.func = func;
document.body.appendChild(fileInput);
clickElem(fileInput);
}

27
app/index.html Normal file
View File

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<title>bpmn-js-spiffworkflow</title>
<meta charset="utf-8"/>
<link rel="stylesheet" href="vendor/bpmn-js/assets/diagram-js.css"/>
<link rel="stylesheet" href="vendor/bpmn-js/assets/bpmn-js.css"/>
<link rel="stylesheet" href="vendor/bpmn-js/assets/bpmn-font/css/bpmn-embedded.css"/>
<link rel="stylesheet" href="vendor/bpmn-js-properties-panel/assets/properties-panel.css"/>
<link rel="stylesheet" href="css/app.css"/>
<link rel="shortcut icon" href="#">
<!-- Just have this for the download file icon -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
</head>
<body>
<div id="menu">
<button id="downloadButton" class="bpmn-js-spiffworkflow-btn"><i class="fa fa-download"></i> Download</button>
<button id="uploadButton" class="bpmn-js-spiffworkflow-btn">Open a file</button>
</div>
<div id="container">
<div id="modeler"></div>
<div id="panel"></div>
</div>
<script src="app.js"></script>
</body>
</html><!---->

View File

@ -0,0 +1,39 @@
/**
* Returns the moddelElement if it is a process, otherwise, returns the
*
* @param container
*/
export function findDataObjects(process) {
let dataObjects = [];
if (!process || !process.flowElements) {
return 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;
}
}
}
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;
}

View File

@ -0,0 +1,59 @@
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { is } from 'bpmn-js/lib/util/ModelUtil';
import { findDataObjects } from './DataObjectHelpers';
var 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.
*
* This handles some special cases we want to handle for DataObjects and DataObjectReferences,
* for instance:
* 1) Use existing data objects if possible when creating a new reference (don't create new objects each time)
* 2) Don't automatically delete a data object when you delete the reference - unless all references are removed.
* 3) Update the name of the DataObjectReference to match the id of the DataObject.
* 4) Don't allow someone to move a DataObjectReference from one process to another process.
*/
export default class DataObjectInterceptor extends CommandInterceptor {
constructor(eventBus, bpmnFactory, bpmnUpdater) {
super(eventBus);
/**
* For DataObjectReferences only ...
* Prevent this from calling the CreateDataObjectBehavior in BPMN-js, as it will
* attempt to crete a dataObject immediately. We can't create the dataObject until
* we know where it is placed - as we want to reuse data objects of the parent when
* possible */
this.preExecute([ 'shape.create' ], HIGH_PRIORITY, function(event) {
const context = event.context, shape = context.shape;
if (is(shape, 'bpmn:DataObjectReference') && shape.type !== 'label') {
event.stopPropagation();
}
});
/**
* Don't just create a new data object, use the first existing one if it already exists
*/
this.executed([ 'shape.create' ], HIGH_PRIORITY, function(event) {
const context = event.context, shape = context.shape;
if (is(shape, 'bpmn:DataObjectReference') && shape.type !== 'label') {
let process = shape.parent.businessObject;
let existingDataObjects = findDataObjects(process);
let dataObject;
if (existingDataObjects.length > 0) {
dataObject = existingDataObjects[0];
} else {
dataObject = bpmnFactory.create('bpmn:DataObject');
}
// Update the name of the reference to match the data object's id.
shape.businessObject.name = dataObject.id;
// set the reference to the DataObject
shape.businessObject.dataObjectRef = dataObject;
}
});
}
}
DataObjectInterceptor.$inject = [ 'eventBus', 'bpmnFactory', 'bpmnUpdater' ];

View File

@ -0,0 +1,44 @@
import BaseRenderer from 'diagram-js/lib/draw/BaseRenderer';
import {
attr as svgAttr
} from 'tiny-svg';
import { getBusinessObject, is } from 'bpmn-js/lib/util/ModelUtil';
import { isAny } from 'bpmn-js/lib/features/modeling/util/ModelingUtil';
import { findDataObject } from './DataObjectHelpers';
const HIGH_PRIORITY = 1500;
/**
* Work in progress -- render data object references in red if they are
* not valid.
*/
export default class DataObjectRenderer extends BaseRenderer {
constructor(eventBus, bpmnRenderer) {
super(eventBus, HIGH_PRIORITY);
this.bpmnRenderer = bpmnRenderer;
}
canRender(element) {
return isAny(element, [ 'bpmn:DataObjectReference' ]) && !element.labelTarget;
}
drawShape(parentNode, element) {
const shape = this.bpmnRenderer.drawShape(parentNode, element);
if (is(element, 'bpmn:DataObjectReference')) {
let businessObject = getBusinessObject(element);
let dataObject = businessObject.dataObjectRef;
if (dataObject && dataObject.id) {
let parentObject = businessObject.$parent;
dataObject = findDataObject(parentObject, dataObject.id);
}
if (!dataObject) {
svgAttr(shape, 'stroke', 'red');
}
return shape;
}
}
}
DataObjectRenderer.$inject = [ 'eventBus', 'bpmnRenderer' ];

View File

@ -0,0 +1,39 @@
/**
* Custom Rules for the DataObject - Rules allow you to prevent an
* action from happening in the diagram, such as dropping an element
* where it doesn't belong.
*
* Here we don't allow people to move a data object Reference
* from one parent to another, as we can't move the data objects
* from one parent to another.
*
*/
import RuleProvider from 'diagram-js/lib/features/rules/RuleProvider';
import inherits from 'inherits-browser';
import { is } from 'bpmn-js/lib/util/ModelUtil';
export default function DataObjectRules(eventBus) {
RuleProvider.call(this, eventBus);
}
inherits(DataObjectRules, RuleProvider);
const HIGH_PRIORITY = 1500;
DataObjectRules.prototype.init = function() {
this.addRule('elements.move', HIGH_PRIORITY,function(context) {
let elements = context.shapes;
let target = context.target;
return canDrop(elements, target);
});
};
function canDrop(elements, target) {
for (let element of elements) {
if (is(element, 'bpmn:DataObjectReference') && element.parent && target) {
return target === element.parent;
}
// Intentionally returning null here to allow other rules to fire.
}
}
DataObjectRules.prototype.canDrop = canDrop;
DataObjectRules.$inject = [ 'eventBus' ];

View File

@ -0,0 +1,20 @@
import DataObjectInterceptor from './DataObjectInterceptor';
import DataObjectRules from './DataObjectRules';
import RulesModule from 'diagram-js/lib/features/rules';
import DataObjectRenderer from './DataObjectRenderer';
import DataObjectPropertiesProvider from './propertiesPanel/DataObjectPropertiesProvider';
export default {
__depends__: [
RulesModule
],
__init__: [ 'dataInterceptor', 'dataObjectRules', 'dataObjectRenderer', 'dataObjectPropertiesProvider' ],
dataInterceptor: [ 'type', DataObjectInterceptor ],
dataObjectRules: [ 'type', DataObjectRules ],
dataObjectRenderer: [ 'type', DataObjectRenderer ],
dataObjectPropertiesProvider: [ 'type', DataObjectPropertiesProvider ]
};

View File

@ -0,0 +1,151 @@
import { useService } from 'bpmn-js-properties-panel';
import {
isTextFieldEntryEdited,
TextFieldEntry,
} from '@bpmn-io/properties-panel';
import { without } from 'min-dash';
import { is } from 'bpmn-js/lib/util/ModelUtil';
import { findDataObjects, findDataReferenceShapes } from '../DataObjectHelpers';
/**
* Provides a list of data objects, and allows you to add / remove data objects, and change their ids.
* @param props
* @constructor
*/
export function DataObjectArray(props) {
const { moddle } = props;
const { element } = props;
const { commandStack } = props;
const { elementRegistry } = props;
let process;
// This element might be a process, or something that will reference a process.
if (is(element.businessObject, 'bpmn:Process')) {
process = element.businessObject;
} else if (element.businessObject.processRef) {
process = element.businessObject.processRef;
}
const dataObjects = findDataObjects(process);
const items = dataObjects.map((dataObject, index) => {
const id = `${process.id}-dataObj-${index}`;
return {
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();
const newDataObject = moddle.create('bpmn:DataObject');
const newElements = process.get('flowElements');
newDataObject.id = moddle.ids.nextPrefixed('DataObject_');
newElements.push(newDataObject);
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: process,
properties: {
flowElements: newElements,
},
});
}
return { items, add };
}
function removeFactory(props) {
const { element, dataObject, process, commandStack } = props;
return function (event) {
event.stopPropagation();
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: process,
properties: {
flowElements: without(process.get('flowElements'), dataObject),
},
});
// Also update the label of all the references
const references = findDataReferenceShapes(element, dataObject.id);
for (const ref of references) {
commandStack.execute('element.updateProperties', {
element: ref,
moddleElement: ref.businessObject,
properties: {
name: '???',
},
changed: [ref], // everything is already marked as changed, don't recalculate.
});
}
};
}
function DataObjectGroup(props) {
const { idPrefix, dataObject } = props;
return [
{
id: `${idPrefix}-dataObject`,
component: DataObjectTextField,
isEdited: isTextFieldEntryEdited,
idPrefix,
dataObject,
},
];
}
function DataObjectTextField(props) {
const { idPrefix, element, parameter, dataObject } = props;
const commandStack = useService('commandStack');
const debounce = useService('debounceInput');
const setValue = (value) => {
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: dataObject,
properties: {
id: value,
},
});
// Also update the label of all the references
const references = findDataReferenceShapes(element, dataObject.id);
for (const ref of references) {
commandStack.execute('element.updateProperties', {
element: ref,
moddleElement: ref.businessObject,
properties: {
name: value,
},
changed: [ref], // everything is already marked as changed, don't recalculate.
});
}
};
const getValue = () => {
return dataObject.id;
};
return TextFieldEntry({
element: parameter,
id: `${idPrefix}-id`,
label: 'Data Object Id',
getValue,
setValue,
debounce,
});
}

View File

@ -0,0 +1,99 @@
import { is, isAny } from 'bpmn-js/lib/util/ModelUtil';
import { ListGroup, isTextFieldEntryEdited } from '@bpmn-io/properties-panel';
import { DataObjectSelect } from './DataObjectSelect';
import { DataObjectArray } from './DataObjectArray';
const LOW_PRIORITY = 500;
export default function DataObjectPropertiesProvider(
propertiesPanel,
translate,
moddle,
commandStack,
elementRegistry
) {
this.getGroups = function (element) {
return function (groups) {
if (is(element, 'bpmn:DataObjectReference')) {
groups.push(
createDataObjectSelector(element, translate, moddle, commandStack)
);
}
if (
isAny(element, ['bpmn:Process', 'bpmn:SubProcess', 'bpmn:Participant'])
) {
groups.push(
createDataObjectEditor(
element,
translate,
moddle,
commandStack,
elementRegistry
)
);
}
return groups;
};
};
propertiesPanel.registerProvider(LOW_PRIORITY, this);
}
DataObjectPropertiesProvider.$inject = [
'propertiesPanel',
'translate',
'moddle',
'commandStack',
'elementRegistry',
];
/**
* 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, commandStack) {
return {
id: 'data_object_properties',
label: translate('Data Object Properties'),
entries: [
{
id: 'selectDataObject',
element,
component: DataObjectSelect,
isEdited: isTextFieldEntryEdited,
moddle,
commandStack,
},
],
};
}
/**
* 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.
* @param element
* @param translate
* @param moddle
* @returns entries
*/
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

@ -0,0 +1,76 @@
import {useService } from 'bpmn-js-properties-panel';
import { SelectEntry } from '@bpmn-io/properties-panel';
/**
* Finds the value of the given type within the extensionElements
* given a type of "spiff:preScript", would find it in this, and return
* the object.
*
* <bpmn:
<bpmn:userTask id="123" name="My User Task!">
<bpmn:extensionElements>
<spiffworkflow:preScript>
me = "100% awesome"
</spiffworkflow:preScript>
</bpmn:extensionElements>
...
</bpmn:userTask>
*
* @returns {string|null|*}
*/
export function DataObjectSelect(props) {
const element = props.element;
const commandStack = props.commandStack;
const debounce = useService('debounceInput');
const getValue = () => {
return element.businessObject.dataObjectRef.id
}
const setValue = value => {
const businessObject = element.businessObject;
for (const flowElem of businessObject.$parent.flowElements) {
if (flowElem.$type === 'bpmn:DataObject' && flowElem.id === value) {
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: businessObject,
properties: {
dataObjectRef: flowElem
}
});
commandStack.execute('element.updateProperties', {
element,
moddleElement: businessObject,
properties: {
'name': flowElem.id
}
});
}
}
}
const getOptions = value => {
const businessObject = element.businessObject;
const parent = businessObject.$parent;
let options = []
for (const element of parent.flowElements) {
if (element.$type === 'bpmn:DataObject') {
options.push({label: element.id, value: element.id})
}
}
return options
}
return <SelectEntry
id={'selectDataObject'}
element={element}
description={"Select the Data Object this represents."}
label={"Which Data Object does this reference?"}
getValue={ getValue }
setValue={ setValue }
getOptions={ getOptions }
debounce={debounce}
/>;
}

View File

@ -0,0 +1,30 @@
import { ListGroup } from '@bpmn-io/properties-panel';
import { DataObjectArray } from './DataObjectArray';
/**
* Also allows you to select which Data Objects are available
* 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(element, moddle) {
const groupSections = [];
const dataObjectArray = {
id: 'editDataObjects',
element,
label: 'Available Data Objects',
component: ListGroup,
...DataObjectArray({ element, moddle })
};
if (dataObjectArray.items) {
groupSections.push(dataObjectArray);
}
return groupSections;
}

View File

@ -0,0 +1,106 @@
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { add as collectionAdd } from 'diagram-js/lib/util/Collections';
import { remove as collectionRemove } from 'diagram-js/lib/util/Collections';
import IdGenerator from 'diagram-js/lib/util/IdGenerator';
var 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.
*
* This handles the case where a new DataInput or DataOutput is added to
* the diagram, it assures that a place exists for the new Data object to go, and it places it there.
* There were a number of paces where I had to patch things in to get it to work correctly:
* * Create a InputOutputSpecification on the BPMN Moddle if it doesn't exist.
* * Correctly connect a new DI (display element in BPMN xml) for the input/output element.
* * Create a new DataInput/DataOutput Object (maybe incorrectly)
* Also handles delete, where it removes the objects from the BPMN Moddle (both the actual input/output and the DI)
* fixme: Assure that we need to create a new DataInput object here, already in IoPalette's call to ElementFactory
* fixme: If all inputs and outputs are deleted, remove the InputOutputSpecification completely.
*/
export default class IoInterceptor extends CommandInterceptor {
constructor(eventBus, bpmnFactory, bpmnUpdater) {
super(eventBus);
this.execute([ 'shape.create' ], HIGH_PRIORITY, function(event) {
let context = event.context;
if ([ 'bpmn:DataInput', 'bpmn:DataOutput' ].includes(context.shape.type)) {
let type = context.shape.type;
let type_name = type.split(':')[1];
let process = context.parent.businessObject;
let ioSpec = assureIOSpecificationExists(process, bpmnFactory);
let di = context.shape.di;
let generator = new IdGenerator(type_name), ioSpecification = process.get('ioSpecification');
let dataIO = bpmnFactory.create(type, { id: generator.next() });
context.shape.businessObject = dataIO;
dataIO.$parent = ioSpec;
di.businessObject = dataIO;
di.bpmnElement = dataIO;
di.id = dataIO.id + 'DI';
bpmnUpdater.updateBounds(context.shape);
if (type == 'bpmn:DataInput') {
collectionAdd(ioSpecification.get('dataInputs'), dataIO);
} else {
collectionAdd(ioSpecification.get('dataOutputs'), dataIO);
}
}
});
this.execute([ 'shape.delete' ], HIGH_PRIORITY, function(event) {
let context = event.context;
if ([ 'bpmn:DataInput', 'bpmn:DataOutput' ].includes(context.shape.type)) {
let type = context.shape.type;
let process = context.shape.parent.businessObject;
let ioSpec = assureIOSpecificationExists(process, bpmnFactory);
if (type == 'bpmn:DataInput') {
collectionRemove(ioSpec.get('dataInputs'), context.shape.businessObject);
} else {
collectionRemove(ioSpec.get('dataOutputs'), context.shape.businessObject);
}
if (context.shape.di.$parent) {
collectionRemove(context.shape.di.$parent.planeElement, context.shape.di);
}
}
});
// Stop propagation on executed, to avoid the BpmnUpdator.js from causing errors.
this.executed([ 'shape.delete', 'shape.create' ], HIGH_PRIORITY, function(event) {
if ([ 'bpmn:DataInput', 'bpmn:DataOutput' ].includes(event.context.shape.type)) {
event.stopPropagation(); // Don't let the main code execute, it will fail.
}
});
}
}
/**
* <bpmndi:BPMNShape id="dataInput_1" bpmnElement="ID_3">
* <dc:Bounds x="152" y="195" width="36" height="50" />
* <bpmndi:BPMNLabel>
* <dc:Bounds x="142" y="245" width="56" height="14" />
* </bpmndi:BPMNLabel>
* </bpmndi:BPMNShape>
* @param process
* @param bpmnFactory
* @returns {bpmn:InputOutputSpecification}
*/
function assureIOSpecificationExists(process, bpmnFactory) {
let ioSpecification = process.get('ioSpecification');
if (!ioSpecification) {
// Create the BPMN
ioSpecification = bpmnFactory.create('bpmn:InputOutputSpecification', {
dataInputs: [],
inputSets: [],
dataOutputs: [],
outputSets: []
});
ioSpecification.$parent = process;
process.ioSpecification = ioSpecification;
}
return ioSpecification;
}
IoInterceptor.$inject = [ 'eventBus', 'bpmnFactory', 'bpmnUpdater' ];

View File

@ -0,0 +1,62 @@
import { assign } from 'min-dash';
import translate from 'diagram-js/lib/i18n/translate/translate';
/**
* Add data inputs and data outputs to the panel.
*/
export default function IoPalette(palette, create, elementFactory,) {
this._create = create;
this._elementFactory = elementFactory;
palette.registerProvider(this);
}
IoPalette.$inject = [
'palette',
'create',
'elementFactory'
];
IoPalette.prototype.getPaletteEntries = function() {
let input_type = 'bpmn:DataInput';
let output_type = 'bpmn:DataOutput';
let elementFactory = this._elementFactory, create = this._create;
function createListener(event, type) {
let shape = elementFactory.createShape(assign({ type: type }, {}));
shape.width = 36; // Fix up the shape dimensions from the defaults.
shape.height = 50;
create.start(event, shape);
}
function createInputListener(event) {
createListener(event, input_type);
}
function createOutputListener(event) {
createListener(event, output_type);
}
return {
'create.data-input': {
group: 'data-object',
className: 'bpmn-icon-data-input',
title: translate('Create DataInput'),
action: {
dragstart: createInputListener,
click: createInputListener
}
},
'create.data-output': {
group: 'data-object',
className: 'bpmn-icon-data-output',
title: translate('Create DataOutput'),
action: {
dragstart: createOutputListener,
click: createOutputListener
}
}
};
};

View File

@ -0,0 +1,45 @@
import inherits from 'inherits';
import RuleProvider from 'diagram-js/lib/features/rules/RuleProvider';
const HIGH_PRIORITY = 1500;
/**
* A custom rule provider that will permit Data Inputs and Data
* Outputs to be placed within a process element (something BPMN.io currently denies)
*
* See {@link BpmnRules} for the default implementation
* of BPMN 2.0 modeling rules provided by bpmn-js.
*
* @param {EventBus} eventBus
*/
export default function IoRules(eventBus) {
RuleProvider.call(this, eventBus);
}
inherits(IoRules, RuleProvider);
IoRules.$inject = [ 'eventBus' ];
IoRules.prototype.init = function() {
this.addRule('shape.create', HIGH_PRIORITY, function(context) {
let element = context.shape;
let target = context.target;
let position = context.position;
return canCreate(element, target, position);
});
};
/**
* Allow folks to drop a dataInput or DataOutput only on the top level process.
*/
function canCreate(element, target, position) {
if ([ 'bpmn:DataInput', 'bpmn:DataOutput' ].includes(element.type)) {
if (target.type == 'bpmn:Process') {
return true;
}
}
}
IoRules.prototype.canCreate = canCreate;

View File

@ -0,0 +1,11 @@
import IoPalette from './IoPalette';
import IoRules from './IoRules';
import IoInterceptor from './IoInterceptor';
export default {
__init__: [ 'IoPalette', 'IoRules', 'IoInterceptor' ],
IoPalette: [ 'type', IoPalette ],
IoRules: [ 'type', IoRules ],
IoInterceptor: [ 'type', IoInterceptor ]
};

View File

@ -0,0 +1,6 @@
import CallActivityPropertiesProvider from './propertiesPanel/CallActivityPropertiesProvider';
export default {
__init__: ['callActivityPropertiesProvider'],
callActivityPropertiesProvider: ['type', CallActivityPropertiesProvider],
};

View File

@ -0,0 +1,77 @@
import { is } from 'bpmn-js/lib/util/ModelUtil';
import { TextFieldEntry } from '@bpmn-io/properties-panel';
import { useService } from 'bpmn-js-properties-panel';
const LOW_PRIORITY = 500;
export default function CallActivityPropertiesProvider(
propertiesPanel,
translate,
moddle,
commandStack,
_elementRegistry
) {
this.getGroups = function getGroupsCallback(element) {
return function pushGroup(groups) {
if (is(element, 'bpmn:CallActivity')) {
groups.push(
createCalledElementGroup(element, translate, moddle, commandStack)
);
}
return groups;
};
};
propertiesPanel.registerProvider(LOW_PRIORITY, this);
}
CallActivityPropertiesProvider.$inject = [
'propertiesPanel',
'translate',
'moddle',
'commandStack',
'elementRegistry',
];
function createCalledElementGroup(element, translate, moddle, commandStack) {
return {
id: 'called_element',
label: translate('Called Element'),
entries: [
{
id: `called_element_text_field`,
element,
component: CalledElementTextField,
moddle,
commandStack,
translate,
},
],
};
}
function CalledElementTextField(props) {
const { element } = props;
const { translate } = props;
const debounce = useService('debounceInput');
const getValue = () => {
const { calledElement } = element.businessObject;
if (calledElement) {
return calledElement;
}
return '';
};
const setValue = (value) => {
element.businessObject.calledElement = value;
};
return TextFieldEntry({
element,
id: 'process_id',
label: translate('Process ID'),
getValue,
setValue,
debounce,
});
}

View File

@ -0,0 +1,6 @@
import ConditionsPropertiesProvider from './propertiesPanel/ConditionsPropertiesProvider';
export default {
__init__: ['conditionsPropertiesProvider'],
conditionsPropertiesProvider: ['type', ConditionsPropertiesProvider],
};

View File

@ -0,0 +1,101 @@
import { is } from 'bpmn-js/lib/util/ModelUtil';
import {
isTextFieldEntryEdited,
TextFieldEntry,
} from '@bpmn-io/properties-panel';
import { useService } from 'bpmn-js-properties-panel';
const LOW_PRIORITY = 500;
export default function ConditionsPropertiesProvider(
propertiesPanel,
translate,
moddle,
commandStack,
_elementRegistry
) {
this.getGroups = function getGroupsCallback(element) {
return function pushGroup(groups) {
if (is(element, 'bpmn:SequenceFlow')) {
const { source } = element;
if (is(source, 'bpmn:ExclusiveGateway')) {
groups.push(
createConditionsGroup(element, translate, moddle, commandStack)
);
}
}
return groups;
};
};
propertiesPanel.registerProvider(LOW_PRIORITY, this);
}
ConditionsPropertiesProvider.$inject = [
'propertiesPanel',
'translate',
'moddle',
'commandStack',
'elementRegistry',
];
function createConditionsGroup(element, translate, moddle, commandStack) {
return {
id: 'conditions',
label: translate('Conditions'),
entries: conditionGroup(
element,
moddle,
'Condition Expression',
'Expression to Execute',
commandStack
),
};
}
function conditionGroup(element, moddle, label, description, commandStack) {
return [
{
id: `condition_expression`,
element,
component: ConditionExpressionTextField,
moddle,
label,
description,
commandStack,
},
];
}
function ConditionExpressionTextField(props) {
const { element } = props;
const { moddle } = props;
const { label } = props;
const debounce = useService('debounceInput');
const getValue = () => {
const { conditionExpression } = element.businessObject;
if (conditionExpression) {
return conditionExpression.body;
}
return '';
};
const setValue = (value) => {
let { conditionExpressionModdleElement } = element.businessObject;
if (!conditionExpressionModdleElement) {
conditionExpressionModdleElement = moddle.create('bpmn:Expression');
}
conditionExpressionModdleElement.body = value;
element.businessObject.conditionExpression =
conditionExpressionModdleElement;
};
return TextFieldEntry({
element,
id: `the-id`,
label,
getValue,
setValue,
debounce,
});
}

View File

@ -0,0 +1 @@
export const SPIFFWORKFLOW_XML_NAMESPACE = 'spiffworkflow';

View File

@ -0,0 +1,7 @@
import ExtensionsPropertiesProvider from './propertiesPanel/ExtensionsPropertiesProvider';
export default {
__init__: [ 'extensionsPropertiesProvider' ],
extensionsPropertiesProvider: [ 'type', ExtensionsPropertiesProvider ],
};

View File

@ -0,0 +1,252 @@
import { ListGroup } from '@bpmn-io/properties-panel';
import { is, isAny } from 'bpmn-js/lib/util/ModelUtil';
import scriptGroup, { SCRIPT_TYPE } from './SpiffScriptGroup';
import { SpiffExtensionCalledDecision } from './SpiffExtensionCalledDecision';
import { SpiffExtensionTextInput } from './SpiffExtensionTextInput';
import { SpiffExtensionInstructionsForEndUser } from './SpiffExtensionInstructionsForEndUser';
import {
ServiceTaskParameterArray,
ServiceTaskOperatorSelect, ServiceTaskResultTextInput,
} from './SpiffExtensionServiceProperties';
const LOW_PRIORITY = 500;
export default function ExtensionsPropertiesProvider(
propertiesPanel,
translate,
moddle,
commandStack,
elementRegistry
) {
this.getGroups = function (element) {
return function (groups) {
if (is(element, 'bpmn:ScriptTask')) {
groups.push(
createScriptGroup(element, translate, moddle, commandStack)
);
} else if (
isAny(element, ['bpmn:Task', 'bpmn:CallActivity', 'bpmn:SubProcess'])
) {
groups.push(preScriptPostScriptGroup(element, translate, moddle));
}
if (is(element, 'bpmn:UserTask')) {
groups.push(createUserGroup(element, translate, moddle, commandStack));
}
if (is(element, 'bpmn:BusinessRuleTask')) {
groups.push(
createBusinessRuleGroup(element, translate, moddle, commandStack)
);
}
if (is(element, 'bpmn:ManualTask')) {
groups.push(
createManualTaskPropertiesGroup(
element,
translate,
moddle,
commandStack
)
);
}
if (is(element, 'bpmn:ServiceTask')) {
groups.push(
createServiceGroup(element, translate, moddle, commandStack)
);
}
return groups;
};
};
propertiesPanel.registerProvider(LOW_PRIORITY, this);
}
ExtensionsPropertiesProvider.$inject = [
'propertiesPanel',
'translate',
'moddle',
'commandStack',
'elementRegistry',
];
/**
* Adds a group to the properties panel for the script task that allows you
* to set the script.
* @param element
* @param translate
* @returns The components to add to the properties panel. */
function createScriptGroup(element, translate, moddle, commandStack) {
return {
id: 'spiff_script',
label: translate('Script'),
entries: scriptGroup({
element,
moddle,
scriptType: SCRIPT_TYPE.bpmn,
label: 'Script',
description: 'Code to execute.',
translate,
commandStack,
}),
};
}
/**
* Adds a section to the properties' panel for NON-Script tasks, so that
* you can define a pre-script and a post-script for modifying data as it comes and out.
* @param element
* @param translate
* @param moddle For altering the underlying XML File.
* @returns The components to add to the properties panel.
*/
function preScriptPostScriptGroup(element, translate, moddle) {
return {
id: 'spiff_pre_post_scripts',
label: translate('SpiffWorkflow Scripts'),
entries: [
...scriptGroup({
element,
moddle,
translate,
scriptType: SCRIPT_TYPE.pre,
label: 'Pre-Script',
description: 'code to execute prior to this task.',
}),
...scriptGroup({
element,
moddle,
translate,
scriptType: SCRIPT_TYPE.post,
label: 'Post-Script',
description: 'code to execute after this task.',
}),
],
};
}
/**
* 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 createUserGroup(element, translate, moddle, commandStack) {
return {
id: 'user_task_properties',
label: translate('SpiffWorkflow Web Form'),
entries: [
{
element,
moddle,
commandStack,
component: SpiffExtensionTextInput,
label: translate('JSON Schema Filename'),
description: translate('RJSF Json Data Structure Filename'),
name: 'formJsonSchemaFilename',
},
{
element,
moddle,
commandStack,
component: SpiffExtensionTextInput,
label: translate('UI Schema Filename'),
description: translate('RJSF User Interface Filename'),
name: 'formUiSchemaFilename',
},
],
};
}
/**
* Create a group on the main panel with a text box (for choosing the dmn to connect)
* @param element
* @param translate
* @param moddle
* @returns entries
*/
function createBusinessRuleGroup(element, translate, moddle, commandStack) {
return {
id: 'business_rule_properties',
label: translate('Business Rule Properties'),
entries: [
{
element,
moddle,
commandStack,
component: SpiffExtensionCalledDecision,
label: translate('Decision Id'),
description: translate('Id of the decision'),
},
],
};
}
/**
* Create a group on the main panel with a text box (for choosing the information to display to the user)
* @param element
* @param translate
* @param moddle
* @returns entries
*/
function createManualTaskPropertiesGroup(
element,
translate,
moddle,
commandStack
) {
return {
id: 'manual_task_properties',
label: translate('Manual Task Properties'),
entries: [
{
element,
moddle,
commandStack,
component: SpiffExtensionInstructionsForEndUser,
label: translate('Instructions For End User'),
description: translate(
'The instructions to show the user(s) who are responsible for completing the task.'
),
},
],
};
}
/**
* Create a group on the main panel with a text box (for choosing the dmn to connect)
* @param element
* @param translate
* @param moddle
* @returns entries
*/
function createServiceGroup(element, translate, moddle, commandStack) {
return {
id: 'service_task_properties',
label: translate('Spiffworkflow Service Properties'),
entries: [
{
element,
moddle,
commandStack,
component: ServiceTaskOperatorSelect,
translate,
},
{
element,
moddle,
commandStack,
component: ServiceTaskResultTextInput,
translate,
},
{
id: 'serviceTaskParameters',
label: translate('Parameters'),
component: ListGroup,
...ServiceTaskParameterArray({
element,
moddle,
translate,
}),
},
],
};
}

View File

@ -0,0 +1,227 @@
import { useService } from 'bpmn-js-properties-panel';
import { TextFieldEntry, TextAreaEntry } from '@bpmn-io/properties-panel';
import {
removeFirstInstanceOfItemFromArrayInPlace,
removeExtensionElementsIfEmpty,
} from '../../helpers';
const getScriptUnitTestsModdleElement = (shapeElement) => {
const bizObj = shapeElement.businessObject;
if (!bizObj.extensionElements) {
return null;
}
if (!bizObj.extensionElements.values) {
return null;
}
return bizObj.extensionElements
.get('values')
.filter(function getInstanceOfType(e) {
return e.$instanceOf('spiffworkflow:unitTests');
})[0];
};
const getScriptUnitTestModdleElements = (shapeElement) => {
const scriptUnitTestsModdleElement =
getScriptUnitTestsModdleElement(shapeElement);
if (scriptUnitTestsModdleElement) {
return scriptUnitTestsModdleElement.unitTests || [];
}
return [];
};
/**
* Provides a list of data objects, and allows you to add / remove data objects, and change their ids.
* @param props
* @constructor
*/
export function ScriptUnitTestArray(props) {
const { element, moddle, commandStack, translate } = props;
const scriptUnitTestModdleElements = getScriptUnitTestModdleElements(element);
const items = scriptUnitTestModdleElements.map(
(scriptUnitTestModdleElement, index) => {
const id = `scriptUnitTest-${index}`;
return {
id,
label: scriptUnitTestModdleElement.id,
entries: scriptUnitTestGroup({
idPrefix: id,
element,
scriptUnitTestModdleElement,
commandStack,
translate,
}),
remove: removeFactory({
element,
scriptUnitTestModdleElement,
commandStack,
moddle,
}),
autoFocusEntry: id,
};
}
);
function add(event) {
event.stopPropagation();
const scriptTaskModdleElement = element.businessObject;
if (!scriptTaskModdleElement.extensionElements) {
scriptTaskModdleElement.extensionElements =
scriptTaskModdleElement.$model.create('bpmn:ExtensionElements');
}
let scriptUnitTestsModdleElement = getScriptUnitTestsModdleElement(element);
if (!scriptUnitTestsModdleElement) {
scriptUnitTestsModdleElement = scriptTaskModdleElement.$model.create(
'spiffworkflow:unitTests'
);
scriptTaskModdleElement.extensionElements
.get('values')
.push(scriptUnitTestsModdleElement);
}
const scriptUnitTestModdleElement = scriptTaskModdleElement.$model.create(
'spiffworkflow:unitTest'
);
const scriptUnitTestInputModdleElement =
scriptTaskModdleElement.$model.create('spiffworkflow:inputJson');
const scriptUnitTestOutputModdleElement =
scriptTaskModdleElement.$model.create('spiffworkflow:expectedOutputJson');
scriptUnitTestModdleElement.id = moddle.ids.nextPrefixed('ScriptUnitTest_');
scriptUnitTestInputModdleElement.value = '{}';
scriptUnitTestOutputModdleElement.value = '{}';
scriptUnitTestModdleElement.inputJson = scriptUnitTestInputModdleElement;
scriptUnitTestModdleElement.expectedOutputJson =
scriptUnitTestOutputModdleElement;
if (!scriptUnitTestsModdleElement.unitTests) {
scriptUnitTestsModdleElement.unitTests = [];
}
scriptUnitTestsModdleElement.unitTests.push(scriptUnitTestModdleElement);
commandStack.execute('element.updateProperties', {
element,
properties: {},
});
}
return { items, add };
}
function removeFactory(props) {
const { element, scriptUnitTestModdleElement, commandStack } = props;
return function (event) {
event.stopPropagation();
const scriptUnitTestsModdleElement =
getScriptUnitTestsModdleElement(element);
removeFirstInstanceOfItemFromArrayInPlace(
scriptUnitTestsModdleElement.unitTests,
scriptUnitTestModdleElement
);
if (scriptUnitTestsModdleElement.unitTests.length < 1) {
const scriptTaskModdleElement = element.businessObject;
removeFirstInstanceOfItemFromArrayInPlace(
scriptTaskModdleElement.extensionElements.values,
scriptUnitTestsModdleElement
);
removeExtensionElementsIfEmpty(scriptTaskModdleElement);
}
commandStack.execute('element.updateProperties', {
element,
properties: {},
});
};
}
// <spiffworkflow:unitTests>
// <spiffworkflow:unitTest id="test1">
// <spiffworkflow:inputJson>{}</spiffworkflow:inputJson>
// <spiffworkflow:expectedOutputJson>{}</spiffworkflow:expectedOutputJson>
// </spiffworkflow:unitTest>
// </spiffworkflow:unitTests>
function scriptUnitTestGroup(props) {
const {
idPrefix,
element,
scriptUnitTestModdleElement,
commandStack,
translate,
} = props;
return [
{
id: `${idPrefix}-id`,
label: translate('ID:'),
element,
component: ScriptUnitTestIdTextField,
scriptUnitTestModdleElement,
commandStack,
},
{
id: `${idPrefix}-input`,
label: translate('Input Json:'),
element,
component: ScriptUnitTestJsonTextArea,
scriptUnitTestJsonModdleElement: scriptUnitTestModdleElement.inputJson,
commandStack,
},
{
id: `${idPrefix}-expected-output`,
label: translate('Expected Output Json:'),
element,
component: ScriptUnitTestJsonTextArea,
scriptUnitTestJsonModdleElement:
scriptUnitTestModdleElement.expectedOutputJson,
commandStack,
},
];
}
function ScriptUnitTestJsonTextArea(props) {
const { id, element, scriptUnitTestJsonModdleElement, label } = props;
const debounce = useService('debounceInput');
const setValue = (value) => {
scriptUnitTestJsonModdleElement.value = value;
};
const getValue = () => {
return scriptUnitTestJsonModdleElement.value;
};
return TextAreaEntry({
element,
id: `${id}-textArea`,
getValue,
setValue,
debounce,
label,
});
}
function ScriptUnitTestIdTextField(props) {
const { id, element, scriptUnitTestModdleElement, label } = props;
const debounce = useService('debounceInput');
const commandStack = useService('commandStack');
const setValue = (value) => {
scriptUnitTestModdleElement.id = value;
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: scriptUnitTestModdleElement,
properties: {},
});
};
const getValue = () => {
return scriptUnitTestModdleElement.id;
};
return TextFieldEntry({
element,
id: `${id}-textArea`,
getValue,
setValue,
debounce,
label,
});
}

View File

@ -0,0 +1,81 @@
import { useService } from 'bpmn-js-properties-panel';
import { TextFieldEntry } from '@bpmn-io/properties-panel';
const SPIFF_PROP = 'spiffworkflow:calledDecisionId';
/**
* 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.
*
*
<bpmn:businessRuleTask id="Activity_0t218za">
<bpmn:extensionElements>
<spiffworkflow:calledDecisionId>my_id</spiffworkflow:calledDecisionId>
</bpmn:extensionElements>
</bpmn:businessRuleTask>
*
* @returns {string|null|*}
*/
export function SpiffExtensionCalledDecision(props) {
const { element } = props;
const { commandStack } = props;
const { moddle } = props;
const { label } = props;
const { description } = props;
const debounce = useService('debounceInput');
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,
},
});
};
return (
<TextFieldEntry
id="extension_called_decision"
element={element}
description={description}
label={label}
getValue={getValue}
setValue={setValue}
debounce={debounce}
/>
);
}

View File

@ -0,0 +1,73 @@
import {useService } from 'bpmn-js-properties-panel';
import { 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|*}
*/
export function SpiffExtensionInstructionsForEndUser(props) {
const element = props.element;
const commandStack = props.commandStack, moddle = props.moddle;
const label = props.label, description = props.description;
const debounce = useService('debounceInput');
const getPropertyObject = () => {
const bizObj = element.businessObject;
if (!bizObj.extensionElements) {
return null;
} else {
return bizObj.extensionElements.get("values").filter(function (e) {
return e.$instanceOf(SPIFF_PROP)
})[0];
}
}
const getValue = () => {
const property = getPropertyObject()
if (property) {
return property.instructionsForEndUser;
}
return ""
}
const setValue = value => {
let property = getPropertyObject()
let businessObject = element.businessObject;
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
}
});
};
return TextAreaEntry({
id: 'extension_instruction_for_end_user',
element: element,
description: description,
label: label,
getValue: getValue,
setValue: setValue,
debounce: debounce,
})
}

View File

@ -0,0 +1,289 @@
import { useService } from 'bpmn-js-properties-panel';
import { TextFieldEntry, SelectEntry } from '@bpmn-io/properties-panel';
import { SPIFFWORKFLOW_XML_NAMESPACE } from '../../constants';
let serviceTaskOperators = [];
// This stores the parameters for a given service task operator
// so that we can remember the values when switching between them
// the values should be the list of moddle elements that we push onto
// the parameterList of service task operator and the key should be
// the service task operator id
const previouslyUsedServiceTaskParameterValuesHash = {};
const LOW_PRIORITY = 500;
const SERVICE_TASK_OPERATOR_ELEMENT_NAME = `${SPIFFWORKFLOW_XML_NAMESPACE}:serviceTaskOperator`;
const SERVICE_TASK_PARAMETERS_ELEMENT_NAME = `${SPIFFWORKFLOW_XML_NAMESPACE}:parameters`;
const SERVICE_TASK_PARAMETER_ELEMENT_NAME = `${SPIFFWORKFLOW_XML_NAMESPACE}:parameter`;
/**
* 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.
*
*
<bpmn:serviceTask id="service_task_one" name="Service Task One">
<bpmn:extensionElements>
<spiffworkflow:serviceTaskOperator id="SlackWebhookOperator" resultVariable="result">
<spiffworkflow:parameters>
<spiffworkflow:parameter name="webhook_token" type="string" value="token" />
<spiffworkflow:parameter name="message" type="string" value="ServiceTask testing" />
<spiffworkflow:parameter name="channel" type="string" value="#" />
</spiffworkflow:parameters>
</spiffworkflow:serviceTaskOperator>
</bpmn:extensionElements>
</bpmn:serviceTask>
*
* @returns {string|null|*}
*/
function requestServiceTaskOperators(eventBus, element, commandStack) {
eventBus.fire('spiff.service_tasks.requested', { eventBus });
eventBus.on('spiff.service_tasks.returned', (event) => {
if (event.serviceTaskOperators.length > 0) {
serviceTaskOperators = event.serviceTaskOperators.sort((a, b) =>
a.id.localeCompare(b.id)
);
commandStack.execute('element.updateProperties', {
element,
properties: {},
});
}
});
}
function getServiceTaskOperatorModdleElement(shapeElement) {
const { extensionElements } = shapeElement.businessObject;
if (extensionElements) {
for (const ee of extensionElements.values) {
if (ee.$type === SERVICE_TASK_OPERATOR_ELEMENT_NAME) {
return ee;
}
}
}
return null;
}
function getServiceTaskParameterModdleElements(shapeElement) {
const serviceTaskOperatorModdleElement =
getServiceTaskOperatorModdleElement(shapeElement);
if (serviceTaskOperatorModdleElement) {
const { parameterList } = serviceTaskOperatorModdleElement;
if (parameterList) {
return parameterList.parameters.sort((a, b) => a.id.localeCompare(b.id));
}
}
return [];
}
export function ServiceTaskOperatorSelect(props) {
const { element } = props;
const { commandStack } = props;
const { translate } = props;
const { moddle } = props;
const debounce = useService('debounceInput');
const eventBus = useService('eventBus');
if (serviceTaskOperators.length === 0) {
requestServiceTaskOperators(eventBus, element, commandStack);
}
const getValue = () => {
const serviceTaskOperatorModdleElement =
getServiceTaskOperatorModdleElement(element);
if (serviceTaskOperatorModdleElement) {
return serviceTaskOperatorModdleElement.id;
}
return '';
};
const setValue = (value) => {
if (!value) {
return;
}
const serviceTaskOperator = serviceTaskOperators.find(
(sto) => sto.id === value
);
if (!serviceTaskOperator) {
console.error(`Could not find service task operator with id: ${value}`);
return;
}
const previouslyUsedServiceTaskParameterValues =
previouslyUsedServiceTaskParameterValuesHash[value];
const { businessObject } = element;
let extensions = businessObject.extensionElements;
if (!extensions) {
extensions = moddle.create('bpmn:ExtensionElements');
}
const oldServiceTaskOperatorModdleElement =
getServiceTaskOperatorModdleElement(element);
const newServiceTaskOperatorModdleElement = moddle.create(
SERVICE_TASK_OPERATOR_ELEMENT_NAME
);
newServiceTaskOperatorModdleElement.id = value;
let newParameterList;
if (previouslyUsedServiceTaskParameterValues) {
newParameterList = previouslyUsedServiceTaskParameterValues;
} else {
newParameterList = moddle.create(SERVICE_TASK_PARAMETERS_ELEMENT_NAME);
newParameterList.parameters = [];
serviceTaskOperator.parameters.forEach((stoParameter) => {
const newParameterModdleElement = moddle.create(
SERVICE_TASK_PARAMETER_ELEMENT_NAME
);
newParameterModdleElement.id = stoParameter.id;
newParameterModdleElement.type = stoParameter.type;
newParameterList.parameters.push(newParameterModdleElement);
});
previouslyUsedServiceTaskParameterValuesHash[value] = newParameterList;
if (oldServiceTaskOperatorModdleElement) {
previouslyUsedServiceTaskParameterValuesHash[
oldServiceTaskOperatorModdleElement.id
] = oldServiceTaskOperatorModdleElement.parameterList;
}
}
newServiceTaskOperatorModdleElement.parameterList = newParameterList;
const newExtensionValues = extensions.get('values').filter((extValue) => {
return extValue.$type !== SERVICE_TASK_OPERATOR_ELEMENT_NAME;
});
newExtensionValues.push(newServiceTaskOperatorModdleElement);
extensions.values = newExtensionValues;
businessObject.extensionElements = extensions;
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: businessObject,
properties: {},
});
};
const getOptions = () => {
const optionList = [];
if (serviceTaskOperators) {
serviceTaskOperators.forEach((sto) => {
optionList.push({
label: sto.id,
value: sto.id,
});
});
}
return optionList;
};
return SelectEntry({
id: 'selectOperatorId',
element,
label: translate('Operator ID'),
getValue,
setValue,
getOptions,
debounce,
});
}
export function ServiceTaskParameterArray(props) {
const { element, commandStack } = props;
const serviceTaskParameterModdleElements =
getServiceTaskParameterModdleElements(element);
const items = serviceTaskParameterModdleElements.map(
(serviceTaskParameterModdleElement, index) => {
const id = `serviceTaskParameter-${index}`;
return {
id,
label: serviceTaskParameterModdleElement.id,
entries: serviceTaskParameterEntries({
idPrefix: id,
element,
serviceTaskParameterModdleElement,
commandStack,
}),
autoFocusEntry: id,
};
}
);
return { items };
}
function serviceTaskParameterEntries(props) {
const { idPrefix, serviceTaskParameterModdleElement, commandStack } = props;
return [
{
idPrefix: `${idPrefix}-parameter`,
component: ServiceTaskParameterTextField,
serviceTaskParameterModdleElement,
commandStack,
},
];
}
function ServiceTaskParameterTextField(props) {
const { idPrefix, element, serviceTaskParameterModdleElement } = props;
const debounce = useService('debounceInput');
const setValue = (value) => {
serviceTaskParameterModdleElement.value = value;
};
const getValue = () => {
return serviceTaskParameterModdleElement.value;
};
return TextFieldEntry({
element,
id: `${idPrefix}-textField`,
getValue,
setValue,
debounce,
});
}
export function ServiceTaskResultTextInput(props) {
const { element, translate, commandStack } = props;
const debounce = useService('debounceInput');
const serviceTaskOperatorModdleElement =
getServiceTaskOperatorModdleElement(element);
const setValue = (value) => {
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: serviceTaskOperatorModdleElement,
properties: {
resultVariable: value,
},
});
};
const getValue = () => {
if (serviceTaskOperatorModdleElement) {
return serviceTaskOperatorModdleElement.resultVariable;
}
return '';
};
if (serviceTaskOperatorModdleElement) {
return TextFieldEntry({
element,
label: translate('Response Variable'),
description: translate(
'response will be saved to this variable. Leave empty to discard the response.'
),
id: `result-textField`,
getValue,
setValue,
debounce,
});
}
return null;
}

View File

@ -0,0 +1,99 @@
import {useService } from 'bpmn-js-properties-panel';
import { TextFieldEntry } from '@bpmn-io/properties-panel';
const SPIFF_PARENT_PROP = "spiffworkflow:properties"
const SPIFF_PROP = "spiffworkflow:property"
/**
* 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.
*
<bpmn:extensionElements>
<spiffworkflow:properties>
<spiffworkflow:property name="formJsonSchemaFilename" value="json_schema.json" />
<spiffworkflow:property name="formUiSchemaFilename" value="ui_schema.json" />
</spiffworkflow:properties>
</bpmn:extensionElements>
*
* @returns {string|null|*}
*/
export function SpiffExtensionTextInput(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 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()
if (property) {
return property.value;
}
return ""
}
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
}
});
};
return <TextFieldEntry
id={'extension_' + name}
element={element}
description={description}
label={label}
getValue={getValue}
setValue={setValue}
debounce={debounce}
/>;
}

View File

@ -0,0 +1,163 @@
import {
HeaderButton,
TextAreaEntry,
isTextFieldEntryEdited,
ListGroup,
} from '@bpmn-io/properties-panel';
import { useService } from 'bpmn-js-properties-panel';
import { ScriptUnitTestArray } from './ScriptUnitTestArray';
export const SCRIPT_TYPE = {
bpmn: 'bpmn:script',
pre: 'spiffworkflow:preScript',
post: 'spiffworkflow:postScript',
};
function PythonScript(props) {
const { element, id } = props;
const { type } = props;
const { moddle } = props;
const { label } = props;
const { description } = props;
const translate = useService('translate');
const debounce = useService('debounceInput');
/**
* Finds the value of the given type within the extensionElements
* given a type of "spiff:preScript", would find it in this, and return
* the object.
*
* <bpmn:
<bpmn:userTask id="123" name="My User Task!">
<bpmn:extensionElements>
<spiff:preScript>
me = "100% awesome"
</spiff:preScript>
</bpmn:extensionElements>
...
</bpmn:userTask>
*
* @returns {string|null|*}
*/
const getScriptObject = () => {
const bizObj = element.businessObject;
if (type === SCRIPT_TYPE.bpmn) {
return bizObj;
}
if (!bizObj.extensionElements) {
return null;
}
return bizObj.extensionElements
.get('values')
.filter(function getInstanceOfType(e) {
return e.$instanceOf(type);
})[0];
};
const getValue = () => {
const scriptObj = getScriptObject();
if (scriptObj) {
return scriptObj.script;
}
return '';
};
const setValue = (value) => {
const { businessObject } = element;
let scriptObj = getScriptObject();
// Create the script object if needed.
if (!scriptObj) {
scriptObj = moddle.create(type);
if (type !== SCRIPT_TYPE.bpmn) {
if (!businessObject.extensionElements) {
businessObject.extensionElements = moddle.create(
'bpmn:ExtensionElements'
);
}
businessObject.extensionElements.get('values').push(scriptObj);
}
}
scriptObj.script = value;
};
return TextAreaEntry({
id,
element,
description: translate(description),
label: translate(label),
getValue,
setValue,
debounce,
});
}
function LaunchEditorButton(props) {
const { element, type } = props;
const eventBus = useService('eventBus');
// fixme: add a call up date as a property
return HeaderButton({
className: 'spiffworkflow-properties-panel-button',
onClick: () => {
eventBus.fire('launch.script.editor', { element, type });
},
children: 'Launch Editor',
});
}
/**
* Generates a python script.
* @param element The elemment that should get the script task.
* @param scriptType The type of script -- can be a preScript, postScript or a BPMN:Script for script tags
* @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,
scriptType,
label,
description,
translate,
commandStack,
} = props;
const entries = [
{
id: `pythonScript_${scriptType}`,
element,
type: scriptType,
component: PythonScript,
isEdited: isTextFieldEntryEdited,
moddle,
label,
description,
},
{
id: `launchEditorButton${scriptType}`,
type: scriptType,
element,
component: LaunchEditorButton,
isEdited: isTextFieldEntryEdited,
moddle,
},
];
// do not support testing pre and post scripts at the moment
if (scriptType === SCRIPT_TYPE.bpmn) {
entries.push({
id: `scriptUnitTests${scriptType}`,
label: translate('Unit Tests'),
component: ListGroup,
...ScriptUnitTestArray({
element,
moddle,
translate,
commandStack,
}),
});
}
return entries;
}

View File

@ -0,0 +1,14 @@
// https://stackoverflow.com/a/5767357/6090676
export function removeFirstInstanceOfItemFromArrayInPlace(arr, value) {
const index = arr.indexOf(value);
if (index > -1) {
arr.splice(index, 1);
}
return arr;
}
export function removeExtensionElementsIfEmpty(moddleElement) {
if (moddleElement.extensionElements.values.length < 1) {
moddleElement.extensionElements = null;
}
}

View File

@ -0,0 +1,40 @@
import RulesModule from 'diagram-js/lib/features/rules';
import IoPalette from './InputOutput/IoPalette';
import IoRules from './InputOutput/IoRules';
import IoInterceptor from './InputOutput/IoInterceptor';
import DataObjectInterceptor from './DataObject/DataObjectInterceptor';
import DataObjectRules from './DataObject/DataObjectRules';
import DataObjectRenderer from './DataObject/DataObjectRenderer';
import DataObjectPropertiesProvider from './DataObject/propertiesPanel/DataObjectPropertiesProvider';
import ConditionsPropertiesProvider from './conditions/propertiesPanel/ConditionsPropertiesProvider';
import ExtensionsPropertiesProvider from './extensions/propertiesPanel/ExtensionsPropertiesProvider';
import MessagesPropertiesProvider from './messages/propertiesPanel/MessagesPropertiesProvider';
import CallActivityPropertiesProvider from './callActivity/propertiesPanel/CallActivityPropertiesProvider';
export default {
__depends__: [RulesModule],
__init__: [
'dataObjectInterceptor',
'dataObjectRules',
'dataObjectPropertiesProvider',
'conditionsPropertiesProvider',
'extensionsPropertiesProvider',
'messagesPropertiesProvider',
'callActivityPropertiesProvider',
'ioPalette',
'ioRules',
'ioInterceptor',
'dataObjectRenderer',
],
dataObjectInterceptor: ['type', DataObjectInterceptor],
dataObjectRules: ['type', DataObjectRules],
dataObjectRenderer: ['type', DataObjectRenderer],
dataObjectPropertiesProvider: ['type', DataObjectPropertiesProvider],
conditionsPropertiesProvider: ['type', ConditionsPropertiesProvider],
extensionsPropertiesProvider: ['type', ExtensionsPropertiesProvider],
messagesPropertiesProvider: ['type', MessagesPropertiesProvider],
callActivityPropertiesProvider: ['type', CallActivityPropertiesProvider],
ioPalette: ['type', IoPalette],
ioRules: ['type', IoRules],
ioInterceptor: ['type', IoInterceptor],
};

View File

@ -0,0 +1,187 @@
import { is } from 'bpmn-js/lib/util/ModelUtil';
/**
* loops up until it can find the root.
* @param element
*/
export function getRoot(businessObject, moddle) {
// HACK: get the root element. need a more formal way to do this
if (moddle) {
for (const elementId in moddle.ids._seed.hats) {
if (elementId.startsWith('Definitions_')) {
return moddle.ids._seed.hats[elementId];
}
}
} else {
// todo: Do we want businessObject to be a shape or moddle object?
if (businessObject.$type === 'bpmn:Definitions') {
return businessObject;
}
if (typeof businessObject.$parent !== 'undefined') {
return getRoot(businessObject.$parent);
}
}
return businessObject;
}
export function isMessageElement(shapeElement) {
return (
is(shapeElement, 'bpmn:SendTask') ||
is(shapeElement, 'bpmn:ReceiveTask') ||
isMessageEvent(shapeElement)
);
}
export function isMessageEvent(shapeElement) {
const { eventDefinitions } = shapeElement.businessObject;
if (eventDefinitions && eventDefinitions[0]) {
return eventDefinitions[0].$type === 'bpmn:MessageEventDefinition';
}
return false;
}
export function canReceiveMessage(shapeElement) {
if (is(shapeElement, 'bpmn:ReceiveTask')) {
return true;
}
if (isMessageEvent(shapeElement)) {
return (
is(shapeElement, 'bpmn:StartEvent') || is(shapeElement, 'bpmn:CatchEvent')
);
}
return false;
}
export function getMessageRefElement(shapeElement) {
if (isMessageEvent(shapeElement)) {
const messageEventDefinition =
shapeElement.businessObject.eventDefinitions[0];
if (messageEventDefinition && messageEventDefinition.messageRef) {
return messageEventDefinition.messageRef;
}
} else if (
isMessageElement(shapeElement) &&
shapeElement.businessObject.messageRef
) {
return shapeElement.businessObject.messageRef;
}
return null;
}
export function findCorrelationKeyForCorrelationProperty(shapeElement, moddle) {
const correlationKeyElements = findCorrelationKeys(shapeElement, moddle);
for (const cke of correlationKeyElements) {
if (cke.correlationPropertyRef) {
for (const correlationPropertyRef of cke.correlationPropertyRef) {
if (correlationPropertyRef.id === shapeElement.id) {
return cke;
}
}
}
}
return null;
}
export function findCorrelationPropertiesAndRetrievalExpressionsForMessage(
shapeElement
) {
const formalExpressions = [];
const messageRefElement = getMessageRefElement(shapeElement);
if (messageRefElement) {
const root = getRoot(shapeElement.businessObject);
if (root.$type === 'bpmn:Definitions') {
for (const childElement of root.rootElements) {
if (childElement.$type === 'bpmn:CorrelationProperty') {
const retrievalExpression =
getRetrievalExpressionFromCorrelationProperty(
childElement,
messageRefElement
);
if (retrievalExpression) {
const formalExpression = {
correlationPropertyModdleElement: childElement,
correlationPropertyRetrievalExpressionModdleElement:
retrievalExpression,
};
formalExpressions.push(formalExpression);
}
}
}
}
}
return formalExpressions;
}
export function getMessageElementForShapeElement(shapeElement) {
const { businessObject } = shapeElement;
const taskMessage = getMessageRefElement(shapeElement);
const messages = findMessageModdleElements(businessObject);
if (taskMessage) {
for (const message of messages) {
if (message.id === taskMessage.id) {
return message;
}
}
}
return null;
}
function getRetrievalExpressionFromCorrelationProperty(
correlationProperty,
message
) {
if (correlationProperty.correlationPropertyRetrievalExpression) {
for (const retrievalExpression of correlationProperty.correlationPropertyRetrievalExpression) {
if (
retrievalExpression.$type ===
'bpmn:CorrelationPropertyRetrievalExpression' &&
retrievalExpression.messageRef &&
retrievalExpression.messageRef.id === message.id
) {
return retrievalExpression;
}
}
}
return null;
}
export function findCorrelationProperties(businessObject, moddle) {
const root = getRoot(businessObject, moddle);
const correlationProperties = [];
for (const rootElement of root.rootElements) {
if (rootElement.$type === 'bpmn:CorrelationProperty') {
correlationProperties.push(rootElement);
}
}
return correlationProperties;
}
export function findCorrelationKeys(businessObject, moddle) {
const root = getRoot(businessObject, moddle);
const correlationKeys = [];
if (root.rootElements) {
for (const rootElement of root.rootElements) {
if (rootElement.$type === 'bpmn:Collaboration') {
const currentKeys = rootElement.correlationKeys;
for (const correlationKey in currentKeys) {
const currentCorrelation = rootElement.correlationKeys[correlationKey];
correlationKeys.push(currentCorrelation);
}
}
}
}
return correlationKeys;
}
export function findMessageModdleElements(businessObject) {
const messages = [];
const root = getRoot(businessObject);
if (root.rootElements) {
for (const rootElement of root.rootElements) {
if (rootElement.$type === 'bpmn:Message') {
messages.push(rootElement);
}
}
}
return messages;
}

View File

@ -0,0 +1,6 @@
import MessagesPropertiesProvider from './propertiesPanel/MessagesPropertiesProvider';
export default {
__init__: ['messagesPropertiesProvider'],
messagesPropertiesProvider: ['type', MessagesPropertiesProvider],
};

View File

@ -0,0 +1,143 @@
import { useService } from 'bpmn-js-properties-panel';
import { SimpleEntry, TextFieldEntry } from '@bpmn-io/properties-panel';
import { findCorrelationKeys, getRoot } from '../MessageHelpers';
import { removeFirstInstanceOfItemFromArrayInPlace } from '../../helpers';
/**
* Provides a list of data objects, and allows you to add / remove data objects, and change their ids.
* @param props
* @constructor
*/
export function CorrelationKeysArray(props) {
const { element, moddle, commandStack } = props;
const correlationKeyElements = findCorrelationKeys(element.businessObject);
const items = correlationKeyElements.map((correlationKeyElement, index) => {
const id = `correlationGroup-${index}`;
return {
id,
label: correlationKeyElement.name,
entries: correlationGroup({
idPrefix: id,
element,
correlationKeyElement,
commandStack,
}),
remove: removeFactory({
element,
correlationKeyElement,
commandStack,
moddle,
}),
autoFocusEntry: id,
};
});
function add(event) {
event.stopPropagation();
if (element.type === 'bpmn:Collaboration') {
const newCorrelationKeyElement = moddle.create('bpmn:CorrelationKey');
newCorrelationKeyElement.name =
moddle.ids.nextPrefixed('CorrelationKey_');
const currentCorrelationKeyElements =
element.businessObject.get('correlationKeys');
currentCorrelationKeyElements.push(newCorrelationKeyElement);
commandStack.execute('element.updateProperties', {
element,
properties: {}
});
}
}
return { items, add };
}
function removeFactory(props) {
const { element, correlationKeyElement, moddle, commandStack } = props;
return function (event) {
event.stopPropagation();
const currentCorrelationKeyElements =
element.businessObject.get('correlationKeys');
removeFirstInstanceOfItemFromArrayInPlace(
currentCorrelationKeyElements,
correlationKeyElement
);
commandStack.execute('element.updateProperties', {
element,
properties: {},
});
};
}
// <bpmn:correlationKey name="lover"> <--- The correlationGroup
// <bpmn:correlationPropertyRef>lover_name</bpmn:correlationPropertyRef>
// <bpmn:correlationPropertyRef>lover_instrument</bpmn:correlationPropertyRef>
// </bpmn:correlationKey>
// <bpmn:correlationKey name="singer" />
function correlationGroup(props) {
const { idPrefix, correlationKeyElement, commandStack } = props;
const entries = [
{
id: `${idPrefix}-key`,
component: CorrelationKeyTextField,
correlationKeyElement,
commandStack,
},
];
(correlationKeyElement.correlationPropertyRef || []).forEach(
(correlationProperty, index) => {
entries.push({
id: `${idPrefix}-${index}-text`,
component: CorrelationPropertyText,
correlationProperty,
});
}
);
return entries;
}
function CorrelationKeyTextField(props) {
const { id, element, correlationKeyElement, commandStack } = props;
const debounce = useService('debounceInput');
const setValue = (value) => {
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: correlationKeyElement,
properties: {
name: value,
},
});
};
const getValue = () => {
return correlationKeyElement.name;
};
return TextFieldEntry({
element,
id: `${id}-textField`,
getValue,
setValue,
debounce,
});
}
function CorrelationPropertyText(props) {
const { id, parameter, correlationProperty } = props;
const debounce = useService('debounceInput');
const getValue = () => {
return correlationProperty.id;
};
return SimpleEntry({
element: parameter,
id: `${id}-textField`,
label: correlationProperty.id,
getValue,
disabled: true,
debounce,
});
}

View File

@ -0,0 +1,334 @@
import { useService } from 'bpmn-js-properties-panel';
import {
SelectEntry,
isTextFieldEntryEdited,
TextFieldEntry,
} from '@bpmn-io/properties-panel';
import {
getRoot,
findCorrelationKeys,
findCorrelationProperties,
findCorrelationKeyForCorrelationProperty,
} from '../MessageHelpers';
import { removeFirstInstanceOfItemFromArrayInPlace } from '../../helpers';
/**
* Allows the creation, or editing of messageCorrelations at the bpmn:sendTask level of a BPMN document.
*/
export function CorrelationPropertiesArray(props) {
const { moddle } = props;
const { element } = props;
const { commandStack } = props;
const { translate } = props;
const correlationPropertyArray = findCorrelationProperties(
element.businessObject
);
const items = correlationPropertyArray.map(
(correlationPropertyModdleElement, index) => {
const id = `correlation-${index}`;
const entries = MessageCorrelationPropertyGroup({
idPrefix: id,
correlationPropertyModdleElement,
translate,
element,
commandStack,
moddle,
});
return {
id,
label: correlationPropertyModdleElement.id,
entries,
autoFocusEntry: id,
remove: removeFactory({
element,
correlationPropertyModdleElement,
commandStack,
moddle,
}),
};
}
);
function add(event) {
event.stopPropagation();
const newCorrelationPropertyElement = moddle.create(
'bpmn:CorrelationProperty'
);
const correlationPropertyId = moddle.ids.nextPrefixed(
'CorrelationProperty_'
);
newCorrelationPropertyElement.id = correlationPropertyId;
newCorrelationPropertyElement.name = correlationPropertyId;
const rootElement = getRoot(element.businessObject);
const { rootElements } = rootElement;
rootElements.push(newCorrelationPropertyElement);
const correlationKeyElements = findCorrelationKeys(
newCorrelationPropertyElement,
moddle
);
const correlationKeyElement = correlationKeyElements[0];
if (correlationKeyElement.correlationPropertyRef) {
correlationKeyElement.correlationPropertyRef.push(
newCorrelationPropertyElement
);
} else {
correlationKeyElement.correlationPropertyRef = [
newCorrelationPropertyElement,
];
}
commandStack.execute('element.updateProperties', {
element,
properties: {},
});
}
return { items, add };
}
function removeFactory(props) {
const { element, correlationPropertyModdleElement, moddle, commandStack } =
props;
return function (event) {
event.stopPropagation();
const rootElement = getRoot(element.businessObject);
const { rootElements } = rootElement;
const oldCorrelationKeyElement = findCorrelationKeyForCorrelationProperty(
correlationPropertyModdleElement,
moddle
);
if (oldCorrelationKeyElement) {
removeFirstInstanceOfItemFromArrayInPlace(
oldCorrelationKeyElement.correlationPropertyRef,
correlationPropertyModdleElement
);
}
removeFirstInstanceOfItemFromArrayInPlace(
rootElements,
correlationPropertyModdleElement
);
commandStack.execute('element.updateProperties', {
element,
properties: {
messages: rootElements,
},
});
};
}
function MessageCorrelationPropertyGroup(props) {
const {
idPrefix,
correlationPropertyModdleElement,
translate,
element,
commandStack,
moddle,
} = props;
return [
{
id: `${idPrefix}-correlation-key`,
component: MessageCorrelationKeySelect,
isEdited: isTextFieldEntryEdited,
idPrefix,
element,
correlationPropertyModdleElement,
translate,
moddle,
commandStack,
},
{
id: `${idPrefix}-correlation-property-id`,
component: CorrelationPropertyIdTextField,
isEdited: isTextFieldEntryEdited,
idPrefix,
element,
correlationPropertyModdleElement,
translate,
commandStack,
},
{
id: `${idPrefix}-correlation-property-name`,
component: CorrelationPropertyNameTextField,
isEdited: isTextFieldEntryEdited,
idPrefix,
element,
correlationPropertyModdleElement,
translate,
commandStack,
},
];
}
function MessageCorrelationKeySelect(props) {
const {
idPrefix,
correlationPropertyModdleElement,
translate,
element,
moddle,
commandStack,
} = props;
const debounce = useService('debounceInput');
const setValue = (value) => {
if (
value === 'placeholder-never-should-be-used-as-an-actual-correlation-key'
) {
return;
}
const correlationKeyElements = findCorrelationKeys(
correlationPropertyModdleElement,
moddle
);
let newCorrelationKeyElement;
for (const cke of correlationKeyElements) {
if (cke.name === value) {
newCorrelationKeyElement = cke;
}
}
const oldCorrelationKeyElement = findCorrelationKeyForCorrelationProperty(
correlationPropertyModdleElement,
moddle
);
if (newCorrelationKeyElement.correlationPropertyRef) {
newCorrelationKeyElement.correlationPropertyRef.push(
correlationPropertyModdleElement
);
} else {
newCorrelationKeyElement.correlationPropertyRef = [
correlationPropertyModdleElement,
];
}
if (oldCorrelationKeyElement) {
removeFirstInstanceOfItemFromArrayInPlace(
oldCorrelationKeyElement.correlationPropertyRef,
correlationPropertyModdleElement
);
}
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: correlationPropertyModdleElement,
properties: {},
});
};
const getValue = () => {
const correlationKeyElement = findCorrelationKeyForCorrelationProperty(
correlationPropertyModdleElement,
moddle
);
if (correlationKeyElement) {
return correlationKeyElement.name;
}
return null;
};
const getOptions = () => {
const correlationKeyElements = findCorrelationKeys(
correlationPropertyModdleElement,
moddle
);
const options = [];
if (correlationKeyElements.length === 0) {
options.push({
label: 'Please Create a Correlation Key',
value: 'placeholder-never-should-be-used-as-an-actual-correlation-key',
disabled: true,
});
}
for (const correlationKeyElement of correlationKeyElements) {
options.push({
label: correlationKeyElement.name,
value: correlationKeyElement.name,
});
}
return options;
};
return SelectEntry({
id: `${idPrefix}-select`,
element,
label: translate('Correlation Key'),
getValue,
setValue,
getOptions,
debounce,
});
}
function CorrelationPropertyIdTextField(props) {
const {
id,
element,
correlationPropertyModdleElement,
commandStack,
translate,
} = props;
const debounce = useService('debounceInput');
const setValue = (value) => {
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: correlationPropertyModdleElement,
properties: {
id: value,
},
});
};
const getValue = () => {
return correlationPropertyModdleElement.id;
};
return TextFieldEntry({
element,
id: `${id}-id-textField`,
label: translate('ID'),
getValue,
setValue,
debounce,
});
}
function CorrelationPropertyNameTextField(props) {
const {
id,
element,
correlationPropertyModdleElement,
commandStack,
translate,
} = props;
const debounce = useService('debounceInput');
const setValue = (value) => {
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: correlationPropertyModdleElement,
properties: {
name: value,
},
});
};
const getValue = () => {
return correlationPropertyModdleElement.name;
};
return TextFieldEntry({
element,
id: `${id}-name-textField`,
label: translate('Name'),
getValue,
setValue,
debounce,
});
}

View File

@ -0,0 +1,170 @@
import { useService } from 'bpmn-js-properties-panel';
import { TextFieldEntry } from '@bpmn-io/properties-panel';
import { getRoot, findMessageModdleElements } from '../MessageHelpers';
import { removeFirstInstanceOfItemFromArrayInPlace } from '../../helpers';
/**
* Provides a list of data objects, and allows you to add / remove data objects, and change their ids.
* @param props
* @constructor
*/
export function MessageArray(props) {
const { element, moddle, commandStack, translate } = props;
const messageElements = findMessageModdleElements(element.businessObject);
const items = messageElements.map((messageElement, index) => {
const id = `messageElement-${index}`;
return {
id,
label: messageElement.name,
entries: messageGroup({
idPrefix: id,
element,
messageElement,
commandStack,
translate,
}),
autoFocusEntry: id,
remove: removeFactory({
element,
messageElement,
commandStack,
moddle,
}),
};
});
function add(event) {
event.stopPropagation();
if (element.type === 'bpmn:Collaboration') {
const newMessageElement = moddle.create('bpmn:Message');
const messageId = moddle.ids.nextPrefixed('Message_');
newMessageElement.id = messageId;
newMessageElement.name = messageId;
const rootElement = getRoot(element.businessObject);
const { rootElements } = rootElement;
rootElements.push(newMessageElement);
commandStack.execute('element.updateProperties', {
element,
properties: {},
});
}
}
return { items, add };
}
function removeMessageRefs(messageElement, moddleElement) {
if (
moddleElement.messageRef &&
moddleElement.messageRef.id === messageElement.id
) {
moddleElement.messageRef = null;
} else if (moddleElement.correlationPropertyRetrievalExpression) {
moddleElement.correlationPropertyRetrievalExpression.forEach((cpre) => {
removeMessageRefs(messageElement, cpre);
});
} else if (moddleElement.flowElements) {
moddleElement.flowElements.forEach((fe) => {
removeMessageRefs(messageElement, fe);
});
} else if (moddleElement.eventDefinitions) {
moddleElement.eventDefinitions.forEach((ed) => {
removeMessageRefs(messageElement, ed);
});
}
}
function removeFactory(props) {
const { element, messageElement, commandStack } = props;
return function (event) {
event.stopPropagation();
const rootElement = getRoot(element.businessObject);
const { rootElements } = rootElement;
removeFirstInstanceOfItemFromArrayInPlace(rootElements, messageElement);
rootElements.forEach((moddleElement) => {
removeMessageRefs(messageElement, moddleElement);
});
commandStack.execute('element.updateProperties', {
element,
properties: {},
});
};
}
function messageGroup(props) {
const { messageElement, commandStack, translate, idPrefix } = props;
return [
{
id: `${idPrefix}-id`,
component: MessageIdTextField,
messageElement,
commandStack,
translate,
},
{
id: `${idPrefix}-name`,
component: MessageNameTextField,
messageElement,
commandStack,
translate,
},
];
}
function MessageIdTextField(props) {
const { id, element, messageElement, commandStack, translate } = props;
const debounce = useService('debounceInput');
const setValue = (value) => {
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: messageElement,
properties: {
id: value,
},
});
};
const getValue = () => {
return messageElement.id;
};
return TextFieldEntry({
element,
id: `${id}-id-textField`,
label: translate('ID'),
getValue,
setValue,
debounce,
});
}
function MessageNameTextField(props) {
const { id, element, messageElement, commandStack, translate } = props;
const debounce = useService('debounceInput');
const setValue = (value) => {
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: messageElement,
properties: {
name: value,
},
});
};
const getValue = () => {
return messageElement.name;
};
return TextFieldEntry({
element,
id: `${id}-name-textField`,
label: translate('Name'),
getValue,
setValue,
debounce,
});
}

View File

@ -0,0 +1,278 @@
import { useService } from 'bpmn-js-properties-panel';
import {
SelectEntry,
isTextFieldEntryEdited,
TextFieldEntry,
} from '@bpmn-io/properties-panel';
import {
findCorrelationPropertiesAndRetrievalExpressionsForMessage,
findCorrelationProperties,
getMessageRefElement,
} from '../MessageHelpers';
import { removeFirstInstanceOfItemFromArrayInPlace } from '../../helpers';
/**
* Allows the creation, or editing of messageCorrelations at the bpmn:sendTask level of a BPMN document.
*/
export function MessageCorrelationPropertiesArray(props) {
const { moddle } = props;
const { element } = props;
const { commandStack } = props;
const { translate } = props;
const correlationPropertyObjectsForCurrentMessage =
findCorrelationPropertiesAndRetrievalExpressionsForMessage(element);
const allCorrelationPropertyModdleElements = findCorrelationProperties(
element,
moddle
);
const items = correlationPropertyObjectsForCurrentMessage.map(
(correlationPropertyObject, index) => {
const {
correlationPropertyModdleElement,
correlationPropertyRetrievalExpressionModdleElement,
} = correlationPropertyObject;
const id = `correlation-${index}`;
const entries = MessageCorrelationPropertyGroup({
idPrefix: id,
correlationPropertyModdleElement,
correlationPropertyRetrievalExpressionModdleElement,
translate,
moddle,
element,
commandStack,
});
return {
id,
label: correlationPropertyModdleElement.id,
entries,
autoFocusEntry: id,
remove: removeFactory({
element,
correlationPropertyModdleElement,
correlationPropertyRetrievalExpressionModdleElement,
commandStack,
}),
};
}
);
function add(event) {
event.stopPropagation();
let correlationPropertyElement;
allCorrelationPropertyModdleElements.forEach((cpe) => {
let foundElement = false;
correlationPropertyObjectsForCurrentMessage.forEach((cpo) => {
const cpme = cpo.correlationPropertyModdleElement;
if (cpme.id === cpe.id) {
foundElement = true;
}
});
if (!foundElement) {
correlationPropertyElement = cpe;
}
});
// TODO: we should have some way to show an error if element is not found instead
// we need to check this since the code assumes each message only has one ref
// and will not display all properties if there are multiple
if (correlationPropertyElement) {
const newRetrievalExpressionElement = moddle.create(
'bpmn:CorrelationPropertyRetrievalExpression'
);
const messageRefElement = getMessageRefElement(element);
const newFormalExpression = moddle.create('bpmn:FormalExpression');
newFormalExpression.body = '';
newRetrievalExpressionElement.messageRef = messageRefElement;
newRetrievalExpressionElement.messagePath = newFormalExpression;
if (!correlationPropertyElement.correlationPropertyRetrievalExpression) {
correlationPropertyElement.correlationPropertyRetrievalExpression = [];
}
correlationPropertyElement.correlationPropertyRetrievalExpression.push(
newRetrievalExpressionElement
);
commandStack.execute('element.updateProperties', {
element,
properties: {},
});
} else {
console.error(
'ERROR: There are not any more correlation properties this message can be added to'
);
}
}
const returnObject = { items };
if (allCorrelationPropertyModdleElements.length !== 0) {
returnObject.add = add;
}
return returnObject;
}
function removeFactory(props) {
const {
element,
correlationPropertyModdleElement,
correlationPropertyRetrievalExpressionModdleElement,
commandStack,
} = props;
return function (event) {
event.stopPropagation();
removeFirstInstanceOfItemFromArrayInPlace(
correlationPropertyModdleElement.correlationPropertyRetrievalExpression,
correlationPropertyRetrievalExpressionModdleElement
);
commandStack.execute('element.updateProperties', {
element,
properties: {},
});
};
}
function MessageCorrelationPropertyGroup(props) {
const {
idPrefix,
correlationPropertyModdleElement,
correlationPropertyRetrievalExpressionModdleElement,
translate,
moddle,
element,
commandStack,
} = props;
return [
{
id: `${idPrefix}-correlation-key`,
component: MessageCorrelationPropertySelect,
isEdited: isTextFieldEntryEdited,
idPrefix,
correlationPropertyModdleElement,
correlationPropertyRetrievalExpressionModdleElement,
translate,
moddle,
element,
commandStack,
},
{
id: `${idPrefix}-expression`,
component: MessageCorrelationExpressionTextField,
isEdited: isTextFieldEntryEdited,
idPrefix,
correlationPropertyRetrievalExpressionModdleElement,
translate,
},
];
}
function MessageCorrelationPropertySelect(props) {
const {
idPrefix,
correlationPropertyModdleElement,
correlationPropertyRetrievalExpressionModdleElement,
translate,
parameter,
moddle,
element,
commandStack,
} = props;
const debounce = useService('debounceInput');
const setValue = (value) => {
const allCorrelationPropertyModdleElements = findCorrelationProperties(
correlationPropertyModdleElement,
moddle
);
const newCorrelationPropertyElement =
allCorrelationPropertyModdleElements.find((cpe) => cpe.id === value);
if (!newCorrelationPropertyElement.correlationPropertyRetrievalExpression) {
newCorrelationPropertyElement.correlationPropertyRetrievalExpression = [];
}
newCorrelationPropertyElement.correlationPropertyRetrievalExpression.push(
correlationPropertyRetrievalExpressionModdleElement
);
removeFirstInstanceOfItemFromArrayInPlace(
correlationPropertyModdleElement.correlationPropertyRetrievalExpression,
correlationPropertyRetrievalExpressionModdleElement
);
commandStack.execute('element.updateProperties', {
element,
properties: {},
});
};
const getValue = () => {
return correlationPropertyModdleElement.id;
};
const getOptions = () => {
const allCorrelationPropertyModdleElements = findCorrelationProperties(
correlationPropertyModdleElement,
moddle
);
const correlationPropertyObjectsForCurrentMessage =
findCorrelationPropertiesAndRetrievalExpressionsForMessage(element);
const options = [];
for (const cpe of allCorrelationPropertyModdleElements) {
const foundElement = correlationPropertyObjectsForCurrentMessage.find(
(cpo) => {
const cpme = cpo.correlationPropertyModdleElement;
return cpme.id === cpe.id;
}
);
if (
!foundElement ||
foundElement.correlationPropertyModdleElement ===
correlationPropertyModdleElement
) {
options.push({
label: cpe.name,
value: cpe.id,
});
}
}
return options;
};
return SelectEntry({
id: `${idPrefix}-select`,
element: parameter,
label: translate('Correlation Property'),
getValue,
setValue,
getOptions,
debounce,
});
}
function MessageCorrelationExpressionTextField(props) {
const {
idPrefix,
parameter,
correlationPropertyRetrievalExpressionModdleElement,
translate,
} = props;
const debounce = useService('debounceInput');
const setValue = (value) => {
correlationPropertyRetrievalExpressionModdleElement.messagePath.body =
value;
};
const getValue = (_parameter) => {
return correlationPropertyRetrievalExpressionModdleElement.messagePath.body;
};
return TextFieldEntry({
element: parameter,
id: `${idPrefix}-textField`,
label: translate('Expression'),
getValue,
setValue,
debounce,
});
}

View File

@ -0,0 +1,64 @@
import { useService } from 'bpmn-js-properties-panel';
import { TextAreaEntry } from '@bpmn-io/properties-panel';
import { getMessageElementForShapeElement } from '../MessageHelpers';
/**
* Allows the creation, or editing of messagePayload at the bpmn:sendTask level of a BPMN document.
*/
export function MessagePayload(props) {
const shapeElement = props.element;
const debounce = useService('debounceInput');
const messageElement = getMessageElementForShapeElement(shapeElement);
const disabled = !messageElement;
const getMessagePayloadObject = () => {
if (messageElement) {
const { extensionElements } = messageElement;
if (extensionElements) {
return messageElement.extensionElements
.get('values')
.filter(function getInstanceOfType(e) {
return e.$instanceOf('spiffworkflow:messagePayload');
})[0];
}
}
return null;
};
const getValue = () => {
const messagePayloadObject = getMessagePayloadObject();
if (messagePayloadObject) {
return messagePayloadObject.messagePayload;
}
return '';
};
const setValue = (value) => {
let messagePayloadObject = getMessagePayloadObject();
if (!messagePayloadObject) {
messagePayloadObject = messageElement.$model.create(
'spiffworkflow:messagePayload'
);
if (!messageElement.extensionElements) {
messageElement.extensionElements = messageElement.$model.create(
'bpmn:ExtensionElements'
);
}
messageElement.extensionElements.get('values').push(messagePayloadObject);
}
messagePayloadObject.messagePayload = value;
};
return (
<TextAreaEntry
id="messagePayload"
element={shapeElement}
description="The payload of the message."
label="Payload"
disabled={disabled}
getValue={getValue}
setValue={setValue}
debounce={debounce}
/>
);
}

View File

@ -0,0 +1,83 @@
import { useService } from 'bpmn-js-properties-panel';
import { SelectEntry } from '@bpmn-io/properties-panel';
import {
findMessageModdleElements,
getMessageRefElement,
isMessageEvent,
} from '../MessageHelpers';
/**
* Allows the selection, or creation, of Message at the Definitions level of a BPMN document.
*/
export function MessageSelect(props) {
const shapeElement = props.element;
const { commandStack } = props;
const debounce = useService('debounceInput');
const getValue = () => {
const messageRefElement = getMessageRefElement(shapeElement);
if (messageRefElement) {
return messageRefElement.id;
}
return '';
};
const setValue = (value) => {
/* Need to add the selected message as the messageRef on the current message task */
const { businessObject } = shapeElement;
const messages = findMessageModdleElements(shapeElement.businessObject);
for (const message of messages) {
if (message.id === value) {
if (isMessageEvent(shapeElement)) {
const messageEventDefinition = businessObject.eventDefinitions[0];
messageEventDefinition.messageRef = message;
// call this to update the other elements in the props panel like payload
commandStack.execute('element.updateModdleProperties', {
element: shapeElement,
moddleElement: businessObject,
});
} else if (
businessObject.$type === 'bpmn:ReceiveTask' ||
businessObject.$type === 'bpmn:SendTask'
) {
commandStack.execute('element.updateModdleProperties', {
element: shapeElement,
moddleElement: businessObject,
properties: {
messageRef: message,
},
});
commandStack.execute('element.updateProperties', {
element: shapeElement,
moddleElement: businessObject,
properties: {
messageRef: message,
},
});
}
}
}
};
const getOptions = (_value) => {
const messages = findMessageModdleElements(shapeElement.businessObject);
const options = [];
for (const message of messages) {
options.push({ label: message.name, value: message.id });
}
return options;
};
return (
<SelectEntry
id="selectMessage"
element={shapeElement}
description="Select the Message to associate with this task or event."
label="Which message is this associated with?"
getValue={getValue}
setValue={setValue}
getOptions={getOptions}
debounce={debounce}
/>
);
}

View File

@ -0,0 +1,66 @@
import { useService } from 'bpmn-js-properties-panel';
import { TextFieldEntry } from '@bpmn-io/properties-panel';
import { getMessageElementForShapeElement } from '../MessageHelpers';
/**
* Allows the creation, or editing of messageVariable at the bpmn:sendTask level of a BPMN document.
*/
export function MessageVariable(props) {
const shapeElement = props.element;
const debounce = useService('debounceInput');
const messageElement = getMessageElementForShapeElement(shapeElement);
const disabled = !messageElement;
const getMessageVariableObject = () => {
if (messageElement) {
const { extensionElements } = messageElement;
if (extensionElements) {
return messageElement.extensionElements
.get('values')
.filter(function getInstanceOfType(e) {
return e.$instanceOf('spiffworkflow:messageVariable');
})[0];
}
}
return null;
};
const getValue = () => {
const messageVariableObject = getMessageVariableObject();
if (messageVariableObject) {
return messageVariableObject.messageVariable;
}
return '';
};
const setValue = (value) => {
let messageVariableObject = getMessageVariableObject();
if (!messageVariableObject) {
messageVariableObject = messageElement.$model.create(
'spiffworkflow:messageVariable'
);
if (!messageElement.extensionElements) {
messageElement.extensionElements = messageElement.$model.create(
'bpmn:ExtensionElements'
);
}
messageElement.extensionElements
.get('values')
.push(messageVariableObject);
}
messageVariableObject.messageVariable = value;
};
return (
<TextFieldEntry
id="messageVariable"
element={shapeElement}
description="The name of the variable where we should store payload."
label="Variable Name"
disabled={disabled}
getValue={getValue}
setValue={setValue}
debounce={debounce}
/>
);
}

View File

@ -0,0 +1,186 @@
import { ListGroup, isTextFieldEntryEdited } from '@bpmn-io/properties-panel';
import { is } from 'bpmn-js/lib/util/ModelUtil';
import { CorrelationKeysArray } from './CorrelationKeysArray';
import { MessageSelect } from './MessageSelect';
import { MessagePayload } from './MessagePayload';
import { MessageVariable } from './MessageVariable';
import { CorrelationPropertiesArray } from './CorrelationPropertiesArray';
import { MessageCorrelationPropertiesArray } from './MessageCorrelationPropertiesArray';
import { MessageArray } from './MessageArray';
import { isMessageElement, canReceiveMessage } from '../MessageHelpers';
const LOW_PRIORITY = 500;
export default function MessagesPropertiesProvider(
propertiesPanel,
translate,
moddle,
commandStack,
elementRegistry
) {
this.getGroups = function getGroupsCallback(element) {
return function pushGroup(groups) {
if (is(element, 'bpmn:Collaboration')) {
groups.push(
...createCollaborationGroup(
element,
translate,
moddle,
commandStack,
elementRegistry
)
);
} else if (isMessageElement(element)) {
const messageIndex = findEntry(groups, 'message');
if (messageIndex) {
groups.splice(messageIndex, 1);
}
groups.push(
createMessageGroup(
element,
translate,
moddle,
commandStack,
elementRegistry
)
);
}
return groups;
};
};
function findEntry(entries, entryId) {
let entryIndex = null;
entries.forEach(function (value, index) {
if (value.id === entryId) {
entryIndex = index;
}
});
return entryIndex;
}
propertiesPanel.registerProvider(LOW_PRIORITY, this);
}
MessagesPropertiesProvider.$inject = [
'propertiesPanel',
'translate',
'moddle',
'commandStack',
'elementRegistry',
];
/**
* Adds a group to the properties panel for the script task that allows you
* to set the script.
* @param element
* @param translate
* @returns The components to add to the properties panel. */
function createCollaborationGroup(
element,
translate,
moddle,
commandStack,
elementRegistry
) {
return [
{
id: 'correlation_keys',
label: translate('Correlation Keys'),
component: ListGroup,
...CorrelationKeysArray({
element,
moddle,
commandStack,
elementRegistry,
translate,
}),
},
{
id: 'correlation_properties',
label: translate('Correlation Properties'),
component: ListGroup,
...CorrelationPropertiesArray({
element,
moddle,
commandStack,
elementRegistry,
translate,
}),
},
{
id: 'messages',
label: translate('Messages'),
component: ListGroup,
...MessageArray({
element,
moddle,
commandStack,
elementRegistry,
translate,
}),
},
];
}
/**
* Adds a group to the properties panel for editing messages for the SendTask
* @param element
* @param translate
* @returns The components to add to the properties panel. */
function createMessageGroup(
element,
translate,
moddle,
commandStack,
elementRegistry
) {
const entries = [
{
id: 'selectMessage',
element,
component: MessageSelect,
isEdited: isTextFieldEntryEdited,
moddle,
commandStack,
},
];
if (canReceiveMessage(element)) {
entries.push({
id: 'messageVariable',
element,
component: MessageVariable,
isEdited: isTextFieldEntryEdited,
moddle,
commandStack,
});
} else {
entries.push({
id: 'messagePayload',
element,
component: MessagePayload,
isEdited: isTextFieldEntryEdited,
moddle,
commandStack,
});
}
entries.push({
id: 'correlationProperties',
label: translate('Correlation'),
component: ListGroup,
...MessageCorrelationPropertiesArray({
element,
moddle,
commandStack,
elementRegistry,
translate,
}),
});
return {
id: 'messages',
label: translate('Message'),
entries,
};
}

View File

@ -0,0 +1,213 @@
{
"name": "SpiffWorkflow",
"uri": "http://spiffworkflow.org/bpmn/schema/1.0/core",
"prefix": "spiffworkflow",
"associations": [],
"types": [
{
"name": "preScript",
"superClass": [ "Element" ],
"properties": [
{
"name": "script",
"isBody": true,
"type": "String"
}
]
},
{
"name": "postScript",
"superClass": [ "Element" ],
"properties": [
{
"name": "script",
"isBody": true,
"type": "String"
}
]
},
{
"name": "messagePayload",
"superClass": [ "Element" ],
"properties": [
{
"name": "messagePayload",
"isBody": true,
"type": "String"
}
]
},
{
"name": "messageVariable",
"superClass": [ "Element" ],
"properties": [
{
"name": "messageVariable",
"isBody": true,
"type": "String"
}
]
},
{
"name": "calledDecisionId",
"superClass": [ "Element" ],
"properties": [
{
"name": "calledDecisionId",
"isBody": true,
"type": "String"
}
]
},
{
"name": "instructionsForEndUser",
"superClass": [ "Element" ],
"properties": [
{
"name": "instructionsForEndUser",
"isBody": true,
"type": "String"
}
]
},
{
"name": "properties",
"superClass": [
"Element"
],
"properties": [
{
"name": "properties",
"type": "property",
"isMany": true
}
]
},
{
"name": "property",
"superClass": [ "Element" ],
"properties": [
{
"name": "name",
"isAttr": true,
"type": "String"
},
{
"name": "value",
"isAttr": true,
"type": "String"
}
]
},
{
"name": "serviceTaskOperator",
"superClass": [
"Element"
],
"properties": [
{
"name": "id",
"isAttr": true,
"type": "String"
},
{
"name": "resultVariable",
"isAttr": true,
"type": "String"
},
{
"name": "parameterList",
"type": "parameters"
}
]
},
{
"name": "parameters",
"superClass": [
"Element"
],
"properties": [
{
"name": "parameters",
"type": "parameter",
"isMany": true
}
]
},
{
"name": "parameter",
"superClass": [ "Element" ],
"properties": [
{
"name": "id",
"isAttr": true,
"type": "String"
},
{
"name": "type",
"isAttr": true,
"type": "String"
},
{
"name": "value",
"isAttr": true,
"type": "String"
}
]
},
{
"name": "unitTests",
"superClass": [
"Element"
],
"properties": [
{
"name": "unitTests",
"type": "unitTest",
"isMany": true
}
]
},
{
"name": "unitTest",
"superClass": [ "Element" ],
"properties": [
{
"name": "id",
"isAttr": true,
"type": "String"
},
{
"name": "inputJson",
"type": "inputJson"
},
{
"name": "expectedOutputJson",
"type": "expectedOutputJson"
}
]
},
{
"name": "inputJson",
"superClass": [ "Element" ],
"properties": [
{
"name": "value",
"isBody": true,
"type": "string"
}
]
},
{
"name": "expectedOutputJson",
"superClass": [ "Element" ],
"properties": [
{
"name": "value",
"isBody": true,
"type": "string"
}
]
}
]
}

40349
dist/index.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/index.js.map vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,14 @@
"use strict";
(self["webpackChunkbpmn_js_spiffworkflow"] = self["webpackChunkbpmn_js_spiffworkflow"] || []).push([["src_moddle_spiffworkflow_json"],{
/***/ "./src/moddle/spiffworkflow.json":
/*!***************************************!*\
!*** ./src/moddle/spiffworkflow.json ***!
\***************************************/
/***/ ((module) => {
module.exports = JSON.parse('{"name":"SpiffWorkflow","uri":"http://spiffworkflow.org/bpmn/schema/1.0/core","prefix":"spiffworkflow","associations":[],"types":[{"name":"preScript","superClass":["Element"],"properties":[{"name":"script","isBody":true,"type":"String"}]},{"name":"postScript","superClass":["Element"],"properties":[{"name":"script","isBody":true,"type":"String"}]},{"name":"messagePayload","superClass":["Element"],"properties":[{"name":"messagePayload","isBody":true,"type":"String"}]},{"name":"messageVariable","superClass":["Element"],"properties":[{"name":"messageVariable","isBody":true,"type":"String"}]},{"name":"calledDecisionId","superClass":["Element"],"properties":[{"name":"calledDecisionId","isBody":true,"type":"String"}]},{"name":"instructionsForEndUser","superClass":["Element"],"properties":[{"name":"instructionsForEndUser","isBody":true,"type":"String"}]},{"name":"properties","superClass":["Element"],"properties":[{"name":"properties","type":"property","isMany":true}]},{"name":"property","superClass":["Element"],"properties":[{"name":"name","isAttr":true,"type":"String"},{"name":"value","isAttr":true,"type":"String"}]},{"name":"serviceTaskOperator","superClass":["Element"],"properties":[{"name":"id","isAttr":true,"type":"String"},{"name":"parameterList","type":"parameters"}]},{"name":"parameters","superClass":["Element"],"properties":[{"name":"parameters","type":"parameter","isMany":true}]},{"name":"parameter","superClass":["Element"],"properties":[{"name":"id","isAttr":true,"type":"String"},{"name":"type","isAttr":true,"type":"String"},{"name":"value","isAttr":true,"type":"String"}]}]}');
/***/ })
}]);

BIN
docs/io.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

126
karma.conf.js Normal file
View File

@ -0,0 +1,126 @@
'use strict';
const coverage = process.env.COVERAGE;
const path = require('path');
const {
DefinePlugin,
NormalModuleReplacementPlugin
} = require('webpack');
const basePath = '.';
const absoluteBasePath = path.resolve(path.join(__dirname, basePath));
module.exports = function(karma) {
karma.set({
frameworks: [
'webpack',
'mocha',
'sinon-chai'
],
files: [
'test/spec/**/*Spec.js',
],
reporters: [ 'dots' ],
preprocessors: {
'test/spec/**/*Spec.js': [ 'webpack', 'env' ]
},
browsers: [ 'ChromeHeadless' ],
browserNoActivityTimeout: 30000,
singleRun: true,
autoWatch: false,
webpack: {
mode: 'development',
module: {
rules: [
{
test: /\.(css|bpmn)$/,
use: 'raw-loader'
},
{
test: /\.m?js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
plugins: [
[ '@babel/plugin-transform-react-jsx', {
'importSource': '@bpmn-io/properties-panel/preact',
'runtime': 'automatic'
} ]
]
}
}
},
{
test: /\.svg$/,
use: [ 'react-svg-loader' ]
}
].concat(coverage ?
{
test: /\.js$/,
use: {
loader: 'istanbul-instrumenter-loader',
options: { esModules: true }
},
enforce: 'post',
include: /src\.*/,
exclude: /node_modules/
} : []
)
},
plugins: [
new DefinePlugin({
// @barmac: process.env has to be defined to make @testing-library/preact work
'process.env': {}
}),
new NormalModuleReplacementPlugin(
/^preact(\/[^/]+)?$/,
function(resource) {
const replMap = {
'preact/hooks': path.resolve('node_modules/@bpmn-io/properties-panel/preact/hooks/dist/hooks.module.js'),
'preact/jsx-runtime': path.resolve('node_modules/@bpmn-io/properties-panel/preact/jsx-runtime/dist/jsxRuntime.module.js'),
'preact': path.resolve('node_modules/@bpmn-io/properties-panel/preact/dist/preact.module.js')
};
const replacement = replMap[resource.request];
if (!replacement) {
return;
}
resource.request = replacement;
}
),
new NormalModuleReplacementPlugin(
/^preact\/hooks/,
path.resolve('node_modules/@bpmn-io/properties-panel/preact/hooks/dist/hooks.module.js')
)
],
resolve: {
mainFields: [
'browser',
'module',
'main'
],
alias: {
'preact': '@bpmn-io/properties-panel/preact',
'react': '@bpmn-io/properties-panel/preact/compat',
'react-dom': '@bpmn-io/properties-panel/preact/compat'
},
modules: [
'node_modules',
absoluteBasePath
]
},
devtool: 'eval-source-map'
}
});
};

16010
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

87
package.json Normal file
View File

@ -0,0 +1,87 @@
{
"name": "bpmn-js-spiffworkflow",
"version": "0.0.8",
"description": "Extensions and modifications of BPMN.js to improve BPMN development for SpiffWorkflow",
"scripts": {
"all": "run-s lint test build",
"build": "webpack --mode production",
"build:watch": "webpack --watch",
"dev": "run-p build:watch serve",
"serve": "sirv public --dev",
"lint": "./node_modules/.bin/eslint src *.js",
"lint:fix": "./node_modules/.bin/eslint --fix src *.js",
"start": "run-s build serve",
"test": "karma start karma.conf.js"
},
"repository": {
"type": "git",
"url": "https://github.com/sartography/bpmn-js-spiffworkflow"
},
"keywords": [
"bpmn",
"spiffworkflow"
],
"author": {
"name": "Dan Funk (Sartography)",
"url": "https://github.com/danfunk"
},
"contributors": [
{
"name": "bpmn.io contributors",
"url": "https://github.com/bpmn-io"
}
],
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.18.6",
"@babel/plugin-transform-react-jsx": "^7.17.12",
"@babel/preset-env": "^7.18.6",
"@babel/preset-react": "^7.18.2",
"@testing-library/preact": "^2.0.1",
"@testing-library/preact-hooks": "^1.1.0",
"@types/mocha": "^9.1.1",
"babel-loader": "^8.2.5",
"chai": "^4.3.6",
"copy-webpack-plugin": "^11.0.0",
"eslint": "^8.18.0",
"eslint_d": "^12.2.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-bpmn-io": "^0.14.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsx-a11y": "^6.6.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-sonarjs": "^0.13.0",
"file-saver": "^2.0.5",
"karma": "^6.3.4",
"karma-chrome-launcher": "^3.1.1",
"karma-coverage": "^2.2.0",
"karma-env-preprocessor": "^0.1.1",
"karma-mocha": "^2.0.1",
"karma-sinon-chai": "^2.0.2",
"karma-webpack": "^5.0.0",
"mocha": "^10.0.0",
"mocha-test-container-support": "^0.2.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.7.1",
"raw-loader": "^4.0.2",
"sinon": "^14.0.0",
"sinon-chai": "^3.7.0",
"sirv-cli": "^2.0.2",
"stringify": "^5.2.0",
"webpack": "^5.73.0",
"webpack-cli": "^4.9.2"
},
"dependencies": {
"@bpmn-io/properties-panel": "^0.19.0",
"bpmn-js": "^9.4.0",
"bpmn-js-properties-panel": "^1.5.0",
"diagram-js": "^8.5.0",
"inherits": "^2.0.4",
"inherits-browser": "^0.0.1",
"min-dash": "^3.8.1",
"min-dom": "^3.2.1",
"moddle": "^5.0.3",
"tiny-svg": "^2.2.3"
}
}

101
resources/diagram.bpmn Normal file
View File

@ -0,0 +1,101 @@
<?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="ProcessTest" isExecutable="true">
<bpmn:ioSpecification>
<bpmn:dataInput id="num_dogs" name="Number of Dogs" />
<bpmn:dataOutput id="happy_index" name="Happiness Index" />
</bpmn:ioSpecification>
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_1mezzcx</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:endEvent id="Event_14wzv4j">
<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" />
<bpmn:manualTask id="Activity_15zz6ya" name="eat hot dog">
<bpmn:incoming>Flow_1mezzcx</bpmn:incoming>
<bpmn:outgoing>Flow_01jg677</bpmn:outgoing>
<bpmn:dataOutputAssociation id="DataOutputAssociation_1uj5jzs">
<bpmn:targetRef>my_data_ref_1</bpmn:targetRef>
</bpmn:dataOutputAssociation>
<bpmn:standardLoopCharacteristics />
</bpmn:manualTask>
<bpmn:sequenceFlow id="Flow_0q4oys2" sourceRef="my_script_task" targetRef="Event_14wzv4j" />
<bpmn:scriptTask id="my_script_task" name="calculate contentment">
<bpmn:incoming>Flow_01jg677</bpmn:incoming>
<bpmn:outgoing>Flow_0q4oys2</bpmn:outgoing>
<bpmn:property id="Property_1w1963p" name="__targetRef_placeholder" />
<bpmn:dataInputAssociation id="DataInputAssociation_0thubmi">
<bpmn:sourceRef>my_data_ref_2</bpmn:sourceRef>
<bpmn:targetRef>Property_1w1963p</bpmn:targetRef>
</bpmn:dataInputAssociation>
</bpmn:scriptTask>
<bpmn:dataObject id="my_data_object" />
<bpmn:dataObject id="my_other_data_object" />
<bpmn:dataObject id="my_third_data_object" />
<bpmn:dataObjectReference id="my_data_ref_1" name="my_data_object" dataObjectRef="my_data_object" />
<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="ProcessTest">
<bpmndi:BPMNEdge id="Flow_0q4oys2_di" bpmnElement="Flow_0q4oys2">
<di:waypoint x="540" y="197" />
<di:waypoint x="602" y="197" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1mezzcx_di" bpmnElement="Flow_1mezzcx">
<di:waypoint x="215" y="197" />
<di:waypoint x="280" y="197" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_01jg677_di" bpmnElement="Flow_01jg677">
<di:waypoint x="380" y="197" />
<di:waypoint x="440" y="197" />
</bpmndi:BPMNEdge>
<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="Activity_0h86vbv_di" bpmnElement="my_script_task">
<dc:Bounds x="440" y="157" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="DataObjectReference_1cezipn_di" bpmnElement="my_data_ref_1">
<dc:Bounds x="312" y="275" width="36" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="314" y="332" width="33" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="DataObjectReference_08bm72g_di" bpmnElement="my_data_ref_2">
<dc:Bounds x="462" y="275" width="36" height="50" />
<bpmndi:BPMNLabel>
<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" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="DataInputAssociation_0thubmi_di" bpmnElement="DataInputAssociation_0thubmi">
<di:waypoint x="483" y="275" />
<di:waypoint x="489" y="237" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

3
test/.eslintrc Normal file
View File

@ -0,0 +1,3 @@
{
"extends": "plugin:bpmn-io/mocha"
}

View File

@ -0,0 +1,29 @@
import {
query as domQuery,
queryAll as domQueryAll
} from 'min-dom';
import { bootstrapPropertiesPanel, CONTAINER } from './helpers';
import inputOutput from '../../app/spiffworkflow/InputOutput';
import { BpmnPropertiesPanelModule, BpmnPropertiesProviderModule } from 'bpmn-js-properties-panel';
describe('BPMN Input / Output', function() {
let xml = require('./bpmn/diagram.bpmn').default;
beforeEach(bootstrapPropertiesPanel(xml, {
debounceInput: false,
additionalModules: [
inputOutput,
BpmnPropertiesPanelModule,
BpmnPropertiesProviderModule,
]
}));
it('should have a data input and data output in the properties panel', function() {
var paletteElement = domQuery('.djs-palette', CONTAINER);
var entries = domQueryAll('.entry', paletteElement);
expect(entries[11].title).to.equals('Create DataInput');
expect(entries[12].title).to.equals('Create DataOutput');
});
});

View File

@ -0,0 +1,70 @@
import {
bootstrapPropertiesPanel, changeInput,
expectSelected,
findEntry,
getPropertiesPanel
} from './helpers';
import {
query as domQuery,
} from 'min-dom';
import spiffModdleExtension from '../../app/spiffworkflow/moddle/spiffworkflow.json';
import { BpmnPropertiesPanelModule, BpmnPropertiesProviderModule } from 'bpmn-js-properties-panel';
import { getBusinessObject } from 'bpmn-js/lib/util/ModelUtil';
import extensions from '../../app/spiffworkflow/extensions';
describe('Business Rule Properties Panel', function() {
const xml = require('./bpmn/diagram.bpmn').default;
beforeEach(bootstrapPropertiesPanel(xml, {
debounceInput: false,
additionalModules: [
extensions,
BpmnPropertiesPanelModule,
BpmnPropertiesProviderModule,
],
moddleExtensions: {
spiffworkflow: spiffModdleExtension
},
}));
it('should display a text box to select the called decision id', async function() {
expectSelected('business_rule_task');
// THEN - a properties panel exists with a section for editing that script
let entry = findEntry('extension_called_decision', getPropertiesPanel());
expect(entry).to.exist;
const textInput = domQuery('input', entry);
expect(textInput).to.exist;
});
it('should update the spiffworkflow:calledDecisionId tag when you modify the called decision text input', async function() {
// IF - a script tag is selected, and you change the script in the properties panel
const businessRuleTask = await expectSelected('business_rule_task');
const entry = findEntry('extension_called_decision', getPropertiesPanel());
const textInput = domQuery('input', entry);
changeInput(textInput, 'wonderful');
// THEN - the script tag in the BPMN Business object / XML is updated as well.
const businessObject = getBusinessObject(businessRuleTask);
expect(businessObject.extensionElements).to.exist;
let element = businessObject.extensionElements.values[0];
expect(element.calledDecisionId).to.equal('wonderful');
});
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');
let entry = findEntry('extension_called_decision', getPropertiesPanel());
const textInput = domQuery('input', entry);
expect(textInput.value).to.equal('test_decision');
// THEN - the script tag in the BPMN Business object / XML is updated as well.
let businessObject = getBusinessObject(businessRuleTask);
expect(businessObject.extensionElements).to.exist;
let element = businessObject.extensionElements.values[0];
expect(element.calledDecisionId).to.equal('test_decision');
});
});

View File

@ -0,0 +1,60 @@
import TestContainer from 'mocha-test-container-support';
import {
BpmnPropertiesPanelModule,
BpmnPropertiesProviderModule,
} from 'bpmn-js-properties-panel';
import { query as domQuery } from 'min-dom';
import { getBusinessObject } from 'bpmn-js/lib/util/ModelUtil';
import {
bootstrapPropertiesPanel,
changeInput,
expectSelected,
findGroupEntry,
} 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;
beforeEach(function () {
container = TestContainer.get(this);
});
beforeEach(
bootstrapPropertiesPanel(xml, {
container,
debounceInput: false,
additionalModules: [
callActivity,
BpmnPropertiesPanelModule,
BpmnPropertiesProviderModule,
],
moddleExtensions: {
spiffworkflow: spiffModdleExtension,
},
})
);
it('should allow you to view the called element section of a Call Activity', async function () {
const shapeElement = await expectSelected('the_call_activity');
expect(shapeElement, "Can't find Call Activity").to.exist;
const entry = findGroupEntry('called_element', container);
expect(entry).to.exist;
});
it('should allow you to edit the called element section of a Call Activity', async function () {
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);
expect(entry).to.exist;
const textInput = domQuery('input', entry);
changeInput(textInput, 'newProcessId');
expect(businessObject.get('calledElement')).to.equal('newProcessId');
});
});

View File

@ -0,0 +1,80 @@
import { bootstrapPropertiesPanel } from './helpers';
import dataObjectInterceptor from '../../app/spiffworkflow/DataObject';
import { BpmnPropertiesPanelModule, BpmnPropertiesProviderModule } from 'bpmn-js-properties-panel';
import {
inject,
} from 'bpmn-js/test/helper';
import { findDataObjects } from '../../app/spiffworkflow/DataObject/DataObjectHelpers';
describe('DataObject Interceptor', function() {
let xml = require('./bpmn/empty_diagram.bpmn').default;
beforeEach(bootstrapPropertiesPanel(xml, {
debounceInput: false,
additionalModules: [
dataObjectInterceptor,
BpmnPropertiesPanelModule,
BpmnPropertiesProviderModule,
]
}));
it('New Data Object References should create a data object if none exist.', inject(function(canvas, modeling) {
// IF - a new dataObjectReference is created
let rootShape = canvas.getRootElement();
const dataObjectRefShape1 = modeling.createShape({ type: 'bpmn:DataObjectReference' },
{ x: 220, y: 220 }, rootShape);
// THEN - a new DataObject is also created.
const dataObjects = findDataObjects(rootShape.businessObject);
expect(dataObjects.length).to.equal(1);
expect(dataObjects[0]).to.equal(dataObjectRefShape1.businessObject.dataObjectRef);
}));
it('New Data Object References should connect to the first available data Object if it exists', inject(function(canvas, modeling) {
// IF - two dataObjectReferences are created
let rootShape = canvas.getRootElement();
const dataObjectRefShape1 = modeling.createShape({ type: 'bpmn:DataObjectReference' },
{ x: 220, y: 220 }, rootShape);
const dataObjectRefShape2 = modeling.createShape({ type: 'bpmn:DataObjectReference' },
{ x: 320, y: 220 }, rootShape);
// THEN - only one new DataObject is created, and both references point to it..
const dataObjects = findDataObjects(rootShape.businessObject);
expect(dataObjects.length).to.equal(1);
expect(dataObjects[0]).to.equal(dataObjectRefShape1.businessObject.dataObjectRef);
expect(dataObjects[0]).to.equal(dataObjectRefShape2.businessObject.dataObjectRef);
}));
it('Creating a new Reference will update the name to match the DataObject', inject(function(canvas, modeling) {
// IF - a Data Reference Exists
let rootShape = canvas.getRootElement();
const dataObjectRefShape1 = modeling.createShape({ type: 'bpmn:DataObjectReference' },
{ x: 220, y: 220 }, rootShape);
const dataObjects = findDataObjects(rootShape.businessObject);
expect(dataObjectRefShape1.businessObject.name).to.equal(dataObjects[0].id);
}));
it('should allow you to add a data object to a subprocess', inject(function(canvas, modeling, elementRegistry) {
// IF - A data object reference is added to a sup-process
let subProcessShape = elementRegistry.get('my_subprocess');
let subProcess = subProcessShape.businessObject;
let dataObjects = findDataObjects(subProcess);
expect(dataObjects.length).to.equal(0);
const dataObjectRefShape = modeling.createShape({ type: 'bpmn:DataObjectReference' },
{ x: 220, y: 220 }, subProcessShape);
// THEN - a new data object is visible in that SubProcess
dataObjects = findDataObjects(subProcess);
expect(dataObjects.length).to.equal(1);
}));
});

View File

@ -0,0 +1,81 @@
import {
bootstrapPropertiesPanel, changeInput,
expectSelected,
findEntry, findInput, findSelect,
} from './helpers';
import { BpmnPropertiesPanelModule, BpmnPropertiesProviderModule } from 'bpmn-js-properties-panel';
import spiffModdleExtension from '../../app/spiffworkflow/moddle/spiffworkflow.json';
import TestContainer from 'mocha-test-container-support';
import DataObjectPropertiesProvider
from '../../app/spiffworkflow/DataObject/propertiesPanel/DataObjectPropertiesProvider';
import spiffworkflow from '../../app/spiffworkflow';
import DataObject from '../../app/spiffworkflow/DataObject';
describe('Properties Panel for Data Objects', function() {
let xml = require('./bpmn/diagram.bpmn').default;
let container;
beforeEach(function() {
container = TestContainer.get(this);
});
beforeEach(bootstrapPropertiesPanel(xml, {
container,
debounceInput: false,
additionalModules: [
DataObject,
BpmnPropertiesPanelModule,
BpmnPropertiesProviderModule,
],
moddleExtensions: {
spiffworkflow: spiffModdleExtension
},
}));
it('should allow you to see a list of data objects', async function() {
// IF - a data object reference is selected
let my_data_ref_1 = await expectSelected('my_data_ref_1');
expect(my_data_ref_1).to.exist;
// THEN - a select Data Object section should appear in the properties panel
let entry = findEntry('selectDataObject', container);
expect(entry).to.exist;
// AND - That that properties' pane selection should contain a dropdown with a value in it.
let selector = findSelect(entry);
expect(selector).to.exist;
expect(selector.length).to.equal(3);
});
it('selecting a different data object should change the data model.', async function() {
// IF - a data object reference is selected
let my_data_ref_1 = await expectSelected('my_data_ref_1');
let entry = findEntry('selectDataObject', container);
let selector = findSelect(entry);
let businessObject = my_data_ref_1.businessObject;
// AND we select a dataObjectReference (we know it has this value, because we created the bpmn file)
changeInput(selector, 'my_third_data_object');
// then this data reference object now references that data object.
expect(businessObject.get('dataObjectRef').id).to.equal('my_third_data_object');
});
it('renaming a data object, changes to the label of references', async function() {
// IF - a process is selected, and the name of a data object is changed.
let entry = findEntry('ProcessTest-dataObj-2-id', container);
let textInput = findInput('text', entry);
changeInput(textInput, 'my_nifty_new_name');
let my_data_ref_1 = await expectSelected('my_data_ref_1');
// THEN - both the data object itself, and the label of any references are updated.
expect(my_data_ref_1.businessObject.dataObjectRef.id).to.equal('my_nifty_new_name');
expect(my_data_ref_1.businessObject.name).to.equal('my_nifty_new_name');
});
});

View File

@ -0,0 +1,37 @@
import { bootstrapPropertiesPanel } from './helpers';
import { BpmnPropertiesPanelModule, BpmnPropertiesProviderModule } from 'bpmn-js-properties-panel';
import dataObject from '../../app/spiffworkflow/DataObject';
import { inject } from 'bpmn-js/test/helper';
describe('BPMN Input / Output', function() {
let xml = require('./bpmn/subprocess.bpmn').default;
beforeEach(bootstrapPropertiesPanel(xml, {
debounceInput: false,
additionalModules: [
dataObject,
BpmnPropertiesPanelModule,
BpmnPropertiesProviderModule,
]
}));
it('will prevent dragging data reference to a different process',
inject(function(canvas, modeling, elementRegistry, dataObjectRules) {
// IF - a data object reference exists on the root element, and a SubProcess Also Exists
let rootShape = canvas.getRootElement();
const dataObjectRefShape = modeling.createShape({ type: 'bpmn:DataObjectReference' },
{ x: 220, y: 220 }, rootShape);
const subProcessElement = elementRegistry.get('my_subprocess');
var canDrop = dataObjectRules.canDrop(
[ dataObjectRefShape ],
subProcessElement
);
expect(canDrop).to.be.false;
}));
});

136
test/spec/MessagesSpec.js Normal file
View File

@ -0,0 +1,136 @@
import TestContainer from 'mocha-test-container-support';
import {
BpmnPropertiesPanelModule,
BpmnPropertiesProviderModule,
} from 'bpmn-js-properties-panel';
import {
bootstrapPropertiesPanel,
expectSelected,
findEntry,
findGroupEntry,
// findInput,
findSelect,
findTextarea,
// findButtonByClass,
// pressButton,
// findDivByClass,
} from './helpers';
import spiffModdleExtension from '../../app/spiffworkflow/moddle/spiffworkflow.json';
import messages from '../../app/spiffworkflow/messages';
describe('Messages should work', function () {
const xml = require('./bpmn/collaboration.bpmn').default;
let container;
beforeEach(function () {
container = TestContainer.get(this);
});
beforeEach(
bootstrapPropertiesPanel(xml, {
container,
debounceInput: false,
additionalModules: [
messages,
BpmnPropertiesPanelModule,
BpmnPropertiesProviderModule,
],
moddleExtensions: {
spiffworkflow: spiffModdleExtension,
},
})
);
it('should allow you to see the collaborations section', async function () {
// THEN - a select Data Object section should appear in the properties panel
const entry = findGroupEntry('correlation_keys', container);
expect(entry).to.exist;
await expectSelected('my_collaboration');
});
it('should show a Message Properties group when a send task is selected', async function () {
// Select the send Task
const sendShape = await expectSelected('ActivitySendLetter');
expect(sendShape, "Can't find Send Task").to.exist;
// THEN - a select Data Object section should appear in the properties panel
const entry = findGroupEntry('messages', container);
expect(entry, "Can't find the message group in the properties panel").to
.exist;
await expectSelected('my_collaboration');
});
it('should show a list of messages in a drop down inside the message group', async function () {
// Select the send Task
const sendShape = await expectSelected('ActivitySendLetter');
expect(sendShape, "Can't find Send Task").to.exist;
// THEN - there are two options to choose from.
const entry = findEntry('selectMessage', container);
expect(entry, "Can't find the message select list").to.exist;
// AND - There should be two entries in it, one for each message.
const selector = findSelect(entry);
expect(selector).to.exist;
expect(selector.length).to.equal(2);
await expectSelected('my_collaboration');
});
it('should show the payload inside the message group', async function () {
// Select the second Task
const sendShape = await expectSelected('ActivitySendLetter');
expect(sendShape, "Can't find Send Task").to.exist;
// THEN - there is a payload.
const payload = findEntry('messagePayload', container);
expect(payload, "Can't find the message payload").to.exist;
const textArea = findTextarea(
'bio-properties-panel-messagePayload',
payload
);
expect(textArea, "Can't find the payload textarea").to.exist;
expect(textArea.value, "Can't find payload value").to.exist;
expect(textArea.value).to.include("'to': { 'name': my_lover_variable }");
await expectSelected('my_collaboration');
});
it('should show the correlations inside the message group', async function () {
// Select the second Task
const sendShape = await expectSelected('ActivitySendLetter');
expect(sendShape, "Can't find Send Task").to.exist;
// THEN - there are correlations.
const correlations = findGroupEntry('correlationProperties', container);
expect(correlations, "Can't find the message correlations").to.exist;
await expectSelected('my_collaboration');
});
// it('should add a new correlation when clicked', async function () {
// // Select the second Task
// const sendShape = await expectSelected('ActivitySendLetter');
// expect(sendShape, "Can't find Send Task").to.exist;
//
// const buttonClass =
// 'bio-properties-panel-group-header-button bio-properties-panel-add-entry';
// const button = findButtonByClass(buttonClass, container);
// pressButton(button);
//
// });
//
// it('should add a new Correlation Key when clicked', async function () {
// const divClass = 'bio-properties-panel-list';
// const divs = findDivByClass(divClass, container);
//
// const buttonClass =
// 'bio-properties-panel-group-header-button bio-properties-panel-add-entry';
// const button = findButtonByClass(buttonClass, container);
// pressButton(button);
//
// // THEN - a select Data Object section should appear in the properties panel
// const entry = findGroupEntry('correlation_keys', container);
// expect(entry).to.exist;
//
// const divs2 = findDivByClass(divClass, container);
// });
});

View File

@ -0,0 +1,94 @@
import {
query as domQuery,
} from 'min-dom';
import {
bootstrapPropertiesPanel, changeInput,
expectSelected, findEntry, findGroupEntry, findInput
} from './helpers';
import { BpmnPropertiesPanelModule, BpmnPropertiesProviderModule } from 'bpmn-js-properties-panel';
import spiffModdleExtension from '../../app/spiffworkflow/moddle/spiffworkflow.json';
import TestContainer from 'mocha-test-container-support';
import { fireEvent } from '@testing-library/preact';
import { findDataObject, findDataObjects } from '../../app/spiffworkflow/DataObject/DataObjectHelpers';
import dataObject from '../../app/spiffworkflow/DataObject';
describe('Properties Panel for a Process', function() {
let xml = require('./bpmn/diagram.bpmn').default;
let container;
beforeEach(function() {
container = TestContainer.get(this);
});
beforeEach(bootstrapPropertiesPanel(xml, {
container,
debounceInput: false,
additionalModules: [
dataObject,
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).to.exist;
let myDataObjEntry = findEntry('ProcessTest-dataObj-2');
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

@ -0,0 +1,60 @@
import TestContainer from 'mocha-test-container-support';
import {
BpmnPropertiesPanelModule,
BpmnPropertiesProviderModule,
} from 'bpmn-js-properties-panel';
import { query as domQuery } from 'min-dom';
import { getBusinessObject } from 'bpmn-js/lib/util/ModelUtil';
import {
bootstrapPropertiesPanel,
changeInput,
expectSelected,
findGroupEntry,
} 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;
beforeEach(function () {
container = TestContainer.get(this);
});
beforeEach(
bootstrapPropertiesPanel(xml, {
container,
debounceInput: false,
additionalModules: [
callActivity,
BpmnPropertiesPanelModule,
BpmnPropertiesProviderModule,
],
moddleExtensions: {
spiffworkflow: spiffModdleExtension,
},
})
);
it('should allow you to view the called element section of a Call Activity', async function () {
const shapeElement = await expectSelected('the_call_activity');
expect(shapeElement, "Can't find Call Activity").to.exist;
const entry = findGroupEntry('called_element', container);
expect(entry).to.exist;
});
it('should allow you to edit the called element section of a Call Activity', async function () {
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);
expect(entry).to.exist;
const textInput = domQuery('input', entry);
changeInput(textInput, 'newProcessId');
expect(businessObject.get('calledElement')).to.equal('newProcessId');
});
});

View File

@ -0,0 +1,74 @@
import { query as domQuery } from 'min-dom';
import {
BpmnPropertiesPanelModule,
BpmnPropertiesProviderModule,
} from 'bpmn-js-properties-panel';
import { getBusinessObject } from 'bpmn-js/lib/util/ModelUtil';
import spiffModdleExtension from '../../app/spiffworkflow/moddle/spiffworkflow.json';
import {
bootstrapPropertiesPanel,
changeInput,
expectSelected,
findEntry,
PROPERTIES_PANEL_CONTAINER,
} from './helpers';
import extensions from '../../app/spiffworkflow/extensions';
describe('Properties Panel Script Tasks', function () {
const xml = require('./bpmn/diagram.bpmn').default;
beforeEach(
bootstrapPropertiesPanel(xml, {
debounceInput: false,
additionalModules: [
extensions,
BpmnPropertiesPanelModule,
BpmnPropertiesProviderModule,
],
moddleExtensions: {
spiffworkflow: spiffModdleExtension,
},
})
);
it('should display a script editing panel when a script task is selected', async function () {
// IF - you select a script task
expectSelected('my_script_task');
// THEN - a properties panel exists with a section for editing that script
const entry = findEntry(
'pythonScript_bpmn:script',
PROPERTIES_PANEL_CONTAINER
);
expect(entry).to.exist;
const scriptInput = domQuery('textarea', entry);
expect(scriptInput).to.exist;
});
it('should update the bpmn:script tag when you modify the script field', async function () {
// IF - a script tag is selected, and you change the script in the properties panel
const scriptTask = await expectSelected('my_script_task');
const entry = findEntry(
'pythonScript_bpmn:script',
PROPERTIES_PANEL_CONTAINER
);
const scriptInput = domQuery('textarea', entry);
changeInput(scriptInput, 'x = 100');
// THEN - the script tag in the BPMN Business object / XML is updated as well.
const businessObject = getBusinessObject(scriptTask);
expect(businessObject.get('script')).to.equal('x = 100');
expect(scriptInput.value).to.equal('x = 100');
});
it('should parse the bpmn:script tag when you open an existing file', async function () {
await expectSelected('task_confirm');
const entry = findEntry(
'pythonScript_spiffworkflow:preScript',
PROPERTIES_PANEL_CONTAINER
);
const scriptInput = domQuery('textarea', entry);
expect(scriptInput.value).to.equal('x=1');
});
});

View File

@ -0,0 +1,109 @@
import {
BpmnPropertiesPanelModule,
BpmnPropertiesProviderModule,
} from 'bpmn-js-properties-panel';
import { getBusinessObject } from 'bpmn-js/lib/util/ModelUtil';
import TestContainer from 'mocha-test-container-support';
import { getBpmnJS } from 'bpmn-js/test/helper';
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 Service Tasks', function () {
const diagramXml = require('./bpmn/service.bpmn').default;
let container;
beforeEach(function () {
container = TestContainer.get(this);
});
function preparePropertiesPanelWithXml(xml) {
return bootstrapPropertiesPanel(xml, {
container,
debounceInput: false,
additionalModules: [
extensions,
BpmnPropertiesPanelModule,
BpmnPropertiesProviderModule,
],
moddleExtensions: {
spiffworkflow: spiffModdleExtension,
},
});
}
function addServicesToModeler(bpmnModeler) {
/**
* This will inject available services into the modeler which should be
* available as a dropdown list when selecting which service you want to call.
*
*/
bpmnModeler.on('spiff.service_tasks.requested', (event) => {
event.eventBus.fire('spiff.service_tasks.returned', {
serviceTaskOperators: [
{
id: 'ExampleService',
parameters: [
{
id: 'name',
type: 'string',
},
],
},
{
id: 'ExampleService2',
parameters: [
{
id: 'number',
type: 'integer',
},
],
},
],
});
});
}
it('should display a panel for selecting service', async function () {
await preparePropertiesPanelWithXml(diagramXml)();
const modeler = getBpmnJS();
addServicesToModeler(modeler);
// IF - you select a service task
const serviceTask = await expectSelected('my_service_task');
expect(serviceTask).to.exist;
// THEN - a property panel exists with a section for editing web forms
const group = findGroupEntry('service_task_properties', container);
expect(group).to.exist;
});
it('should display a list of services to select from.', async function () {
await preparePropertiesPanelWithXml(diagramXml)();
const modeler = getBpmnJS();
addServicesToModeler(modeler);
// IF - you select a service task
const serviceTask = await expectSelected('my_service_task');
const group = findGroupEntry('service_task_properties', container);
const entry = findEntry('selectOperatorId', group)
// THEN - a select list appears and is populated by a list of known services
const selectList = findSelect(entry);
expect(selectList).to.exist;
expect(selectList.options.length).to.equal(2)
expect(selectList.options[0].label).to.equal('ExampleService')
expect(selectList.options[1].label).to.equal('ExampleService2')
});
});

View File

@ -0,0 +1,85 @@
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';
import { getBusinessObject } from 'bpmn-js/lib/util/ModelUtil';
import TestContainer from 'mocha-test-container-support';
import extensions from '../../app/spiffworkflow/extensions';
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() {
container = TestContainer.get(this);
});
function preparePropertiesPanelWithXml(xml) {
return bootstrapPropertiesPanel(xml, {
container,
debounceInput: false,
additionalModules: [
extensions,
BpmnPropertiesPanelModule,
BpmnPropertiesProviderModule,
],
moddleExtensions: {
spiffworkflow: spiffModdleExtension
},
});
}
it('should display a panel for setting the web form properties', async function() {
await preparePropertiesPanelWithXml(user_form_xml)();
// IF - you select a user task
const userTask = await expectSelected('my_user_task');
expect(userTask).to.exist;
// THEN - a property panel exists with a section for editing web forms
let group = findGroupEntry('user_task_properties', container);
expect(group).to.exist;
});
it('should allow you to edit a web form property.', async function() {
await preparePropertiesPanelWithXml(user_form_xml)();
// 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');
// THEN - the input is updated.
let businessObject = getBusinessObject(userTask);
expect(businessObject.extensionElements).to.exist;
let 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.name).to.equal('formJsonSchemaFilename');
});
it('should parse the spiffworkflow:properties tag when you open an existing file', async function() {
await preparePropertiesPanelWithXml(diagram_xml)();
// 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);
expect(formJsonSchemaFilenameInput.value).to.equal('give_me_a_number_form.json');
let formUiSchemaFilenameEntry = findEntry('extension_formUiSchemaFilename', group);
let formUiSchemaFilenameInput = findInput('text', formUiSchemaFilenameEntry);
expect(formUiSchemaFilenameInput.value).to.equal('number_form_schema.json');
});
});

View File

@ -0,0 +1,154 @@
<?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:spiffworkflow="http://spiffworkflow.org/bpmn/schema/1.0/core" id="Definitions_96f6665" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.0.0-dev">
<bpmn:collaboration id="Collaboration_0oye1os">
<bpmn:participant id="message_initiator" name="Message Initiator" processRef="message_send_process" />
<bpmn:participant id="message_receiver" name="Message Receiver" processRef="message_receiver_process" />
<bpmn:messageFlow id="message_send_flow" name="Message Send Flow" sourceRef="send_message" targetRef="receive_message" />
<bpmn:messageFlow id="message_response_flow" name="Message Response Flow" sourceRef="respond_to_message" targetRef="receive_message_response" />
<bpmn:correlationKey name="message_correlation_key">
<bpmn:correlationPropertyRef>correlation_property_one</bpmn:correlationPropertyRef>
</bpmn:correlationKey>
<bpmn:correlationKey name="new_key">
<bpmn:correlationPropertyRef>message_correlation_property</bpmn:correlationPropertyRef>
</bpmn:correlationKey>
</bpmn:collaboration>
<bpmn:correlationProperty id="message_correlation_property" name="Message Correlation Property">
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send">
<bpmn:formalExpression>to</bpmn:formalExpression>
</bpmn:correlationPropertyRetrievalExpression>
<bpmn:correlationPropertyRetrievalExpression messageRef="message_response">
<bpmn:formalExpression>from.name</bpmn:formalExpression>
</bpmn:correlationPropertyRetrievalExpression>
</bpmn:correlationProperty>
<bpmn:process id="message_send_process" name="Message Send Process" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_176e02g</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_037vpjk" sourceRef="send_message" targetRef="receive_message_response" />
<bpmn:sequenceFlow id="Flow_1qgz6p0" sourceRef="receive_message_response" targetRef="Event_0kndoyu" />
<bpmn:sequenceFlow id="Flow_176e02g" sourceRef="StartEvent_1" targetRef="send_message" />
<bpmn:sendTask id="send_message" name="Send Message" messageRef="message_send">
<bpmn:incoming>Flow_176e02g</bpmn:incoming>
<bpmn:outgoing>Flow_037vpjk</bpmn:outgoing>
</bpmn:sendTask>
<bpmn:intermediateCatchEvent id="receive_message_response" name="Receive Message Response">
<bpmn:incoming>Flow_037vpjk</bpmn:incoming>
<bpmn:outgoing>Flow_1qgz6p0</bpmn:outgoing>
<bpmn:messageEventDefinition id="MessageEventDefinition_1l3n0zr" messageRef="message_response" />
</bpmn:intermediateCatchEvent>
<bpmn:endEvent id="Event_0kndoyu">
<bpmn:incoming>Flow_1qgz6p0</bpmn:incoming>
</bpmn:endEvent>
</bpmn:process>
<bpmn:message id="message_send" name="Message Send">
<bpmn:extensionElements>
<spiffworkflow:messagePayload>{"to": "the_recipient1" }</spiffworkflow:messagePayload>
</bpmn:extensionElements>
</bpmn:message>
<bpmn:message id="message_response" name="Message Response">
<bpmn:extensionElements>
<spiffworkflow:messagePayload>{"from": {"name": "the_sender"}}</spiffworkflow:messagePayload>
</bpmn:extensionElements>
</bpmn:message>
<bpmn:process id="message_receiver_process" name="Message Receiver Process">
<bpmn:startEvent id="receive_message" name="Receive Message">
<bpmn:outgoing>Flow_0505x87</bpmn:outgoing>
<bpmn:messageEventDefinition id="MessageEventDefinition_0h33u2n" messageRef="message_send" />
</bpmn:startEvent>
<bpmn:endEvent id="Event_0q5otqd">
<bpmn:incoming>Flow_1273yit</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_0505x87" sourceRef="receive_message" targetRef="respond_to_message" />
<bpmn:sequenceFlow id="Flow_1273yit" sourceRef="respond_to_message" targetRef="Event_0q5otqd" />
<bpmn:intermediateThrowEvent id="respond_to_message" name="Respond to Message">
<bpmn:incoming>Flow_0505x87</bpmn:incoming>
<bpmn:outgoing>Flow_1273yit</bpmn:outgoing>
<bpmn:messageEventDefinition id="MessageEventDefinition_0kakria" />
</bpmn:intermediateThrowEvent>
</bpmn:process>
<bpmn:correlationProperty id="correlation_property_one" name="Correlation Property One">
<bpmn:correlationPropertyRetrievalExpression messageRef="message_send">
<bpmn:formalExpression>new</bpmn:formalExpression>
</bpmn:correlationPropertyRetrievalExpression>
</bpmn:correlationProperty>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Collaboration_0oye1os">
<bpmndi:BPMNShape id="Participant_0bjh770_di" bpmnElement="message_initiator" isHorizontal="true">
<dc:Bounds x="120" y="52" width="600" height="250" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="159" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0vm33bu_di" bpmnElement="send_message">
<dc:Bounds x="280" y="137" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0yt48xb_di" bpmnElement="receive_message_response">
<dc:Bounds x="442" y="159" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="417" y="129" width="88" height="27" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0kndoyu_di" bpmnElement="Event_0kndoyu">
<dc:Bounds x="552" y="159" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_037vpjk_di" bpmnElement="Flow_037vpjk">
<di:waypoint x="380" y="177" />
<di:waypoint x="442" y="177" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1qgz6p0_di" bpmnElement="Flow_1qgz6p0">
<di:waypoint x="478" y="177" />
<di:waypoint x="552" y="177" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_176e02g_di" bpmnElement="Flow_176e02g">
<di:waypoint x="215" y="177" />
<di:waypoint x="280" y="177" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="Participant_0mr0gg1_di" bpmnElement="message_receiver" isHorizontal="true">
<dc:Bounds x="120" y="350" width="600" height="250" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_052nccg_di" bpmnElement="receive_message">
<dc:Bounds x="212" y="462" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="187" y="505" width="88" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0q5otqd_di" bpmnElement="Event_0q5otqd">
<dc:Bounds x="532" y="462" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1p63v3w_di" bpmnElement="respond_to_message">
<dc:Bounds x="372" y="462" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="362" y="505" width="57" height="27" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_0505x87_di" bpmnElement="Flow_0505x87">
<di:waypoint x="248" y="480" />
<di:waypoint x="372" y="480" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1273yit_di" bpmnElement="Flow_1273yit">
<di:waypoint x="408" y="480" />
<di:waypoint x="532" y="480" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1ueajoz_di" bpmnElement="message_send_flow">
<di:waypoint x="300" y="217" />
<di:waypoint x="300" y="340" />
<di:waypoint x="230" y="340" />
<di:waypoint x="230" y="462" />
<bpmndi:BPMNLabel>
<dc:Bounds x="303" y="315" width="74" height="27" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1n96n67_di" bpmnElement="message_response_flow">
<di:waypoint x="391" y="462" />
<di:waypoint x="400" y="329" />
<di:waypoint x="460" y="329" />
<di:waypoint x="460" y="195" />
<bpmndi:BPMNLabel>
<dc:Bounds x="462" y="315" width="76" height="27" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -0,0 +1,39 @@
<?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" id="Definitions_96f6665" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.0.0-dev">
<bpmn:process id="Process_bd2e724" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_1rcteeq</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_1rcteeq" sourceRef="StartEvent_1" targetRef="the_call_activity" />
<bpmn:callActivity id="the_call_activity" name="The Call Activity" calledElement="ProcessIdTBD1">
<bpmn:incoming>Flow_1rcteeq</bpmn:incoming>
<bpmn:outgoing>Flow_1rid3w7</bpmn:outgoing>
</bpmn:callActivity>
<bpmn:endEvent id="Event_1w7nqwy">
<bpmn:incoming>Flow_1rid3w7</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_1rid3w7" sourceRef="the_call_activity" targetRef="Event_1w7nqwy" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_bd2e724">
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="159" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0pmcny7_di" bpmnElement="the_call_activity">
<dc:Bounds x="270" y="137" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1w7nqwy_di" bpmnElement="Event_1w7nqwy">
<dc:Bounds x="432" y="159" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_1rcteeq_di" bpmnElement="Flow_1rcteeq">
<di:waypoint x="215" y="177" />
<di:waypoint x="270" y="177" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1rid3w7_di" bpmnElement="Flow_1rid3w7">
<di:waypoint x="370" y="177" />
<di:waypoint x="432" y="177" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -0,0 +1,195 @@
<?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:spiffworkflow="http://spiffworkflow.org/bpmn/schema/1.0/core" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_0qmxumb" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.0.0" modeler:executionPlatform="Camunda Platform" modeler:executionPlatformVersion="7.17.0">
<bpmn:collaboration id="my_collaboration">
<bpmn:participant id="buddy" name="Buddy" processRef="process_buddy" />
<bpmn:participant id="Person" name="Person" processRef="random_person_process" />
<bpmn:participant id="Participant_1tlkvw1" name="Tom Petty" processRef="Process_04o9jmt" />
<bpmn:messageFlow id="love_letter_flow" name="Love Letter Flow" sourceRef="ActivitySendLetter" targetRef="Event_0ym6ptw" />
<bpmn:messageFlow id="Flow_10ya1ap" sourceRef="Activity_0ra5uc1" targetRef="Participant_1tlkvw1" />
<bpmn:messageFlow id="Flow_0vonyt2" sourceRef="Participant_1tlkvw1" targetRef="Event_06829ki" />
<bpmn:messageFlow id="Flow_0wwbhd6" sourceRef="Event_0e1t8xh" targetRef="EventReceiveLetter" />
<bpmn:correlationKey name="lover">
<bpmn:correlationPropertyRef>lover_name</bpmn:correlationPropertyRef>
<bpmn:correlationPropertyRef>lover_instrument</bpmn:correlationPropertyRef>
</bpmn:correlationKey>
<bpmn:correlationKey name="singer" />
</bpmn:collaboration>
<bpmn:message id="love_letter" name="Love Letter">
<bpmn:extensionElements>
<spiffworkflow:messagePayload>
{
'to': { 'name': my_lover_variable }
}
</spiffworkflow:messagePayload>
</bpmn:extensionElements>
</bpmn:message>
<bpmn:message id="love_letter_response" name="Love Letter Response" />
<bpmn:correlationProperty id="lover_instrument" name="Lover&#39;s Instrument">
<bpmn:correlationPropertyRetrievalExpression messageRef="love_letter">
<bpmn:formalExpression>lover.instrument</bpmn:formalExpression>
</bpmn:correlationPropertyRetrievalExpression>
<bpmn:correlationPropertyRetrievalExpression messageRef="love_letter_response">
<bpmn:formalExpression>from.instrument</bpmn:formalExpression>
</bpmn:correlationPropertyRetrievalExpression>
</bpmn:correlationProperty>
<bpmn:correlationProperty id="lover_name" name="Lover&#39;s Name">
<bpmn:correlationPropertyRetrievalExpression messageRef="love_letter">
<bpmn:formalExpression>lover.name</bpmn:formalExpression>
</bpmn:correlationPropertyRetrievalExpression>
<bpmn:correlationPropertyRetrievalExpression messageRef="love_letter_response">
<bpmn:formalExpression>from.name</bpmn:formalExpression>
</bpmn:correlationPropertyRetrievalExpression>
<bpmn:correlationPropertyRetrievalExpression>
<bpmn:formalExpression>heartbreaker</bpmn:formalExpression>
</bpmn:correlationPropertyRetrievalExpression>
</bpmn:correlationProperty>
<bpmn:correlationProperty id="singer_name" name="Singer&#39;s Name">
<bpmn:correlationPropertyRetrievalExpression messageRef="love_letter_response">
<bpmn:formalExpression>to.name</bpmn:formalExpression>
</bpmn:correlationPropertyRetrievalExpression>
</bpmn:correlationProperty>
<bpmn:process id="process_buddy" name="Process Buddy" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_1bl6jeh</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sendTask id="ActivitySendLetter" name="Send Letter" messageRef="love_letter">
<bpmn:incoming>Flow_1bl6jeh</bpmn:incoming>
<bpmn:outgoing>Flow_0tp8uut</bpmn:outgoing>
</bpmn:sendTask>
<bpmn:intermediateCatchEvent id="EventReceiveLetter" name="receive Letter">
<bpmn:incoming>Flow_0tp8uut</bpmn:incoming>
<bpmn:outgoing>Flow_1ai45pq</bpmn:outgoing>
<bpmn:messageEventDefinition id="medllr" messageRef="love_letter_response" />
</bpmn:intermediateCatchEvent>
<bpmn:endEvent id="Event_01h5zqa">
<bpmn:incoming>Flow_0rygg2d</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sendTask id="Activity_0ra5uc1" name="Get support">
<bpmn:incoming>Flow_1ai45pq</bpmn:incoming>
<bpmn:outgoing>Flow_1f0m6hd</bpmn:outgoing>
</bpmn:sendTask>
<bpmn:intermediateCatchEvent id="Event_06829ki">
<bpmn:incoming>Flow_1f0m6hd</bpmn:incoming>
<bpmn:outgoing>Flow_0rygg2d</bpmn:outgoing>
<bpmn:messageEventDefinition id="MessageEventDefinition_07bwnfa" />
</bpmn:intermediateCatchEvent>
<bpmn:sequenceFlow id="Flow_1f0m6hd" sourceRef="Activity_0ra5uc1" targetRef="Event_06829ki" />
<bpmn:sequenceFlow id="Flow_1ai45pq" sourceRef="EventReceiveLetter" targetRef="Activity_0ra5uc1" />
<bpmn:sequenceFlow id="Flow_0tp8uut" sourceRef="ActivitySendLetter" targetRef="EventReceiveLetter" />
<bpmn:sequenceFlow id="Flow_1bl6jeh" sourceRef="StartEvent_1" targetRef="ActivitySendLetter" />
<bpmn:sequenceFlow id="Flow_0rygg2d" sourceRef="Event_06829ki" targetRef="Event_01h5zqa" />
</bpmn:process>
<bpmn:process id="random_person_process" name="Process" isExecutable="true">
<bpmn:startEvent id="Event_0ym6ptw">
<bpmn:outgoing>Flow_1bnzzx2</bpmn:outgoing>
<bpmn:messageEventDefinition id="MessageEventDefinition_05bnll8" />
</bpmn:startEvent>
<bpmn:endEvent id="Event_0yy6dsf">
<bpmn:incoming>Flow_11malws</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_1bnzzx2" sourceRef="Event_0ym6ptw" targetRef="Event_0e1t8xh" />
<bpmn:sequenceFlow id="Flow_11malws" sourceRef="Event_0e1t8xh" targetRef="Event_0yy6dsf" />
<bpmn:intermediateThrowEvent id="Event_0e1t8xh">
<bpmn:incoming>Flow_1bnzzx2</bpmn:incoming>
<bpmn:outgoing>Flow_11malws</bpmn:outgoing>
<bpmn:messageEventDefinition id="MessageEventDefinition_145e86u" />
</bpmn:intermediateThrowEvent>
</bpmn:process>
<bpmn:process id="Process_04o9jmt" isExecutable="false" />
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="my_collaboration">
<bpmndi:BPMNShape id="Participant_12ffz3p_di" bpmnElement="buddy" isHorizontal="true">
<dc:Bounds x="129" y="190" width="721" height="200" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_0rygg2d_di" bpmnElement="Flow_0rygg2d">
<di:waypoint x="718" y="265" />
<di:waypoint x="782" y="265" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1bl6jeh_di" bpmnElement="Flow_1bl6jeh">
<di:waypoint x="215" y="265" />
<di:waypoint x="270" y="265" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0tp8uut_di" bpmnElement="Flow_0tp8uut">
<di:waypoint x="370" y="265" />
<di:waypoint x="432" y="265" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1ai45pq_di" bpmnElement="Flow_1ai45pq">
<di:waypoint x="468" y="265" />
<di:waypoint x="540" y="265" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1f0m6hd_di" bpmnElement="Flow_1f0m6hd">
<di:waypoint x="640" y="265" />
<di:waypoint x="682" y="265" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="247" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0p9c4se_di" bpmnElement="ActivitySendLetter">
<dc:Bounds x="270" y="225" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0s2n1j8_di" bpmnElement="EventReceiveLetter">
<dc:Bounds x="432" y="247" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="416" y="223" width="67" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_01h5zqa_di" bpmnElement="Event_01h5zqa">
<dc:Bounds x="782" y="247" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_07t6p94_di" bpmnElement="Activity_0ra5uc1">
<dc:Bounds x="540" y="225" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1f1k5np_di" bpmnElement="Event_06829ki">
<dc:Bounds x="682" y="247" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Participant_0nbivcp_di" bpmnElement="Person" isHorizontal="true">
<dc:Bounds x="129" y="430" width="721" height="160" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_1bnzzx2_di" bpmnElement="Flow_1bnzzx2">
<di:waypoint x="328" y="530" />
<di:waypoint x="412" y="530" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_11malws_di" bpmnElement="Flow_11malws">
<di:waypoint x="448" y="530" />
<di:waypoint x="572" y="530" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="Event_09ldq03_di" bpmnElement="Event_0ym6ptw">
<dc:Bounds x="292" y="512" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0yy6dsf_di" bpmnElement="Event_0yy6dsf">
<dc:Bounds x="572" y="512" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_12u2p44_di" bpmnElement="Event_0e1t8xh">
<dc:Bounds x="412" y="512" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Participant_1tlkvw1_di" bpmnElement="Participant_1tlkvw1" isHorizontal="true">
<dc:Bounds x="129" y="80" width="721" height="60" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_11c08m8_di" bpmnElement="love_letter_flow">
<di:waypoint x="310" y="305" />
<di:waypoint x="310" y="512" />
<bpmndi:BPMNLabel>
<dc:Bounds x="219" y="377" width="82" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_10ya1ap_di" bpmnElement="Flow_10ya1ap">
<di:waypoint x="590" y="225" />
<di:waypoint x="590" y="140" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0vonyt2_di" bpmnElement="Flow_0vonyt2">
<di:waypoint x="700" y="140" />
<di:waypoint x="700" y="247" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0wwbhd6_di" bpmnElement="Flow_0wwbhd6">
<di:waypoint x="430" y="512" />
<di:waypoint x="430" y="398" />
<di:waypoint x="450" y="398" />
<di:waypoint x="450" y="283" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

150
test/spec/bpmn/diagram.bpmn Normal file
View File

@ -0,0 +1,150 @@
<?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:spiffworkflow="http://spiffworkflow.org/bpmn/schema/1.0/core" 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" />
</bpmn:ioSpecification>
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_1mezzcx</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:endEvent id="Event_14wzv4j">
<bpmn:incoming>Flow_1lu1qyz</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" />
<bpmn:manualTask id="Activity_15zz6ya" name="eat hot dog">
<bpmn:incoming>Flow_1mezzcx</bpmn:incoming>
<bpmn:outgoing>Flow_01jg677</bpmn:outgoing>
<bpmn:dataOutputAssociation id="DataOutputAssociation_1uj5jzs">
<bpmn:targetRef>my_data_ref_1</bpmn:targetRef>
</bpmn:dataOutputAssociation>
<bpmn:standardLoopCharacteristics />
</bpmn:manualTask>
<bpmn:sequenceFlow id="Flow_0q4oys2" sourceRef="my_script_task" targetRef="task_confirm" />
<bpmn:scriptTask id="my_script_task" name="calculate contentment">
<bpmn:extensionElements>
<camunda:inputOutput>
<camunda:inputParameter name="Input_34evvjn_23" />
</camunda:inputOutput>
</bpmn:extensionElements>
<bpmn:incoming>Flow_01jg677</bpmn:incoming>
<bpmn:outgoing>Flow_0q4oys2</bpmn:outgoing>
<bpmn:property id="Property_0lgu12u" name="__targetRef_placeholder" />
<bpmn:dataInputAssociation id="DataInputAssociation_0ve9sql">
<bpmn:sourceRef>my_data_ref_2</bpmn:sourceRef>
<bpmn:targetRef>Property_0lgu12u</bpmn:targetRef>
</bpmn:dataInputAssociation>
<bpmn:script>elizabeth="awesome"</bpmn:script>
</bpmn:scriptTask>
<bpmn:dataObject id="my_other_data_object" />
<bpmn:dataObject id="my_third_data_object" />
<bpmn:dataObjectReference id="my_data_ref_1" name="my_data_object" dataObjectRef="my_data_object" />
<bpmn:sequenceFlow id="Flow_132laxn" sourceRef="task_confirm" targetRef="business_rule_task" />
<bpmn:userTask id="task_confirm" name="confirm contentment">
<bpmn:extensionElements>
<spiffworkflow:preScript>x=1</spiffworkflow:preScript>
<spiffworkflow:postScript>y=2</spiffworkflow:postScript>
<spiffworkflow:properties>
<spiffworkflow:property name="formJsonSchemaFilename" value="give_me_a_number_form.json" />
<spiffworkflow:property name="formUiSchemaFilename" value="number_form_schema.json" />
</spiffworkflow:properties>
</bpmn:extensionElements>
<bpmn:incoming>Flow_0q4oys2</bpmn:incoming>
<bpmn:outgoing>Flow_132laxn</bpmn:outgoing>
</bpmn:userTask>
<bpmn:dataObjectReference id="my_data_ref_2" name="my_data_object" dataObjectRef="my_data_object">
<bpmn:extensionElements>
<camunda:properties>
<camunda:property />
<camunda:property />
</camunda:properties>
</bpmn:extensionElements>
</bpmn:dataObjectReference>
<bpmn:dataObject id="my_data_object" />
<bpmn:sequenceFlow id="Flow_1lu1qyz" sourceRef="business_rule_task" targetRef="Event_14wzv4j" />
<bpmn:businessRuleTask id="business_rule_task">
<bpmn:extensionElements>
<spiffworkflow:calledDecisionId>test_decision</spiffworkflow:calledDecisionId>
</bpmn:extensionElements>
<bpmn:incoming>Flow_132laxn</bpmn:incoming>
<bpmn:outgoing>Flow_1lu1qyz</bpmn:outgoing>
</bpmn:businessRuleTask>
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="ProcessTest">
<bpmndi:BPMNEdge id="Flow_132laxn_di" bpmnElement="Flow_132laxn">
<di:waypoint x="690" y="187" />
<di:waypoint x="730" y="187" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0q4oys2_di" bpmnElement="Flow_0q4oys2">
<di:waypoint x="540" y="187" />
<di:waypoint x="590" y="187" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1mezzcx_di" bpmnElement="Flow_1mezzcx">
<di:waypoint x="215" y="187" />
<di:waypoint x="280" y="187" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_01jg677_di" bpmnElement="Flow_01jg677">
<di:waypoint x="380" y="187" />
<di:waypoint x="440" y="187" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1lu1qyz_di" bpmnElement="Flow_1lu1qyz">
<di:waypoint x="830" y="187" />
<di:waypoint x="892" y="187" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="169" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0t7iwfm_di" bpmnElement="Activity_15zz6ya">
<dc:Bounds x="280" y="147" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0h86vbv_di" bpmnElement="my_script_task">
<dc:Bounds x="440" y="147" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="DataObjectReference_1cezipn_di" bpmnElement="my_data_ref_1">
<dc:Bounds x="312" y="265" width="36" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="292" y="322" width="78" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1gk19al_di" bpmnElement="task_confirm">
<dc:Bounds x="590" y="147" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="DataObjectReference_08bm72g_di" bpmnElement="my_data_ref_2">
<dc:Bounds x="472" y="265" width="36" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="451" y="322" width="78" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="dataInput_1" bpmnElement="num_dogs">
<dc:Bounds x="172" y="85" width="36" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="151" y="135" width="81" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="dataInput_2" bpmnElement="happy_index">
<dc:Bounds x="772" y="65" width="36" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="748" y="122" width="83" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_14wzv4j_di" bpmnElement="Event_14wzv4j">
<dc:Bounds x="892" y="169" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0fqcbcx_di" bpmnElement="business_rule_task">
<dc:Bounds x="730" y="147" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="DataOutputAssociation_1uj5jzs_di" bpmnElement="DataOutputAssociation_1uj5jzs">
<di:waypoint x="329" y="227" />
<di:waypoint x="328" y="265" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="DataInputAssociation_0ve9sql_di" bpmnElement="DataInputAssociation_0ve9sql">
<di:waypoint x="490" y="265" />
<di:waypoint x="490" y="227" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -0,0 +1,23 @@
<?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:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_1qnx3d3" 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_16xfaqc" isExecutable="true">
<bpmn:startEvent id="StartEvent_1" />
<bpmn:subProcess id="my_subprocess" name="my_subprocess">
<bpmn:startEvent id="Event_1u7naip" />
</bpmn:subProcess>
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_16xfaqc">
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="172" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_11pqp1v_di" bpmnElement="my_subprocess" isExpanded="true">
<dc:Bounds x="330" y="90" width="350" height="200" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1u7naip_di" bpmnElement="Event_1u7naip">
<dc:Bounds x="370" y="172" width="36" height="36" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

110
test/spec/bpmn/gateway.bpmn Normal file
View File

@ -0,0 +1,110 @@
<?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_1qnx3d3" 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="test_gateway_expression" name="Test Gateway ExpressionPath" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_0ik6wwl</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_0ik6wwl" sourceRef="StartEvent_1" targetRef="set_var" />
<bpmn:sequenceFlow id="Flow_0l0l3ie" sourceRef="set_var" targetRef="choose_path" />
<bpmn:scriptTask id="set_var" name="Set Var">
<bpmn:incoming>Flow_0ik6wwl</bpmn:incoming>
<bpmn:outgoing>Flow_0l0l3ie</bpmn:outgoing>
<bpmn:script>the_var = 1</bpmn:script>
</bpmn:scriptTask>
<bpmn:scriptTask id="set_result_var_top" name="Set Result Var Top">
<bpmn:incoming>top_flow</bpmn:incoming>
<bpmn:outgoing>Flow_13gkhe0</bpmn:outgoing>
<bpmn:script>result_var = "TOP"</bpmn:script>
</bpmn:scriptTask>
<bpmn:scriptTask id="set_result_var_bottom" name="Set Result Var Bottom">
<bpmn:incoming>bottom_flow</bpmn:incoming>
<bpmn:outgoing>Flow_1u2cwim</bpmn:outgoing>
<bpmn:script>result_var = "BOTTOM"</bpmn:script>
</bpmn:scriptTask>
<bpmn:endEvent id="top_end" name="Top End">
<bpmn:incoming>Flow_13gkhe0</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_13gkhe0" sourceRef="set_result_var_top" targetRef="top_end" />
<bpmn:endEvent id="bottom_end" name="Bottom End">
<bpmn:incoming>Flow_1u2cwim</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_1u2cwim" sourceRef="set_result_var_bottom" targetRef="bottom_end" />
<bpmn:exclusiveGateway id="choose_path" name="Choose Path">
<bpmn:incoming>Flow_0l0l3ie</bpmn:incoming>
<bpmn:outgoing>bottom_flow</bpmn:outgoing>
<bpmn:outgoing>top_flow</bpmn:outgoing>
</bpmn:exclusiveGateway>
<bpmn:sequenceFlow id="bottom_flow" name="Bottom Flow" sourceRef="choose_path" targetRef="set_result_var_bottom" />
<bpmn:sequenceFlow id="top_flow" name="Top Flow" sourceRef="choose_path" targetRef="set_result_var_top">
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">the_var == 1</bpmn:conditionExpression>
</bpmn:sequenceFlow>
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="test_gateway_expression">
<bpmndi:BPMNEdge id="Flow_0ik6wwl_di" bpmnElement="Flow_0ik6wwl">
<di:waypoint x="215" y="190" />
<di:waypoint x="270" y="190" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0l0l3ie_di" bpmnElement="Flow_0l0l3ie">
<di:waypoint x="370" y="190" />
<di:waypoint x="425" y="190" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_13gkhe0_di" bpmnElement="Flow_13gkhe0">
<di:waypoint x="630" y="110" />
<di:waypoint x="692" y="110" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1u2cwim_di" bpmnElement="Flow_1u2cwim">
<di:waypoint x="630" y="250" />
<di:waypoint x="692" y="250" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_15p61p3_di" bpmnElement="bottom_flow">
<di:waypoint x="450" y="215" />
<di:waypoint x="450" y="250" />
<di:waypoint x="530" y="250" />
<bpmndi:BPMNLabel>
<dc:Bounds x="435" y="230" width="62" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_14md7ua_di" bpmnElement="top_flow">
<di:waypoint x="450" y="165" />
<di:waypoint x="450" y="110" />
<di:waypoint x="530" y="110" />
<bpmndi:BPMNLabel>
<dc:Bounds x="443" y="135" width="45" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="172" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0knhsfp_di" bpmnElement="set_var">
<dc:Bounds x="270" y="150" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0b5ya8m_di" bpmnElement="set_result_var_top">
<dc:Bounds x="530" y="70" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_157isb4_di" bpmnElement="set_result_var_bottom">
<dc:Bounds x="530" y="210" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1vplzst_di" bpmnElement="top_end">
<dc:Bounds x="692" y="92" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="690" y="135" width="41" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0hcyrv2_di" bpmnElement="bottom_end">
<dc:Bounds x="692" y="232" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="682" y="275" width="58" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_1d6xh77_di" bpmnElement="choose_path" isMarkerVisible="true">
<dc:Bounds x="425" y="165" width="50" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="498" y="180" width="64" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -0,0 +1,69 @@
<?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:spiffworkflow="http://spiffworkflow.org/bpmn/schema/1.0/core" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_1ny7jp4" 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="sample" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_10jwwqy</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_10jwwqy" sourceRef="StartEvent_1" targetRef="Activity_03fldr6" />
<bpmn:endEvent id="Event_1qb1u6a">
<bpmn:incoming>Flow_0htxke7</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_0htxke7" sourceRef="script_with_unit_test_id" targetRef="Event_1qb1u6a" />
<bpmn:scriptTask id="script_with_unit_test_id" name="Script with unit test">
<bpmn:extensionElements>
<spiffworkflow:unitTests>
<spiffworkflow:unitTest id="sets_hey_to_true_if_hey_is_false">
<spiffworkflow:inputJson>{"hey": false}</spiffworkflow:inputJson>
<spiffworkflow:expectedOutputJson>{"hey": true}</spiffworkflow:expectedOutputJson>
</spiffworkflow:unitTest>
<spiffworkflow:unitTest id="sets_something_else_if_no_hey">
<spiffworkflow:inputJson>{}</spiffworkflow:inputJson>
<spiffworkflow:expectedOutputJson>{"something_else": true}</spiffworkflow:expectedOutputJson>
</spiffworkflow:unitTest>
</spiffworkflow:unitTests>
</bpmn:extensionElements>
<bpmn:incoming>Flow_0niwe1y</bpmn:incoming>
<bpmn:outgoing>Flow_0htxke7</bpmn:outgoing>
<bpmn:script>if 'hey' in locals():
hey = True
else:
something_else = True</bpmn:script>
</bpmn:scriptTask>
<bpmn:sequenceFlow id="Flow_0niwe1y" sourceRef="Activity_03fldr6" targetRef="script_with_unit_test_id" />
<bpmn:scriptTask id="Activity_03fldr6" name="Set var">
<bpmn:incoming>Flow_10jwwqy</bpmn:incoming>
<bpmn:outgoing>Flow_0niwe1y</bpmn:outgoing>
<bpmn:script>hey = False</bpmn:script>
</bpmn:scriptTask>
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="sample">
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="132" y="102" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1qb1u6a_di" bpmnElement="Event_1qb1u6a">
<dc:Bounds x="642" y="102" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_17ohe7r_di" bpmnElement="script_with_unit_test_id">
<dc:Bounds x="440" y="80" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1fab1y2_di" bpmnElement="Activity_03fldr6">
<dc:Bounds x="250" y="80" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_10jwwqy_di" bpmnElement="Flow_10jwwqy">
<di:waypoint x="168" y="120" />
<di:waypoint x="250" y="120" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0htxke7_di" bpmnElement="Flow_0htxke7">
<di:waypoint x="540" y="120" />
<di:waypoint x="642" y="120" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0niwe1y_di" bpmnElement="Flow_0niwe1y">
<di:waypoint x="350" y="120" />
<di:waypoint x="440" y="120" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -0,0 +1,39 @@
<?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_116xv2e" 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_0z3z6vd" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_09rugkh</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_09rugkh" sourceRef="StartEvent_1" targetRef="my_service_task" />
<bpmn:endEvent id="Event_1tb182h">
<bpmn:incoming>Flow_0kql76n</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_0kql76n" sourceRef="my_service_task" targetRef="Event_1tb182h" />
<bpmn:serviceTask id="my_service_task" name="My Service Task">
<bpmn:incoming>Flow_09rugkh</bpmn:incoming>
<bpmn:outgoing>Flow_0kql76n</bpmn:outgoing>
</bpmn:serviceTask>
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_0z3z6vd">
<bpmndi:BPMNEdge id="Flow_09rugkh_di" bpmnElement="Flow_09rugkh">
<di:waypoint x="215" y="117" />
<di:waypoint x="270" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0kql76n_di" bpmnElement="Flow_0kql76n">
<di:waypoint x="370" y="117" />
<di:waypoint x="432" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1tb182h_di" bpmnElement="Event_1tb182h">
<dc:Bounds x="432" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_04smezy_di" bpmnElement="my_service_task">
<dc:Bounds x="270" y="77" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -0,0 +1,221 @@
<?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:spiffworkflow="http://spiffworkflow.org/bpmn/schema/1.0/core" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_0qmxumb" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.0.0" modeler:executionPlatform="Camunda Platform" modeler:executionPlatformVersion="7.17.0">
<bpmn:collaboration id="my_collaboration">
<bpmn:participant id="buddy" name="Alpha" processRef="process_buddy" />
<bpmn:participant id="Person" name="Beta" processRef="random_person_process" />
<bpmn:messageFlow id="love_letter_flow" name="Message Flow" sourceRef="ActivitySendLetter" targetRef="Event_0ym6ptw" />
<bpmn:messageFlow id="Flow_0zpmqjr" name="Response Flow" sourceRef="Event_0e1t8xh" targetRef="Activity_1dagl6a" />
<bpmn:correlationKey name="invoice">
<bpmn:correlationPropertyRef>invoice_id</bpmn:correlationPropertyRef>
<bpmn:correlationPropertyRef>invoice_date</bpmn:correlationPropertyRef>
<bpmn:correlationPropertyRef>invoice_total</bpmn:correlationPropertyRef>
</bpmn:correlationKey>
<bpmn:correlationKey name="payment">
<bpmn:correlationPropertyRef>payment_id</bpmn:correlationPropertyRef>
<bpmn:correlationPropertyRef>payment_date</bpmn:correlationPropertyRef>
<bpmn:correlationPropertyRef>payment_total</bpmn:correlationPropertyRef>
</bpmn:correlationKey>
</bpmn:collaboration>
<bpmn:message id="send_invoice" name="Send Invoice">
<bpmn:extensionElements>
<spiffworkflow:messagePayload>
{
'invoice': { 'id': my_invoice_id, 'date': my_invoice_date, 'total': my_invoice_total }
}
</spiffworkflow:messagePayload>
</bpmn:extensionElements>
</bpmn:message>
<bpmn:message id="send_payment" name="Send Payment">
<bpmn:extensionElements>
<spiffworkflow:messagePayload>
{ 'payment':
{
'id': my_payment_id,
'date': my_payment_date,
'total': my_payment_total,
'invoice_id': invoice_id,
'invoice_date': invoice_data,
'invoice_amount': invoice_amount,
}
}
</spiffworkflow:messagePayload>
</bpmn:extensionElements>
</bpmn:message>
<bpmn:correlationProperty id="invoice_id" name="Invoice ID">
<bpmn:correlationPropertyRetrievalExpression messageRef="send_invoice">
<bpmn:formalExpression>invoice.invoice_id</bpmn:formalExpression>
</bpmn:correlationPropertyRetrievalExpression>
<bpmn:correlationPropertyRetrievalExpression messageRef="send_payment">
<bpmn:formalExpression>payment.invoice_id</bpmn:formalExpression>
</bpmn:correlationPropertyRetrievalExpression>
</bpmn:correlationProperty>
<bpmn:correlationProperty id="invoice_total" name="Invoice Total">
<bpmn:correlationPropertyRetrievalExpression messageRef="send_invoice">
<bpmn:formalExpression>invoice.total</bpmn:formalExpression>
</bpmn:correlationPropertyRetrievalExpression>
<bpmn:correlationPropertyRetrievalExpression messageRef="send_payment">
<bpmn:formalExpression>payment.invoice_amount</bpmn:formalExpression>
</bpmn:correlationPropertyRetrievalExpression>
</bpmn:correlationProperty>
<bpmn:correlationProperty id="invoice_date" name="Invoice Date">
<bpmn:correlationPropertyRetrievalExpression messageRef="send_invoice">
<bpmn:formalExpression>invoice.date</bpmn:formalExpression>
</bpmn:correlationPropertyRetrievalExpression>
<bpmn:correlationPropertyRetrievalExpression messageRef="send_payment">
<bpmn:formalExpression>payment.invoice_date</bpmn:formalExpression>
</bpmn:correlationPropertyRetrievalExpression>
</bpmn:correlationProperty>
<bpmn:correlationProperty id="payment_date" name="Payment Date">
<bpmn:correlationPropertyRetrievalExpression messageRef="send_payment">
<bpmn:formalExpression>payment.date</bpmn:formalExpression>
</bpmn:correlationPropertyRetrievalExpression>
</bpmn:correlationProperty>
<bpmn:correlationProperty id="payment_total" name="Payment Total">
<bpmn:correlationPropertyRetrievalExpression messageRef="send_payment">
<bpmn:formalExpression>payment.total</bpmn:formalExpression>
</bpmn:correlationPropertyRetrievalExpression>
</bpmn:correlationProperty>
<bpmn:correlationProperty id="payment_id" name="Payment ID">
<bpmn:correlationPropertyRetrievalExpression messageRef="send_payment">
<bpmn:formalExpression>payment.id</bpmn:formalExpression>
</bpmn:correlationPropertyRetrievalExpression>
</bpmn:correlationProperty>
<bpmn:process id="process_buddy" name="Process Buddy" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_1bl6jeh</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sendTask id="ActivitySendLetter" name="Send Message" messageRef="love_letter">
<bpmn:incoming>Flow_1bl6jeh</bpmn:incoming>
<bpmn:outgoing>Flow_1vgtrb2</bpmn:outgoing>
</bpmn:sendTask>
<bpmn:sequenceFlow id="Flow_1bl6jeh" sourceRef="StartEvent_1" targetRef="ActivitySendLetter" />
<bpmn:sequenceFlow id="Flow_1vgtrb2" sourceRef="ActivitySendLetter" targetRef="Activity_1dagl6a" />
<bpmn:endEvent id="Event_01h5zqa">
<bpmn:incoming>Flow_1f0m6hd</bpmn:incoming>
</bpmn:endEvent>
<bpmn:receiveTask id="Activity_1dagl6a" name="Reveive Response">
<bpmn:incoming>Flow_1vgtrb2</bpmn:incoming>
<bpmn:outgoing>Flow_1ygtaxw</bpmn:outgoing>
</bpmn:receiveTask>
<bpmn:sequenceFlow id="Flow_1f0m6hd" sourceRef="Activity_0ra5uc1" targetRef="Event_01h5zqa" />
<bpmn:sequenceFlow id="Flow_1ygtaxw" sourceRef="Activity_1dagl6a" targetRef="Activity_0ra5uc1" />
<bpmn:manualTask id="Activity_0ra5uc1" name="Display Stuff">
<bpmn:incoming>Flow_1ygtaxw</bpmn:incoming>
<bpmn:outgoing>Flow_1f0m6hd</bpmn:outgoing>
</bpmn:manualTask>
</bpmn:process>
<bpmn:process id="random_person_process" name="Process" isExecutable="true">
<bpmn:startEvent id="Event_0ym6ptw">
<bpmn:outgoing>Flow_1bnzzx2</bpmn:outgoing>
<bpmn:messageEventDefinition id="MessageEventDefinition_05bnll8" />
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_11malws" sourceRef="Event_0e1t8xh" targetRef="Event_0yy6dsf" />
<bpmn:sequenceFlow id="Flow_1bnzzx2" sourceRef="Event_0ym6ptw" targetRef="Activity_15kdfc4" />
<bpmn:sequenceFlow id="Flow_07sdx2y" sourceRef="Activity_15kdfc4" targetRef="Event_0e1t8xh" />
<bpmn:endEvent id="Event_0yy6dsf">
<bpmn:incoming>Flow_11malws</bpmn:incoming>
</bpmn:endEvent>
<bpmn:intermediateThrowEvent id="Event_0e1t8xh">
<bpmn:incoming>Flow_07sdx2y</bpmn:incoming>
<bpmn:outgoing>Flow_11malws</bpmn:outgoing>
<bpmn:messageEventDefinition id="MessageEventDefinition_145e86u" />
</bpmn:intermediateThrowEvent>
<bpmn:scriptTask id="Activity_15kdfc4" name="Do Something">
<bpmn:incoming>Flow_1bnzzx2</bpmn:incoming>
<bpmn:outgoing>Flow_07sdx2y</bpmn:outgoing>
</bpmn:scriptTask>
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="my_collaboration">
<bpmndi:BPMNShape id="Participant_12ffz3p_di" bpmnElement="buddy" isHorizontal="true">
<dc:Bounds x="129" y="190" width="889" height="290" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_1vgtrb2_di" bpmnElement="Flow_1vgtrb2">
<di:waypoint x="370" y="265" />
<di:waypoint x="510" y="265" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1bl6jeh_di" bpmnElement="Flow_1bl6jeh">
<di:waypoint x="215" y="265" />
<di:waypoint x="270" y="265" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1f0m6hd_di" bpmnElement="Flow_1f0m6hd">
<di:waypoint x="770" y="265" />
<di:waypoint x="882" y="265" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1ygtaxw_di" bpmnElement="Flow_1ygtaxw">
<di:waypoint x="610" y="265" />
<di:waypoint x="670" y="265" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="247" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0p9c4se_di" bpmnElement="ActivitySendLetter">
<dc:Bounds x="270" y="225" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_01h5zqa_di" bpmnElement="Event_01h5zqa">
<dc:Bounds x="882" y="247" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_021bozj_di" bpmnElement="Activity_1dagl6a">
<dc:Bounds x="510" y="225" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_05js9ii_di" bpmnElement="Activity_0ra5uc1">
<dc:Bounds x="670" y="225" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Participant_0nbivcp_di" bpmnElement="Person" isHorizontal="true">
<dc:Bounds x="129" y="520" width="889" height="160" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_11malws_di" bpmnElement="Flow_11malws">
<di:waypoint x="578" y="620" />
<di:waypoint x="702" y="620" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1bnzzx2_di" bpmnElement="Flow_1bnzzx2">
<di:waypoint x="328" y="620" />
<di:waypoint x="370" y="620" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_07sdx2y_di" bpmnElement="Flow_07sdx2y">
<di:waypoint x="470" y="620" />
<di:waypoint x="542" y="620" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="Event_09ldq03_di" bpmnElement="Event_0ym6ptw">
<dc:Bounds x="292" y="602" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0yy6dsf_di" bpmnElement="Event_0yy6dsf">
<dc:Bounds x="702" y="602" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_12u2p44_di" bpmnElement="Event_0e1t8xh">
<dc:Bounds x="542" y="602" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0itanit_di" bpmnElement="Activity_15kdfc4">
<dc:Bounds x="370" y="580" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_11c08m8_di" bpmnElement="love_letter_flow">
<di:waypoint x="310" y="305" />
<di:waypoint x="310" y="602" />
<bpmndi:BPMNLabel>
<dc:Bounds x="225" y="411" width="71" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0zpmqjr_di" bpmnElement="Flow_0zpmqjr">
<di:waypoint x="560" y="602" />
<di:waypoint x="560" y="305" />
<bpmndi:BPMNLabel>
<dc:Bounds x="582" y="410" width="76" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -0,0 +1,42 @@
<?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_19j8hlu" 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_1636npq" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_0bk9zgm</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_0bk9zgm" sourceRef="StartEvent_1" targetRef="my_subprocess" />
<bpmn:subProcess id="my_subprocess" name="my_subprocess">
<bpmn:incoming>Flow_0bk9zgm</bpmn:incoming>
<bpmn:outgoing>Flow_1lzv56z</bpmn:outgoing>
<bpmn:startEvent id="Event_16nawbb" />
</bpmn:subProcess>
<bpmn:endEvent id="Event_139c1vo">
<bpmn:incoming>Flow_1lzv56z</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_1lzv56z" sourceRef="my_subprocess" targetRef="Event_139c1vo" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1636npq">
<bpmndi:BPMNEdge id="Flow_0bk9zgm_di" bpmnElement="Flow_0bk9zgm">
<di:waypoint x="215" y="165" />
<di:waypoint x="310" y="165" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1lzv56z_di" bpmnElement="Flow_1lzv56z">
<di:waypoint x="640" y="165" />
<di:waypoint x="742" y="165" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="147" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_139c1vo_di" bpmnElement="Event_139c1vo">
<dc:Bounds x="742" y="147" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0w131va_di" bpmnElement="my_subprocess" isExpanded="true">
<dc:Bounds x="310" y="80" width="330" height="170" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_16nawbb_di" bpmnElement="Event_16nawbb">
<dc:Bounds x="331.6666666666667" y="152" width="36" height="36" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -0,0 +1,51 @@
<?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:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_1qnx3d3" 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_16xfaqc" isExecutable="true" camunda:versionTag="1">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_0vt1twq</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:endEvent id="Event_0yxpeto">
<bpmn:incoming>Flow_1udyjxo</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_0vt1twq" sourceRef="StartEvent_1" targetRef="my_user_task" />
<bpmn:sequenceFlow id="Flow_1udyjxo" sourceRef="my_user_task" targetRef="Event_0yxpeto" />
<bpmn:userTask id="my_user_task" name="Complete Web Form">
<bpmn:extensionElements>
<camunda:formData>
<camunda:formField type="boolean">
<camunda:properties>
<camunda:property />
</camunda:properties>
<camunda:validation>
<camunda:constraint />
</camunda:validation>
</camunda:formField>
</camunda:formData>
</bpmn:extensionElements>
<bpmn:incoming>Flow_0vt1twq</bpmn:incoming>
<bpmn:outgoing>Flow_1udyjxo</bpmn:outgoing>
</bpmn:userTask>
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_16xfaqc">
<bpmndi:BPMNEdge id="Flow_1udyjxo_di" bpmnElement="Flow_1udyjxo">
<di:waypoint x="360" y="100" />
<di:waypoint x="422" y="100" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0vt1twq_di" bpmnElement="Flow_0vt1twq">
<di:waypoint x="178" y="100" />
<di:waypoint x="260" y="100" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="142" y="82" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0096bsk_di" bpmnElement="my_user_task">
<dc:Bounds x="260" y="60" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0yxpeto_di" bpmnElement="Event_0yxpeto">
<dc:Bounds x="422" y="82" width="36" height="36" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

144
test/spec/helpers.js Normal file
View File

@ -0,0 +1,144 @@
import { query as domQuery } from 'min-dom';
import { act, fireEvent } from '@testing-library/preact';
import {
getBpmnJS,
bootstrapBpmnJS,
inject,
insertCSS,
} from 'bpmn-js/test/helper';
import Modeler from 'bpmn-js/lib/Modeler';
import TestContainer from 'mocha-test-container-support';
import { getBusinessObject } from 'bpmn-js/lib/util/ModelUtil';
import { createMoveEvent } from 'diagram-js/lib/features/mouse/Mouse';
export let PROPERTIES_PANEL_CONTAINER;
export let CONTAINER;
export function bootstrapPropertiesPanel(diagram, options, locals) {
return async function () {
let { container } = options;
if (!container) {
container = TestContainer.get(this);
}
CONTAINER = container;
insertBpmnStyles();
insertCoreStyles();
const createModeler = bootstrapBpmnJS(Modeler, diagram, options, locals);
await act(() => createModeler.call(this));
// (2) clean-up properties panel
clearPropertiesPanelContainer();
// (3) attach properties panel
const attachPropertiesPanel = inject(function (propertiesPanel) {
PROPERTIES_PANEL_CONTAINER = document.createElement('div');
PROPERTIES_PANEL_CONTAINER.classList.add('properties-container');
container.appendChild(PROPERTIES_PANEL_CONTAINER);
return act(() => propertiesPanel.attachTo(PROPERTIES_PANEL_CONTAINER));
});
await attachPropertiesPanel();
};
}
export function clearPropertiesPanelContainer() {
if (PROPERTIES_PANEL_CONTAINER) {
PROPERTIES_PANEL_CONTAINER.remove();
}
}
export function insertCoreStyles() {
insertCSS(
'properties-panel.css',
require('bpmn-js-properties-panel/dist/assets/properties-panel.css').default
);
insertCSS('test.css', require('./test.css').default);
}
export function insertBpmnStyles() {
insertCSS(
'diagram.css',
require('bpmn-js/dist/assets/diagram-js.css').default
);
// @barmac: this fails before bpmn-js@9
insertCSS('bpmn-js.css', require('bpmn-js/dist/assets/bpmn-js.css').default);
insertCSS(
'bpmn-font.css',
require('bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css').default
);
}
export function expectSelected(id) {
return getBpmnJS().invoke(async function (elementRegistry, selection) {
const element = elementRegistry.get(id);
await act(() => {
selection.select(element);
});
return element;
});
}
export function getPropertiesPanel() {
return PROPERTIES_PANEL_CONTAINER;
}
export function findEntry(id, container) {
return domQuery(`[data-entry-id='${id}']`, container);
}
export function findGroupEntry(id, container) {
return domQuery(`[data-group-id='group-${id}']`, container);
}
export function findInput(type, container) {
return domQuery(`input[type='${type}']`, container);
}
export function findTextarea(id, container) {
return domQuery(`textarea[id='${id}']`, container);
}
export function findButton(id, container) {
return domQuery(`button[id='${id}']`, container);
}
export function findButtonByClass(buttonClass, container) {
return domQuery(`button[class='${buttonClass}']`, container);
}
export function findSelect(container) {
return domQuery('select', container);
}
export function changeInput(input, value) {
fireEvent.input(input, { target: { value } });
}
export function pressButton(button) {
fireEvent.click(button);
}
export function findDivByClass(divClass, container) {
return domQuery(`div[class='${divClass}']`, container);
}
/**
* Drags an element from the palette onto the canvas.
* @param id
*/
export function triggerPaletteEntry(id) {
getBpmnJS().invoke(function (palette) {
const entry = palette.getEntries()[id];
if (entry && entry.action && entry.action.click) {
entry.action.click(createMoveEvent(0, 0));
}
});
}

40
test/spec/test.css Normal file
View File

@ -0,0 +1,40 @@
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@300;400;500;600&display=swap');
html, body, .modeler-container, .modeler-container > div {
height: 100%;
margin: 0;
font-family: 'IBM Plex Sans', sans-serif;
}
.test-container {
height: 800px !important;
}
.test-content-container {
width: 100%;
height: calc(100% - 24px) !important;
display: flex;
flex: 1;
flex-direction: row;
}
.modeler-container {
flex: 1;
position: relative;
}
.modeler-container, .properties-container {
overflow-y: auto;
}
.properties-container {
position: relative;
flex: none;
height: 100%;
width: 300px;
border-left: solid 1px #cccccc;
}
.properties-container .bio-properties-panel {
--font-family: 'IBM Plex Sans', sans-serif !important;
}

49
webpack.config.js Normal file
View File

@ -0,0 +1,49 @@
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
entry: {
bundle: ['./app/app.js']
},
output: {
path: __dirname + '/public',
filename: 'app.js'
},
module: {
rules: [
{
test: /\.m?js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
plugins: [
[ '@babel/plugin-transform-react-jsx', {
'importSource': '@bpmn-io/properties-panel/preact',
'runtime': 'automatic'
} ]
]
}
}
},
{
test: /\.bpmn$/,
use: 'raw-loader'
}
]
},
plugins: [
new CopyWebpackPlugin({
patterns: [
{ from: 'assets/**', to: 'vendor/bpmn-js', context: 'node_modules/bpmn-js/dist/' },
{
from: 'assets/**',
to: 'vendor/bpmn-js-properties-panel',
context: 'node_modules/bpmn-js-properties-panel/dist/'
},
{from: '**/*.{html,css}', context: 'app/'}
]
})
],
mode: 'development',
devtool: 'source-map'
};