Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 58 additions & 24 deletions lib/ElementConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ import { isAny } from 'bpmn-js/lib/util/ModelUtil';

import { isString, omit } from 'min-dash';

import {
DEFAULT_INPUT_CONFIG,
computeDefaultInput,
computeMergedInput
} from './utils/prefill';

export { DEFAULT_INPUT_CONFIG };

export const DEFAULT_CONFIG = {
input: {},
output: {}
Expand All @@ -28,8 +36,6 @@ export class ElementConfig extends EventEmitter {
...config
};

this._selectedElement = null;
this._variablesForElements = new Map();
}

setConfig(newConfig) {
Expand All @@ -43,9 +49,7 @@ export class ElementConfig extends EventEmitter {
}

setInputConfigForElement(element, newConfig) {
if (!isAny(element, SUPPORTED_ELEMENT_TYPES)) {
throw new Error(`Unsupported element type: ${element.type}`);
}
this._assertSupportedElement(element);

this._config = {
...this._config,
Expand All @@ -59,9 +63,7 @@ export class ElementConfig extends EventEmitter {
}

resetInputConfigForElement(element) {
if (!isAny(element, SUPPORTED_ELEMENT_TYPES)) {
throw new Error(`Unsupported element type: ${element.type}`);
}
this._assertSupportedElement(element);

this._config = {
...this._config,
Expand All @@ -72,9 +74,7 @@ export class ElementConfig extends EventEmitter {
}

setOutputConfigForElement(element, newConfig) {
if (!isAny(element, SUPPORTED_ELEMENT_TYPES)) {
throw new Error(`Unsupported element type: ${element.type}`);
}
this._assertSupportedElement(element);

this._config = {
...this._config,
Expand All @@ -88,9 +88,7 @@ export class ElementConfig extends EventEmitter {
}

resetOutputConfigForElement(element) {
if (!isAny(element, SUPPORTED_ELEMENT_TYPES)) {
throw new Error(`Unsupported element type: ${element.type}`);
}
this._assertSupportedElement(element);

this._config = {
...this._config,
Expand All @@ -101,25 +99,55 @@ export class ElementConfig extends EventEmitter {
}

getInputConfigForElement(element) {
if (!isAny(element, SUPPORTED_ELEMENT_TYPES)) {
throw new Error(`Unsupported element type: ${element.type}`);
}
this._assertSupportedElement(element);

if (!isString(this._config.input[element.id])) {
return this._getDefaultInputConfig();
return DEFAULT_INPUT_CONFIG;
}

return this._config.input[element.id];
}

/**
* Computes a fresh input config from the element's input requirements,
* ignoring any stored user config. Used for resetting input to defaults.
*
* @param {Object} element
* @returns {Promise<string>} JSON string
*/
async getDefaultInputForElement(element) {
this._assertSupportedElement(element);

return computeDefaultInput(this._elementVariables, element);
}

/**
* Merges current user input with fresh input requirements from the element.
* Removes null values (unfilled stubs) from user input, then adds new
* requirement stubs for any variables not yet present.
*
* Returns `null` when the current input is invalid JSON, signalling that
* no merge was possible and the caller should skip overwriting the config.
*
* @param {Object} element
* @returns {Promise<string|null>} merged JSON string, or null if current input is unparseable
*/
async getMergedInputConfigForElement(element) {
this._assertSupportedElement(element);

return computeMergedInput(
this._config.input[element.id],
this._elementVariables,
element
);
}

/**
* @param {import('./types').Element} element
* @returns {import('./types').ElementOutput}
*/
getOutputConfigForElement(element) {
if (!isAny(element, SUPPORTED_ELEMENT_TYPES)) {
throw new Error(`Unsupported element type: ${element.type}`);
}
this._assertSupportedElement(element);

if (!this._config.output[element.id]) {
return DEFAULT_OUTPUT;
Expand All @@ -128,7 +156,13 @@ export class ElementConfig extends EventEmitter {
return this._config.output[element.id];
}

_getDefaultInputConfig() {
return '{}';
/**
* @param {Object} element
* @throws {Error} if the element type is not supported
*/
_assertSupportedElement(element) {
if (!isAny(element, SUPPORTED_ELEMENT_TYPES)) {
throw new Error(`Unsupported element type: ${element.type}`);
}
}
}
}
16 changes: 15 additions & 1 deletion lib/ElementVariables.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,18 @@ export class ElementVariables extends EventEmitter {

return variablesWithoutLocal;
}
}

/**
* Returns input requirement variables for an element — variables
* the element needs as input for its expressions and mappings.
*
* @param {Object} element
* @returns {Promise<Array>}
*/
async getConsumedVariablesForElement(element) {
return this._variableResolver.getConsumedVariablesForElement(element)
.catch(() => {
return [];
});
}
}
17 changes: 12 additions & 5 deletions lib/components/Input/Input.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useRef } from 'react';

import { Link } from '@carbon/react';
import { Launch } from '@carbon/icons-react';
Expand All @@ -13,12 +13,19 @@ export default function Input({
onSetInput,
variablesForElement
}) {
const handleResetInput = () => {

const containerRef = /** @type {import('react').RefObject<HTMLDivElement | null>} */ (useRef(null));

const handleReset = () => {
onResetInput();
const cmContent = /** @type {HTMLElement | undefined} */ (
containerRef.current?.querySelector('.cm-content')
);
cmContent?.focus();
};

return (
<div className="input">
<div className="input" ref={ containerRef }>
<div className="input__header">
<div className="input__header--title">
Input process variables
Expand All @@ -28,8 +35,8 @@ export default function Input({
</Link>
<Link
className="input__header--button-reset"
onClick={ handleResetInput }
role="button">Clear</Link>
onClick={ handleReset }
role="button">Reset</Link>
</div>
<InputEditor
allOutputs={ allOutputs }
Expand Down
28 changes: 23 additions & 5 deletions lib/components/Input/InputEditor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import React, { useEffect, useMemo, useRef, useState } from 'react';
import { renderToStaticMarkup } from 'react-dom/server';

import { autocompletion, closeBrackets } from '@codemirror/autocomplete';
import { defaultKeymap } from '@codemirror/commands';
import { defaultKeymap, history, historyKeymap } from '@codemirror/commands';
import { bracketMatching, indentOnInput } from '@codemirror/language';
import { Compartment, EditorState, Annotation } from '@codemirror/state';
import { Compartment, EditorState, Annotation, Transaction } from '@codemirror/state';
import { EditorView, keymap, placeholder } from '@codemirror/view';
import { linter } from '@codemirror/lint';
import { json, jsonParseLinter } from '@codemirror/lang-json';
Expand Down Expand Up @@ -66,6 +66,9 @@ export default function InputEditor({

const ref = useRef(null);

/** @type {import('react').MutableRefObject<EditorView | null>} */
const initializedViewRef = useRef(null);

/**
* @type {ReturnType<typeof useState<EditorView>>}
*/
Expand Down Expand Up @@ -100,8 +103,10 @@ export default function InputEditor({
closeBrackets(),
bracketMatching(),
indentOnInput(),
history(),
keymap.of([
...defaultKeymap
...defaultKeymap,
...historyKeymap
]),
new Compartment().of(json()),
new Compartment().of(EditorState.tabSize.of(2)),
Expand Down Expand Up @@ -135,6 +140,10 @@ export default function InputEditor({

setEditorView(view);

if (value) {
initializedViewRef.current = view;
}

return () => {
view.destroy();
};
Expand All @@ -156,13 +165,21 @@ export default function InputEditor({
const editorValue = editorView.state.doc.toString();

if (value !== editorValue) {
const isInitialFill = initializedViewRef.current !== editorView;
if (value) {
initializedViewRef.current = editorView;
}

editorView.dispatch({
changes: {
from: 0,
to: editorValue.length,
insert: value
},
annotations: fromPropAnnotation.of(true)
annotations: [
fromPropAnnotation.of(true),
...isInitialFill ? [ Transaction.addToHistory.of(false) ] : []
]
});
}
}, [ editorView, value ]);
Expand Down Expand Up @@ -249,4 +266,5 @@ function getDetail(value) {
}

return type.charAt(0).toUpperCase() + type.slice(1);
}
}

25 changes: 22 additions & 3 deletions lib/components/TaskTesting/TaskTesting.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,16 @@ export default function TaskTesting({
const variables = await elementVariablesRef.current.getVariablesForElement(element);

setVariablesForElement(variables);

// Merge updated input requirements into the current input
if (elementConfigRef.current) {
const mergedInput = await elementConfigRef.current.getMergedInputConfigForElement(element);

// Skip update when merge was not possible (e.g. invalid JSON)
if (mergedInput !== null) {
elementConfigRef.current.setInputConfigForElement(element, mergedInput);
}
}
};

elementVariablesRef.current.on('variables.changed', handleVariablesChanged);
Expand Down Expand Up @@ -272,7 +282,15 @@ export default function TaskTesting({
return;
}

setInput(elementConfigRef?.current?.getInputConfigForElement(element));
elementConfigRef?.current?.getMergedInputConfigForElement(element).then(
merged => {
if (merged !== null) {
setInput(merged);
} else {
setInput(elementConfigRef?.current?.getInputConfigForElement(element));
}
}
);
setOutput(elementConfigRef?.current?.getOutputConfigForElement(element));
}, [ element ]);

Expand All @@ -284,9 +302,10 @@ export default function TaskTesting({
}
}, [ element ]);

const handleResetInput = useCallback(() => {
const handleResetInput = useCallback(async () => {
if (element && elementConfigRef.current) {
elementConfigRef.current.resetInputConfigForElement(element);
const prefilled = await elementConfigRef.current.getDefaultInputForElement(element);
elementConfigRef.current.setInputConfigForElement(element, prefilled);
}
}, [ element ]);

Expand Down
Loading
Loading