├── .eslintrc.js
├── .github
├── dependabot.yml
├── labels.yml
├── release-drafter.yml
└── workflows
│ ├── auto-merge-dependabot-prs.yml
│ ├── labeler.yml
│ ├── publish.yml
│ └── tests.yml
├── .gitignore
├── .prettierrc.js
├── .tool-versions
├── LICENSE
├── README.md
├── RELEASE_PROCESS.md
├── app
├── app.js
├── css
│ └── app.css
├── fileOperations.js
├── index.html
└── spiffworkflow
│ ├── DataObject
│ ├── DataObjectHelpers.js
│ ├── DataObjectInterceptor.js
│ ├── DataObjectLabelEditingProvider.js
│ ├── DataObjectRenderer.js
│ ├── DataObjectRules.js
│ ├── index.js
│ └── propertiesPanel
│ │ ├── DataObjectArray.js
│ │ ├── DataObjectPropertiesProvider.js
│ │ ├── DataObjectSelect.jsx
│ │ └── DataReferenceGroup.js
│ ├── DataStoreReference
│ ├── DataStoreHelpers.js
│ ├── DataStoreInterceptor.js
│ ├── index.js
│ └── propertiesPanel
│ │ ├── DataStorePropertiesProvider.js
│ │ └── DataStoreSelect.js
│ ├── InputOutput
│ ├── IoInterceptor.js
│ ├── IoPalette.js
│ ├── IoRules.js
│ ├── helpers.js
│ ├── index.js
│ └── propertiesProvider
│ │ ├── InputParametersArray.js
│ │ ├── IoGroup.js
│ │ ├── IoPropertiesProvider.js
│ │ └── OutputParametersArray.js
│ ├── callActivity
│ ├── CallActivityInterceptor.js
│ ├── index.js
│ └── propertiesPanel
│ │ └── CallActivityPropertiesProvider.js
│ ├── conditions
│ ├── index.js
│ └── propertiesPanel
│ │ └── ConditionsPropertiesProvider.js
│ ├── constants.js
│ ├── errors
│ ├── index.js
│ └── propertiesPanel
│ │ └── ErrorPropertiesProvider.js
│ ├── escalations
│ ├── index.js
│ └── propertiesPanel
│ │ └── EscalationPropertiesProvider.js
│ ├── eventList.js
│ ├── eventSelect.js
│ ├── extensions
│ ├── contextPad
│ │ └── CustomContextPadProvider.js
│ ├── extensionHelpers.js
│ ├── index.js
│ └── propertiesPanel
│ │ ├── ExtensionsPropertiesProvider.jsx
│ │ ├── ScriptUnitTestArray.js
│ │ ├── SpiffExtensionCheckboxEntry.jsx
│ │ ├── SpiffExtensionLaunchButton.js
│ │ ├── SpiffExtensionSelect.js
│ │ ├── SpiffExtensionServiceProperties.js
│ │ ├── SpiffExtensionTextArea.jsx
│ │ ├── SpiffExtensionTextInput.jsx
│ │ └── SpiffScriptGroup.js
│ ├── helpers.js
│ ├── index.js
│ ├── loops
│ ├── MultiInstancePropertiesProvider.js
│ ├── StandardLoopPropertiesProvider.js
│ ├── helpers.js
│ └── propertiesPanel
│ │ ├── CompletionConditionEntry.js
│ │ ├── InputCollectionEntry.js
│ │ ├── InputItemEntry.js
│ │ ├── IsIOSyncEntry.js
│ │ ├── LoopCardinalityEntry.js
│ │ ├── OutputCollectionEntry.js
│ │ └── OutputItemEntry.js
│ ├── messages
│ ├── MessageHelpers.js
│ ├── MessageInterceptor.js
│ ├── index.js
│ └── propertiesPanel
│ │ ├── MessageCorrelationPropertiesArray.js
│ │ ├── MessagesPropertiesProvider.js
│ │ ├── elementLevelProvider
│ │ ├── CorrelationCheckbox.jsx
│ │ ├── CorrelationPropertiesList.js
│ │ ├── MatchingConditionArray.js
│ │ ├── MatchingCorrelationCheckbox.jsx
│ │ ├── MessageJsonSchemaSelect.js
│ │ ├── MessageLaunchEditorButton.js
│ │ ├── MessagePayload.jsx
│ │ ├── MessageSelect.jsx
│ │ ├── MessageVariable.jsx
│ │ └── TaskEventMessageProvider.js
│ │ └── processLevelProvider
│ │ ├── CollaborationPropertiesProvider.js
│ │ ├── CorrelationKeysArray.js
│ │ ├── CorrelationPropertiesArray.js
│ │ ├── MessageArray.js
│ │ └── MessagePropertiesMultiSelect.js
│ ├── moddle
│ └── spiffworkflow.json
│ └── signals
│ ├── index.js
│ └── propertiesPanel
│ └── SignalPropertiesProvider.js
├── docs
└── io.png
├── karma.conf.js
├── package-lock.json
├── package.json
├── resources
└── diagram.bpmn
├── test
├── .eslintrc
└── spec
│ ├── BpmnInputOutputSpec.js
│ ├── BusinessRulePropsSpec.js
│ ├── CallActivitySpec.js
│ ├── ConditionSpec.js
│ ├── DataObjectInPoolsSpec.js
│ ├── DataObjectInterceptorSpec.js
│ ├── DataObjectPropsSpec.js
│ ├── DataObjectRulesSpec.js
│ ├── DataStoreInterceptorSpec.js
│ ├── DataStoreReferenceSpec.js
│ ├── IOInterceptorSpec.js
│ ├── IoVariablesSpec.js
│ ├── MessagesCorrSpec.js
│ ├── MessagesSpec.js
│ ├── ProcessPropsSpec.js
│ ├── ScriptTaskUnitTestSpec.js
│ ├── ScriptsPropsSpec.js
│ ├── ServiceTaskPropsSpec.js
│ ├── UserTaskPropsSpec.js
│ ├── bpmn
│ ├── basic_message.bpmn
│ ├── call_activity.bpmn
│ ├── collaboration.bpmn
│ ├── conditional_event.bpmn
│ ├── data_objects_in_pools.bpmn
│ ├── data_store.bpmn
│ ├── diagram.bpmn
│ ├── empty_diagram.bpmn
│ ├── gateway.bpmn
│ ├── io_variables.bpmn
│ ├── request_new_role.bpmn
│ ├── script_task.bpmn
│ ├── service.bpmn
│ ├── simple_collab.bpmn
│ ├── subprocess.bpmn
│ ├── two_messages.bpmn
│ └── user_form.bpmn
│ ├── helpers.js
│ └── test.css
└── webpack.config.js
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | es2021: true,
5 | },
6 | extends: [
7 | 'airbnb',
8 | 'plugin:bpmn-io/es6',
9 | 'plugin:prettier/recommended',
10 | 'plugin:sonarjs/recommended',
11 | 'plugin:import/errors',
12 | 'plugin:import/warnings',
13 | ],
14 | parserOptions: {
15 | ecmaFeatures: {
16 | jsx: true,
17 | },
18 | ecmaVersion: 'latest',
19 | sourceType: 'module',
20 | },
21 | rules: {
22 | 'jsx-a11y/no-autofocus': 'off',
23 | 'jsx-a11y/label-has-associated-control': 'off',
24 | 'no-console': 'off',
25 | 'no-unused-vars': [
26 | 'error',
27 | {
28 | destructuredArrayIgnorePattern: '^_',
29 | varsIgnorePattern: '_',
30 | argsIgnorePattern: '^_',
31 | },
32 | ],
33 | 'import/extensions': [
34 | 'error',
35 | 'ignorePackages',
36 | {
37 | js: 'never',
38 | jsx: 'never',
39 | ts: 'never',
40 | tsx: 'never',
41 | },
42 | ],
43 |
44 | // We could try turning these on at some point but do not want to force it now
45 | 'react/react-in-jsx-scope': 'off',
46 | 'react/prop-types': 'off',
47 | 'react/jsx-filename-extension': [1, { extensions: ['.js', '.jsx'] }],
48 | 'no-use-before-define': 0,
49 | 'func-names': 'off',
50 | 'react/destructuring-assignment': 'off',
51 | 'import/prefer-default-export': 'off',
52 | 'no-restricted-syntax': 'off',
53 | },
54 | };
55 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: github-actions
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | - package-ecosystem: pip
8 | directory: "/.github/workflows"
9 | schedule:
10 | interval: daily
11 | - package-ecosystem: pip
12 | directory: "/docs"
13 | schedule:
14 | interval: daily
15 | - package-ecosystem: pip
16 | directory: "/"
17 | schedule:
18 | interval: daily
19 |
--------------------------------------------------------------------------------
/.github/labels.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # Labels names are important as they are used by Release Drafter to decide
3 | # regarding where to record them in changelog or if to skip them.
4 | #
5 | # The repository labels will be automatically configured using this file and
6 | # the GitHub Action https://github.com/marketplace/actions/github-labeler.
7 | - name: breaking
8 | description: Breaking Changes
9 | color: bfd4f2
10 | - name: bug
11 | description: Something isn't working
12 | color: d73a4a
13 | - name: build
14 | description: Build System and Dependencies
15 | color: bfdadc
16 | - name: ci
17 | description: Continuous Integration
18 | color: 4a97d6
19 | - name: dependencies
20 | description: Pull requests that update a dependency file
21 | color: 0366d6
22 | - name: documentation
23 | description: Improvements or additions to documentation
24 | color: 0075ca
25 | - name: duplicate
26 | description: This issue or pull request already exists
27 | color: cfd3d7
28 | - name: enhancement
29 | description: New feature or request
30 | color: a2eeef
31 | - name: github_actions
32 | description: Pull requests that update Github_actions code
33 | color: "000000"
34 | - name: good first issue
35 | description: Good for newcomers
36 | color: 7057ff
37 | - name: help wanted
38 | description: Extra attention is needed
39 | color: 008672
40 | - name: invalid
41 | description: This doesn't seem right
42 | color: e4e669
43 | - name: performance
44 | description: Performance
45 | color: "016175"
46 | - name: python
47 | description: Pull requests that update Python code
48 | color: 2b67c6
49 | - name: question
50 | description: Further information is requested
51 | color: d876e3
52 | - name: refactoring
53 | description: Refactoring
54 | color: ef67c4
55 | - name: removal
56 | description: Removals and Deprecations
57 | color: 9ae7ea
58 | - name: style
59 | description: Style
60 | color: c120e5
61 | - name: testing
62 | description: Testing
63 | color: b1fc6f
64 | - name: wontfix
65 | description: This will not be worked on
66 | color: ffffff
67 |
--------------------------------------------------------------------------------
/.github/release-drafter.yml:
--------------------------------------------------------------------------------
1 | categories:
2 | - title: ":boom: Breaking Changes"
3 | label: "breaking"
4 | - title: ":rocket: Features"
5 | label: "enhancement"
6 | - title: ":fire: Removals and Deprecations"
7 | label: "removal"
8 | - title: ":beetle: Fixes"
9 | label: "bug"
10 | - title: ":racehorse: Performance"
11 | label: "performance"
12 | - title: ":rotating_light: Testing"
13 | label: "testing"
14 | - title: ":construction_worker: Continuous Integration"
15 | label: "ci"
16 | - title: ":books: Documentation"
17 | label: "documentation"
18 | - title: ":hammer: Refactoring"
19 | label: "refactoring"
20 | - title: ":lipstick: Style"
21 | label: "style"
22 | - title: ":package: Dependencies"
23 | labels:
24 | - "dependencies"
25 | - "build"
26 | template: |
27 | ## Changes
28 |
29 | $CHANGES
30 |
--------------------------------------------------------------------------------
/.github/workflows/auto-merge-dependabot-prs.yml:
--------------------------------------------------------------------------------
1 | name: Dependabot auto-merge
2 | on:
3 | workflow_run:
4 | workflows: ["Tests"]
5 | types:
6 | - completed
7 |
8 | permissions:
9 | contents: write
10 |
11 | jobs:
12 | dependabot:
13 | runs-on: ubuntu-latest
14 | if: ${{ github.actor == 'dependabot[bot]' && github.event.workflow_run.conclusion == 'success' && github.event_name == 'pull_request' }}
15 | steps:
16 | - name: Dependabot metadata
17 | id: metadata
18 | uses: dependabot/fetch-metadata@v2.2.0
19 | with:
20 | github-token: "${{ secrets.GITHUB_TOKEN }}"
21 | - name: Enable auto-merge for Dependabot PRs
22 | # if: ${{contains(steps.metadata.outputs.dependency-names, 'pytest') && steps.metadata.outputs.update-type == 'version-update:semver-patch'}}
23 | # if: ${{contains(steps.metadata.outputs.dependency-names, 'pytest')}}
24 | # ideally we auto-merge if all checks pass
25 | run: gh pr merge --auto --merge "$PR_URL"
26 | env:
27 | PR_URL: ${{github.event.pull_request.html_url}}
28 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
29 |
--------------------------------------------------------------------------------
/.github/workflows/labeler.yml:
--------------------------------------------------------------------------------
1 | name: Labeler
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - master
8 |
9 | jobs:
10 | labeler:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Check out the repository
14 | uses: actions/checkout@v4
15 |
16 | - name: Run Labeler
17 | uses: crazy-max/ghaction-github-labeler@v5.1.0
18 | with:
19 | skip-delete: true
20 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish NPM
2 |
3 | # On a published release, run tests and deploy to NPM
4 | on:
5 | workflow_dispatch:
6 | release:
7 | types: [published]
8 |
9 | # Job Setup
10 | jobs:
11 | publish:
12 | runs-on: ubuntu-latest
13 | steps:
14 |
15 | - uses: actions/checkout@v4 #Checkout Repo
16 | - uses: actions/setup-node@v4 #Setup Node
17 | with:
18 | node-version: 18
19 | - run: npm install
20 | - run: npm test
21 | - uses: JS-DevTools/npm-publish@v3
22 | with:
23 | token: ${{ secrets.NPM_TOKEN }}
24 | access: public
25 | - if: steps.publish.outputs.type != 'none'
26 | run: |
27 | echo "Version changed: ${{ steps.publish.outputs.old-version }} => ${{ steps.publish.outputs.version }}"
28 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | # Run on Pull Requests and pushes to main
4 | on:
5 | workflow_dispatch:
6 | pull_request:
7 | push:
8 | branches:
9 | - main
10 |
11 | # This allows a subsequently queued workflow run to interrupt previous runs
12 | concurrency:
13 | group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}'
14 | cancel-in-progress: true
15 |
16 | # Job Setup
17 | jobs:
18 | build:
19 | runs-on: ubuntu-latest
20 | steps:
21 | - uses: actions/checkout@v4 #Checkout Repo
22 | - uses: actions/setup-node@v4 #Setup Node
23 | - uses: nanasess/setup-chromedriver@v2 #Setup ChromeDriver
24 | - name: Run Karma Tests
25 | run: |
26 | npm ci
27 | npm run test
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | public/
3 | .idea/
4 | .aider.*
5 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | singleQuote: true,
3 | };
4 |
--------------------------------------------------------------------------------
/.tool-versions:
--------------------------------------------------------------------------------
1 | nodejs 22.3.0
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Sartography
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/RELEASE_PROCESS.md:
--------------------------------------------------------------------------------
1 | ## Releases
2 |
3 | Be sure to edit the package.json, and update the version. Releases won't create
4 | a new NPM package unless the version was updated.
5 | 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
6 | there is more information on [NPM Version](https://docs.npmjs.com/cli/v8/commands/npm-version).
7 |
8 | For doing a patch release, you can do
9 | ```bash
10 | npm version patch -m "Upgrade to %s for reasons"
11 | ```
12 | aside from patch, you can use the keywords `minor`, and `major` (there are some others).
13 |
14 | 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)
15 |
16 |
--------------------------------------------------------------------------------
/app/fileOperations.js:
--------------------------------------------------------------------------------
1 | // FileSaver isn't really a dependency, we use it here to provide an example.
2 | // eslint-disable-next-line import/no-extraneous-dependencies
3 | import FileSaver from 'file-saver';
4 |
5 | /** ****************************************
6 | * Below are a few helper methods so we can upload and download files
7 | * easily from the editor for testing purposes.
8 | * -----------------------------------------
9 | */
10 | export default function setupFileOperations(bpmnModeler) {
11 | /**
12 | * Just a quick bit of code so we can save the XML that is output.
13 | * Helps for debugging against other libraries (like SpiffWorkflow)
14 | */
15 |
16 | const btn = document.getElementById('downloadButton');
17 | btn.addEventListener('click', (_event) => {
18 | saveXML();
19 | });
20 |
21 | async function saveXML() {
22 | const { xml } = await bpmnModeler.saveXML({ format: true });
23 | const blob = new Blob([xml], { type: 'text/xml' });
24 | FileSaver.saveAs(blob, 'diagram.bpmn');
25 | }
26 |
27 | /**
28 | * Just a quick bit of code so we can open a local XML file
29 | * Helps for debugging against other libraries (like SpiffWorkflow)
30 | */
31 | const uploadBtn = document.getElementById('uploadButton');
32 | uploadBtn.addEventListener('click', (_event) => {
33 | openFile(bpmnModeler);
34 | });
35 | }
36 |
37 | function clickElem(elem) {
38 | const eventMouse = document.createEvent('MouseEvents');
39 | eventMouse.initMouseEvent(
40 | 'click',
41 | true,
42 | false,
43 | window,
44 | 0,
45 | 0,
46 | 0,
47 | 0,
48 | 0,
49 | false,
50 | false,
51 | false,
52 | false,
53 | 0,
54 | null
55 | );
56 | elem.dispatchEvent(eventMouse);
57 | }
58 |
59 | export function openFile(bpmnModeler) {
60 | const readFile = function readFileCallback(e) {
61 | const file = e.target.files[0];
62 | if (!file) {
63 | return;
64 | }
65 | const reader = new FileReader();
66 | reader.onload = function onloadCallback(onloadEvent) {
67 | const contents = onloadEvent.target.result;
68 | bpmnModeler.importXML(contents);
69 | document.body.removeChild(fileInput);
70 | };
71 | reader.readAsText(file);
72 | };
73 | let fileInput = document.createElement('input');
74 | fileInput.type = 'file';
75 | fileInput.style.display = 'none';
76 | fileInput.onchange = readFile;
77 | document.body.appendChild(fileInput);
78 | clickElem(fileInput);
79 | }
80 |
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 | bpmn-js-spiffworkflow
10 |
11 |
12 |
13 |
14 |
15 |
19 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
34 |
35 |
36 |
37 |
41 |
42 |
43 |
44 |
48 |
49 |
53 |
54 |
55 |
56 |
57 |
65 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/app/spiffworkflow/DataObject/DataObjectHelpers.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Returns the moddelElement if it is a process, otherwise, returns the
3 | *
4 | * @param container
5 | */
6 |
7 | export function findDataObjects(parent, dataObjects) {
8 | if (typeof (dataObjects) === 'undefined')
9 | dataObjects = [];
10 | let process;
11 | if (!parent) {
12 | return [];
13 | }
14 | if (parent.processRef) {
15 | process = parent.processRef;
16 | } else {
17 | process = parent;
18 | if (process.$type === 'bpmn:SubProcess')
19 | findDataObjects(process.$parent, dataObjects);
20 | }
21 | if (typeof (process.flowElements) !== 'undefined') {
22 | for (const element of process.flowElements) {
23 | if (element.$type === 'bpmn:DataObject')
24 | dataObjects.push(element);
25 | }
26 | }
27 | return dataObjects;
28 | }
29 |
30 | export function findDataObject(process, id) {
31 | for (const dataObj of findDataObjects(process)) {
32 | if (dataObj.id === id) {
33 | return dataObj;
34 | }
35 | }
36 | }
37 |
38 | export function findDataObjectReferences(children, dataObjectId) {
39 | if (children == null) {
40 | return [];
41 | }
42 | return children.flatMap((child) => {
43 | if (child.$type == 'bpmn:DataObjectReference' && child.dataObjectRef.id == dataObjectId)
44 | return [child];
45 | else if (child.$type == 'bpmn:SubProcess')
46 | return findDataObjectReferences(child.get('flowElements'), dataObjectId);
47 | else
48 | return [];
49 | });
50 | }
51 |
52 | export function findDataObjectReferenceShapes(children, dataObjectId) {
53 | return children.flatMap((child) => {
54 | if (child.type == 'bpmn:DataObjectReference' && child.businessObject.dataObjectRef.id == dataObjectId)
55 | return [child];
56 | else if (child.type == 'bpmn:SubProcess')
57 | return findDataObjectReferenceShapes(child.children, dataObjectId);
58 | else
59 | return [];
60 | });
61 | }
62 |
63 | export function idToHumanReadableName(id) {
64 | const words = id.match(/[A-Za-z][a-z]*|[0-9]+/g) || [id];
65 | return words.map(capitalize).join(' ');
66 |
67 | function capitalize(word) {
68 | return word.charAt(0).toUpperCase() + word.substring(1);
69 | }
70 | }
71 |
72 | export function updateDataObjectReferencesName(parent, nameValue, dataObjectId, commandStack) {
73 | const references = findDataObjectReferenceShapes(parent.children, dataObjectId);
74 | for (const ref of references) {
75 | const stateName = ref.businessObject.dataState && ref.businessObject.dataState.name ? ref.businessObject.dataState.name : '';
76 | const newName = stateName ? `${nameValue} [${stateName}]` : nameValue;
77 | commandStack.execute('element.updateProperties', {
78 | element: ref,
79 | moddleElement: ref.businessObject,
80 | properties: {
81 | name: newName,
82 | },
83 | changed: [ref],
84 | });
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/app/spiffworkflow/DataObject/DataObjectLabelEditingProvider.js:
--------------------------------------------------------------------------------
1 | import { is } from 'bpmn-js/lib/util/ModelUtil';
2 | import { findDataObject, updateDataObjectReferencesName } from './DataObjectHelpers';
3 |
4 | export default function DataObjectLabelEditingProvider(eventBus, directEditing, commandStack, modeling) {
5 |
6 | let el;
7 |
8 | // listen to dblclick on non-root elements
9 | eventBus.on('element.dblclick', function (event) {
10 | const { element } = event;
11 | if (is(element.businessObject, 'bpmn:DataObjectReference')) {
12 | let label = element.businessObject.name;
13 | label = label.replace(/\s*\[.*?\]\s*$/, '');
14 | modeling.updateLabel(element, label);
15 | directEditing.activate(element);
16 | el = element;
17 | }
18 | });
19 |
20 | eventBus.on('directEditing.complete', function (event) {
21 |
22 | const element = el;
23 |
24 | if (element && is(element.businessObject, 'bpmn:DataObjectReference')) {
25 |
26 | setTimeout(() => {
27 | const process = element.parent.businessObject;
28 | const dataObject = findDataObject(process, element.businessObject.dataObjectRef.id);
29 | const dataState = element.businessObject.dataState && element.businessObject.dataState.name;
30 |
31 | let newLabel = element.businessObject.name;
32 |
33 | commandStack.execute('element.updateModdleProperties', {
34 | element,
35 | moddleElement: dataObject,
36 | properties: {
37 | name: newLabel,
38 | },
39 | });
40 |
41 | // Update references name
42 | updateDataObjectReferencesName(element.parent, newLabel, dataObject.id, commandStack);
43 |
44 | // Append the data state if it exists
45 | if (dataState) {
46 | newLabel += ` [${dataState}]`;
47 | }
48 |
49 | // Update the label with the data state
50 | modeling.updateLabel(element, newLabel);
51 | el = undefined;
52 | }, 100);
53 | }
54 | });
55 | }
56 |
57 | DataObjectLabelEditingProvider.$inject = [
58 | 'eventBus',
59 | 'directEditing',
60 | 'commandStack',
61 | 'modeling'
62 | ];
--------------------------------------------------------------------------------
/app/spiffworkflow/DataObject/DataObjectRenderer.js:
--------------------------------------------------------------------------------
1 | import BaseRenderer from 'diagram-js/lib/draw/BaseRenderer';
2 |
3 | import {
4 | attr as svgAttr
5 | } from 'tiny-svg';
6 |
7 | import { getBusinessObject, is } from 'bpmn-js/lib/util/ModelUtil';
8 | import { isAny } from 'bpmn-js/lib/features/modeling/util/ModelingUtil';
9 | import { findDataObject } from './DataObjectHelpers';
10 |
11 | const HIGH_PRIORITY = 1500;
12 |
13 | /**
14 | * Work in progress -- render data object references in red if they are
15 | * not valid.
16 | */
17 | export default class DataObjectRenderer extends BaseRenderer {
18 | constructor(eventBus, bpmnRenderer) {
19 | super(eventBus, HIGH_PRIORITY);
20 | this.bpmnRenderer = bpmnRenderer;
21 | }
22 |
23 | canRender(element) {
24 | return isAny(element, [ 'bpmn:DataObjectReference' ]) && !element.labelTarget;
25 | }
26 |
27 | drawShape(parentNode, element) {
28 | const shape = this.bpmnRenderer.drawShape(parentNode, element);
29 | if (is(element, 'bpmn:DataObjectReference')) {
30 | let businessObject = getBusinessObject(element);
31 | let dataObject = businessObject.dataObjectRef;
32 | if (dataObject && dataObject.id) {
33 | let parentObject = businessObject.$parent;
34 | dataObject = findDataObject(parentObject, dataObject.id);
35 | }
36 | if (!dataObject) {
37 | svgAttr(shape, 'stroke', 'red');
38 | }
39 | return shape;
40 | }
41 | }
42 | }
43 |
44 | DataObjectRenderer.$inject = [ 'eventBus', 'bpmnRenderer' ];
45 |
--------------------------------------------------------------------------------
/app/spiffworkflow/DataObject/DataObjectRules.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Custom Rules for the DataObject - Rules allow you to prevent an
3 | * action from happening in the diagram, such as dropping an element
4 | * where it doesn't belong.
5 | *
6 | * Here we don't allow people to move a data object Reference
7 | * from one parent to another, as we can't move the data objects
8 | * from one parent to another.
9 | *
10 | */
11 | import RuleProvider from 'diagram-js/lib/features/rules/RuleProvider';
12 | import inherits from 'inherits-browser';
13 | import { is } from 'bpmn-js/lib/util/ModelUtil';
14 |
15 | export default function DataObjectRules(eventBus) {
16 | RuleProvider.call(this, eventBus);
17 | }
18 | inherits(DataObjectRules, RuleProvider);
19 | const HIGH_PRIORITY = 1500;
20 |
21 | DataObjectRules.prototype.init = function() {
22 | this.addRule('elements.move', HIGH_PRIORITY,function(context) {
23 | let elements = context.shapes;
24 | let target = context.target;
25 | return canDrop(elements, target);
26 | });
27 | };
28 |
29 | function canDrop(elements, target) {
30 | for (let element of elements) {
31 | if (is(element, 'bpmn:DataObjectReference') && element.parent && target) {
32 | return target === element.parent;
33 | }
34 | // Intentionally returning null here to allow other rules to fire.
35 | }
36 | }
37 |
38 | DataObjectRules.prototype.canDrop = canDrop;
39 | DataObjectRules.$inject = [ 'eventBus' ];
40 |
--------------------------------------------------------------------------------
/app/spiffworkflow/DataObject/index.js:
--------------------------------------------------------------------------------
1 | import DataObjectInterceptor from './DataObjectInterceptor';
2 | import DataObjectRules from './DataObjectRules';
3 | import RulesModule from 'diagram-js/lib/features/rules';
4 | import DataObjectRenderer from './DataObjectRenderer';
5 | import DataObjectPropertiesProvider from './propertiesPanel/DataObjectPropertiesProvider';
6 | import DataObjectLabelEditingProvider from './DataObjectLabelEditingProvider';
7 |
8 | export default {
9 | __depends__: [
10 | RulesModule
11 | ],
12 | __init__: [ 'dataInterceptor', 'dataObjectRules', 'dataObjectRenderer', 'dataObjectPropertiesProvider', 'dataObjectLabelEditingProvider' ],
13 | dataInterceptor: [ 'type', DataObjectInterceptor ],
14 | dataObjectRules: [ 'type', DataObjectRules ],
15 | dataObjectRenderer: [ 'type', DataObjectRenderer ],
16 | dataObjectPropertiesProvider: [ 'type', DataObjectPropertiesProvider ],
17 | dataObjectLabelEditingProvider: [ 'type', DataObjectLabelEditingProvider ]
18 | };
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/spiffworkflow/DataObject/propertiesPanel/DataObjectSelect.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useService } from 'bpmn-js-properties-panel';
3 | import { SelectEntry } from '@bpmn-io/properties-panel';
4 | import { findDataObjects } from '../DataObjectHelpers';
5 |
6 | /**
7 | * Finds the value of the given type within the extensionElements
8 | * given a type of "spiff:preScript", would find it in this, and return
9 | * the object.
10 | *
11 | *
13 |
14 |
15 | me = "100% awesome"
16 |
17 |
18 | ...
19 |
20 | *
21 | * @returns {string|null|*}
22 | */
23 | export function DataObjectSelect(props) {
24 | const element = props.element;
25 | const commandStack = props.commandStack;
26 | const debounce = useService('debounceInput');
27 |
28 | const getValue = () => {
29 | return element.businessObject.dataObjectRef.id;
30 | };
31 |
32 | const setValue = (value) => {
33 | const businessObject = element.businessObject;
34 | const dataObjects = findDataObjects(businessObject.$parent);
35 | for (const dataObject of dataObjects) {
36 | if (dataObject.$type === 'bpmn:DataObject' && dataObject.id === value) {
37 | commandStack.execute('element.updateModdleProperties', {
38 | element: element,
39 | moddleElement: businessObject,
40 | properties: {
41 | dataObjectRef: dataObject,
42 | },
43 | });
44 |
45 | // Construct the new name by : the dataObject name and the current state
46 | const stateName =
47 | businessObject.dataState && businessObject.dataState.name
48 | ? businessObject.dataState.name
49 | : '';
50 | const newName = stateName
51 | ? `${dataObject.name} [${stateName}]`
52 | : dataObject.name;
53 | // Update the name property of the DataObjectReference
54 | commandStack.execute('element.updateProperties', {
55 | element: element,
56 | properties: {
57 | name: newName,
58 | },
59 | });
60 | }
61 | }
62 | };
63 |
64 | const getOptions = (value) => {
65 | const businessObject = element.businessObject;
66 | const parent = businessObject.$parent;
67 | let dataObjects = findDataObjects(parent);
68 | let options = [];
69 | dataObjects.forEach((dataObj) => {
70 | options.push({ label: dataObj.id, value: dataObj.id });
71 | });
72 | return options;
73 | };
74 |
75 | return (
76 |
86 | );
87 | }
88 |
--------------------------------------------------------------------------------
/app/spiffworkflow/DataObject/propertiesPanel/DataReferenceGroup.js:
--------------------------------------------------------------------------------
1 | import { ListGroup } from '@bpmn-io/properties-panel';
2 | import { DataObjectArray } from './DataObjectArray';
3 |
4 | /**
5 | * Also allows you to select which Data Objects are available
6 | * in the process element.
7 | * @param element The selected process
8 | * @param moddle For updating the underlying xml object
9 | * @returns {[{component: (function(*)), isEdited: *, id: string, element},{component:
10 | * (function(*)), isEdited: *, id: string, element}]}
11 | */
12 | export default function(element, moddle) {
13 |
14 | const groupSections = [];
15 | const dataObjectArray = {
16 | id: 'editDataObjects',
17 | element,
18 | label: 'Available Data Objects',
19 | component: ListGroup,
20 | ...DataObjectArray({ element, moddle })
21 | };
22 |
23 | if (dataObjectArray.items) {
24 | groupSections.push(dataObjectArray);
25 | }
26 |
27 | return groupSections;
28 | }
29 |
30 |
31 |
--------------------------------------------------------------------------------
/app/spiffworkflow/DataStoreReference/DataStoreHelpers.js:
--------------------------------------------------------------------------------
1 | export function isDataStoreReferenced(process, dataStoreId) {
2 | const status = process.get('flowElements').some(elem =>
3 | elem.$type === 'bpmn:DataStoreReference' && elem.dataStoreRef && elem.dataStoreRef.id === dataStoreId
4 | );
5 | return status;
6 | }
7 |
8 | export function isDataStoreReferencedV2(definitions, dataStoreId) {
9 | return definitions.get('rootElements').some(elem =>
10 | elem.$type === 'bpmn:DataStoreReference' && elem.dataStoreRef && elem.dataStoreRef.id === dataStoreId
11 | );
12 | }
13 |
14 | export function removeDataStore(definitions, dataStoreId) {
15 | definitions.set('rootElements', definitions.get('rootElements').filter(elem =>
16 | !(elem.$type === 'bpmn:DataStore' && elem.id === dataStoreId)
17 | ));
18 | }
--------------------------------------------------------------------------------
/app/spiffworkflow/DataStoreReference/DataStoreInterceptor.js:
--------------------------------------------------------------------------------
1 | import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
2 | import { getDi, is } from 'bpmn-js/lib/util/ModelUtil';
3 | import { isDataStoreReferenced, removeDataStore } from './DataStoreHelpers';
4 |
5 | const HIGH_PRIORITY = 1500;
6 |
7 | /**
8 | *
9 | */
10 | export default class DataStoreInterceptor extends CommandInterceptor {
11 |
12 | constructor(eventBus, bpmnFactory, commandStack, bpmnUpdater) {
13 | super(eventBus);
14 |
15 | /*
16 | *
17 | */
18 | // bpmnUpdater.updateSemanticParent = (businessObject, parentBusinessObject) => {
19 | // if (is(businessObject, 'bpmn:DataStoreReference')) {
20 | // console.log('updateSemanticParent', businessObject, parentBusinessObject);
21 | // bpmnUpdater.__proto__.updateSemanticParent.call(bpmnUpdater, businessObject, parentBusinessObject);
22 | // }
23 | // };
24 |
25 | /**
26 | *
27 | */
28 | // this.preExecute(['shape.create'], HIGH_PRIORITY, function (event) {
29 | // const { context } = event;
30 | // const { shape } = context;
31 | // if (is(shape, 'bpmn:DataStoreReference') && shape.type !== 'label') {
32 | // // event.stopPropagation();*
33 | // console.log('preExecute shape.create', shape, context);
34 | // }
35 | // });
36 |
37 | /**
38 | *
39 | */
40 | // this.executed(['shape.create'], HIGH_PRIORITY, function (event) {
41 | // const { context } = event;
42 | // const { shape } = context;
43 | // if (is(shape, 'bpmn:DataStoreReference') && shape.type !== 'label') {
44 | // console.log('executed shape.create', shape, context);
45 | // }
46 | // });
47 |
48 | /**
49 | *
50 | */
51 | // this.postExecuted(['shape.create'], HIGH_PRIORITY, function (event) {
52 | // const { context } = event;
53 | // const { shape } = context;
54 | // if (is(shape, 'bpmn:DataStoreReference') && shape.type !== 'label') {
55 | // console.log('postExecuted shape.create', shape, context);
56 | // }
57 | // });
58 |
59 | /**
60 | *
61 | */
62 | this.postExecuted(['shape.delete'], HIGH_PRIORITY, function (event) {
63 | const { context } = event;
64 | const { shape } = context;
65 |
66 | if (is(shape, 'bpmn:DataStoreReference') && shape.type !== 'label') {
67 | const definitions = context.oldParent.businessObject.$parent;
68 | const dataStore = shape.businessObject.dataStoreRef;
69 | if (dataStore && !isDataStoreReferenced(context.oldParent.businessObject, dataStore.id)) {
70 | // Remove datastore if it's not linked with another datastore ref
71 | removeDataStore(definitions, dataStore.id);
72 | }
73 | }
74 | });
75 | }
76 | }
77 |
78 | DataStoreInterceptor.$inject = ['eventBus', 'bpmnFactory', 'commandStack', 'bpmnUpdater'];
79 |
--------------------------------------------------------------------------------
/app/spiffworkflow/DataStoreReference/index.js:
--------------------------------------------------------------------------------
1 | import RulesModule from 'diagram-js/lib/features/rules';
2 | import DataStorePropertiesProvider from './propertiesPanel/DataStorePropertiesProvider';
3 | import DataStoreInterceptor from './DataStoreInterceptor';
4 |
5 | export default {
6 | __depends__: [
7 | RulesModule
8 | ],
9 | __init__: [ 'dataStoreInterceptor', 'dataStorePropertiesProvider' ],
10 | dataStoreInterceptor: [ 'type', DataStoreInterceptor ],
11 | dataStorePropertiesProvider: [ 'type', DataStorePropertiesProvider ]
12 | };
13 |
--------------------------------------------------------------------------------
/app/spiffworkflow/DataStoreReference/propertiesPanel/DataStorePropertiesProvider.js:
--------------------------------------------------------------------------------
1 | import { is } from 'bpmn-js/lib/util/ModelUtil';
2 | import { DataStoreSelect, OPTION_TYPE } from './DataStoreSelect';
3 |
4 | const LOW_PRIORITY = 500;
5 |
6 | export default function DataStorePropertiesProvider(
7 | modeling,
8 | propertiesPanel,
9 | translate,
10 | moddle,
11 | commandStack,
12 | bpmnFactory,
13 | ) {
14 | this.getGroups = function (element) {
15 | return function (groups) {
16 | if (is(element, 'bpmn:DataStoreReference')) {
17 | groups.push(
18 | createCustomDataStoreGroup(
19 | modeling,
20 | element,
21 | translate,
22 | moddle,
23 | commandStack,
24 | bpmnFactory
25 | )
26 | );
27 | }
28 | return groups;
29 | };
30 | };
31 | propertiesPanel.registerProvider(LOW_PRIORITY, this);
32 | }
33 |
34 | DataStorePropertiesProvider.$inject = [
35 | 'modeling',
36 | 'propertiesPanel',
37 | 'translate',
38 | 'moddle',
39 | 'commandStack',
40 | 'bpmnFactory',
41 | ];
42 |
43 | function createCustomDataStoreGroup(
44 | modeling,
45 | element,
46 | translate,
47 | moddle,
48 | commandStack,
49 | bpmnFactory
50 | ) {
51 |
52 | const { businessObject } = element;
53 |
54 | const group = {
55 | label: translate('Custom Data Store Properties'),
56 | id: 'custom-datastore-properties',
57 | entries: [],
58 | };
59 |
60 | let description = translate('Select a datasource from the list');
61 | if(businessObject.dataStoreRef){
62 | const dataStoreId = businessObject.dataStoreRef.id;
63 | const type = businessObject.get('type');
64 | description = `The selected data store is of type: ${type}`;
65 | }
66 |
67 | // other custom properties as needed
68 | group.entries.push({
69 | id: 'selectDataStore',
70 | element,
71 | component: DataStoreSelect,
72 | optionType: OPTION_TYPE.data_stores,
73 | moddle,
74 | commandStack,
75 | translate,
76 | name: 'dataStoreRef',
77 | label: translate('Select DataSource'),
78 | description,
79 | modeling,
80 | bpmnFactory,
81 | });
82 |
83 | return group;
84 | }
85 |
--------------------------------------------------------------------------------
/app/spiffworkflow/InputOutput/IoPalette.js:
--------------------------------------------------------------------------------
1 | import { assign } from 'min-dash';
2 | import translate from 'diagram-js/lib/i18n/translate/translate';
3 |
4 | /**
5 | * Add data inputs and data outputs to the panel.
6 | */
7 | export default function IoPalette(palette, create, elementFactory,) {
8 | this._create = create;
9 | this._elementFactory = elementFactory;
10 | palette.registerProvider(this);
11 | }
12 |
13 | IoPalette.$inject = [
14 | 'palette',
15 | 'create',
16 | 'elementFactory'
17 | ];
18 |
19 | IoPalette.prototype.getPaletteEntries = function() {
20 |
21 | let input_type = 'bpmn:DataInput';
22 | let output_type = 'bpmn:DataOutput';
23 | let elementFactory = this._elementFactory, create = this._create;
24 |
25 | function createListener(event, type) {
26 | let shape = elementFactory.createShape(assign({ type: type }, {}));
27 | shape.width = 36; // Fix up the shape dimensions from the defaults.
28 | shape.height = 50;
29 | create.start(event, shape);
30 | }
31 |
32 | function createInputListener(event) {
33 | createListener(event, input_type);
34 | }
35 |
36 | function createOutputListener(event) {
37 | createListener(event, output_type);
38 | }
39 |
40 | return {
41 | 'create.data-input': {
42 | group: 'data-object',
43 | className: 'bpmn-icon-data-input',
44 | title: translate('Create DataInput'),
45 | action: {
46 | dragstart: createInputListener,
47 | click: createInputListener
48 | }
49 | },
50 | 'create.data-output': {
51 | group: 'data-object',
52 | className: 'bpmn-icon-data-output',
53 | title: translate('Create DataOutput'),
54 | action: {
55 | dragstart: createOutputListener,
56 | click: createOutputListener
57 | }
58 | }
59 |
60 | };
61 | };
62 |
63 |
--------------------------------------------------------------------------------
/app/spiffworkflow/InputOutput/IoRules.js:
--------------------------------------------------------------------------------
1 | import inherits from 'inherits';
2 | import RuleProvider from 'diagram-js/lib/features/rules/RuleProvider';
3 |
4 | const HIGH_PRIORITY = 1500;
5 |
6 | /**
7 | * A custom rule provider that will permit Data Inputs and Data
8 | * Outputs to be placed within a process element (something BPMN.io currently denies)
9 | *
10 | * See {@link BpmnRules} for the default implementation
11 | * of BPMN 2.0 modeling rules provided by bpmn-js.
12 | *
13 | * @param {EventBus} eventBus
14 | */
15 | export default function IoRules(eventBus) {
16 | RuleProvider.call(this, eventBus);
17 | }
18 |
19 | inherits(IoRules, RuleProvider);
20 |
21 | IoRules.$inject = [ 'eventBus' ];
22 |
23 | IoRules.prototype.init = function() {
24 | this.addRule('shape.create', HIGH_PRIORITY, function(context) {
25 |
26 | let element = context.shape;
27 | let target = context.target;
28 | let position = context.position;
29 |
30 | return canCreate(element, target, position);
31 | });
32 | };
33 |
34 | /**
35 | * Allow folks to drop a dataInput or DataOutput only on the top level process.
36 | */
37 | function canCreate(element, target, position) {
38 | if ([ 'bpmn:DataInput', 'bpmn:DataOutput' ].includes(element.type)) {
39 | if (target.type == 'bpmn:Process') {
40 | return true;
41 | }
42 | }
43 | }
44 |
45 | IoRules.prototype.canCreate = canCreate;
46 |
--------------------------------------------------------------------------------
/app/spiffworkflow/InputOutput/helpers.js:
--------------------------------------------------------------------------------
1 |
2 | export function createSpecification(bpmnFactory, businessObject, type, newElement) {
3 | let ioSpecification = businessObject.ioSpecification;
4 | if (!ioSpecification) {
5 | ioSpecification = bpmnFactory.create('bpmn:InputOutputSpecification', {
6 | dataInputs: [],
7 | dataOutputs: [],
8 | inputSets: [],
9 | outputSets: [],
10 | });
11 |
12 | businessObject.ioSpecification = ioSpecification;
13 | }
14 |
15 | if (type === 'input') {
16 | ioSpecification.dataInputs.push(newElement);
17 | if (!ioSpecification.inputSets.length) {
18 | ioSpecification.inputSets.push(bpmnFactory.create('bpmn:InputSet', { dataInputRefs: [newElement] }));
19 | } else {
20 | ioSpecification.inputSets[0].dataInputRefs.push(newElement);
21 | }
22 | } else if (type === 'output') {
23 | ioSpecification.dataOutputs.push(newElement);
24 | if (!ioSpecification.outputSets.length) {
25 | ioSpecification.outputSets.push(bpmnFactory.create('bpmn:OutputSet', { dataOutputRefs: [newElement] }));
26 | } else {
27 | ioSpecification.outputSets[0].dataOutputRefs.push(newElement);
28 | }
29 | }
30 |
31 | return ioSpecification;
32 | }
33 |
34 | export function removeElementFromSpecification(element, entry, type) {
35 | const ioSpecification = element.businessObject.ioSpecification;
36 | if (!ioSpecification) {
37 | console.error('No ioSpecification found for this element.');
38 | return;
39 | }
40 |
41 | const collection = type === 'input' ? ioSpecification.dataInputs : ioSpecification.dataOutputs;
42 | const setCollection = type === 'input' ? ioSpecification.inputSets : ioSpecification.outputSets;
43 | const index = collection.findIndex(item => item.id === entry.id);
44 |
45 | if (index > -1) {
46 | const [removedElement] = collection.splice(index, 1);
47 | setCollection.forEach(set => {
48 | const refIndex = set[type === 'input' ? 'dataInputRefs' : 'dataOutputRefs'].indexOf(removedElement);
49 | if (refIndex > -1) {
50 | set[type === 'input' ? 'dataInputRefs' : 'dataOutputRefs'].splice(refIndex, 1);
51 | }
52 | });
53 | } else {
54 | console.error(`No ${type === 'input' ? 'DataInput' : 'DataOutput'} found for id ${entry.id}`);
55 | }
56 | }
57 |
58 | export function updateElementProperties(commandStack, element) {
59 | commandStack.execute('element.updateProperties', {
60 | element: element,
61 | moddleElement: element.businessObject,
62 | properties: {}
63 | });
64 | }
65 |
--------------------------------------------------------------------------------
/app/spiffworkflow/InputOutput/index.js:
--------------------------------------------------------------------------------
1 | import IoPalette from './IoPalette';
2 | import IoRules from './IoRules';
3 | import IoInterceptor from './IoInterceptor';
4 | import IoPropertiesProvider from './propertiesProvider/IoPropertiesProvider';
5 |
6 | export default {
7 | __init__: [ 'IoPalette', 'IoRules', 'IoInterceptor', 'IoPropertiesProvider' ],
8 | IoPalette: [ 'type', IoPalette ],
9 | IoRules: [ 'type', IoRules ],
10 | IoInterceptor: [ 'type', IoInterceptor ],
11 | IoPropertiesProvider: [ 'type', IoPropertiesProvider ]
12 | };
13 |
14 |
--------------------------------------------------------------------------------
/app/spiffworkflow/InputOutput/propertiesProvider/InputParametersArray.js:
--------------------------------------------------------------------------------
1 | import { useService } from 'bpmn-js-properties-panel';
2 | import {
3 | isTextFieldEntryEdited,
4 | TextFieldEntry,
5 | } from '@bpmn-io/properties-panel';
6 | import { createSpecification, removeElementFromSpecification, updateElementProperties } from '../helpers';
7 |
8 | export function InputParametersArray(props) {
9 |
10 | const { element, moddle, translate, commandStack, bpmnFactory } = props;
11 | const { businessObject } = element;
12 |
13 | const ioSpecification = businessObject.ioSpecification;
14 |
15 | const inputsEntries = (ioSpecification) ? ioSpecification.dataInputs : [];
16 |
17 | const items = (inputsEntries) ? inputsEntries.map((inputEntry, index) => {
18 | const id = `inputEntry-${index}`;
19 | return {
20 | id,
21 | label: translate(inputEntry.name),
22 | entries: InputParamGroup({
23 | element,
24 | commandStack,
25 | moddle,
26 | translate,
27 | bpmnFactory,
28 | inputEntry
29 | }),
30 | autoFocusEntry: `input-focus-entry`,
31 | remove: removeFactory({
32 | element, moddle, commandStack, inputEntry
33 | }),
34 | };
35 | }) : [];
36 |
37 | function add(event) {
38 | const { businessObject } = element;
39 |
40 | const newInputID = moddle.ids.nextPrefixed('DataInput_');
41 |
42 | // Create a new DataInput
43 | const newInput = bpmnFactory.create('bpmn:DataInput', { id: newInputID, name: newInputID });
44 |
45 | // Check if ioSpecification already exists
46 | createSpecification(bpmnFactory, businessObject, 'input', newInput)
47 |
48 | // Update the element
49 | updateElementProperties(commandStack, element);
50 |
51 | event.stopPropagation();
52 | }
53 |
54 | return { items, add };
55 | }
56 |
57 | function removeFactory(props) {
58 | const { element, bpmnFactory, commandStack, inputEntry } = props;
59 | return function (event) {
60 | event.stopPropagation();
61 | removeElementFromSpecification(element, inputEntry, 'input');
62 | updateElementProperties(commandStack, element);
63 | };
64 | }
65 |
66 | function InputParamGroup(props) {
67 |
68 | const { id, inputEntry, element, moddle, commandStack, translate, bpmnFactory } = props;
69 |
70 | return [
71 | {
72 | id,
73 | inputEntry,
74 | component: InputParamTextField,
75 | isEdited: isTextFieldEntryEdited,
76 | element,
77 | moddle,
78 | commandStack,
79 | translate,
80 | bpmnFactory
81 | }
82 | ];
83 | }
84 |
85 | function InputParamTextField(props) {
86 |
87 | const { id, element, inputEntry, moddle, commandStack, translate, bpmnFactory } = props;
88 |
89 | const debounce = useService('debounceInput');
90 |
91 | const setValue = (value) => {
92 | try {
93 | const ioSpecification = element.businessObject.ioSpecification;
94 |
95 | if (!value || value == '') {
96 | console.error('No value provided for this input.');
97 | return;
98 | }
99 |
100 | if (!ioSpecification) {
101 | console.error('No ioSpecification found for this element.');
102 | return;
103 | }
104 |
105 | let existingInput = ioSpecification.dataInputs.find(input => input.id === inputEntry.name || input.name === inputEntry.name);
106 |
107 | if (existingInput) {
108 | existingInput.name = value;
109 | existingInput.id = value;
110 | } else {
111 | console.error(`No DataInput found :> ${inputEntry.name}`);
112 | return;
113 | }
114 |
115 | updateElementProperties(commandStack, element);
116 |
117 | } catch (error) {
118 | console.log('Setting Value Error : ', error);
119 | }
120 | };
121 |
122 | const getValue = () => {
123 | return inputEntry.name;
124 | };
125 |
126 | return TextFieldEntry({
127 | element,
128 | id: `${id}-input`,
129 | label: translate('Input Name'),
130 | getValue,
131 | setValue,
132 | debounce,
133 | });
134 | }
135 |
--------------------------------------------------------------------------------
/app/spiffworkflow/InputOutput/propertiesProvider/IoGroup.js:
--------------------------------------------------------------------------------
1 | import { ListGroup, DescriptionEntry } from '@bpmn-io/properties-panel';
2 | import { InputParametersArray } from './InputParametersArray.js';
3 | import { OutputParametersArray } from './OutputParametersArray.js';
4 |
5 | export function createIoGroup(
6 | element,
7 | translate,
8 | moddle,
9 | commandStack,
10 | bpmnFactory
11 | ) {
12 |
13 | const group = {
14 | label: translate('Input/Output Management'),
15 | id: 'ioProperties',
16 | entries: [],
17 | };
18 |
19 | // add description input
20 | group.entries.push({
21 | id: `infos-textField`,
22 | component: DescriptionEntry,
23 | value:
24 | 'ℹ️ When no specific inputs/outputs are defined, all process variables are accessible.',
25 | element,
26 | translate,
27 | commandStack,
28 | });
29 |
30 | // add input list component
31 | group.entries.push({
32 | id: 'inputParameters',
33 | label: translate('Inputs'),
34 | component: ListGroup,
35 | ...InputParametersArray({
36 | element,
37 | moddle,
38 | translate,
39 | commandStack,
40 | bpmnFactory
41 | }),
42 | });
43 |
44 | // add output list component
45 | group.entries.push({
46 | id: 'outputParameters',
47 | label: translate('Outputs'),
48 | component: ListGroup,
49 | ...OutputParametersArray({
50 | element,
51 | moddle,
52 | translate,
53 | commandStack,
54 | bpmnFactory
55 | })
56 | });
57 |
58 | return group;
59 | }
60 |
--------------------------------------------------------------------------------
/app/spiffworkflow/InputOutput/propertiesProvider/IoPropertiesProvider.js:
--------------------------------------------------------------------------------
1 | import { is } from 'bpmn-js/lib/util/ModelUtil';
2 | import { createIoGroup } from './IoGroup.js';
3 |
4 | const LOW_PRIORITY = 500;
5 |
6 | export default function IoPropertiesProvider(
7 | propertiesPanel,
8 | translate,
9 | moddle,
10 | commandStack,
11 | elementRegistry,
12 | bpmnFactory
13 | ) {
14 | this.getGroups = function getGroupsCallback(element) {
15 | return function pushGroup(groups) {
16 | if (isBpmnTask(element)) {
17 | groups.push(
18 | createIoGroup(
19 | element,
20 | translate,
21 | moddle,
22 | commandStack,
23 | bpmnFactory
24 | )
25 | );
26 | }
27 | return groups;
28 | };
29 | };
30 |
31 | propertiesPanel.registerProvider(LOW_PRIORITY, this);
32 | }
33 |
34 | IoPropertiesProvider.$inject = [
35 | 'propertiesPanel',
36 | 'translate',
37 | 'moddle',
38 | 'commandStack',
39 | 'elementRegistry',
40 | 'bpmnFactory',
41 | ];
42 |
43 | function isBpmnTask(element) {
44 | if (!element) {
45 | return false;
46 | }
47 | return (
48 | is(element, 'bpmn:UserTask') ||
49 | is(element, 'bpmn:ScriptTask') ||
50 | is(element, 'bpmn:ServiceTask') ||
51 | is(element, 'bpmn:SendTask') ||
52 | is(element, 'bpmn:ReceiveTask') ||
53 | is(element, 'bpmn:ManualTask')
54 | );
55 | }
56 |
--------------------------------------------------------------------------------
/app/spiffworkflow/InputOutput/propertiesProvider/OutputParametersArray.js:
--------------------------------------------------------------------------------
1 | import { useService } from 'bpmn-js-properties-panel';
2 | import {
3 | isTextFieldEntryEdited,
4 | TextFieldEntry,
5 | } from '@bpmn-io/properties-panel';
6 | import { createSpecification, removeElementFromSpecification, updateElementProperties } from '../helpers';
7 |
8 | export function OutputParametersArray(props) {
9 |
10 | const { element, moddle, translate, commandStack, bpmnFactory } = props;
11 | const { businessObject } = element;
12 |
13 | const ioSpecification = businessObject.ioSpecification;
14 |
15 | const outputsEntries = (ioSpecification) ? ioSpecification.dataOutputs : [];
16 |
17 | const items = (outputsEntries) ? outputsEntries.map((outputEntry, index) => {
18 | const id = `outputEntry-${index}`;
19 | return {
20 | id,
21 | label: translate(outputEntry.name),
22 | entries: OutputParamGroup({
23 | element,
24 | commandStack,
25 | moddle,
26 | translate,
27 | bpmnFactory,
28 | outputEntry
29 | }),
30 | autoFocusEntry: `output-focus-entry`,
31 | remove: removeFactory({
32 | element, moddle, commandStack, outputEntry
33 | }),
34 | };
35 | }) : [];
36 |
37 | function add(event) {
38 | const { businessObject } = element;
39 |
40 | const newOutputID = moddle.ids.nextPrefixed('DataOutput_');
41 |
42 | // Create a new DataOutput
43 | const newOutput = bpmnFactory.create('bpmn:DataOutput', { id: newOutputID, name: newOutputID });
44 |
45 | // Check if ioSpecification already exists
46 | createSpecification(bpmnFactory, businessObject, 'output', newOutput)
47 |
48 | // Update the element
49 | updateElementProperties(commandStack, element);
50 |
51 | event.stopPropagation();
52 | }
53 |
54 | return { items, add };
55 | }
56 |
57 | function removeFactory(props) {
58 | const { element, bpmnFactory, commandStack, outputEntry } = props;
59 | return function (event) {
60 | event.stopPropagation();
61 | removeElementFromSpecification(element, outputEntry, 'output');
62 | updateElementProperties(commandStack, element);
63 | };
64 | }
65 |
66 | function OutputParamGroup(props) {
67 |
68 | const { id, outputEntry, element, moddle, commandStack, translate, bpmnFactory } = props;
69 |
70 | return [
71 | {
72 | id,
73 | outputEntry,
74 | component: OutputParamTextField,
75 | isEdited: isTextFieldEntryEdited,
76 | element,
77 | moddle,
78 | commandStack,
79 | translate,
80 | bpmnFactory
81 | }
82 | ];
83 | }
84 |
85 | function OutputParamTextField(props) {
86 |
87 | const { id, element, outputEntry, moddle, commandStack, translate, bpmnFactory } = props;
88 |
89 | const debounce = useService('debounceInput');
90 |
91 | const setValue = (value) => {
92 | try {
93 | const ioSpecification = element.businessObject.ioSpecification;
94 |
95 | if (!value || value == '') {
96 | console.error('No value provided for this input.');
97 | return;
98 | }
99 |
100 | if (!ioSpecification) {
101 | console.error('No ioSpecification found for this element.');
102 | return;
103 | }
104 |
105 | let existingInput = ioSpecification.dataOutputs.find(input => input.id === outputEntry.name || input.name === outputEntry.name);
106 |
107 | if (existingInput) {
108 | existingInput.name = value;
109 | existingInput.id = value;
110 | } else {
111 | console.error(`No DataOutput found :> ${outputEntry.name}`);
112 | return;
113 | }
114 |
115 | updateElementProperties(commandStack, element);
116 |
117 | } catch (error) {
118 | console.log('Setting Value Error : ', error);
119 | }
120 | };
121 |
122 | const getValue = () => {
123 | return outputEntry.name;
124 | };
125 |
126 | return TextFieldEntry({
127 | element,
128 | id: `${id}-output`,
129 | label: translate('Output Name'),
130 | getValue,
131 | setValue,
132 | debounce,
133 | });
134 | }
135 |
--------------------------------------------------------------------------------
/app/spiffworkflow/callActivity/CallActivityInterceptor.js:
--------------------------------------------------------------------------------
1 | import { is } from 'bpmn-js/lib/util/ModelUtil';
2 |
3 | export default function CallActivityInterceptor(eventBus, bpmnUpdater, overlays) {
4 |
5 | let OVERLAY_ID;
6 |
7 | eventBus.on('selection.changed', function (event) {
8 |
9 | var newSelection = event.newSelection;
10 | const element = (newSelection.length > 0) ? newSelection[0] : null;
11 |
12 | (OVERLAY_ID) ? overlays.remove(OVERLAY_ID) : null;
13 |
14 | if (element && is(element.businessObject, 'bpmn:CallActivity') && newSelection.length === 1) {
15 | var ARROW_DOWN_SVG = '';
16 | var button = domify('');
17 | button.addEventListener('click', function () {
18 | const processId = getCalledElementValue(element);
19 | if (!processId || processId === '') {
20 | alert('Please select a process model first');
21 | return;
22 | }
23 | eventBus.fire('spiff.callactivity.edit', {
24 | element,
25 | processId,
26 | });
27 | });
28 | OVERLAY_ID = overlays.add(element.id, 'drilldown', {
29 | position: {
30 | bottom: -10,
31 | right: -8
32 | },
33 | html: button
34 | });
35 | }
36 |
37 | });
38 | }
39 |
40 | function domify(htmlString) {
41 | const template = document.createElement('template');
42 | template.innerHTML = htmlString.trim();
43 | return template.content.firstChild;
44 | }
45 |
46 | function getCalledElementValue(element) {
47 | const { calledElement } = element.businessObject;
48 | if (calledElement) {
49 | return calledElement;
50 | }
51 | return '';
52 | }
53 |
54 | CallActivityInterceptor.$inject = ['eventBus', 'bpmnUpdater', 'overlays'];
55 |
56 |
--------------------------------------------------------------------------------
/app/spiffworkflow/callActivity/index.js:
--------------------------------------------------------------------------------
1 | import CallActivityInterceptor from './CallActivityInterceptor';
2 | import CallActivityPropertiesProvider from './propertiesPanel/CallActivityPropertiesProvider';
3 |
4 | export default {
5 | __init__: ['callActivityPropertiesProvider'],
6 | callActivityPropertiesProvider: ['type', CallActivityPropertiesProvider],
7 | callActivityInterceptor: [ 'type', CallActivityInterceptor ],
8 | };
9 |
--------------------------------------------------------------------------------
/app/spiffworkflow/conditions/index.js:
--------------------------------------------------------------------------------
1 | import ConditionsPropertiesProvider from './propertiesPanel/ConditionsPropertiesProvider';
2 |
3 | export default {
4 | __init__: ['conditionsPropertiesProvider'],
5 | conditionsPropertiesProvider: ['type', ConditionsPropertiesProvider],
6 | };
7 |
--------------------------------------------------------------------------------
/app/spiffworkflow/conditions/propertiesPanel/ConditionsPropertiesProvider.js:
--------------------------------------------------------------------------------
1 | import { is } from 'bpmn-js/lib/util/ModelUtil';
2 | import {
3 | TextAreaEntry,
4 | isTextAreaEntryEdited
5 | } from '@bpmn-io/properties-panel';
6 | import { useService } from 'bpmn-js-properties-panel';
7 |
8 | const LOW_PRIORITY = 500;
9 |
10 | export default function ConditionsPropertiesProvider(
11 | propertiesPanel,
12 | translate,
13 | moddle,
14 | commandStack,
15 | _elementRegistry
16 | ) {
17 | this.getGroups = function getGroupsCallback(element) {
18 | return function pushGroup(groups) {
19 | if (is(element, 'bpmn:SequenceFlow')) {
20 | const { source } = element;
21 | if (is(source, 'bpmn:ExclusiveGateway') || is(source, 'bpmn:InclusiveGateway')) {
22 | groups.push(
23 | createConditionsGroup(element, translate, moddle, commandStack)
24 | );
25 | }
26 | } else if (is(element, 'bpmn:Event')) {
27 | const eventDefinitions = element.businessObject.eventDefinitions;
28 | if (eventDefinitions.filter(ev => is(ev, 'bpmn:ConditionalEventDefinition')).length > 0) {
29 | groups.push(
30 | createConditionsGroup(element, translate, moddle, commandStack)
31 | );
32 | }
33 | }
34 | return groups;
35 | };
36 | };
37 | propertiesPanel.registerProvider(LOW_PRIORITY, this);
38 | }
39 |
40 | ConditionsPropertiesProvider.$inject = [
41 | 'propertiesPanel',
42 | 'translate',
43 | 'moddle',
44 | 'commandStack',
45 | 'elementRegistry',
46 | ];
47 |
48 | function createConditionsGroup(element, translate, moddle, commandStack) {
49 | return {
50 | id: 'conditions',
51 | label: translate('Conditions'),
52 | entries: conditionGroup(
53 | element,
54 | moddle,
55 | 'Condition Expression',
56 | 'Expression to Execute',
57 | commandStack
58 | ),
59 | };
60 | }
61 |
62 | function conditionGroup(element, moddle, label, description, commandStack) {
63 | return [
64 | {
65 | id: `condition_expression`,
66 | element,
67 | component: ConditionExpressionTextField,
68 | moddle,
69 | label,
70 | description,
71 | commandStack,
72 | isEdited: isTextAreaEntryEdited,
73 | },
74 | ];
75 | }
76 |
77 | function ConditionExpressionTextField(props) {
78 | const { element } = props;
79 | const { moddle } = props;
80 | const { label } = props;
81 | const { commandStack } = props;
82 |
83 | const debounce = useService('debounceInput');
84 |
85 | const getValue = () => {
86 | let conditionExpression;
87 | if (is(element, 'bpmn:SequenceFlow')) {
88 | conditionExpression = element.businessObject.conditionExpression;
89 | } else if (is(element, 'bpmn:Event')) {
90 | const eventDef = element.businessObject.eventDefinitions.find((ev) =>
91 | is(ev, 'bpmn:ConditionalEventDefinition')
92 | );
93 | conditionExpression = eventDef.condition;
94 | }
95 | if (conditionExpression) {
96 | return conditionExpression.body;
97 | }
98 | return '';
99 | };
100 |
101 | const setValue = (value) => {
102 | let { conditionExpressionModdleElement } = element.businessObject;
103 | if (!conditionExpressionModdleElement) {
104 | conditionExpressionModdleElement = moddle.create('bpmn:Expression');
105 | }
106 | conditionExpressionModdleElement.body = value;
107 | if (is(element, 'bpmn:SequenceFlow')) {
108 | element.businessObject.conditionExpression =
109 | conditionExpressionModdleElement;
110 | } else if (is(element, 'bpmn:Event')) {
111 | const eventDef = element.businessObject.eventDefinitions.find((ev) =>
112 | is(ev, 'bpmn:ConditionalEventDefinition')
113 | );
114 | eventDef.condition = conditionExpressionModdleElement;
115 | }
116 |
117 | commandStack.execute('element.updateModdleProperties', {
118 | element,
119 | moddleElement: conditionExpressionModdleElement,
120 | });
121 | };
122 |
123 | return TextAreaEntry({
124 | element,
125 | id: 'condition_expression',
126 | label,
127 | getValue,
128 | setValue,
129 | debounce,
130 | });
131 | }
132 |
--------------------------------------------------------------------------------
/app/spiffworkflow/constants.js:
--------------------------------------------------------------------------------
1 | export const SPIFFWORKFLOW_XML_NAMESPACE = 'spiffworkflow';
2 | export const SPIFF_ADD_MESSAGE_REQUESTED_EVENT = 'spiff.add_message.requested';
3 | export const SPIFF_ADD_MESSAGE_RETURNED_EVENT = 'spiff.add_message.returned';
4 |
--------------------------------------------------------------------------------
/app/spiffworkflow/errors/index.js:
--------------------------------------------------------------------------------
1 | import ErrorPropertiesProvider from './propertiesPanel/ErrorPropertiesProvider';
2 |
3 | export default {
4 | __init__: ['errorPropertiesProvider'],
5 | errorPropertiesProvider: ['type', ErrorPropertiesProvider],
6 | }
7 |
--------------------------------------------------------------------------------
/app/spiffworkflow/errors/propertiesPanel/ErrorPropertiesProvider.js:
--------------------------------------------------------------------------------
1 | import { is } from 'bpmn-js/lib/util/ModelUtil';
2 | import { getRoot } from '../../helpers';
3 | import { getArrayForType, getListGroupForType } from '../../eventList.js';
4 | import { hasEventType,
5 | replaceGroup,
6 | getSelectorForType,
7 | getConfigureGroupForType
8 | } from '../../eventSelect.js';
9 |
10 | const LOW_PRIORITY = 500;
11 |
12 | const eventDetails = {
13 | 'eventType': 'bpmn:Error',
14 | 'eventDefType': 'bpmn:ErrorEventDefinition',
15 | 'referenceType': 'errorRef',
16 | 'idPrefix': 'error',
17 | };
18 |
19 | export default function ErrorPropertiesProvider(
20 | propertiesPanel,
21 | translate,
22 | moddle,
23 | commandStack,
24 | ) {
25 |
26 | this.getGroups = function (element) {
27 | return function (groups) {
28 | if (is(element, 'bpmn:Process') || is(element, 'bpmn:Collaboration')) {
29 | const getErrorArray = getArrayForType('bpmn:Error', 'errorRef', 'Error');
30 | const errorGroup = getListGroupForType('errors', 'Errors', getErrorArray);
31 | groups.push(errorGroup({ element, translate, moddle, commandStack }));
32 | } else if (hasEventType(element, 'bpmn:ErrorEventDefinition')) {
33 | const getErrorSelector = getSelectorForType(eventDetails);
34 | const errorGroup = getConfigureGroupForType(eventDetails, 'Error', true, getErrorSelector);
35 | const group = errorGroup({ element, translate, moddle, commandStack });
36 | replaceGroup('error', groups, group);
37 | }
38 | return groups;
39 | };
40 | };
41 | propertiesPanel.registerProvider(LOW_PRIORITY, this);
42 | }
43 |
44 | ErrorPropertiesProvider.$inject = [
45 | 'propertiesPanel',
46 | 'translate',
47 | 'moddle',
48 | 'commandStack',
49 | ];
50 |
--------------------------------------------------------------------------------
/app/spiffworkflow/escalations/index.js:
--------------------------------------------------------------------------------
1 | import EscalationPropertiesProvider from './propertiesPanel/EscalationPropertiesProvider';
2 |
3 | export default {
4 | __init__: ['escalationPropertiesProvider'],
5 | escalationrrorPropertiesProvider: ['type', EscalationPropertiesProvider],
6 | }
7 |
--------------------------------------------------------------------------------
/app/spiffworkflow/escalations/propertiesPanel/EscalationPropertiesProvider.js:
--------------------------------------------------------------------------------
1 | import { is } from 'bpmn-js/lib/util/ModelUtil';
2 | import { getRoot } from '../../helpers';
3 | import { getArrayForType, getListGroupForType } from '../../eventList.js';
4 | import { hasEventType,
5 | replaceGroup,
6 | getSelectorForType,
7 | getConfigureGroupForType
8 | } from '../../eventSelect.js';
9 |
10 | const LOW_PRIORITY = 500;
11 |
12 | const eventDetails = {
13 | 'eventType': 'bpmn:Escalation',
14 | 'eventDefType': 'bpmn:EscalationEventDefinition',
15 | 'referenceType': 'escalationRef',
16 | 'idPrefix': 'escalation',
17 | };
18 |
19 | export default function EscalationPropertiesProvider(
20 | propertiesPanel,
21 | translate,
22 | moddle,
23 | commandStack,
24 | ) {
25 |
26 | this.getGroups = function (element) {
27 | return function (groups) {
28 | if (is(element, 'bpmn:Process') || is(element, 'bpmn:Collaboration')) {
29 | const getEscalationArray = getArrayForType('bpmn:Escalation', 'escalationRef', 'Escalation');
30 | const escalationGroup = getListGroupForType('escalations', 'Escalations', getEscalationArray);
31 | groups.push(escalationGroup({ element, translate, moddle, commandStack }));
32 | } else if (hasEventType(element, 'bpmn:EscalationEventDefinition')) {
33 | const getEscalationSelector = getSelectorForType(eventDetails);
34 | const escalationGroup = getConfigureGroupForType(eventDetails, 'Escalation', true, getEscalationSelector);
35 | const group = escalationGroup({ element, translate, moddle, commandStack });
36 | replaceGroup('escalation', groups, group);
37 | }
38 | return groups;
39 | };
40 | };
41 | propertiesPanel.registerProvider(LOW_PRIORITY, this);
42 | }
43 |
44 | EscalationPropertiesProvider.$inject = [
45 | 'propertiesPanel',
46 | 'translate',
47 | 'moddle',
48 | 'commandStack',
49 | ];
50 |
--------------------------------------------------------------------------------
/app/spiffworkflow/extensions/contextPad/CustomContextPadProvider.js:
--------------------------------------------------------------------------------
1 | import { is } from "bpmn-js/lib/util/ModelUtil";
2 | import { getScriptString, updateScript } from "../propertiesPanel/SpiffScriptGroup";
3 |
4 | const LOW_PRIORITY = 500;
5 |
6 | export default function CustomContextPadProvider(contextPad, eventBus, commandStack, moddle) {
7 | contextPad.registerProvider(LOW_PRIORITY, this);
8 |
9 | this.getContextPadEntries = function (element) {
10 | return function (entries) {
11 | if (is(element, 'bpmn:ScriptTask')) {
12 | entries['trigger-script'] = {
13 | group: 'connect',
14 | className: 'bpmn-icon-script',
15 | title: 'Open Script Editor',
16 | action: {
17 | click: function (event, element) {
18 | triggerScript(element, 'bpmn:script', eventBus, commandStack, moddle)
19 | }
20 | }
21 | };
22 | } else if (
23 | hasPreAndPostScript(element)
24 | ) {
25 | const PreScript = getScriptString(element, 'spiffworkflow:PreScript');
26 | if (PreScript && PreScript !== '') {
27 | entries['trigger-preScript'] = {
28 | group: 'connect',
29 | className: 'bpmn-icon-pre-script-trigger',
30 | title: 'Open PreScript Editor',
31 | action: {
32 | click: function (event, element) {
33 | triggerScript(element, 'spiffworkflow:PreScript', eventBus, commandStack, moddle)
34 | }
35 | }
36 | };
37 | }
38 |
39 | const PostScript = getScriptString(element, 'spiffworkflow:PostScript');
40 | if (PostScript && PostScript !== '') {
41 | entries['trigger-postScript'] = {
42 | group: 'connect',
43 | className: 'bpmn-icon-post-script-trigger',
44 | title: 'Open PostScript Editor',
45 | action: {
46 | click: function (event, element) {
47 | triggerScript(element, 'spiffworkflow:PostScript', eventBus, commandStack, moddle)
48 | }
49 | }
50 | };
51 | }
52 | }
53 |
54 | return entries;
55 | };
56 | };
57 | }
58 |
59 | CustomContextPadProvider.$inject = [
60 | 'contextPad',
61 | 'eventBus',
62 | 'commandStack',
63 | 'moddle'
64 | ];
65 |
66 | function hasPreAndPostScript(element) {
67 | return is(element, 'bpmn:Task') ||
68 | is(element, 'bpmn:UserTask') ||
69 | is(element, 'bpmn:ServiceTask') ||
70 | is(element, 'bpmn:SendTask') ||
71 | is(element, 'bpmn:ReceiveTask') ||
72 | is(element, 'bpmn:ManualTask') ||
73 | is(element, 'bpmn:CallActivity') ||
74 | is(element, 'bpmn:BusinessRuleTask') ||
75 | is(element, 'bpmn:SubProcess');
76 | }
77 |
78 | function triggerScript(element, type, eventBus, commandStack, moddle) {
79 | const script = getScriptString(element, type);
80 | eventBus.fire('spiff.script.edit', {
81 | element,
82 | scriptType: type,
83 | script,
84 | eventBus,
85 | });
86 | eventBus.once('spiff.script.update', (event) => {
87 | updateScript(
88 | commandStack,
89 | moddle,
90 | element,
91 | event.scriptType,
92 | event.script
93 | );
94 | });
95 | }
96 |
--------------------------------------------------------------------------------
/app/spiffworkflow/extensions/extensionHelpers.js:
--------------------------------------------------------------------------------
1 | const SPIFF_PARENT_PROP = 'spiffworkflow:Properties';
2 | const SPIFF_PROP = 'spiffworkflow:Property';
3 | const PREFIX = 'spiffworkflow:';
4 |
5 | /**
6 | *
7 | * Spiff Extensions can show up in two distinct ways. The useProperties toggles between them
8 | *
9 | * 1. They might be a top level extension, such as a buisness rule, for example:
10 | *
11 | *
12 | * my_id
13 | *
14 | *
15 | * 2. Or the extension value may exist in a name/value pair inside a Spiffworkflow Properties extension. You would
16 | * do this if you wanted to hide the values from the SpiffWorkflow enginge completely, and pass these values
17 | * through unaltered to your actual application. For Example:
18 | *
19 | *
20 | *
21 | *
22 | *
23 | *
24 | *
25 | *
26 | */
27 |
28 |
29 | /**
30 | * Returns the string value of the spiff extension with the given name on the provided element. ""
31 | * @param useProperties if set to true, will look inside extensions/spiffworkflow:properties otherwise, just
32 | * looks for a spiffworkflow:[name] and returns that value inside of it.
33 | * @param element
34 | * @param name
35 | */
36 | export function getExtensionValue(businessObject, name) {
37 |
38 | const useProperties = !name.startsWith(PREFIX);
39 | let extension;
40 | if (useProperties) {
41 | extension = getExtensionProperty(businessObject, name);
42 | } else {
43 | extension = getExtension(businessObject, name);
44 | }
45 | if (extension) {
46 | return extension.value;
47 | }
48 | return '';
49 | }
50 |
51 | export function setExtensionValue(element, name, value, moddle, commandStack, businessObject) {
52 |
53 | const useProperties = !name.startsWith(PREFIX)
54 |
55 | let businessObjectToUse = businessObject
56 | if (!businessObjectToUse) {
57 | businessObjectToUse = element.businessObject;
58 | }
59 |
60 | // Assure we have extensions
61 | let extensions = businessObjectToUse.extensionElements;
62 | if (!extensions) {
63 | extensions = moddle.create('bpmn:ExtensionElements');
64 | }
65 |
66 | if (useProperties) {
67 | let properties = getExtension(businessObjectToUse, SPIFF_PARENT_PROP);
68 | let property = getExtensionProperty(businessObjectToUse, name);
69 | if (!properties) {
70 | properties = moddle.create(SPIFF_PARENT_PROP);
71 | extensions.get('values').push(properties);
72 | }
73 | if (!property) {
74 | property = moddle.create(SPIFF_PROP);
75 | properties.get('properties').push(property);
76 | }
77 | property.value = value;
78 | property.name = name;
79 | } else {
80 | let extension = getExtension(businessObjectToUse, name);
81 | if (!extension) {
82 | extension = moddle.create(name);
83 | extensions.get('values').push(extension)
84 | }
85 | extension.value = value;
86 | }
87 |
88 | commandStack.execute('element.updateModdleProperties', {
89 | element,
90 | moddleElement: businessObjectToUse,
91 | properties: {
92 | extensionElements: extensions,
93 | },
94 | });
95 | }
96 |
97 | function getExtension(businessObject, name) {
98 | if (!businessObject || !businessObject.extensionElements) {
99 | return null;
100 | }
101 | const extensionElements = businessObject.extensionElements.get('values');
102 | return extensionElements.filter(function (extensionElement) {
103 | if (extensionElement.$instanceOf(name)) {
104 | return true;
105 | }
106 | })[0];
107 | }
108 |
109 |
110 | function getExtensionProperty(businessObject, name) {
111 | const parentElement = getExtension(businessObject, SPIFF_PARENT_PROP);
112 | if (parentElement) {
113 | return parentElement.get('properties').filter(function (propertyElement) {
114 | return (
115 | propertyElement.$instanceOf(SPIFF_PROP) && propertyElement.name === name
116 | );
117 | })[0];
118 | }
119 | return null;
120 | }
121 |
--------------------------------------------------------------------------------
/app/spiffworkflow/extensions/index.js:
--------------------------------------------------------------------------------
1 | import CustomContextPadProvider from './contextPad/CustomContextPadProvider';
2 | import ExtensionsPropertiesProvider from './propertiesPanel/ExtensionsPropertiesProvider';
3 |
4 | export default {
5 | __init__: [ 'extensionsPropertiesProvider', 'customContextPadProvider' ],
6 | extensionsPropertiesProvider: [ 'type', ExtensionsPropertiesProvider ],
7 | customContextPadProvider: [ 'type', CustomContextPadProvider ],
8 | };
9 |
10 |
--------------------------------------------------------------------------------
/app/spiffworkflow/extensions/propertiesPanel/SpiffExtensionCheckboxEntry.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { CheckboxEntry } from '@bpmn-io/properties-panel';
4 | import { useService } from 'bpmn-js-properties-panel';
5 | import { getExtensionValue, setExtensionValue } from '../extensionHelpers';
6 |
7 | /**
8 | * A generic properties' editor for text area.
9 | */
10 | export function SpiffExtensionCheckboxEntry(props) {
11 | const element = props.element;
12 | const commandStack = props.commandStack,
13 | moddle = props.moddle;
14 | const name = props.name,
15 | label = props.label,
16 | description = props.description;
17 | const debounce = useService('debounceInput');
18 |
19 | const getValue = () => {
20 | return getExtensionValue(element.businessObject, name);
21 | };
22 |
23 | const setValue = (value) => {
24 | setExtensionValue(element, name, value, moddle, commandStack);
25 | };
26 |
27 | return (
28 |
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/app/spiffworkflow/extensions/propertiesPanel/SpiffExtensionLaunchButton.js:
--------------------------------------------------------------------------------
1 | import { HeaderButton } from '@bpmn-io/properties-panel';
2 | import { useService } from 'bpmn-js-properties-panel';
3 | import {getExtensionValue, setExtensionValue} from '../extensionHelpers';
4 |
5 | /**
6 | * Sends a notification to the host application saying the user
7 | * would like to edit something. Hosting application can then
8 | * update the value and send it back.
9 | */
10 | export function SpiffExtensionLaunchButton(props) {
11 | const { element, name, event, listenEvent, listenFunction } = props;
12 | const eventBus = useService('eventBus');
13 | return HeaderButton({
14 | className: 'spiffworkflow-properties-panel-button',
15 | id: `launch_editor_button_${name}`,
16 | onClick: () => {
17 | const value = getExtensionValue(element.businessObject, name);
18 | eventBus.fire(event, {
19 | value,
20 | eventBus,
21 | listenEvent,
22 | });
23 |
24 | // Listen for a response if the listenEvent is provided, and
25 | // set the value to the response
26 | // Optional additional arguments if we should listen for a reponse.
27 | if (listenEvent) {
28 | const { commandStack, moddle } = props;
29 | // Listen for a response, to update the script.
30 | eventBus.once(listenEvent, (response) => {
31 | if(listenFunction) {
32 | listenFunction(element, name, response.value, moddle, commandStack);
33 | } else {
34 | setExtensionValue(element, name, response.value, moddle, commandStack);
35 | }
36 | });
37 | }
38 |
39 | },
40 | children: 'Launch Editor',
41 | });
42 | }
43 |
--------------------------------------------------------------------------------
/app/spiffworkflow/extensions/propertiesPanel/SpiffExtensionSelect.js:
--------------------------------------------------------------------------------
1 | import { SelectEntry } from '@bpmn-io/properties-panel';
2 | import { useService } from 'bpmn-js-properties-panel';
3 | import { getExtensionValue, setExtensionValue } from '../extensionHelpers';
4 |
5 | export const spiffExtensionOptions = {};
6 |
7 | export const OPTION_TYPE = {
8 | json_schema_files: 'json_schema_files',
9 | dmn_files: 'dmn_files',
10 | };
11 |
12 | /**
13 | * Allow selecting an option from a list of available options, and setting
14 | * the name and value of a SpiffWorkflow Property to the one selected in the
15 | * dropdown list.
16 | * The list of options must be provided by the containing library - by responding
17 | * to a request passed to the eventBus.
18 | * When needed, the event "spiff.${optionType}.requested" will be fired.
19 | * The response should be sent to "spiff.${optionType}.returned". The response
20 | * event should include an 'options' attribute that is list of labels and values:
21 | * [ { label: 'Product Prices DMN', value: 'Process_16xfaqc' } ]
22 | */
23 | export function SpiffExtensionSelect(props) {
24 | const { element } = props;
25 | const { commandStack } = props;
26 | const { moddle } = props;
27 | const { label, description } = props;
28 |
29 | const { name } = props;
30 | const { optionType } = props;
31 |
32 | const debounce = useService('debounceInput');
33 | const eventBus = useService('eventBus');
34 |
35 | const getValue = () => {
36 | return getExtensionValue(element.businessObject, name);
37 | };
38 |
39 | const setValue = (value) => {
40 | setExtensionValue(element, name, value, moddle, commandStack);
41 | };
42 |
43 | const getOptions = () => {
44 | const optionList = [];
45 | optionList.push({
46 | label: '',
47 | value: '',
48 | });
49 | if (
50 | optionType in spiffExtensionOptions &&
51 | spiffExtensionOptions[optionType] !== null
52 | ) {
53 | spiffExtensionOptions[optionType].forEach((opt) => {
54 | optionList.push({
55 | label: opt.label,
56 | value: opt.value,
57 | });
58 | });
59 | }
60 | return optionList;
61 | };
62 |
63 | // always call this code and let the caller determine how to deal with it.
64 | // this is to avoid state loading issues with react where it doesn't clear out the variable.
65 | spiffExtensionOptions[optionType] = null;
66 | requestOptions(eventBus, element, commandStack, optionType);
67 |
68 | return SelectEntry({
69 | id: `extension_${name}`,
70 | element,
71 | label,
72 | description,
73 | getValue,
74 | setValue,
75 | getOptions,
76 | debounce,
77 | });
78 | }
79 |
80 | function requestOptions(eventBus, element, commandStack, optionType) {
81 | // Little backwards, but you want to assure you are ready to catch, before you throw
82 | // or you risk a race condition.
83 | eventBus.on(`spiff.${optionType}.returned`, (event) => {
84 | spiffExtensionOptions[optionType] = event.options;
85 | });
86 | eventBus.fire(`spiff.${optionType}.requested`, { eventBus });
87 | }
88 |
--------------------------------------------------------------------------------
/app/spiffworkflow/extensions/propertiesPanel/SpiffExtensionTextArea.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useService } from 'bpmn-js-properties-panel';
3 | import { TextAreaEntry, TextFieldEntry } from '@bpmn-io/properties-panel';
4 | import { getExtensionValue, setExtensionValue } from '../extensionHelpers';
5 |
6 | /**
7 | * A generic properties' editor for text area.
8 | */
9 | export function SpiffExtensionTextArea(props) {
10 | const element = props.element;
11 | const commandStack = props.commandStack,
12 | moddle = props.moddle;
13 | const name = props.name,
14 | label = props.label,
15 | description = props.description,
16 | id = props.id;
17 | const debounce = useService('debounceInput');
18 |
19 | const getValue = () => {
20 | return getExtensionValue(element.businessObject, name);
21 | };
22 |
23 | const setValue = (value) => {
24 | setExtensionValue(element, name, value, moddle, commandStack);
25 | };
26 |
27 | return (
28 |
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/app/spiffworkflow/extensions/propertiesPanel/SpiffExtensionTextInput.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useService } from 'bpmn-js-properties-panel';
3 | import { TextFieldEntry } from '@bpmn-io/properties-panel';
4 | import { getExtensionValue, setExtensionValue } from '../extensionHelpers';
5 |
6 | /**
7 | * A generic properties' editor for text input.
8 | * Allows you to provide additional SpiffWorkflow extension properties. Just
9 | * uses whatever name is provide on the property, and adds or updates it as
10 | * needed.
11 | *
12 |
13 |
14 |
15 |
16 |
17 |
18 | *
19 | * @returns {string|null|*}
20 | */
21 | export function SpiffExtensionTextInput(props) {
22 | const {
23 | element,
24 | commandStack,
25 | moddle,
26 | name,
27 | label,
28 | description
29 | } = props;
30 | const debounce = useService('debounceInput');
31 | const { businessObject } = element;
32 |
33 | const getValue = () => {
34 | const value = getExtensionValue(businessObject, name);
35 | return value;
36 | };
37 |
38 | const setValue = (value) => {
39 | setExtensionValue(
40 | element,
41 | name,
42 | value,
43 | moddle,
44 | commandStack,
45 | businessObject,
46 | );
47 | };
48 |
49 | return (
50 |
59 | );
60 | }
61 |
--------------------------------------------------------------------------------
/app/spiffworkflow/helpers.js:
--------------------------------------------------------------------------------
1 | // https://stackoverflow.com/a/5767357/6090676
2 | export function removeFirstInstanceOfItemFromArrayInPlace(arr, value) {
3 | const index = arr.indexOf(value);
4 | if (index > -1) {
5 | arr.splice(index, 1);
6 | }
7 | return arr;
8 | }
9 |
10 | export function removeExtensionElementsIfEmpty(moddleElement) {
11 | if (moddleElement.extensionElements.values.length < 1) {
12 | moddleElement.extensionElements = null;
13 | }
14 | }
15 |
16 | /**
17 | * loops up until it can find the root.
18 | * @param element
19 | */
20 | export function getRoot(businessObject, moddle) {
21 | // HACK: get the root element. need a more formal way to do this
22 | if (moddle) {
23 | for (const elementId in moddle.ids._seed.hats) {
24 | if (elementId.startsWith('Definitions_')) {
25 | return moddle.ids._seed.hats[elementId];
26 | }
27 | }
28 | } else {
29 | // todo: Do we want businessObject to be a shape or moddle object?
30 | if (businessObject.$type === 'bpmn:Definitions') {
31 | return businessObject;
32 | }
33 | if (typeof businessObject.$parent !== 'undefined') {
34 | return getRoot(businessObject.$parent);
35 | }
36 | }
37 | return businessObject;
38 | }
39 |
40 |
41 | export function processId(id) {
42 | let trimmedId = id.trim();
43 | let processedId = trimmedId.replace(/\s+/g, '');
44 | if (id !== processedId) {
45 | alert('ID should not contain spaces. It has been adjusted.');
46 | }
47 | return processedId;
48 | }
49 |
50 | export function checkIfServiceTaskHasParameters(extensionElements) {
51 | let hasParameters = false;
52 | extensionElements.values.forEach((item) => {
53 | if ("parameterList" in item && "parameters" in item.parameterList && typeof (item.parameterList.parameters) !== "undefined") {
54 | hasParameters = true
55 | }
56 | })
57 | return hasParameters;
58 | }
59 |
--------------------------------------------------------------------------------
/app/spiffworkflow/index.js:
--------------------------------------------------------------------------------
1 | import RulesModule from 'diagram-js/lib/features/rules';
2 | import IoPalette from './InputOutput/IoPalette';
3 | import IoRules from './InputOutput/IoRules';
4 | import IoInterceptor from './InputOutput/IoInterceptor';
5 | import DataObjectInterceptor from './DataObject/DataObjectInterceptor';
6 | import DataObjectRules from './DataObject/DataObjectRules';
7 | import DataObjectRenderer from './DataObject/DataObjectRenderer';
8 | import DataObjectPropertiesProvider from './DataObject/propertiesPanel/DataObjectPropertiesProvider';
9 | import DataObjectLabelEditingProvider from './DataObject/DataObjectLabelEditingProvider';
10 | import DataStorePropertiesProvider from './DataStoreReference/propertiesPanel/DataStorePropertiesProvider';
11 | import DataStoreInterceptor from './DataStoreReference/DataStoreInterceptor';
12 | import ConditionsPropertiesProvider from './conditions/propertiesPanel/ConditionsPropertiesProvider';
13 | import ExtensionsPropertiesProvider from './extensions/propertiesPanel/ExtensionsPropertiesProvider';
14 | import MessagesPropertiesProvider from './messages/propertiesPanel/MessagesPropertiesProvider';
15 | import SignalPropertiesProvider from './signals/propertiesPanel/SignalPropertiesProvider';
16 | import ErrorPropertiesProvider from './errors/propertiesPanel/ErrorPropertiesProvider';
17 | import EscalationPropertiesProvider from './escalations/propertiesPanel/EscalationPropertiesProvider';
18 | import CallActivityPropertiesProvider from './callActivity/propertiesPanel/CallActivityPropertiesProvider';
19 | import IoPropertiesProvider from './InputOutput/propertiesProvider/IoPropertiesProvider';
20 | import StandardLoopPropertiesProvider from './loops/StandardLoopPropertiesProvider';
21 | import MultiInstancePropertiesProvider from './loops/MultiInstancePropertiesProvider';
22 | import CallActivityInterceptor from './callActivity/CallActivityInterceptor';
23 | import MessageInterceptor from './messages/MessageInterceptor';
24 | import CustomContextPadProvider from './extensions/contextPad/CustomContextPadProvider';
25 |
26 | export default {
27 | __depends__: [RulesModule],
28 | __init__: [
29 | 'dataObjectInterceptor',
30 | 'dataObjectRules',
31 | 'dataObjectPropertiesProvider',
32 | 'dataObjectLabelEditingProvider',
33 | 'dataStoreInterceptor',
34 | 'dataStorePropertiesProvider',
35 | 'conditionsPropertiesProvider',
36 | 'extensionsPropertiesProvider',
37 | 'customContextPadProvider',
38 | 'messagesPropertiesProvider',
39 | 'messageInterceptor',
40 | 'signalPropertiesProvider',
41 | 'errorPropertiesProvider',
42 | 'escalationPropertiesProvider',
43 | 'callActivityPropertiesProvider',
44 | 'ioPalette',
45 | 'ioRules',
46 | 'ioInterceptor',
47 | 'dataObjectRenderer',
48 | 'multiInstancePropertiesProvider',
49 | 'standardLoopPropertiesProvider',
50 | 'IoPropertiesProvider',
51 | 'callActivityInterceptor'
52 | ],
53 | dataObjectInterceptor: ['type', DataObjectInterceptor],
54 | dataObjectRules: ['type', DataObjectRules],
55 | dataObjectRenderer: ['type', DataObjectRenderer],
56 | dataObjectPropertiesProvider: ['type', DataObjectPropertiesProvider],
57 | dataObjectLabelEditingProvider: ['type', DataObjectLabelEditingProvider],
58 | dataStoreInterceptor: ['type', DataStoreInterceptor],
59 | dataStorePropertiesProvider: ['type', DataStorePropertiesProvider],
60 | conditionsPropertiesProvider: ['type', ConditionsPropertiesProvider],
61 | extensionsPropertiesProvider: ['type', ExtensionsPropertiesProvider],
62 | customContextPadProvider: ['type', CustomContextPadProvider],
63 | signalPropertiesProvider: ['type', SignalPropertiesProvider],
64 | errorPropertiesProvider: ['type', ErrorPropertiesProvider],
65 | escalationPropertiesProvider: ['type', EscalationPropertiesProvider],
66 | messagesPropertiesProvider: ['type', MessagesPropertiesProvider],
67 | messageInterceptor: ['type', MessageInterceptor],
68 | callActivityPropertiesProvider: ['type', CallActivityPropertiesProvider],
69 | ioPalette: ['type', IoPalette],
70 | ioRules: ['type', IoRules],
71 | ioInterceptor: ['type', IoInterceptor],
72 | multiInstancePropertiesProvider: ['type', MultiInstancePropertiesProvider],
73 | standardLoopPropertiesProvider: ['type', StandardLoopPropertiesProvider],
74 | IoPropertiesProvider: ['type', IoPropertiesProvider],
75 | callActivityInterceptor: [ 'type', CallActivityInterceptor ]
76 | };
77 |
--------------------------------------------------------------------------------
/app/spiffworkflow/loops/MultiInstancePropertiesProvider.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable prettier/prettier */
2 | /* eslint-disable no-param-reassign */
3 | /* eslint-disable import/no-extraneous-dependencies */
4 | import { is } from 'bpmn-js/lib/util/ModelUtil';
5 | import { isTextFieldEntryEdited, isCheckboxEntryEdited } from '@bpmn-io/properties-panel';
6 | import { InputItem } from './propertiesPanel/InputItemEntry';
7 | import { LoopCardinality } from './propertiesPanel/LoopCardinalityEntry';
8 | import { InputCollection } from './propertiesPanel/InputCollectionEntry';
9 | import { OutputItem } from './propertiesPanel/OutputItemEntry';
10 | import { OutputCollection } from './propertiesPanel/OutputCollectionEntry';
11 | import { CompletionCondition } from './propertiesPanel/CompletionConditionEntry';
12 | import { IsOutputElSync } from './propertiesPanel/IsIOSyncEntry';
13 |
14 | const LOW_PRIORITY = 500;
15 |
16 | export default function MultiInstancePropertiesProvider(propertiesPanel) {
17 | this.getGroups = function getGroupsCallback(element) {
18 | return function pushGroup(groups) {
19 | if (
20 | is(element, 'bpmn:Task') ||
21 | is(element, 'bpmn:CallActivity') ||
22 | is(element, 'bpmn:SubProcess')
23 | ) {
24 | const group = groups.filter((g) => g.id === 'multiInstance');
25 | if (group.length === 1) updateMultiInstanceGroup(element, group[0]);
26 | }
27 | return groups;
28 | };
29 | };
30 | propertiesPanel.registerProvider(LOW_PRIORITY, this);
31 | }
32 |
33 | MultiInstancePropertiesProvider.$inject = ['propertiesPanel'];
34 |
35 | function updateMultiInstanceGroup(element, group) {
36 | group.entries = MultiInstanceProps({ element });
37 | group.shouldOpen = true;
38 | }
39 |
40 | function MultiInstanceProps(props) {
41 | const { element } = props;
42 | const { businessObject } = element;
43 |
44 | return [
45 | {
46 | id: 'loopCardinality',
47 | component: LoopCardinality,
48 | isEdited: isTextFieldEntryEdited,
49 | },
50 | {
51 | id: 'loopDataInputRef',
52 | component: InputCollection,
53 | isEdited: isTextFieldEntryEdited,
54 | },
55 | {
56 | id: 'dataInputItem',
57 | component: InputItem,
58 | isEdited: isTextFieldEntryEdited,
59 | },
60 | {
61 | id: 'isOutputElSynchronized',
62 | component: IsOutputElSync,
63 | isEdited: isCheckboxEntryEdited,
64 | },
65 | {
66 | id: 'loopDataOutputRef',
67 | component: OutputCollection,
68 | isEdited: isTextFieldEntryEdited,
69 | },
70 | !businessObject.get('spiffworkflow:isOutputSynced')
71 | ? {
72 | id: 'dataOutputItem',
73 | component: OutputItem,
74 | isEdited: isTextFieldEntryEdited,
75 | }
76 | : {},
77 | {
78 | id: 'completionCondition',
79 | component: CompletionCondition,
80 | isEdited: isTextFieldEntryEdited,
81 | },
82 | ];
83 | }
84 |
--------------------------------------------------------------------------------
/app/spiffworkflow/loops/StandardLoopPropertiesProvider.js:
--------------------------------------------------------------------------------
1 | import { is } from 'bpmn-js/lib/util/ModelUtil';
2 | import { useService } from 'bpmn-js-properties-panel';
3 | import {
4 | Group,
5 | TextFieldEntry,
6 | isTextFieldEntryEdited,
7 | CheckboxEntry,
8 | isCheckboxEntryEdited,
9 | } from '@bpmn-io/properties-panel';
10 |
11 | import { getLoopProperty, setLoopProperty } from './helpers';
12 |
13 | const LOW_PRIORITY = 500;
14 |
15 | export default function StandardLoopPropertiesProvider(propertiesPanel) {
16 | this.getGroups = function getGroupsCallback(element) {
17 | return function pushGroup(groups) {
18 | if (
19 | (is(element, 'bpmn:Task') || is(element, 'bpmn:CallActivity') || is(element, 'bpmn:SubProcess')) &&
20 | typeof(element.businessObject.loopCharacteristics) !== 'undefined' &&
21 | element.businessObject.loopCharacteristics.$type === 'bpmn:StandardLoopCharacteristics'
22 | ) {
23 | const group = {
24 | id: 'standardLoopCharacteristics',
25 | component: Group,
26 | label: 'Standard Loop',
27 | entries: StandardLoopProps(element),
28 | shouldOpen: true,
29 | };
30 | if (groups.length < 3)
31 | groups.push(group);
32 | else
33 | groups.splice(2, 0, group);
34 | }
35 | return groups;
36 | };
37 | };
38 | propertiesPanel.registerProvider(LOW_PRIORITY, this);
39 | }
40 |
41 | StandardLoopPropertiesProvider.$inject = ['propertiesPanel'];
42 |
43 | function StandardLoopProps(props) {
44 | const { element } = props;
45 | return [{
46 | id: 'loopMaximum',
47 | component: LoopMaximum,
48 | isEdited: isTextFieldEntryEdited
49 | }, {
50 | id: 'loopCondition',
51 | component: LoopCondition,
52 | isEdited: isTextFieldEntryEdited
53 | }, {
54 | id: 'testBefore',
55 | component: TestBefore,
56 | isEdited: isCheckboxEntryEdited
57 | }];
58 | }
59 |
60 | function LoopMaximum(props) {
61 | const { element } = props;
62 | const debounce = useService('debounceInput');
63 | const translate = useService('translate');
64 | const commandStack = useService('commandStack');
65 | const bpmnFactory = useService('bpmnFactory');
66 |
67 | const getValue = () => {
68 | return getLoopProperty(element, 'loopMaximum');
69 | };
70 |
71 | const setValue = value => {
72 | setLoopProperty(element, 'loopMaximum', value, commandStack);
73 | };
74 |
75 | return TextFieldEntry({
76 | element,
77 | id: 'loopMaximum',
78 | label: translate('Loop Maximum'),
79 | getValue,
80 | setValue,
81 | debounce
82 | });
83 | }
84 |
85 | function TestBefore(props) {
86 | const { element } = props;
87 | const debounce = useService('debounceInput');
88 | const translate = useService('translate');
89 | const commandStack = useService('commandStack');
90 | const bpmnFactory = useService('bpmnFactory');
91 |
92 | const getValue = () => {
93 | return getLoopProperty(element, 'testBefore');
94 | };
95 |
96 | const setValue = value => {
97 | setLoopProperty(element, 'testBefore', value, commandStack);
98 | };
99 |
100 | return CheckboxEntry({
101 | element,
102 | id: 'testBefore',
103 | label: translate('Test Before'),
104 | getValue,
105 | setValue,
106 | });
107 | }
108 |
109 | function LoopCondition(props) {
110 | const { element } = props;
111 | const debounce = useService('debounceInput');
112 | const translate = useService('translate');
113 | const commandStack = useService('commandStack');
114 | const bpmnFactory = useService('bpmnFactory');
115 |
116 | const getValue = () => {
117 | return getLoopProperty(element, 'loopCondition');
118 | };
119 |
120 | const setValue = value => {
121 | const loopCondition = bpmnFactory.create('bpmn:FormalExpression', {body: value})
122 | setLoopProperty(element, 'loopCondition', loopCondition, commandStack);
123 | };
124 |
125 | return TextFieldEntry({
126 | element,
127 | id: 'loopCondition',
128 | label: translate('Loop Condition'),
129 | getValue,
130 | setValue,
131 | debounce
132 | });
133 | }
134 |
--------------------------------------------------------------------------------
/app/spiffworkflow/loops/helpers.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable prettier/prettier */
2 | /* eslint-disable no-param-reassign */
3 |
4 | export function getLoopProperty(element, propertyName) {
5 | const { loopCharacteristics } = element.businessObject;
6 | const prop = loopCharacteristics.get(propertyName);
7 | let value = '';
8 | if (typeof (prop) !== 'object') {
9 | value = prop;
10 | } else if (typeof (prop) !== 'undefined') {
11 | if (prop.$type === 'bpmn:FormalExpression')
12 | value = prop.get('body');
13 | else
14 | value = prop.get('id');
15 | }
16 | return value;
17 | }
18 |
19 | export function setLoopProperty(element, propertyName, value, commandStack) {
20 | const { loopCharacteristics } = element.businessObject;
21 |
22 | if (typeof (value) === 'object') {
23 | value.$parent = loopCharacteristics;
24 | }
25 |
26 | const properties = { [propertyName]: value };
27 | if (propertyName === 'loopCardinality') properties.loopDataInputRef = undefined;
28 | if (propertyName === 'loopDataInputRef') properties.loopCardinality = undefined;
29 |
30 | commandStack.execute('element.updateModdleProperties', {
31 | element,
32 | moddleElement: loopCharacteristics,
33 | properties,
34 | });
35 | }
36 |
37 | export function removeLoopProperty(element, propertyName, commandStack) {
38 | const { loopCharacteristics } = element.businessObject;
39 | const properties = { [propertyName]: undefined };
40 | commandStack.execute('element.updateModdleProperties', {
41 | element,
42 | moddleElement: loopCharacteristics,
43 | properties,
44 | });
45 | }
46 |
47 | export function setIsIOValue(element, value, commandStack) {
48 | commandStack.execute('element.updateProperties', {
49 | element,
50 | properties: {
51 | 'spiffworkflow:isOutputSynced': value,
52 | },
53 | });
54 | }
55 |
--------------------------------------------------------------------------------
/app/spiffworkflow/loops/propertiesPanel/CompletionConditionEntry.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable prettier/prettier */
2 | /* eslint-disable import/no-extraneous-dependencies */
3 | /* eslint-disable import/order */
4 |
5 | import { useService } from "bpmn-js-properties-panel";
6 | import { getLoopProperty, removeLoopProperty, setLoopProperty } from "../helpers";
7 | import { TextFieldEntry } from '@bpmn-io/properties-panel';
8 |
9 | export function CompletionCondition(props) {
10 | const { element } = props;
11 | const debounce = useService('debounceInput');
12 | const translate = useService('translate');
13 | const commandStack = useService('commandStack');
14 | const bpmnFactory = useService('bpmnFactory');
15 |
16 | const getValue = () => {
17 | return getLoopProperty(element, 'completionCondition');
18 | };
19 |
20 | const setValue = (value) => {
21 | if (!value || value === '') {
22 | // If value is empty, remove completionCondition from XML
23 | removeLoopProperty(element, 'completionCondition', commandStack);
24 | return;
25 | }
26 | const completionCondition = bpmnFactory.create('bpmn:FormalExpression', {
27 | body: value,
28 | });
29 | setLoopProperty(
30 | element,
31 | 'completionCondition',
32 | completionCondition,
33 | commandStack
34 | );
35 | };
36 |
37 | return TextFieldEntry({
38 | element,
39 | id: 'completionCondition',
40 | label: translate('Completion Condition'),
41 | getValue,
42 | setValue,
43 | debounce,
44 | description: 'Stop executing this task when this condition is met',
45 | });
46 | }
--------------------------------------------------------------------------------
/app/spiffworkflow/loops/propertiesPanel/InputCollectionEntry.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable prettier/prettier */
2 | /* eslint-disable import/no-extraneous-dependencies */
3 | /* eslint-disable import/order */
4 |
5 | import { useService } from "bpmn-js-properties-panel";
6 | import { getLoopProperty, removeLoopProperty, setLoopProperty } from "../helpers";
7 | import { TextFieldEntry } from '@bpmn-io/properties-panel';
8 |
9 | export function InputCollection(props) {
10 | const { element } = props;
11 | const debounce = useService('debounceInput');
12 | const translate = useService('translate');
13 | const commandStack = useService('commandStack');
14 | const bpmnFactory = useService('bpmnFactory');
15 |
16 | const getValue = () => {
17 | return getLoopProperty(element, 'loopDataInputRef');
18 | };
19 |
20 | const setValue = (value) => {
21 | if (!value || value === '') {
22 | // If value is empty or undefined, remove loopDataInputRef from XML
23 | removeLoopProperty(element, 'loopDataInputRef', commandStack);
24 | return;
25 | }
26 | const collection = bpmnFactory.create('bpmn:ItemAwareElement', {
27 | id: value,
28 | });
29 | setLoopProperty(element, 'loopDataInputRef', collection, commandStack);
30 | };
31 |
32 | return TextFieldEntry({
33 | element,
34 | id: 'loopDataInputRef',
35 | label: translate('Input Collection'),
36 | getValue,
37 | setValue,
38 | debounce,
39 | description: 'Create an instance for each item in this collection',
40 | });
41 | }
--------------------------------------------------------------------------------
/app/spiffworkflow/loops/propertiesPanel/InputItemEntry.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable prettier/prettier */
2 | /* eslint-disable import/no-extraneous-dependencies */
3 | /* eslint-disable import/order */
4 |
5 | import { useService } from "bpmn-js-properties-panel";
6 | import { getLoopProperty, setLoopProperty } from "../helpers";
7 | import { TextFieldEntry } from '@bpmn-io/properties-panel';
8 |
9 | export function InputItem(props) {
10 | const { element } = props;
11 | const debounce = useService('debounceInput');
12 | const translate = useService('translate');
13 | const commandStack = useService('commandStack');
14 | const bpmnFactory = useService('bpmnFactory');
15 |
16 | const getValue = () => {
17 | return getLoopProperty(element, 'inputDataItem');
18 | };
19 |
20 | const setValue = (value) => {
21 | const item =
22 | typeof value !== 'undefined' && value !== ''
23 | ? bpmnFactory.create('bpmn:DataInput', { id: value, name: value })
24 | : undefined;
25 | setLoopProperty(element, 'inputDataItem', item, commandStack);
26 |
27 | try {
28 | const { businessObject } = element;
29 | if (businessObject.get('spiffworkflow:isOutputSynced')) {
30 | setLoopProperty(element, 'outputDataItem', item, commandStack);
31 | }
32 | } catch (error) {
33 | console.log('Error caught while set value Input item', error);
34 | }
35 | };
36 |
37 | return TextFieldEntry({
38 | element,
39 | id: 'inputDataItem',
40 | label: translate('Input Element'),
41 | getValue,
42 | setValue,
43 | debounce,
44 | description: 'Each item in the collection will be copied to this variable',
45 | });
46 | }
--------------------------------------------------------------------------------
/app/spiffworkflow/loops/propertiesPanel/IsIOSyncEntry.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable prettier/prettier */
2 | /* eslint-disable import/no-extraneous-dependencies */
3 | /* eslint-disable import/order */
4 |
5 | import { useService } from "bpmn-js-properties-panel";
6 | import { getLoopProperty, removeLoopProperty, setIsIOValue, setLoopProperty } from "../helpers";
7 | import { CheckboxEntry } from '@bpmn-io/properties-panel';
8 |
9 | export function IsOutputElSync(props) {
10 | const { element } = props;
11 | const translate = useService('translate');
12 | const commandStack = useService('commandStack');
13 | const bpmnFactory = useService('bpmnFactory');
14 |
15 | const getValue = () => {
16 | const { businessObject } = element;
17 | return businessObject.get('spiffworkflow:isOutputSynced')
18 | ? businessObject.get('spiffworkflow:isOutputSynced')
19 | : false;
20 | };
21 |
22 | const setValue = (value) => {
23 | if (value) {
24 | const valIn = getLoopProperty(element, 'inputDataItem');
25 | const item =
26 | typeof valIn !== 'undefined' && valIn !== ''
27 | ? bpmnFactory.create('bpmn:DataOutput', { id: valIn, name: valIn })
28 | : undefined;
29 | if(item){
30 | // If DataInput Item is found and set, add new DataOut with same value
31 | setLoopProperty(element, 'outputDataItem', item, commandStack);
32 | }
33 | } else {
34 | // Remove DataOutput value when isIoSync is disabled
35 | removeLoopProperty(element, 'outputDataItem', commandStack);
36 | }
37 |
38 | setIsIOValue(element, value, commandStack);
39 | };
40 |
41 | return CheckboxEntry({
42 | element,
43 | id: 'testBefore',
44 | label: translate('Output Element is Synchronized with Input Element'),
45 | getValue,
46 | setValue,
47 | });
48 | }
49 |
--------------------------------------------------------------------------------
/app/spiffworkflow/loops/propertiesPanel/LoopCardinalityEntry.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable prettier/prettier */
2 | /* eslint-disable import/no-extraneous-dependencies */
3 | /* eslint-disable import/order */
4 |
5 | import { useService } from "bpmn-js-properties-panel";
6 | import { getLoopProperty, removeLoopProperty, setLoopProperty } from "../helpers";
7 | import { TextFieldEntry } from '@bpmn-io/properties-panel';
8 |
9 | export function LoopCardinality(props) {
10 | const { element } = props;
11 | const debounce = useService('debounceInput');
12 | const translate = useService('translate');
13 | const commandStack = useService('commandStack');
14 | const bpmnFactory = useService('bpmnFactory');
15 |
16 | const getValue = () => {
17 | return getLoopProperty(element, 'loopCardinality');
18 | };
19 |
20 | const setValue = (value) => {
21 | if (!value || value === '') {
22 | // If value is empty or undefined, remove loopCardinality from XML
23 | removeLoopProperty(element, 'loopCardinality', commandStack);
24 | return;
25 | }
26 | const loopCardinality = bpmnFactory.create('bpmn:FormalExpression', {
27 | body: value,
28 | });
29 | setLoopProperty(element, 'loopCardinality', loopCardinality, commandStack);
30 | };
31 |
32 | return TextFieldEntry({
33 | element,
34 | id: 'loopCardinality',
35 | label: translate('Loop Cardinality'),
36 | getValue,
37 | setValue,
38 | debounce,
39 | description: 'Explicitly set the number of instances',
40 | });
41 | }
--------------------------------------------------------------------------------
/app/spiffworkflow/loops/propertiesPanel/OutputCollectionEntry.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable prettier/prettier */
2 | /* eslint-disable import/no-extraneous-dependencies */
3 | /* eslint-disable import/order */
4 |
5 | import { useService } from "bpmn-js-properties-panel";
6 | import { getLoopProperty, removeLoopProperty, setLoopProperty } from "../helpers";
7 | import { TextFieldEntry } from '@bpmn-io/properties-panel';
8 |
9 | export function OutputCollection(props) {
10 | const { element } = props;
11 | const debounce = useService('debounceInput');
12 | const translate = useService('translate');
13 | const commandStack = useService('commandStack');
14 | const bpmnFactory = useService('bpmnFactory');
15 |
16 | const getValue = () => {
17 | return getLoopProperty(element, 'loopDataOutputRef');
18 | };
19 |
20 | const setValue = (value) => {
21 | if (!value || value === '') {
22 | // If value is empty or undefined, remove loopDataOutputRef from XML
23 | removeLoopProperty(element, 'loopDataOutputRef', commandStack);
24 | return;
25 | }
26 | const collection = bpmnFactory.create('bpmn:ItemAwareElement', {
27 | id: value,
28 | });
29 | setLoopProperty(element, 'loopDataOutputRef', collection, commandStack);
30 | };
31 |
32 | return TextFieldEntry({
33 | element,
34 | id: 'loopDataOutputRef',
35 | label: translate('Output Collection'),
36 | getValue,
37 | setValue,
38 | debounce,
39 | description: 'Create or update this collection with the instance results',
40 | });
41 | }
--------------------------------------------------------------------------------
/app/spiffworkflow/loops/propertiesPanel/OutputItemEntry.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable prettier/prettier */
2 | /* eslint-disable import/no-extraneous-dependencies */
3 | /* eslint-disable import/order */
4 | import { useService } from "bpmn-js-properties-panel";
5 | import { getLoopProperty, setLoopProperty } from "../helpers";
6 | import { TextFieldEntry } from '@bpmn-io/properties-panel';
7 |
8 |
9 | export function OutputItem(props) {
10 | const { element } = props;
11 | const debounce = useService('debounceInput');
12 | const translate = useService('translate');
13 | const commandStack = useService('commandStack');
14 | const bpmnFactory = useService('bpmnFactory');
15 |
16 | const getValue = () => {
17 | return getLoopProperty(element, 'outputDataItem');
18 | };
19 |
20 | const setValue = (value) => {
21 | try {
22 | const inVal = getLoopProperty(element, 'inputDataItem');
23 | if (inVal === value) {
24 | alert( 'You have entered the same value for both Input and Output elements without enabling synchronization. Please confirm if this is intended.' );
25 | return;
26 | }
27 | } catch (error) {
28 | console.log('Error caught while Set Value OutputItem', error);
29 | }
30 |
31 | const item =
32 | typeof value !== 'undefined' && value !== ''
33 | ? bpmnFactory.create('bpmn:DataOutput', { id: value, name: value })
34 | : undefined;
35 | setLoopProperty(element, 'outputDataItem', item, commandStack);
36 | };
37 |
38 | return TextFieldEntry({
39 | element,
40 | id: 'outputDataItem',
41 | label: translate('Output Element'),
42 | getValue,
43 | setValue,
44 | debounce,
45 | description:
46 | 'The value of this variable will be added to the output collection',
47 | });
48 | }
--------------------------------------------------------------------------------
/app/spiffworkflow/messages/MessageInterceptor.js:
--------------------------------------------------------------------------------
1 | import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
2 | import { isMessageElement, isMessageRefUsed, setParentCorrelationKeys, syncCorrelationProperties, getRoot} from './MessageHelpers';
3 |
4 | const HIGH_PRIORITY = 90500;
5 |
6 | export default class MessageInterceptor extends CommandInterceptor {
7 |
8 | constructor(eventBus, bpmnFactory, commandStack, bpmnUpdater, moddle) {
9 | super(eventBus);
10 |
11 | this.postExecuted(['shape.delete'], HIGH_PRIORITY, function (event) {
12 | const { context } = event;
13 | const { shape, rootElement } = context;
14 | const { businessObject } = shape;
15 |
16 | if (isMessageElement(shape)) {
17 |
18 | let oldMessageRef = (businessObject.eventDefinitions) ? businessObject.eventDefinitions[0].messageRef : businessObject.messageRef;
19 |
20 | let definitions = getRoot(rootElement, moddle)
21 | if (!definitions.get('rootElements')) {
22 | definitions.set('rootElements', []);
23 | }
24 |
25 | if (oldMessageRef) {
26 | // Remove previous message in case it's not used anymore
27 | const isOldMessageUsed = isMessageRefUsed(definitions, oldMessageRef.id);
28 | if (!isOldMessageUsed) {
29 | const rootElements = definitions.get('rootElements');
30 | const oldMessageIndex = rootElements.findIndex(
31 | (element) =>
32 | element.$type === 'bpmn:Message' && element.id === oldMessageRef.id,
33 | );
34 | if (oldMessageIndex !== -1) {
35 | rootElements.splice(oldMessageIndex, 1);
36 | definitions.rootElements = rootElements;
37 | }
38 | }
39 |
40 | // Automatic deletion of previous message correlation properties
41 | syncCorrelationProperties(shape, definitions, moddle);
42 | }
43 |
44 | // Update Correlation key if Process has collaboration
45 | try {
46 | setParentCorrelationKeys(definitions, bpmnFactory, shape, moddle);
47 | } catch (error) {
48 | console.error('Error Caught while synchronizing Correlation key', error);
49 | }
50 |
51 | }
52 | });
53 |
54 | }
55 | }
56 |
57 | MessageInterceptor.$inject = ['eventBus', 'bpmnFactory', 'commandStack', 'bpmnUpdater', 'moddle'];
58 |
--------------------------------------------------------------------------------
/app/spiffworkflow/messages/index.js:
--------------------------------------------------------------------------------
1 | import MessageInterceptor from './MessageInterceptor';
2 | import MessagesPropertiesProvider from './propertiesPanel/MessagesPropertiesProvider';
3 |
4 | export default {
5 | __init__: ['messagesPropertiesProvider', 'messageInterceptor'],
6 | messagesPropertiesProvider: ['type', MessagesPropertiesProvider],
7 | messageInterceptor: [ 'type', MessageInterceptor ],
8 |
9 | };
10 |
--------------------------------------------------------------------------------
/app/spiffworkflow/messages/propertiesPanel/MessagesPropertiesProvider.js:
--------------------------------------------------------------------------------
1 | import { isMessageElement } from '../MessageHelpers';
2 |
3 | import { createMessageGroup } from './elementLevelProvider/TaskEventMessageProvider';
4 |
5 | const LOW_PRIORITY = 500;
6 |
7 | export default function MessagesPropertiesProvider(
8 | propertiesPanel,
9 | translate,
10 | moddle,
11 | commandStack,
12 | elementRegistry
13 | ) {
14 | this.getGroups = function getGroupsCallback(element) {
15 | return function pushGroup(groups) {
16 | if (isMessageElement(element)) {
17 | const messageIndex = findEntry(groups, 'message');
18 | if (messageIndex) {
19 | groups.splice(messageIndex, 1);
20 | }
21 | groups.push(
22 | ...createMessageGroup(
23 | element,
24 | translate,
25 | moddle,
26 | commandStack,
27 | elementRegistry
28 | )
29 | );
30 | }
31 | return groups;
32 | };
33 | };
34 |
35 | function findEntry(entries, entryId) {
36 | let entryIndex = null;
37 | entries.forEach(function (value, index) {
38 | if (value.id === entryId) {
39 | entryIndex = index;
40 | }
41 | });
42 | return entryIndex;
43 | }
44 |
45 | propertiesPanel.registerProvider(LOW_PRIORITY, this);
46 | }
47 |
48 | MessagesPropertiesProvider.$inject = [
49 | 'propertiesPanel',
50 | 'translate',
51 | 'moddle',
52 | 'commandStack',
53 | 'elementRegistry',
54 | ];
55 |
56 |
--------------------------------------------------------------------------------
/app/spiffworkflow/messages/propertiesPanel/elementLevelProvider/CorrelationCheckbox.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useService } from 'bpmn-js-properties-panel';
3 | import { CheckboxEntry } from '@bpmn-io/properties-panel';
4 |
5 | /**
6 | * A generic properties' editor for text area.
7 | */
8 | export function CorrelationCheckboxEntry(props) {
9 | const { element, commandStack, moddle } = props;
10 | const { name, label, description } = props;
11 | const { businessObject } = element;
12 |
13 | const debounce = useService('debounceInput');
14 |
15 | const getValue = () => {
16 | const value = businessObject.get('isCorrelated')
17 | ? businessObject.get('isCorrelated')
18 | : false;
19 | return value;
20 | };
21 |
22 | const setValue = (value) => {
23 | commandStack.execute('element.updateProperties', {
24 | element,
25 | properties: {
26 | isCorrelated: value,
27 | },
28 | });
29 | };
30 |
31 | return (
32 |
41 | );
42 | }
43 |
--------------------------------------------------------------------------------
/app/spiffworkflow/messages/propertiesPanel/elementLevelProvider/MatchingCorrelationCheckbox.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useService } from 'bpmn-js-properties-panel';
3 | import { CheckboxEntry } from '@bpmn-io/properties-panel';
4 |
5 | /**
6 | * A generic properties' editor for text area.
7 | */
8 | export function MatchingCorrelationCheckboxEntry(props) {
9 | const { element, commandStack, moddle } = props;
10 | const { name, label, description } = props;
11 | const { businessObject } = element;
12 |
13 | const debounce = useService('debounceInput');
14 |
15 | const getValue = () => {
16 | const isMatchingCorrelation = businessObject.get('spiffworkflow:isMatchingCorrelation');
17 | const value = isMatchingCorrelation && isMatchingCorrelation !== 'false'
18 | ? true
19 | : false;
20 | return value;
21 | };
22 |
23 | const setValue = (value) => {
24 | commandStack.execute('element.updateProperties', {
25 | element,
26 | properties: {
27 | 'spiffworkflow:isMatchingCorrelation': value,
28 | },
29 | });
30 | };
31 |
32 | return (
33 |
42 | );
43 | }
44 |
--------------------------------------------------------------------------------
/app/spiffworkflow/messages/propertiesPanel/elementLevelProvider/MessageLaunchEditorButton.js:
--------------------------------------------------------------------------------
1 | import { HeaderButton } from '@bpmn-io/properties-panel';
2 | import { useService } from 'bpmn-js-properties-panel';
3 | import {
4 | findCorrelationPropertiesAndRetrievalExpressionsForMessage,
5 | getMessageRefElement,
6 | } from '../../MessageHelpers';
7 |
8 | /**
9 | * Sends a notification to the host application saying the user
10 | * would like to edit something. Hosting application can then
11 | * update the value and send it back.
12 | */
13 | export function MessageLaunchEditorButton(props) {
14 | const { element, moddle } = props;
15 |
16 | const sendEvent = 'spiff.message.edit';
17 | const listenEvent = 'spiff.message.update';
18 |
19 | const eventBus = useService('eventBus');
20 |
21 | const messageRef = getMessageRefElement(element);
22 | const correlationProperties =
23 | findCorrelationPropertiesAndRetrievalExpressionsForMessage(element);
24 | const parsedCorrelationProperties = correlationProperties.map((item) => ({
25 | id: item.correlationPropertyModdleElement.id,
26 | retrievalExpression:
27 | item.correlationPropertyRetrievalExpressionModdleElement.messagePath.body,
28 | }));
29 |
30 | let messageId = null;
31 | if (messageRef && messageRef.id) {
32 | messageId = messageRef.id;
33 | }
34 | if (messageRef && messageRef.name && messageRef.name !== messageId) {
35 | messageId = messageRef.name;
36 | }
37 | return HeaderButton({
38 | className: 'spiffworkflow-properties-panel-button',
39 | id: `message_launch_message_editor_button`,
40 | onClick: () => {
41 |
42 | // eventBus.fire('spiff.add_message.requested', { eventBus });
43 |
44 | eventBus.fire(sendEvent, {
45 | value: {
46 | elementId: element.id,
47 | messageId,
48 | correlation_properties: parsedCorrelationProperties,
49 | },
50 | eventBus,
51 | listenEvent,
52 | });
53 |
54 | // Listen for a response, to update the script.
55 | eventBus.once(listenEvent, (response) => {
56 | messageRef.id = response.value;
57 | });
58 | },
59 | children: 'Open message editor',
60 | });
61 | }
62 |
--------------------------------------------------------------------------------
/app/spiffworkflow/messages/propertiesPanel/elementLevelProvider/MessagePayload.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useService } from 'bpmn-js-properties-panel';
3 | import { TextAreaEntry } from '@bpmn-io/properties-panel';
4 | import {
5 | getMessageElementForShapeElement,
6 | isMessageEvent,
7 | } from '../../MessageHelpers';
8 |
9 | /**
10 | * Allows the creation, or editing of messagePayload at the bpmn:sendTask level of a BPMN document.
11 | */
12 | export function MessagePayload(props) {
13 | const shapeElement = props.element;
14 | const { moddle, element, commandStack } = props;
15 | const debounce = useService('debounceInput');
16 | const messageElement = getMessageElementForShapeElement(shapeElement);
17 | const disabled = !messageElement;
18 |
19 | const getMessagePayloadObject = () => {
20 | if (element) {
21 | const { extensionElements } = isMessageEvent(element)
22 | ? element.businessObject.eventDefinitions[0]
23 | : element.businessObject;
24 | if (extensionElements && extensionElements.get('values')) {
25 | let payloadResp = extensionElements
26 | .get('values')
27 | .filter(function getInstanceOfType(e) {
28 | return e.$instanceOf('spiffworkflow:MessagePayload');
29 | })[0];
30 | return payloadResp;
31 | }
32 | }
33 | return null;
34 | };
35 |
36 | const getValue = () => {
37 | const messagePayloadObject = getMessagePayloadObject();
38 | if (messagePayloadObject) {
39 | return messagePayloadObject.value;
40 | } else {
41 | // Check : for old models where payload exists on message level
42 | const bo = isMessageEvent(element)
43 | ? element.businessObject.eventDefinitions[0]
44 | : element.businessObject;
45 |
46 | const { messageRef } = bo;
47 | if (messageRef) {
48 | const { extensionElements } = messageRef;
49 | const payloadResp = (extensionElements) ? extensionElements
50 | .get('values')
51 | .filter(function getInstanceOfType(e) {
52 | return e.$instanceOf('spiffworkflow:MessagePayload');
53 | })[0] : undefined;
54 |
55 | if (payloadResp) {
56 | setValue(payloadResp.value);
57 | return payloadResp.value;
58 | }
59 | }
60 | }
61 | return '';
62 | };
63 |
64 | const setValue = (value) => {
65 |
66 | var extensions = isMessageEvent(element)
67 | ? element.businessObject.eventDefinitions[0].get('extensionElements') ||
68 | moddle.create('bpmn:ExtensionElements')
69 | : element.businessObject.get('extensionElements') ||
70 | moddle.create('bpmn:ExtensionElements');
71 |
72 | let messagePayloadObject = getMessagePayloadObject();
73 | if (!messagePayloadObject) {
74 | messagePayloadObject = moddle.create('spiffworkflow:MessagePayload');
75 | extensions.get('values').push(messagePayloadObject);
76 | }
77 | messagePayloadObject.value = value;
78 |
79 | isMessageEvent(element)
80 | ? element.businessObject.eventDefinitions[0].set(
81 | 'extensionElements',
82 | extensions
83 | )
84 | : element.businessObject.set('extensionElements', extensions);
85 |
86 | commandStack.execute('element.updateProperties', {
87 | element,
88 | properties: {},
89 | });
90 | };
91 |
92 | return (
93 |
103 | );
104 | }
105 |
--------------------------------------------------------------------------------
/app/spiffworkflow/messages/propertiesPanel/elementLevelProvider/MessageVariable.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useService } from 'bpmn-js-properties-panel';
3 | import { TextFieldEntry } from '@bpmn-io/properties-panel';
4 | import {
5 | getMessageElementForShapeElement,
6 | isMessageEvent,
7 | } from '../../MessageHelpers';
8 |
9 | /**
10 | * Allows the creation, or editing of messageVariable at the bpmn:sendTask level of a BPMN document.
11 | */
12 | export function MessageVariable(props) {
13 | const shapeElement = props.element;
14 | const { moddle, element, commandStack } = props;
15 | const debounce = useService('debounceInput');
16 | const messageElement = getMessageElementForShapeElement(shapeElement);
17 | const disabled = !messageElement;
18 |
19 | const getMessageVariableObject = () => {
20 | if (element) {
21 | const { extensionElements } = isMessageEvent(element)
22 | ? element.businessObject.eventDefinitions[0]
23 | : element.businessObject;
24 | if (extensionElements && extensionElements.get('values')) {
25 | let variableResp = extensionElements
26 | .get('values')
27 | .filter(function getInstanceOfType(e) {
28 | return e.$instanceOf('spiffworkflow:MessageVariable');
29 | })[0];
30 | return variableResp;
31 | }
32 | }
33 | return null;
34 | };
35 |
36 | const getValue = () => {
37 | const messageVariableObject = getMessageVariableObject();
38 | if (messageVariableObject) {
39 | return messageVariableObject.value;
40 | }
41 |
42 | // Check : for old models where messageVariable exists on message level
43 | const bo = isMessageEvent(element)
44 | ? element.businessObject.eventDefinitions[0]
45 | : element.businessObject;
46 |
47 | const { messageRef } = bo;
48 | if (messageRef) {
49 | const { extensionElements } = messageRef;
50 | const messageResp = extensionElements
51 | ? extensionElements.get('values').filter(function getInstanceOfType(e) {
52 | return e.$instanceOf('spiffworkflow:MessageVariable');
53 | })[0]
54 | : undefined;
55 |
56 | if (messageResp) {
57 | setValue(messageResp.value);
58 | return messageResp.value;
59 | }
60 | }
61 |
62 | return '';
63 | };
64 |
65 | const setValue = (value) => {
66 | var extensions = isMessageEvent(element)
67 | ? element.businessObject.eventDefinitions[0].get('extensionElements') ||
68 | moddle.create('bpmn:ExtensionElements')
69 | : element.businessObject.get('extensionElements') ||
70 | moddle.create('bpmn:ExtensionElements');
71 |
72 | let messageVariableObject = getMessageVariableObject();
73 | if (!messageVariableObject) {
74 | messageVariableObject = moddle.create('spiffworkflow:MessageVariable');
75 | extensions.get('values').push(messageVariableObject);
76 | }
77 | messageVariableObject.value = value;
78 | isMessageEvent(element)
79 | ? element.businessObject.eventDefinitions[0].set(
80 | 'extensionElements',
81 | extensions
82 | )
83 | : element.businessObject.set('extensionElements', extensions);
84 | commandStack.execute('element.updateProperties', {
85 | element,
86 | properties: {},
87 | });
88 | };
89 |
90 | return (
91 |
101 | );
102 | }
103 |
--------------------------------------------------------------------------------
/app/spiffworkflow/messages/propertiesPanel/processLevelProvider/CollaborationPropertiesProvider.js:
--------------------------------------------------------------------------------
1 | import { MessageArray } from './MessageArray';
2 | import { CorrelationKeysArray } from './CorrelationKeysArray';
3 | import { CorrelationPropertiesArray } from './CorrelationPropertiesArray';
4 | import { ListGroup } from '@bpmn-io/properties-panel';
5 |
6 | /**
7 | * Adds a group to the properties panel for the script task that allows you
8 | * to set the script.
9 | * @param element
10 | * @param translate
11 | * @returns The components to add to the properties panel. */
12 | export function createCollaborationGroup(
13 | element,
14 | translate,
15 | moddle,
16 | commandStack,
17 | elementRegistry
18 | ) {
19 |
20 | const results = [
21 | {
22 | id: 'messages',
23 | label: translate('Messages'),
24 | isDefault: true,
25 | component: ListGroup,
26 | ...MessageArray({
27 | element,
28 | moddle,
29 | commandStack,
30 | elementRegistry,
31 | translate,
32 | }),
33 | },
34 | {
35 | id: 'correlation_properties',
36 | class: 'correlation_properties',
37 | className: 'correlation_properties',
38 | label: translate('Correlation Properties'),
39 | isDefault: true,
40 | component: ListGroup,
41 | ...CorrelationPropertiesArray({
42 | element,
43 | moddle,
44 | commandStack,
45 | elementRegistry,
46 | translate,
47 | }),
48 | }
49 | ];
50 |
51 | // Hide Correlation Keys
52 | // if (element.type === 'bpmn:Collaboration') {
53 | // results.push({
54 | // id: 'correlation_keys',
55 | // label: translate('Correlation Keys'),
56 | // isDefault: true,
57 | // component: ListGroup,
58 | // ...CorrelationKeysArray({
59 | // element,
60 | // moddle,
61 | // commandStack,
62 | // elementRegistry,
63 | // translate,
64 | // }),
65 | // })
66 | // }
67 |
68 | return results;
69 | }
--------------------------------------------------------------------------------
/app/spiffworkflow/messages/propertiesPanel/processLevelProvider/CorrelationKeysArray.js:
--------------------------------------------------------------------------------
1 | import { useService } from 'bpmn-js-properties-panel';
2 | import { SimpleEntry, TextFieldEntry } from '@bpmn-io/properties-panel';
3 | import { createNewCorrelationKey, findCorrelationKeys, getRoot } from '../../MessageHelpers';
4 | import { removeFirstInstanceOfItemFromArrayInPlace } from '../../../helpers';
5 |
6 | /**
7 | * Provides a list of data objects, and allows you to add / remove data objects, and change their ids.
8 | * @param props
9 | * @constructor
10 | */
11 | export function CorrelationKeysArray(props) {
12 | const { element, moddle, commandStack } = props;
13 |
14 | const correlationKeyElements = findCorrelationKeys(element.businessObject);
15 |
16 | const items = correlationKeyElements.map((correlationKeyElement, index) => {
17 | const id = `correlationGroup-${index}`;
18 | return {
19 | id,
20 | label: correlationKeyElement.name,
21 | entries: correlationGroup({
22 | idPrefix: id,
23 | element,
24 | correlationKeyElement,
25 | commandStack,
26 | }),
27 | remove: removeFactory({
28 | element,
29 | correlationKeyElement,
30 | commandStack,
31 | moddle,
32 | }),
33 | autoFocusEntry: id,
34 | };
35 | });
36 |
37 | function add(event) {
38 | event.stopPropagation();
39 | createNewCorrelationKey(element, moddle, commandStack);
40 | }
41 |
42 | return { items, add };
43 | }
44 |
45 | function removeFactory(props) {
46 | const { element, correlationKeyElement, moddle, commandStack } = props;
47 |
48 | return function (event) {
49 | event.stopPropagation();
50 | const currentCorrelationKeyElements =
51 | element.businessObject.get('correlationKeys');
52 | removeFirstInstanceOfItemFromArrayInPlace(
53 | currentCorrelationKeyElements,
54 | correlationKeyElement
55 | );
56 | commandStack.execute('element.updateProperties', {
57 | element,
58 | properties: {},
59 | });
60 | };
61 | }
62 |
63 | function correlationGroup(props) {
64 | const { idPrefix, correlationKeyElement, commandStack } = props;
65 | const entries = [
66 | {
67 | id: `${idPrefix}-key`,
68 | component: CorrelationKeyTextField,
69 | correlationKeyElement,
70 | commandStack,
71 | },
72 | ];
73 | (correlationKeyElement.correlationPropertyRef || []).forEach(
74 | (correlationProperty, index) => {
75 | entries.push({
76 | id: `${idPrefix}-${index}-text`,
77 | component: CorrelationPropertyText,
78 | correlationProperty,
79 | });
80 | }
81 | );
82 | return entries;
83 | }
84 |
85 | function CorrelationKeyTextField(props) {
86 | const { id, element, correlationKeyElement, commandStack } = props;
87 |
88 | const debounce = useService('debounceInput');
89 | const setValue = (value) => {
90 | commandStack.execute('element.updateModdleProperties', {
91 | element,
92 | moddleElement: correlationKeyElement,
93 | properties: {
94 | name: value,
95 | },
96 | });
97 | };
98 |
99 | const getValue = () => {
100 | return correlationKeyElement.name;
101 | };
102 |
103 | return TextFieldEntry({
104 | element,
105 | id: `${id}-textField`,
106 | getValue,
107 | setValue,
108 | debounce,
109 | });
110 | }
111 |
112 | function CorrelationPropertyText(props) {
113 | const { id, parameter, correlationProperty } = props;
114 | const debounce = useService('debounceInput');
115 |
116 | const getValue = () => {
117 | return correlationProperty.id;
118 | };
119 |
120 | return SimpleEntry({
121 | element: parameter,
122 | id: `${id}-textField`,
123 | label: correlationProperty.id,
124 | getValue,
125 | disabled: true,
126 | debounce,
127 | });
128 | }
129 |
--------------------------------------------------------------------------------
/app/spiffworkflow/messages/propertiesPanel/processLevelProvider/MessagePropertiesMultiSelect.js:
--------------------------------------------------------------------------------
1 | import { html } from 'htm/preact';
2 | import { useService } from 'bpmn-js-properties-panel';
3 | import { getCorrelationPropertiesIDsFiltredByMessageRef, setMessageRefToListofCorrelationProperties } from '../../MessageHelpers';
4 |
5 | import NiceSelect from 'nice-select2/dist/js/nice-select2';
6 |
7 | let niceSelectInputs = {};
8 |
9 | export function MessagePropertiesMultiSelect(props) {
10 |
11 | const { element, id, moddle, messageElement, commandStack } = props;
12 |
13 | const modeling = useService('modeling');
14 | const translate = useService('translate');
15 | const debounce = useService('debounceInput');
16 |
17 | const setValue = (value) => {
18 | // Add message ref to the selected correlation properties
19 | setMessageRefToListofCorrelationProperties(messageElement, value, element, moddle, commandStack)
20 | };
21 |
22 | const getOptions = () => {
23 | const correlationProperties = getCorrelationPropertiesIDsFiltredByMessageRef(element.businessObject, moddle, messageElement);
24 | return correlationProperties;
25 | };
26 |
27 | function handleSelectChange(e) {
28 | const selectedOptions = Array.from(e.target.selectedOptions).map(option => option.value);
29 | setValue(selectedOptions);
30 | }
31 |
32 | const initializeNiceSelect = () => {
33 | const selectElement = document.getElementById(`${id}_select`);
34 | if (!selectElement) return;
35 |
36 | if (niceSelectInputs[id] && niceSelectInputs[id] !== null ) {
37 | niceSelectInputs[id].destroy();
38 | }
39 |
40 | const opts = {
41 | // data: options,
42 | searchable: true,
43 | placeholder: 'Select message properties',
44 | showSelectedItems: true
45 | };
46 |
47 | niceSelectInputs[id] = new NiceSelect(selectElement, opts);
48 | updateSelectOptions(selectElement)
49 |
50 |
51 | };
52 |
53 | function updateSelectOptions(selectElement) {
54 | for (let option of selectElement.options) {
55 | const matchingOption = options.find(opt => opt.value === option.value);
56 | if (matchingOption && matchingOption.selected) {
57 | option.setAttribute('selected', 'true');
58 | }
59 | }
60 | }
61 |
62 | const renderSelect = (options) => {
63 | return html`
64 |
71 | `;
72 | }
73 |
74 | const options = getOptions();
75 |
76 | setTimeout(() => {
77 | initializeNiceSelect();
78 | }, 0);
79 |
80 | return html`
81 |
82 |
83 | ${renderSelect(options)}
84 |
85 | `;
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/app/spiffworkflow/signals/index.js:
--------------------------------------------------------------------------------
1 | import SignalPropertiesProvider from './propertiesPanel/SignalPropertiesProvider';
2 |
3 | export default {
4 | __init__: ['signalPropertiesProvider'],
5 | signalPropertiesProvider: ['type', SignalPropertiesProvider],
6 | }
7 |
--------------------------------------------------------------------------------
/app/spiffworkflow/signals/propertiesPanel/SignalPropertiesProvider.js:
--------------------------------------------------------------------------------
1 | import { is } from 'bpmn-js/lib/util/ModelUtil';
2 | import { getRoot } from '../../helpers';
3 | import { getArrayForType, getListGroupForType } from '../../eventList.js';
4 | import { hasEventType,
5 | replaceGroup,
6 | getSelectorForType,
7 | getConfigureGroupForType
8 | } from '../../eventSelect.js';
9 |
10 | const LOW_PRIORITY = 500;
11 |
12 | const eventDetails = {
13 | 'eventType': 'bpmn:Signal',
14 | 'eventDefType': 'bpmn:SignalEventDefinition',
15 | 'referenceType': 'signalRef',
16 | 'idPrefix': 'signal',
17 | };
18 |
19 | export default function SignalPropertiesProvider(
20 | propertiesPanel,
21 | translate,
22 | moddle,
23 | commandStack,
24 | ) {
25 |
26 | this.getGroups = function (element) {
27 | return function (groups) {
28 | if (is(element, 'bpmn:Process') || is(element, 'bpmn:Collaboration')) {
29 | const getSignalArray = getArrayForType('bpmn:Signal', 'signalRef', 'Signal');
30 | const signalGroup = getListGroupForType('signals', 'Signals', getSignalArray);
31 | groups.push(signalGroup({ element, translate, moddle, commandStack }));
32 | } else if (hasEventType(element, 'bpmn:SignalEventDefinition')) {
33 | const getSignalSelector = getSelectorForType(eventDetails);
34 | const signalGroup = getConfigureGroupForType(eventDetails, 'Signal', false, getSignalSelector);
35 | const group = signalGroup({ element, translate, moddle, commandStack });
36 | replaceGroup('signal', groups, group);
37 | }
38 | return groups;
39 | };
40 | };
41 | propertiesPanel.registerProvider(LOW_PRIORITY, this);
42 | }
43 |
44 | SignalPropertiesProvider.$inject = [
45 | 'propertiesPanel',
46 | 'translate',
47 | 'moddle',
48 | 'commandStack',
49 | ];
50 |
--------------------------------------------------------------------------------
/docs/io.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sartography/bpmn-js-spiffworkflow/6208169162a5a7375a783f189d19c3e896b75e6d/docs/io.png
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | const coverage = process.env.COVERAGE;
2 | const path = require('path');
3 | const { DefinePlugin, NormalModuleReplacementPlugin } = require('webpack');
4 |
5 | const basePath = '.';
6 | const absoluteBasePath = path.resolve(path.join(__dirname, basePath));
7 |
8 | module.exports = function (karma) {
9 | karma.set({
10 | frameworks: ['webpack', 'mocha', 'sinon-chai'],
11 |
12 | files: ['test/spec/**/*Spec.js'],
13 |
14 | reporters: ['dots'],
15 |
16 | preprocessors: {
17 | 'test/spec/**/*Spec.js': ['webpack', 'env'],
18 | },
19 |
20 | browsers: ['ChromeHeadless'],
21 |
22 | browserNoActivityTimeout: 30000,
23 |
24 | singleRun: true,
25 | autoWatch: false,
26 |
27 | webpack: {
28 | mode: 'development',
29 | module: {
30 | rules: [
31 | {
32 | test: /\.(css|bpmn)$/,
33 | use: 'raw-loader',
34 | },
35 | {
36 | test: /\.m?jsx?$/,
37 | exclude: /node_modules/,
38 | use: {
39 | loader: 'babel-loader',
40 | options: {
41 | plugins: [
42 | [
43 | '@babel/plugin-transform-react-jsx',
44 | {
45 | importSource: '@bpmn-io/properties-panel/preact',
46 | runtime: 'automatic',
47 | },
48 | ],
49 | ],
50 | },
51 | },
52 | },
53 | {
54 | test: /\.svg$/,
55 | use: ['react-svg-loader'],
56 | },
57 | ].concat(
58 | coverage
59 | ? {
60 | test: /\.js$/,
61 | use: {
62 | loader: 'istanbul-instrumenter-loader',
63 | options: { esModules: true },
64 | },
65 | enforce: 'post',
66 | include: /src\.*/,
67 | exclude: /node_modules/,
68 | }
69 | : []
70 | ),
71 | },
72 | plugins: [
73 | new DefinePlugin({
74 | // @barmac: process.env has to be defined to make @testing-library/preact work
75 | 'process.env': {},
76 | }),
77 | new NormalModuleReplacementPlugin(/^preact(\/[^/]+)?$/, function (
78 | resource
79 | ) {
80 | const replMap = {
81 | 'preact/hooks': path.resolve(
82 | 'node_modules/@bpmn-io/properties-panel/preact/hooks/dist/hooks.module.js'
83 | ),
84 | 'preact/jsx-runtime': path.resolve(
85 | 'node_modules/@bpmn-io/properties-panel/preact/jsx-runtime/dist/jsxRuntime.module.js'
86 | ),
87 | preact: path.resolve(
88 | 'node_modules/@bpmn-io/properties-panel/preact/dist/preact.module.js'
89 | ),
90 | };
91 |
92 | const replacement = replMap[resource.request];
93 |
94 | if (!replacement) {
95 | return;
96 | }
97 |
98 | resource.request = replacement;
99 | }),
100 | new NormalModuleReplacementPlugin(
101 | /^preact\/hooks/,
102 | path.resolve(
103 | 'node_modules/@bpmn-io/properties-panel/preact/hooks/dist/hooks.module.js'
104 | )
105 | ),
106 | ],
107 | resolve: {
108 | extensions: ['', '.js', '.jsx', '.tsx'],
109 | mainFields: ['browser', 'module', 'main'],
110 | alias: {
111 | preact: '@bpmn-io/properties-panel/preact',
112 | react: '@bpmn-io/properties-panel/preact/compat',
113 | 'react-dom': '@bpmn-io/properties-panel/preact/compat',
114 | },
115 | modules: ['node_modules', absoluteBasePath],
116 | },
117 | devtool: 'eval-source-map',
118 | },
119 | });
120 | };
121 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bpmn-js-spiffworkflow",
3 | "version": "0.0.8",
4 | "description": "Extensions and modifications of BPMN.js to improve BPMN development for SpiffWorkflow",
5 | "scripts": {
6 | "all": "run-s lint test build",
7 | "build": "webpack --mode production",
8 | "build:watch": "webpack --watch",
9 | "dev": "run-p build:watch serve",
10 | "serve": "sirv public --dev",
11 | "lint": "./node_modules/.bin/eslint app *.js",
12 | "lint:fix": "./node_modules/.bin/eslint --fix app *.js",
13 | "start": "run-s build serve",
14 | "test": "karma start karma.conf.js"
15 | },
16 | "repository": {
17 | "type": "git",
18 | "url": "https://github.com/sartography/bpmn-js-spiffworkflow"
19 | },
20 | "keywords": [
21 | "bpmn",
22 | "spiffworkflow"
23 | ],
24 | "author": {
25 | "name": "Dan Funk (Sartography)",
26 | "url": "https://github.com/danfunk"
27 | },
28 | "contributors": [
29 | {
30 | "name": "bpmn.io contributors",
31 | "url": "https://github.com/bpmn-io"
32 | }
33 | ],
34 | "license": "LGPL",
35 | "devDependencies": {
36 | "@babel/core": "^7.18.6",
37 | "@babel/plugin-transform-react-jsx": "^7.17.12",
38 | "@babel/preset-env": "^7.18.6",
39 | "@babel/preset-react": "^7.18.2",
40 | "@testing-library/preact": "^2.0.1",
41 | "@testing-library/preact-hooks": "^1.1.0",
42 | "@types/mocha": "^9.1.1",
43 | "babel-loader": "^8.2.5",
44 | "chai": "^4.3.6",
45 | "copy-webpack-plugin": "^11.0.0",
46 | "eslint": "^8.18.0",
47 | "eslint_d": "^12.2.0",
48 | "eslint-config-airbnb": "^19.0.4",
49 | "eslint-config-prettier": "^8.5.0",
50 | "eslint-plugin-bpmn-io": "^0.14.0",
51 | "eslint-plugin-import": "^2.26.0",
52 | "eslint-plugin-jsx-a11y": "^6.6.0",
53 | "eslint-plugin-prettier": "^4.2.1",
54 | "eslint-plugin-sonarjs": "^0.13.0",
55 | "file-saver": "^2.0.5",
56 | "karma": "^6.3.4",
57 | "karma-chrome-launcher": "^3.1.1",
58 | "karma-coverage": "^2.2.0",
59 | "karma-env-preprocessor": "^0.1.1",
60 | "karma-mocha": "^2.0.1",
61 | "karma-sinon-chai": "^2.0.2",
62 | "karma-webpack": "^5.0.0",
63 | "mocha": "^10.0.0",
64 | "mocha-test-container-support": "^0.2.0",
65 | "npm-run-all": "^4.1.5",
66 | "prettier": "^2.7.1",
67 | "raw-loader": "^4.0.2",
68 | "sinon": "^14.0.0",
69 | "sinon-chai": "^3.7.0",
70 | "sirv-cli": "^2.0.2",
71 | "stringify": "^5.2.0",
72 | "webpack": "^5.73.0",
73 | "webpack-cli": "^4.9.2"
74 | },
75 | "peerDependencies": {
76 | "bpmn-js": "^17.0.0",
77 | "bpmn-js-properties-panel": "*",
78 | "diagram-js": "*"
79 | },
80 | "dependencies": {
81 | "inherits": "^2.0.4",
82 | "inherits-browser": "^0.0.1",
83 | "min-dash": "^3.8.1",
84 | "min-dom": "^3.2.1",
85 | "moddle": "^5.0.3",
86 | "nice-select2": "^2.1.0",
87 | "react": "^18.2.0",
88 | "react-dom": "18.2.0",
89 | "tiny-svg": "^2.2.3"
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/test/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "plugin:bpmn-io/mocha"
3 | }
--------------------------------------------------------------------------------
/test/spec/BpmnInputOutputSpec.js:
--------------------------------------------------------------------------------
1 | import {
2 | query as domQuery,
3 | queryAll as domQueryAll
4 | } from 'min-dom';
5 | import { bootstrapPropertiesPanel, CONTAINER } from './helpers';
6 | import inputOutput from '../../app/spiffworkflow/InputOutput';
7 | import { BpmnPropertiesPanelModule, BpmnPropertiesProviderModule } from 'bpmn-js-properties-panel';
8 |
9 | describe('BPMN Input / Output', function() {
10 |
11 | let xml = require('./bpmn/diagram.bpmn').default;
12 |
13 | beforeEach(bootstrapPropertiesPanel(xml, {
14 | debounceInput: false,
15 | additionalModules: [
16 | inputOutput,
17 | BpmnPropertiesPanelModule,
18 | BpmnPropertiesProviderModule,
19 | ]
20 | }));
21 |
22 | it('should have a data input and data output in the properties panel', function() {
23 | var paletteElement = domQuery('.djs-palette', CONTAINER);
24 | var entries = domQueryAll('.entry', paletteElement);
25 | expect(entries[11].title).to.equals('Create DataInput');
26 | expect(entries[12].title).to.equals('Create DataOutput');
27 | });
28 |
29 | });
30 |
--------------------------------------------------------------------------------
/test/spec/BusinessRulePropsSpec.js:
--------------------------------------------------------------------------------
1 | import { getBpmnJS } from 'bpmn-js/test/helper';
2 |
3 | import {
4 | BpmnPropertiesPanelModule,
5 | BpmnPropertiesProviderModule,
6 | } from 'bpmn-js-properties-panel';
7 | import { getBusinessObject } from 'bpmn-js/lib/util/ModelUtil';
8 | import spiffModdleExtension from '../../app/spiffworkflow/moddle/spiffworkflow.json';
9 | import {
10 | bootstrapPropertiesPanel,
11 | changeInput,
12 | expectSelected,
13 | findEntry,
14 | findSelect,
15 | getPropertiesPanel,
16 | } from './helpers';
17 | import extensions from '../../app/spiffworkflow/extensions';
18 |
19 | describe('Business Rule Properties Panel', function () {
20 | const xml = require('./bpmn/diagram.bpmn').default;
21 |
22 | beforeEach(
23 | bootstrapPropertiesPanel(xml, {
24 | debounceInput: false,
25 | additionalModules: [
26 | BpmnPropertiesPanelModule,
27 | BpmnPropertiesProviderModule,
28 | extensions,
29 | ],
30 | moddleExtensions: {
31 | spiffworkflow: spiffModdleExtension,
32 | },
33 | })
34 | );
35 |
36 | const return_files = (event) => {
37 | event.eventBus.fire('spiff.dmn_files.returned', {
38 | options: [
39 | { label: 'Calculate Pizza Price', value: 'Decision_Pizza_Price' },
40 | { label: 'Viking Availability', value: 'Decision_Vikings' },
41 | { label: 'Test Decision', value: 'test_decision' },
42 | ],
43 | });
44 | };
45 |
46 | it('should display a dropdown to select from available decision tables', async function () {
47 | const modeler = getBpmnJS();
48 | modeler.get('eventBus').once('spiff.dmn_files.requested', return_files);
49 | expectSelected('business_rule_task');
50 | // THEN - a properties panel exists with a section for editing that script
51 | const entry = findEntry(
52 | 'extension_spiffworkflow:CalledDecisionId',
53 | getPropertiesPanel()
54 | );
55 | expect(entry, 'No Entry').to.exist;
56 | const selectList = findSelect(entry);
57 | expect(selectList, 'No Select').to.exist;
58 | modeler.get('eventBus').off('spiff.dmn_files.requested', return_files);
59 | });
60 |
61 | it('should update the spiffworkflow:calledDecisionId tag when you modify the called decision select box', async function () {
62 | // IF - a script tag is selected, and you change the script in the properties panel
63 | const modeler = getBpmnJS();
64 | modeler.get('eventBus').on('spiff.dmn_files.requested', return_files);
65 | const businessRuleTask = await expectSelected('business_rule_task');
66 | const entry = findEntry('extension_CalledDecisionId', getPropertiesPanel());
67 | const selectList = findSelect(entry);
68 | changeInput(selectList, 'Decision_Pizza_Price');
69 |
70 | // THEN - the decision id in the BPMN Business object / XML is updated as well.
71 | const businessObject = getBusinessObject(businessRuleTask);
72 | expect(businessObject.extensionElements).to.exist;
73 | const element = businessObject.extensionElements.values[0];
74 | expect(element.value).to.equal('Decision_Pizza_Price');
75 | modeler.get('eventBus').off('spiff.dmn_files.requested', return_files);
76 | });
77 |
78 | it('should load up the xml and the value for the called decision should match the xml', async function () {
79 | const modeler = getBpmnJS();
80 | modeler.get('eventBus').on('spiff.dmn_files.requested', return_files);
81 | const businessRuleTask = await expectSelected('business_rule_task');
82 | const entry = findEntry('extension_CalledDecisionId', getPropertiesPanel());
83 | const selectList = findSelect(entry);
84 | expect(selectList.value, 'initial value is wrong').to.equal(
85 | 'test_decision'
86 | );
87 |
88 | const businessObject = getBusinessObject(businessRuleTask);
89 | expect(businessObject.extensionElements).to.exist;
90 | const element = businessObject.extensionElements.values[0];
91 | expect(element.value).to.equal('test_decision');
92 | modeler.get('eventBus').off('spiff.dmn_files.requested', return_files);
93 | });
94 | });
95 |
--------------------------------------------------------------------------------
/test/spec/ConditionSpec.js:
--------------------------------------------------------------------------------
1 | import {
2 | query as domQuery,
3 | queryAll as domQueryAll
4 | } from 'min-dom';
5 | import {
6 | bootstrapPropertiesPanel,
7 | expectSelected,
8 | findGroupEntry,
9 | changeInput,
10 | PROPERTIES_PANEL_CONTAINER,
11 | } from './helpers';
12 | import conditionsPanel from '../../app/spiffworkflow/conditions';
13 | import { BpmnPropertiesPanelModule, BpmnPropertiesProviderModule } from 'bpmn-js-properties-panel';
14 | import { getBusinessObject } from 'bpmn-js/lib/util/ModelUtil';
15 |
16 | describe('BPMN Condition', function() {
17 |
18 | let xml = require('./bpmn/conditional_event.bpmn').default;
19 |
20 | beforeEach(bootstrapPropertiesPanel(xml, {
21 | debounceInput: false,
22 | additionalModules: [
23 | conditionsPanel,
24 | BpmnPropertiesPanelModule,
25 | BpmnPropertiesProviderModule,
26 | ]
27 | }));
28 |
29 | it('should add a condition panel when Conditional Event is selected', async function() {
30 | const shapeElement = await expectSelected('conditional_event');
31 | const businessObject = getBusinessObject(shapeElement);
32 | const conditions = findGroupEntry('conditions', PROPERTIES_PANEL_CONTAINER);
33 | expect(conditions).to.exist;
34 |
35 | const textInput = domQuery('textarea', conditions);
36 | expect(textInput.value).to.equal('cancel_task_2');
37 | changeInput(textInput, 'True');
38 | });
39 | });
40 |
41 |
--------------------------------------------------------------------------------
/test/spec/DataObjectInPoolsSpec.js:
--------------------------------------------------------------------------------
1 | import {
2 | BpmnPropertiesPanelModule,
3 | BpmnPropertiesProviderModule,
4 | } from 'bpmn-js-properties-panel';
5 | import TestContainer from 'mocha-test-container-support';
6 | import {
7 | bootstrapPropertiesPanel,
8 | changeInput,
9 | expectSelected,
10 | findEntry,
11 | findSelect,
12 | } from './helpers';
13 | import spiffModdleExtension from '../../app/spiffworkflow/moddle/spiffworkflow.json';
14 | import DataObject from '../../app/spiffworkflow/DataObject';
15 |
16 | describe('Properties Panel for Data Objects', function () {
17 | const xml = require('./bpmn/data_objects_in_pools.bpmn').default;
18 | let container;
19 |
20 | beforeEach(function () {
21 | container = TestContainer.get(this);
22 | });
23 |
24 | beforeEach(
25 | bootstrapPropertiesPanel(xml, {
26 | container,
27 | debounceInput: false,
28 | additionalModules: [
29 | DataObject,
30 | BpmnPropertiesPanelModule,
31 | BpmnPropertiesProviderModule,
32 | ],
33 | moddleExtensions: {
34 | spiffworkflow: spiffModdleExtension,
35 | },
36 | })
37 | );
38 |
39 | it('should allow you to select other data objects within the same participant', async function () {
40 | // IF - a data object reference is selected
41 | const doREF = await expectSelected('pool1Do1_REF');
42 | expect(doREF).to.exist;
43 |
44 | // THEN - a select Data Object section should appear in the properties panel
45 | const entry = findEntry('selectDataObject', container);
46 | const selector = findSelect(entry);
47 | changeInput(selector, 'pool1Do2');
48 | // then this data reference object now references that data object.
49 | const { businessObject } = doREF;
50 | expect(businessObject.get('dataObjectRef').id).to.equal('pool1Do2');
51 | });
52 |
53 | it('should NOT allow you to select data objects within other participants', async function () {
54 | // IF - a data object reference is selected
55 | const doREF = await expectSelected('pool1Do1_REF');
56 | expect(doREF).to.exist;
57 |
58 | // THEN - a select Data Object section should appear in the properties panel but pool2Do1 should not be an option
59 | const entry = findEntry('selectDataObject', container);
60 | const selector = findSelect(entry);
61 | expect(selector.length).to.equal(2);
62 | expect(selector[0].value === 'pool1Do2');
63 | expect(selector[1].value === 'pool1Do1');
64 | });
65 |
66 | });
67 |
--------------------------------------------------------------------------------
/test/spec/DataObjectRulesSpec.js:
--------------------------------------------------------------------------------
1 | import { bootstrapPropertiesPanel } from './helpers';
2 | import { BpmnPropertiesPanelModule, BpmnPropertiesProviderModule } from 'bpmn-js-properties-panel';
3 | import dataObject from '../../app/spiffworkflow/DataObject';
4 | import { inject } from 'bpmn-js/test/helper';
5 |
6 | describe('BPMN Input / Output', function() {
7 |
8 | let xml = require('./bpmn/subprocess.bpmn').default;
9 |
10 | beforeEach(bootstrapPropertiesPanel(xml, {
11 | debounceInput: false,
12 | additionalModules: [
13 | dataObject,
14 | BpmnPropertiesPanelModule,
15 | BpmnPropertiesProviderModule,
16 | ]
17 | }));
18 |
19 |
20 | it('will prevent dragging data reference to a different process',
21 | inject(function(canvas, modeling, elementRegistry, dataObjectRules) {
22 |
23 | // IF - a data object reference exists on the root element, and a SubProcess Also Exists
24 | let rootShape = canvas.getRootElement();
25 | const dataObjectRefShape = modeling.createShape({ type: 'bpmn:DataObjectReference' },
26 | { x: 220, y: 220 }, rootShape);
27 | const subProcessElement = elementRegistry.get('my_subprocess');
28 |
29 | var canDrop = dataObjectRules.canDrop(
30 | [ dataObjectRefShape ],
31 | subProcessElement
32 | );
33 |
34 | expect(canDrop).to.be.false;
35 |
36 | }));
37 | });
38 |
--------------------------------------------------------------------------------
/test/spec/DataStoreInterceptorSpec.js:
--------------------------------------------------------------------------------
1 | import { bootstrapPropertiesPanel, expectSelected } from './helpers';
2 | import { BpmnPropertiesPanelModule, BpmnPropertiesProviderModule } from 'bpmn-js-properties-panel';
3 | import {
4 | getBpmnJS,
5 | inject
6 | } from 'bpmn-js/test/helper';
7 | import dataStoreInterceptor from '../../app/spiffworkflow/DataStoreReference';
8 |
9 | describe('DataStore Interceptor', function () {
10 |
11 | let xml = require('./bpmn/data_store.bpmn').default;
12 |
13 | beforeEach(bootstrapPropertiesPanel(xml, {
14 | debounceInput: false,
15 | additionalModules: [
16 | dataStoreInterceptor,
17 | BpmnPropertiesPanelModule,
18 | BpmnPropertiesProviderModule,
19 | ]
20 | }));
21 |
22 |
23 | it('should delete dataStore in case dataStoreRef is deleted - DataStoreReference element', inject(async function (modeling) {
24 |
25 | const modeler = getBpmnJS();
26 |
27 | // We Select a DataStoreReference element
28 | const shapeElement = await expectSelected('DataStoreReference_0eqeh4p');
29 | expect(shapeElement, "I can't find DataStoreReference element").to.exist;
30 |
31 | let definitions = await modeler.getDefinitions();
32 | let dataStoreExists = definitions.get('rootElements').some(element =>
33 | element.$type === 'bpmn:DataStore' && element.id === 'countries'
34 | );
35 | expect(dataStoreExists, "DataStore 'countries' should be added at the root level").to.be.true;
36 |
37 | // Remove dataStoreReference
38 | await modeler.get('modeling').removeShape(shapeElement);
39 | const nwshapeElement = await expectSelected('DataStoreReference_0eqeh4p');
40 | expect(nwshapeElement, "I can't find DataStoreReference element").not.to.exist;
41 |
42 | // Check that DataStore foods is removed from the root of the process
43 | definitions = await modeler.getDefinitions();
44 | dataStoreExists = definitions.get('rootElements').some(element =>
45 | element.$type === 'bpmn:DataStore' && element.id === 'countries'
46 | );
47 | expect(dataStoreExists, "DataStore 'countries' should be removed from the root level").not.to.be.true;
48 | }));
49 |
50 | });
51 |
--------------------------------------------------------------------------------
/test/spec/IOInterceptorSpec.js:
--------------------------------------------------------------------------------
1 | import { bootstrapPropertiesPanel } from './helpers';
2 | import dataObjectInterceptor from '../../app/spiffworkflow/DataObject';
3 | import { BpmnPropertiesPanelModule, BpmnPropertiesProviderModule } from 'bpmn-js-properties-panel';
4 | import {
5 | inject,
6 | } from 'bpmn-js/test/helper';
7 | import { findDataObjects } from '../../app/spiffworkflow/DataObject/DataObjectHelpers';
8 | import IoInterceptor from '../../app/spiffworkflow/InputOutput/IoInterceptor';
9 | import InputOutput from '../../app/spiffworkflow/InputOutput';
10 |
11 | describe('Input/Output Interceptor', function() {
12 |
13 | let xml = require('./bpmn/empty_diagram.bpmn').default;
14 |
15 | beforeEach(bootstrapPropertiesPanel(xml, {
16 | debounceInput: false,
17 | additionalModules: [
18 | InputOutput,
19 | BpmnPropertiesPanelModule,
20 | BpmnPropertiesProviderModule,
21 | ]
22 | }));
23 |
24 | it('New Data Input should create an IOSpecification with a single dataInput object', inject(function(canvas, modeling) {
25 |
26 | expect(canvas.getRootElement().businessObject.ioSpecification).to.be.undefined;
27 |
28 | // IF - a new dataObjectReference is created
29 | let rootShape = canvas.getRootElement();
30 | const dataInput = modeling.createShape({ type: 'bpmn:DataInput' },
31 | { x: 220, y: 220 }, rootShape);
32 |
33 | // THEN - the process should now have an IO Specification
34 | const iospec = canvas.getRootElement().businessObject.ioSpecification;
35 | expect(iospec).to.not.be.null;
36 | expect(iospec.dataInputs.length).to.equal(1);
37 | expect(iospec.inputSets[0].dataInputRefs.length).to.equal(1);
38 | }));
39 |
40 |
41 | it('IOSpecification always contain input sets and output sets if they exist at all.', inject(function(canvas, modeling) {
42 |
43 | expect(canvas.getRootElement().businessObject.ioSpecification).to.be.undefined;
44 |
45 | // IF - a new dataObjectReference is created
46 | let rootShape = canvas.getRootElement();
47 | const dataInput = modeling.createShape({type: 'bpmn:DataInput'},
48 | {x: 220, y: 220}, rootShape);
49 |
50 | // THEN - there are inputSets and outputSets
51 | const iospec = canvas.getRootElement().businessObject.ioSpecification;
52 | expect(iospec.inputSets).to.not.be.null;
53 | expect(iospec.outputSets).to.not.be.null;
54 | }));
55 |
56 | it('After removing all input sets, the ioSpecification should be null.', inject(function(canvas, modeling) {
57 | // IF - a new dataObjectReference is created and then deleted.
58 | let rootShape = canvas.getRootElement();
59 | const dataInput = modeling.createShape({type: 'bpmn:DataInput'},
60 | {x: 220, y: 220}, rootShape);
61 | modeling.removeShape(dataInput)
62 | expect(canvas.getRootElement().businessObject.ioSpecification).to.be.null;
63 | }));
64 |
65 | it('deleting a data input should remove it from the input set', inject(function(canvas, modeling) {
66 | let rootShape = canvas.getRootElement();
67 | const dataInput = modeling.createShape({type: 'bpmn:DataInput'},
68 | {x: 220, y: 220}, rootShape);
69 | const dataOutput = modeling.createShape({type: 'bpmn:DataOutput'},
70 | {x: 240, y: 220}, rootShape);
71 | modeling.removeShape(dataInput);
72 | const iospec = canvas.getRootElement().businessObject.ioSpecification;
73 | expect(iospec.dataInputs.length).to.equal(0);
74 | expect(iospec.inputSets[0].dataInputRefs.length).to.equal(0);
75 | }));
76 | });
77 |
--------------------------------------------------------------------------------
/test/spec/IoVariablesSpec.js:
--------------------------------------------------------------------------------
1 | import {
2 | query as domQuery
3 | } from 'min-dom';
4 | import { bootstrapPropertiesPanel, CONTAINER, expectSelected, findGroupEntry } from './helpers';
5 | import inputOutput from '../../app/spiffworkflow/InputOutput';
6 | import { BpmnPropertiesPanelModule, BpmnPropertiesProviderModule } from 'bpmn-js-properties-panel';
7 | import { fireEvent } from '@testing-library/preact';
8 |
9 | describe('BPMN Input / Output Variables', function () {
10 |
11 | let xml = require('./bpmn/io_variables.bpmn').default;
12 |
13 | beforeEach(bootstrapPropertiesPanel(xml, {
14 | debounceInput: false,
15 | additionalModules: [
16 | inputOutput,
17 | BpmnPropertiesPanelModule,
18 | BpmnPropertiesProviderModule
19 | ]
20 | }));
21 |
22 | it('should be able to add a new input to the user task', async function () {
23 |
24 | // We Select a userTask element
25 | const shapeElement = await expectSelected('Activity_1hmit5k');
26 | expect(shapeElement, "I can't find User Task element").to.exist;
27 |
28 | // Expect shapeElement.businessObject.ioSpecification to be undefined
29 | expect(shapeElement.businessObject.ioSpecification).to.be.undefined;
30 |
31 | // Add new dataInput
32 | const entry = findGroupEntry('inputParameters', CONTAINER);
33 | let addButton = domQuery('.bio-properties-panel-add-entry', entry);
34 | fireEvent.click(addButton);
35 |
36 | expect(shapeElement.businessObject.ioSpecification).not.to.be.undefined;
37 | expect(shapeElement.businessObject.ioSpecification.dataInputs.length).to.equal(1);
38 | });
39 |
40 | it('should be able to add a new output to the user task', async function () {
41 |
42 | // We Select a userTask element
43 | const shapeElement = await expectSelected('Activity_1hmit5k');
44 | expect(shapeElement, "I can't find User Task element").to.exist;
45 |
46 | // Expect shapeElement.businessObject.ioSpecification to be undefined
47 | expect(shapeElement.businessObject.ioSpecification).to.be.undefined;
48 |
49 | // Add new dataOutput
50 | const entry = findGroupEntry('outputParameters', CONTAINER);
51 | let addButton = domQuery('.bio-properties-panel-add-entry', entry);
52 | fireEvent.click(addButton);
53 |
54 | expect(shapeElement.businessObject.ioSpecification).not.to.be.undefined;
55 | expect(shapeElement.businessObject.ioSpecification.dataOutputs.length).to.equal(1);
56 |
57 | });
58 |
59 | it('should be able to delete an existing input variable from script task', async function () {
60 |
61 | // We Select a scriptTask element
62 | const shapeElement = await expectSelected('Activity_1dkj93x');
63 | expect(shapeElement, "I can't find Script Task element").to.exist;
64 | expect(shapeElement.businessObject.ioSpecification.dataInputs.length).to.equal(1);
65 |
66 | const entry = findGroupEntry('inputParameters', CONTAINER);
67 | let removeButton = domQuery('.bio-properties-panel-remove-entry', entry);
68 | fireEvent.click(removeButton);
69 |
70 | expect(shapeElement.businessObject.ioSpecification.dataInputs.length).to.equal(0);
71 | expect(shapeElement.businessObject.ioSpecification.dataOutputs.length).to.equal(1);
72 | })
73 |
74 |
75 | it('should be able to delete an existing output variable from script task', async function () {
76 |
77 | // We Select a scriptTask element
78 | const shapeElement = await expectSelected('Activity_1dkj93x');
79 | expect(shapeElement, "I can't find Script Task element").to.exist;
80 | expect(shapeElement.businessObject.ioSpecification.dataInputs.length).to.equal(1);
81 |
82 | const entry = findGroupEntry('outputParameters', CONTAINER);
83 | let removeButton = domQuery('.bio-properties-panel-remove-entry', entry);
84 | fireEvent.click(removeButton);
85 |
86 | expect(shapeElement.businessObject.ioSpecification.dataInputs.length).to.equal(1);
87 | expect(shapeElement.businessObject.ioSpecification.dataOutputs.length).to.equal(0);
88 | })
89 |
90 |
91 | });
92 |
--------------------------------------------------------------------------------
/test/spec/MessagesCorrSpec.js:
--------------------------------------------------------------------------------
1 | import TestContainer from 'mocha-test-container-support';
2 | import {
3 | query as domQuery,
4 | queryAll as domQueryAll,
5 | } from 'min-dom';
6 | import {
7 | BpmnPropertiesPanelModule,
8 | BpmnPropertiesProviderModule,
9 | } from 'bpmn-js-properties-panel';
10 | import {
11 | bootstrapPropertiesPanel,
12 | expectSelected,
13 | findEntry,
14 | findGroupEntry,
15 | findSelect,
16 | findTextarea,
17 | findInput,
18 | pressButton,
19 | findButtonByClass,
20 | getPropertiesPanel,
21 | changeInput,
22 | findDivByClass
23 | } from './helpers';
24 | import spiffModdleExtension from '../../app/spiffworkflow/moddle/spiffworkflow.json';
25 | import messages from '../../app/spiffworkflow/messages';
26 | import { fireEvent } from '@testing-library/preact';
27 | import { getBpmnJS, inject } from 'bpmn-js/test/helper';
28 | import { findCorrelationProperties, findMessageModdleElements } from '../../app/spiffworkflow/messages/MessageHelpers';
29 | import {spiffExtensionOptions} from "../../app/spiffworkflow/extensions/propertiesPanel/SpiffExtensionSelect";
30 |
31 | describe('Multiple messages should work', function () {
32 | const xml = require('./bpmn/two_messages.bpmn').default;
33 | let container;
34 |
35 | beforeEach(function () {
36 | container = TestContainer.get(this);
37 | });
38 |
39 | beforeEach(
40 | bootstrapPropertiesPanel(xml, {
41 | container,
42 | debounceInput: false,
43 | additionalModules: [
44 | messages,
45 | BpmnPropertiesPanelModule,
46 | BpmnPropertiesProviderModule,
47 | ],
48 | moddleExtensions: {
49 | spiffworkflow: spiffModdleExtension,
50 | },
51 | })
52 | );
53 |
54 |
55 | const new_message_event = (eventBus) => {
56 | eventBus.fire('spiff.add_message.returned', {
57 | elementId: "ActivityA",
58 | name: "messageA",
59 | correlation_properties:
60 | { "new_name": { retrieval_expression: "new_exp" }}
61 | });
62 | };
63 |
64 |
65 | it('and it should be possible to change a correlation property name', async function () {
66 | const modeler = getBpmnJS();
67 |
68 | const sendShape = await expectSelected('ActivityA');
69 | expect(sendShape, "Can't find Send Task").to.exist;
70 |
71 | const oldName = "old_name"
72 | const newName = "new_name"
73 |
74 | const labels = domQueryAll(`.bio-properties-panel-label`, container);
75 | const oldNameFound = Array.from(labels).some(label => label.textContent.includes(oldName));
76 | expect(oldNameFound).to.be.true;
77 |
78 | //Update message
79 | new_message_event(modeler.get('eventBus'))
80 | const sendShape2 = await expectSelected('ActivityA');
81 |
82 | // The old name should no longer be there, but the new name should exist.
83 | const labels2 = domQueryAll(`.bio-properties-panel-label`, container);
84 | const oldNameFound2 = Array.from(labels2).some(label => label.textContent.includes(oldName));
85 | expect(oldNameFound2).to.be.false;
86 | const newNameFound2 = Array.from(labels2).some(label => label.textContent.includes(newName));
87 | expect(newNameFound2).to.be.true;
88 |
89 |
90 | });
91 |
92 | });
93 |
--------------------------------------------------------------------------------
/test/spec/ProcessPropsSpec.js:
--------------------------------------------------------------------------------
1 | import {
2 | query as domQuery,
3 | } from 'min-dom';
4 | import {
5 | bootstrapPropertiesPanel, changeInput,
6 | expectSelected, findEntry, findGroupEntry, findInput
7 | } from './helpers';
8 | import { BpmnPropertiesPanelModule, BpmnPropertiesProviderModule } from 'bpmn-js-properties-panel';
9 | import spiffModdleExtension from '../../app/spiffworkflow/moddle/spiffworkflow.json';
10 | import TestContainer from 'mocha-test-container-support';
11 | import { fireEvent } from '@testing-library/preact';
12 | import { findDataObject, findDataObjects } from '../../app/spiffworkflow/DataObject/DataObjectHelpers';
13 | import dataObject from '../../app/spiffworkflow/DataObject';
14 |
15 | describe('Properties Panel for a Process', function() {
16 | let xml = require('./bpmn/diagram.bpmn').default;
17 | let container;
18 |
19 | beforeEach(function() {
20 | container = TestContainer.get(this);
21 | });
22 |
23 | beforeEach(bootstrapPropertiesPanel(xml, {
24 | container,
25 | debounceInput: false,
26 | additionalModules: [
27 | dataObject,
28 | BpmnPropertiesPanelModule,
29 | BpmnPropertiesProviderModule,
30 | ],
31 | moddleExtensions: {
32 | spiffworkflow: spiffModdleExtension
33 | },
34 | }));
35 |
36 | it('should allow you to edit the data objects', async function() {
37 |
38 | // IF - a process is selected
39 | await expectSelected('ProcessTest');
40 |
41 | // THEN - there is a section where you can modify data objects.
42 | let entry = findGroupEntry('editDataObjects', container);
43 | expect(entry).to.exist;
44 | });
45 |
46 | it('should be possible to change a data objects id', async function() {
47 |
48 | // IF - a process is selected and the id of a data object is changed
49 | const process_svg = await expectSelected('ProcessTest');
50 |
51 | let newId = 'a_brand_new_id';
52 |
53 | // ID here is [process id]-dataObj-[data object index]-id
54 | let myDataObjEntry = findEntry('ProcessTest-dataObj-0-id');
55 | let textBox = findInput('text', myDataObjEntry);
56 | changeInput(textBox, newId);
57 |
58 | // THEN - there is a section where you can modify data objects.
59 | let dataObject = findDataObject(process_svg.businessObject, newId);
60 | expect(dataObject.id).to.equal(newId);
61 | });
62 |
63 | it('should be possible to remove a data object', async function() {
64 |
65 | // IF - a process is selected and the delete button is clicked.
66 | const process_svg = await expectSelected('ProcessTest');
67 | const data_id = 'my_data_object';
68 | let dataObject = findDataObject(process_svg.businessObject, data_id);
69 | expect(dataObject).to.exist;
70 | let myDataObjEntry = findEntry('ProcessTest-dataObj-2');
71 | let deleteButton = domQuery('.bio-properties-panel-remove-entry', myDataObjEntry);
72 | fireEvent.click(deleteButton);
73 |
74 | // THEN - there should not be a 'my_data_object' anymore.
75 | dataObject = findDataObject(process_svg.businessObject, data_id);
76 | expect(dataObject).to.not.exist;
77 |
78 | });
79 |
80 | it('should be possible to add a data object', async function() {
81 |
82 | // IF - a process is selected and the add button is clicked.
83 | const process_svg = await expectSelected('ProcessTest');
84 | let entry = findGroupEntry('editDataObjects', container);
85 | let addButton = domQuery('.bio-properties-panel-add-entry', entry);
86 | fireEvent.click(addButton);
87 |
88 | // THEN - there should now be 4 data objects instead of just 3.
89 | expect(findDataObjects(process_svg.businessObject).length).to.equal(4);
90 |
91 | });
92 |
93 |
94 | });
95 |
--------------------------------------------------------------------------------
/test/spec/ScriptTaskUnitTestSpec.js:
--------------------------------------------------------------------------------
1 | import TestContainer from 'mocha-test-container-support';
2 | import {
3 | BpmnPropertiesPanelModule,
4 | BpmnPropertiesProviderModule,
5 | } from 'bpmn-js-properties-panel';
6 | import { query as domQuery } from 'min-dom';
7 | import { getBusinessObject } from 'bpmn-js/lib/util/ModelUtil';
8 | import {
9 | bootstrapPropertiesPanel,
10 | changeInput,
11 | expectSelected,
12 | findGroupEntry,
13 | } from './helpers';
14 | import spiffModdleExtension from '../../app/spiffworkflow/moddle/spiffworkflow.json';
15 | import callActivity from '../../app/spiffworkflow/callActivity';
16 |
17 | describe('Call Activities should work', function () {
18 | const xml = require('./bpmn/call_activity.bpmn').default;
19 | let container;
20 |
21 | beforeEach(function () {
22 | container = TestContainer.get(this);
23 | });
24 |
25 | beforeEach(
26 | bootstrapPropertiesPanel(xml, {
27 | container,
28 | debounceInput: false,
29 | additionalModules: [
30 | callActivity,
31 | BpmnPropertiesPanelModule,
32 | BpmnPropertiesProviderModule,
33 | ],
34 | moddleExtensions: {
35 | spiffworkflow: spiffModdleExtension,
36 | },
37 | })
38 | );
39 |
40 | it('should allow you to view the called element section of a Call Activity', async function () {
41 | const shapeElement = await expectSelected('the_call_activity');
42 | expect(shapeElement, "Can't find Call Activity").to.exist;
43 | const entry = findGroupEntry('called_element', container);
44 | expect(entry).to.exist;
45 | });
46 |
47 | it('should allow you to edit the called element section of a Call Activity', async function () {
48 | const shapeElement = await expectSelected('the_call_activity');
49 | expect(shapeElement, "Can't find Call Activity").to.exist;
50 | const businessObject = getBusinessObject(shapeElement);
51 | expect(businessObject.get('calledElement')).to.equal('ProcessIdTBD1');
52 |
53 | const entry = findGroupEntry('called_element', container);
54 | expect(entry).to.exist;
55 |
56 | const textInput = domQuery('input', entry);
57 | changeInput(textInput, 'newProcessId');
58 | expect(businessObject.get('calledElement')).to.equal('newProcessId');
59 | });
60 | });
61 |
--------------------------------------------------------------------------------
/test/spec/ScriptsPropsSpec.js:
--------------------------------------------------------------------------------
1 | import { query as domQuery } from 'min-dom';
2 |
3 | import {
4 | BpmnPropertiesPanelModule,
5 | BpmnPropertiesProviderModule,
6 | } from 'bpmn-js-properties-panel';
7 | import { getBusinessObject } from 'bpmn-js/lib/util/ModelUtil';
8 | import spiffModdleExtension from '../../app/spiffworkflow/moddle/spiffworkflow.json';
9 | import {
10 | bootstrapPropertiesPanel,
11 | changeInput,
12 | expectSelected,
13 | findEntry,
14 | PROPERTIES_PANEL_CONTAINER,
15 | } from './helpers';
16 | import extensions from '../../app/spiffworkflow/extensions';
17 |
18 | describe('Properties Panel Script Tasks', function () {
19 | const xml = require('./bpmn/diagram.bpmn').default;
20 |
21 | beforeEach(
22 | bootstrapPropertiesPanel(xml, {
23 | debounceInput: false,
24 | additionalModules: [
25 | extensions,
26 | BpmnPropertiesPanelModule,
27 | BpmnPropertiesProviderModule,
28 | ],
29 | moddleExtensions: {
30 | spiffworkflow: spiffModdleExtension,
31 | },
32 | })
33 | );
34 |
35 | it('should display a script editing panel when a script task is selected', async function () {
36 | // IF - you select a script task
37 | expectSelected('my_script_task');
38 |
39 | // THEN - a properties panel exists with a section for editing that script
40 | const entry = findEntry(
41 | 'pythonScript_bpmn:script',
42 | PROPERTIES_PANEL_CONTAINER
43 | );
44 | expect(entry).to.exist;
45 | const scriptInput = domQuery('textarea', entry);
46 | expect(scriptInput).to.exist;
47 | });
48 |
49 | it('should update the bpmn:script tag when you modify the script field', async function () {
50 | // IF - a script tag is selected, and you change the script in the properties panel
51 | const scriptTask = await expectSelected('my_script_task');
52 | const entry = findEntry(
53 | 'pythonScript_bpmn:script',
54 | PROPERTIES_PANEL_CONTAINER
55 | );
56 | const scriptInput = domQuery('textarea', entry);
57 | changeInput(scriptInput, 'x = 100');
58 |
59 | // THEN - the script tag in the BPMN Business object / XML is updated as well.
60 | const businessObject = getBusinessObject(scriptTask);
61 | expect(businessObject.get('script')).to.equal('x = 100');
62 | expect(scriptInput.value).to.equal('x = 100');
63 | });
64 |
65 | it('should parse the spiffworkflow:prescript tag when you open an existing file', async function () {
66 | await expectSelected('task_confirm');
67 | const entry = findEntry(
68 | 'pythonScript_spiffworkflow:PreScript',
69 | PROPERTIES_PANEL_CONTAINER
70 | );
71 | const scriptInput = domQuery('textarea', entry);
72 | expect(scriptInput.value).to.equal('x=1');
73 | });
74 | });
75 |
--------------------------------------------------------------------------------
/test/spec/ServiceTaskPropsSpec.js:
--------------------------------------------------------------------------------
1 | import {
2 | BpmnPropertiesPanelModule,
3 | BpmnPropertiesProviderModule,
4 | } from 'bpmn-js-properties-panel';
5 | import { getBusinessObject } from 'bpmn-js/lib/util/ModelUtil';
6 | import TestContainer from 'mocha-test-container-support';
7 | import { getBpmnJS } from 'bpmn-js/test/helper';
8 | import spiffModdleExtension from '../../app/spiffworkflow/moddle/spiffworkflow.json';
9 | import {
10 | bootstrapPropertiesPanel,
11 | changeInput,
12 | expectSelected,
13 | findEntry,
14 | findGroupEntry,
15 | findInput, findSelect,
16 | } from './helpers';
17 | import extensions from '../../app/spiffworkflow/extensions';
18 |
19 | describe('Properties Panel for Service Tasks', function () {
20 | const diagramXml = require('./bpmn/service.bpmn').default;
21 | let container;
22 |
23 | beforeEach(function () {
24 | container = TestContainer.get(this);
25 | });
26 |
27 | function preparePropertiesPanelWithXml(xml) {
28 | return bootstrapPropertiesPanel(xml, {
29 | container,
30 | debounceInput: false,
31 | additionalModules: [
32 | extensions,
33 | BpmnPropertiesPanelModule,
34 | BpmnPropertiesProviderModule,
35 | ],
36 | moddleExtensions: {
37 | spiffworkflow: spiffModdleExtension,
38 | },
39 | });
40 | }
41 |
42 | function addServicesToModeler(bpmnModeler) {
43 | /**
44 | * This will inject available services into the modeler which should be
45 | * available as a dropdown list when selecting which service you want to call.
46 | *
47 | */
48 | bpmnModeler.on('spiff.service_tasks.requested', (event) => {
49 | event.eventBus.fire('spiff.service_tasks.returned', {
50 | serviceTaskOperators: [
51 | {
52 | id: 'ExampleService',
53 | parameters: [
54 | {
55 | id: 'name',
56 | type: 'string',
57 | },
58 | ],
59 | },
60 | {
61 | id: 'ExampleService2',
62 | parameters: [
63 | {
64 | id: 'number',
65 | type: 'integer',
66 | },
67 | ],
68 | },
69 | ],
70 | });
71 | });
72 | }
73 |
74 | it('should display a panel for selecting service', async function () {
75 | await preparePropertiesPanelWithXml(diagramXml)();
76 |
77 | const modeler = getBpmnJS();
78 | addServicesToModeler(modeler);
79 |
80 | // IF - you select a service task
81 | const serviceTask = await expectSelected('my_service_task');
82 | expect(serviceTask).to.exist;
83 |
84 | // THEN - a property panel exists with a section for editing web forms
85 | const group = findGroupEntry('service_task_properties', container);
86 | expect(group).to.exist;
87 | });
88 |
89 | it('should display a list of services to select from.', async function () {
90 | await preparePropertiesPanelWithXml(diagramXml)();
91 | const modeler = getBpmnJS();
92 | addServicesToModeler(modeler);
93 |
94 | // IF - you select a service task
95 | const serviceTask = await expectSelected('my_service_task');
96 | const group = findGroupEntry('service_task_properties', container);
97 | const entry = findEntry('selectOperatorId', group)
98 |
99 | // THEN - a select list appears and is populated by a list of known services
100 | const selectList = findSelect(entry);
101 | expect(selectList).to.exist;
102 | expect(selectList.options.length).to.equal(2)
103 | expect(selectList.options[0].label).to.equal('ExampleService')
104 | expect(selectList.options[1].label).to.equal('ExampleService2')
105 | });
106 |
107 |
108 |
109 | });
110 |
--------------------------------------------------------------------------------
/test/spec/bpmn/call_activity.bpmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Flow_1rcteeq
6 |
7 |
8 |
9 | Flow_1rcteeq
10 | Flow_1rid3w7
11 |
12 |
13 | Flow_1rid3w7
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/test/spec/bpmn/data_objects_in_pools.bpmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/test/spec/bpmn/data_store.bpmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Flow_0vt1twq
6 |
7 |
8 | Flow_1udyjxo
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | Flow_0vt1twq
26 | Flow_1udyjxo
27 |
28 | DataStoreReference_0eqeh4p
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/test/spec/bpmn/empty_diagram.bpmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/test/spec/bpmn/io_variables.bpmn:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 | Flow_0vt1twq
13 |
14 |
15 | Flow_1oukz5y
16 |
17 |
18 |
19 |
20 | Flow_05w3wu8
21 | Flow_1oukz5y
22 |
23 |
24 |
25 |
26 | DataInput_0ab29sz
27 |
28 |
29 | DataOutput_1n1fg4r
30 |
31 |
32 |
33 |
35 |
36 | Flow_0vt1twq
37 | Flow_05w3wu8
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/test/spec/bpmn/script_task.bpmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Flow_10jwwqy
6 |
7 |
8 |
9 | Flow_0htxke7
10 |
11 |
12 |
13 |
14 |
15 |
16 | {"hey": false}
17 | {"hey": true}
18 |
19 |
20 | {}
21 | {"something_else": true}
22 |
23 |
24 |
25 | Flow_0niwe1y
26 | Flow_0htxke7
27 | if 'hey' in locals():
28 | hey = True
29 | else:
30 | something_else = True
31 |
32 |
33 |
34 | Flow_10jwwqy
35 | Flow_0niwe1y
36 | hey = False
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/test/spec/bpmn/service.bpmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Flow_09rugkh
6 |
7 |
8 |
9 | Flow_0kql76n
10 |
11 |
12 |
13 | Flow_09rugkh
14 | Flow_0kql76n
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/test/spec/bpmn/subprocess.bpmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Flow_0bk9zgm
6 |
7 |
8 |
9 | Flow_0bk9zgm
10 | Flow_1lzv56z
11 |
12 |
13 |
14 | Flow_1lzv56z
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/test/spec/bpmn/two_messages.bpmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | old_name
10 | "a"
11 |
12 | varA
13 |
14 |
15 |
16 |
17 |
18 | old_name
19 | "b"
20 |
21 | varB
22 |
23 |
24 |
25 |
26 |
27 |
28 | old_exp
29 |
30 |
31 | old_exp
32 |
33 |
34 |
35 | old_name
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/test/spec/bpmn/user_form.bpmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Flow_0vt1twq
6 |
7 |
8 | Flow_1udyjxo
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | Flow_0vt1twq
26 | Flow_1udyjxo
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/test/spec/helpers.js:
--------------------------------------------------------------------------------
1 | import { query as domQuery } from 'min-dom';
2 | import { act, fireEvent } from '@testing-library/preact';
3 |
4 | import {
5 | getBpmnJS,
6 | bootstrapBpmnJS,
7 | inject,
8 | insertCSS,
9 | } from 'bpmn-js/test/helper';
10 | import Modeler from 'bpmn-js/lib/Modeler';
11 | import TestContainer from 'mocha-test-container-support';
12 | import { getBusinessObject } from 'bpmn-js/lib/util/ModelUtil';
13 | import { createMoveEvent } from 'diagram-js/lib/features/mouse/Mouse';
14 |
15 | export let PROPERTIES_PANEL_CONTAINER;
16 | export let CONTAINER;
17 |
18 | export function bootstrapPropertiesPanel(diagram, options, locals) {
19 | return async function () {
20 | let { container } = options;
21 | if (!container) {
22 | container = TestContainer.get(this);
23 | }
24 | CONTAINER = container;
25 |
26 | insertBpmnStyles();
27 | const createModeler = bootstrapBpmnJS(Modeler, diagram, options, locals);
28 | await act(() => createModeler.call(this));
29 |
30 | // (2) clean-up properties panel
31 | clearPropertiesPanelContainer();
32 |
33 | // (3) attach properties panel
34 | const attachPropertiesPanel = inject(function (propertiesPanel) {
35 | PROPERTIES_PANEL_CONTAINER = document.createElement('div');
36 | PROPERTIES_PANEL_CONTAINER.classList.add('properties-container');
37 |
38 | container.appendChild(PROPERTIES_PANEL_CONTAINER);
39 |
40 | return act(() => propertiesPanel.attachTo(PROPERTIES_PANEL_CONTAINER));
41 | });
42 | await attachPropertiesPanel();
43 | };
44 | }
45 |
46 | export function clearPropertiesPanelContainer() {
47 | if (PROPERTIES_PANEL_CONTAINER) {
48 | PROPERTIES_PANEL_CONTAINER.remove();
49 | }
50 | }
51 |
52 | export function insertBpmnStyles() {
53 | insertCSS(
54 | 'diagram.css',
55 | require('bpmn-js/dist/assets/diagram-js.css').default
56 | );
57 |
58 | // @barmac: this fails before bpmn-js@9
59 | insertCSS('bpmn-js.css', require('bpmn-js/dist/assets/bpmn-js.css').default);
60 |
61 | insertCSS(
62 | 'bpmn-font.css',
63 | require('bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css').default
64 | );
65 | }
66 |
67 | export function expectSelected(id) {
68 | return getBpmnJS().invoke(async function (elementRegistry, selection) {
69 | const element = elementRegistry.get(id);
70 |
71 | await act(() => {
72 | selection.select(element);
73 | });
74 |
75 | return element;
76 | });
77 | }
78 |
79 | export function getPropertiesPanel() {
80 | return PROPERTIES_PANEL_CONTAINER;
81 | }
82 |
83 | export function findEntry(id, container) {
84 | return domQuery(`[data-entry-id='${id}']`, container);
85 | }
86 |
87 | export function findGroupEntry(id, container) {
88 | return domQuery(`[data-group-id='group-${id}']`, container);
89 | }
90 |
91 | export function findInput(type, container) {
92 | return domQuery(`input[type='${type}']`, container);
93 | }
94 |
95 | export function findTextarea(id, container) {
96 | return domQuery(`textarea[id='${id}']`, container);
97 | }
98 |
99 | export function findButton(id, container) {
100 | return domQuery(`button[id='${id}']`, container);
101 | }
102 |
103 | export function findButtonByClass(buttonClass, container) {
104 | return domQuery(`button[class='${buttonClass}']`, container);
105 | }
106 |
107 | export function findSelect(container) {
108 | return domQuery('select', container);
109 | }
110 |
111 | export function changeInput(input, value) {
112 | fireEvent.input(input, { target: { value } });
113 | }
114 |
115 | export function pressButton(button) {
116 | fireEvent.click(button);
117 | }
118 |
119 | export function findDivByClass(divClass, container) {
120 | return domQuery(`div[class='${divClass}']`, container);
121 | }
122 |
123 | /**
124 | * Drags an element from the palette onto the canvas.
125 | * @param id
126 | */
127 | export function triggerPaletteEntry(id) {
128 | getBpmnJS().invoke(function (palette) {
129 | const entry = palette.getEntries()[id];
130 |
131 | if (entry && entry.action && entry.action.click) {
132 | entry.action.click(createMoveEvent(0, 0));
133 | }
134 | });
135 | }
136 |
--------------------------------------------------------------------------------
/test/spec/test.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@300;400;500;600&display=swap');
2 |
3 | html, body, .modeler-container, .modeler-container > div {
4 | height: 100%;
5 | margin: 0;
6 | font-family: 'IBM Plex Sans', sans-serif;
7 | }
8 |
9 | .test-container {
10 | height: 800px !important;
11 | }
12 |
13 | .test-content-container {
14 | width: 100%;
15 | height: calc(100% - 24px) !important;
16 | display: flex;
17 | flex: 1;
18 | flex-direction: row;
19 | }
20 |
21 | .modeler-container {
22 | flex: 1;
23 | position: relative;
24 | }
25 |
26 | .modeler-container, .properties-container {
27 | overflow-y: auto;
28 | }
29 |
30 | .properties-container {
31 | position: relative;
32 | flex: none;
33 | height: 100%;
34 | width: 300px;
35 | border-left: solid 1px #cccccc;
36 | }
37 |
38 | .properties-container .bio-properties-panel {
39 | --font-family: 'IBM Plex Sans', sans-serif !important;
40 | }
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const CopyWebpackPlugin = require('copy-webpack-plugin');
2 |
3 | module.exports = {
4 | entry: {
5 | bundle: ['./app/app.js'],
6 | },
7 | output: {
8 | path: `${__dirname}/public`,
9 | filename: 'app.js',
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.m?jsx?$/,
15 | exclude: /node_modules/,
16 | use: {
17 | loader: 'babel-loader',
18 | options: {
19 | plugins: [
20 | [
21 | '@babel/plugin-transform-react-jsx',
22 | {
23 | importSource: '@bpmn-io/properties-panel/preact',
24 | runtime: 'automatic',
25 | },
26 | ],
27 | ],
28 | },
29 | },
30 | },
31 | {
32 | test: /\.bpmn$/,
33 | use: 'raw-loader',
34 | },
35 | ],
36 | },
37 | resolve: {
38 | extensions: ['.js', '.jsx'],
39 | },
40 | plugins: [
41 | new CopyWebpackPlugin({
42 | patterns: [
43 | {
44 | from: 'assets/**',
45 | to: 'vendor/bpmn-js',
46 | context: 'node_modules/bpmn-js/dist/',
47 | },
48 | {
49 | from: 'assets/**',
50 | to: 'vendor/bpmn-js-properties-panel',
51 | context: 'node_modules/@bpmn-io/properties-panel/dist/',
52 | },
53 | { from: '**/*.{html,css}', context: 'app/' },
54 | ],
55 | }),
56 | ],
57 | mode: 'development',
58 | devtool: 'source-map',
59 | };
60 |
--------------------------------------------------------------------------------