2022-10-12 10:21:49 -04:00
|
|
|
import { useContext, useEffect, useRef, useState } from 'react';
|
2022-11-07 14:35:49 -05:00
|
|
|
import {
|
|
|
|
generatePath,
|
|
|
|
useNavigate,
|
|
|
|
useParams,
|
|
|
|
useSearchParams,
|
|
|
|
} from 'react-router-dom';
|
2022-10-31 15:09:21 -04:00
|
|
|
// @ts-ignore
|
2022-10-31 16:03:14 -04:00
|
|
|
import { Button, Modal, Stack, Content } from '@carbon/react';
|
2022-10-12 10:21:49 -04:00
|
|
|
import Row from 'react-bootstrap/Row';
|
|
|
|
import Col from 'react-bootstrap/Col';
|
|
|
|
|
|
|
|
import Editor from '@monaco-editor/react';
|
|
|
|
|
2022-11-07 14:35:49 -05:00
|
|
|
import MDEditor from '@uiw/react-md-editor';
|
2022-10-12 10:21:49 -04:00
|
|
|
import ReactDiagramEditor from '../components/ReactDiagramEditor';
|
|
|
|
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
|
|
|
import HttpService from '../services/HttpService';
|
|
|
|
import ErrorContext from '../contexts/ErrorContext';
|
2022-11-08 16:00:44 -05:00
|
|
|
import { makeid, modifyProcessModelPath } from '../helpers';
|
2022-11-07 14:35:49 -05:00
|
|
|
import { ProcessFile, ProcessModel } from '../interfaces';
|
2022-10-12 10:21:49 -04:00
|
|
|
|
|
|
|
export default function ProcessModelEditDiagram() {
|
|
|
|
const [showFileNameEditor, setShowFileNameEditor] = useState(false);
|
|
|
|
const handleShowFileNameEditor = () => setShowFileNameEditor(true);
|
2022-11-07 14:35:49 -05:00
|
|
|
const [processModel, setProcessModel] = useState<ProcessModel | null>(null);
|
2022-10-12 10:21:49 -04:00
|
|
|
|
2022-10-31 16:51:27 -04:00
|
|
|
const [scriptText, setScriptText] = useState<string>('');
|
|
|
|
const [scriptType, setScriptType] = useState<string>('');
|
|
|
|
const [scriptEventBus, setScriptEventBus] = useState<any>(null);
|
2022-10-12 10:21:49 -04:00
|
|
|
const [scriptModeling, setScriptModeling] = useState(null);
|
|
|
|
const [scriptElement, setScriptElement] = useState(null);
|
|
|
|
const [showScriptEditor, setShowScriptEditor] = useState(false);
|
|
|
|
const handleShowScriptEditor = () => setShowScriptEditor(true);
|
|
|
|
|
2022-11-07 14:35:49 -05:00
|
|
|
const [markdownText, setMarkdownText] = useState<string | undefined>('');
|
|
|
|
const [markdownEventBus, setMarkdownEventBus] = useState<any>(null);
|
|
|
|
const [showMarkdownEditor, setShowMarkdownEditor] = useState(false);
|
|
|
|
|
|
|
|
const handleShowMarkdownEditor = () => setShowMarkdownEditor(true);
|
|
|
|
|
2022-10-12 10:21:49 -04:00
|
|
|
const editorRef = useRef(null);
|
|
|
|
const monacoRef = useRef(null);
|
|
|
|
|
|
|
|
const failingScriptLineClassNamePrefix = 'failingScriptLineError';
|
|
|
|
|
|
|
|
function handleEditorDidMount(editor: any, monaco: any) {
|
|
|
|
// here is the editor instance
|
|
|
|
// you can store it in `useRef` for further usage
|
|
|
|
editorRef.current = editor;
|
|
|
|
monacoRef.current = monaco;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface ScriptUnitTest {
|
|
|
|
id: string;
|
|
|
|
inputJson: any;
|
|
|
|
expectedOutputJson: any;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface ScriptUnitTestResult {
|
|
|
|
result: boolean;
|
|
|
|
context: object;
|
|
|
|
error: string;
|
|
|
|
line_number: number;
|
|
|
|
offset: number;
|
|
|
|
}
|
|
|
|
|
|
|
|
const [currentScriptUnitTest, setCurrentScriptUnitTest] =
|
|
|
|
useState<ScriptUnitTest | null>(null);
|
|
|
|
const [currentScriptUnitTestIndex, setCurrentScriptUnitTestIndex] =
|
|
|
|
useState<number>(-1);
|
|
|
|
const [scriptUnitTestResult, setScriptUnitTestResult] =
|
|
|
|
useState<ScriptUnitTestResult | null>(null);
|
|
|
|
|
|
|
|
const params = useParams();
|
|
|
|
const navigate = useNavigate();
|
|
|
|
const [searchParams] = useSearchParams();
|
|
|
|
|
|
|
|
const setErrorMessage = (useContext as any)(ErrorContext)[1];
|
2022-11-08 17:20:17 -05:00
|
|
|
const [processModelFile, setProcessModelFile] = useState<ProcessFile | null>(
|
|
|
|
null
|
|
|
|
);
|
2022-10-12 10:21:49 -04:00
|
|
|
const [newFileName, setNewFileName] = useState('');
|
|
|
|
const [bpmnXmlForDiagramRendering, setBpmnXmlForDiagramRendering] =
|
|
|
|
useState(null);
|
|
|
|
|
2022-11-07 17:22:46 -05:00
|
|
|
const modifiedProcessModelId = modifyProcessModelPath(
|
|
|
|
(params as any).process_model_id
|
|
|
|
);
|
|
|
|
|
|
|
|
const processModelPath = `process-models/${modifiedProcessModelId}`;
|
2022-10-12 10:21:49 -04:00
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
const processResult = (result: ProcessModel) => {
|
|
|
|
setProcessModel(result);
|
|
|
|
};
|
|
|
|
HttpService.makeCallToBackend({
|
|
|
|
path: `/${processModelPath}`,
|
|
|
|
successCallback: processResult,
|
|
|
|
});
|
|
|
|
}, [processModelPath]);
|
|
|
|
|
|
|
|
useEffect(() => {
|
2022-11-07 14:35:49 -05:00
|
|
|
const fileResult = (result: any) => {
|
2022-10-12 10:21:49 -04:00
|
|
|
setProcessModelFile(result);
|
|
|
|
setBpmnXmlForDiagramRendering(result.file_contents);
|
|
|
|
};
|
|
|
|
|
|
|
|
if (params.file_name) {
|
|
|
|
HttpService.makeCallToBackend({
|
|
|
|
path: `/${processModelPath}/files/${params.file_name}`,
|
2022-11-07 14:35:49 -05:00
|
|
|
successCallback: fileResult,
|
2022-10-12 10:21:49 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}, [processModelPath, params]);
|
|
|
|
|
|
|
|
const handleFileNameCancel = () => {
|
|
|
|
setShowFileNameEditor(false);
|
|
|
|
setNewFileName('');
|
|
|
|
};
|
|
|
|
|
|
|
|
const navigateToProcessModelFile = (_result: any) => {
|
|
|
|
if (!params.file_name) {
|
|
|
|
const fileNameWithExtension = `${newFileName}.${searchParams.get(
|
|
|
|
'file_type'
|
|
|
|
)}`;
|
|
|
|
navigate(
|
2022-11-07 17:22:46 -05:00
|
|
|
`/admin/process-models/${modifiedProcessModelId}/files/${fileNameWithExtension}`
|
2022-10-12 10:21:49 -04:00
|
|
|
);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const saveDiagram = (bpmnXML: any, fileName = params.file_name) => {
|
2022-10-18 16:41:13 -04:00
|
|
|
setErrorMessage(null);
|
2022-10-12 10:21:49 -04:00
|
|
|
setBpmnXmlForDiagramRendering(bpmnXML);
|
|
|
|
|
2022-11-07 17:22:46 -05:00
|
|
|
let url = `/process-models/${modifiedProcessModelId}/files`;
|
2022-10-12 10:21:49 -04:00
|
|
|
let httpMethod = 'PUT';
|
|
|
|
let fileNameWithExtension = fileName;
|
|
|
|
|
|
|
|
if (newFileName) {
|
|
|
|
fileNameWithExtension = `${newFileName}.${searchParams.get('file_type')}`;
|
|
|
|
httpMethod = 'POST';
|
|
|
|
} else {
|
|
|
|
url += `/${fileNameWithExtension}`;
|
|
|
|
}
|
|
|
|
if (!fileNameWithExtension) {
|
|
|
|
handleShowFileNameEditor();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const bpmnFile = new File([bpmnXML], fileNameWithExtension);
|
|
|
|
const formData = new FormData();
|
|
|
|
formData.append('file', bpmnFile);
|
|
|
|
formData.append('fileName', bpmnFile.name);
|
|
|
|
|
|
|
|
HttpService.makeCallToBackend({
|
|
|
|
path: url,
|
|
|
|
successCallback: navigateToProcessModelFile,
|
|
|
|
failureCallback: setErrorMessage,
|
|
|
|
httpMethod,
|
|
|
|
postBody: formData,
|
|
|
|
});
|
|
|
|
|
|
|
|
// after saving the file, make sure we null out newFileName
|
|
|
|
// so it does not get used over the params
|
|
|
|
setNewFileName('');
|
|
|
|
};
|
|
|
|
|
|
|
|
const onDeleteFile = (fileName = params.file_name) => {
|
2022-11-07 17:22:46 -05:00
|
|
|
const url = `/process-models/${modifiedProcessModelId}/files/${fileName}`;
|
2022-10-12 10:21:49 -04:00
|
|
|
const httpMethod = 'DELETE';
|
|
|
|
|
|
|
|
const navigateToProcessModelShow = (_httpResult: any) => {
|
2022-11-08 14:09:03 -05:00
|
|
|
navigate(`/admin/process-models/${modifiedProcessModelId}`);
|
2022-10-12 10:21:49 -04:00
|
|
|
};
|
|
|
|
HttpService.makeCallToBackend({
|
|
|
|
path: url,
|
|
|
|
successCallback: navigateToProcessModelShow,
|
|
|
|
httpMethod,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const onSetPrimaryFile = (fileName = params.file_name) => {
|
2022-11-07 17:22:46 -05:00
|
|
|
const url = `/process-models/${modifiedProcessModelId}`;
|
2022-10-12 10:21:49 -04:00
|
|
|
const httpMethod = 'PUT';
|
|
|
|
|
|
|
|
const navigateToProcessModelShow = (_httpResult: any) => {
|
|
|
|
navigate(`/admin${url}`);
|
|
|
|
};
|
|
|
|
const processModelToPass = {
|
|
|
|
primary_file_name: fileName,
|
|
|
|
};
|
|
|
|
HttpService.makeCallToBackend({
|
|
|
|
path: url,
|
|
|
|
successCallback: navigateToProcessModelShow,
|
|
|
|
httpMethod,
|
|
|
|
postBody: processModelToPass,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleFileNameSave = (event: any) => {
|
|
|
|
event.preventDefault();
|
|
|
|
setShowFileNameEditor(false);
|
|
|
|
saveDiagram(bpmnXmlForDiagramRendering);
|
|
|
|
};
|
|
|
|
|
|
|
|
const newFileNameBox = () => {
|
|
|
|
const fileExtension = `.${searchParams.get('file_type')}`;
|
|
|
|
return (
|
2022-11-04 12:48:05 -04:00
|
|
|
<Modal
|
|
|
|
open={showFileNameEditor}
|
|
|
|
modalHeading="Processs Model File Name"
|
|
|
|
primaryButtonText="Save Changes"
|
|
|
|
secondaryButtonText="Cancel"
|
|
|
|
onSecondarySubmit={handleFileNameCancel}
|
|
|
|
onRequestSubmit={handleFileNameSave}
|
|
|
|
>
|
|
|
|
<label>File Name:</label>
|
|
|
|
<span>
|
|
|
|
<input
|
|
|
|
name="file_name"
|
|
|
|
type="text"
|
|
|
|
value={newFileName}
|
|
|
|
onChange={(e) => setNewFileName(e.target.value)}
|
|
|
|
autoFocus
|
|
|
|
/>
|
|
|
|
{fileExtension}
|
|
|
|
</span>
|
2022-10-12 10:21:49 -04:00
|
|
|
</Modal>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const resetUnitTextResult = () => {
|
|
|
|
setScriptUnitTestResult(null);
|
|
|
|
const styleSheet = document.styleSheets[0];
|
|
|
|
const ruleList = styleSheet.cssRules;
|
|
|
|
for (let ii = ruleList.length - 1; ii >= 0; ii -= 1) {
|
|
|
|
const regexp = new RegExp(
|
|
|
|
`^.${failingScriptLineClassNamePrefix}_.*::after `
|
|
|
|
);
|
|
|
|
if (ruleList[ii].cssText.match(regexp)) {
|
|
|
|
styleSheet.deleteRule(ii);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const makeApiHandler = (event: any) => {
|
|
|
|
return function fireEvent(results: any) {
|
|
|
|
event.eventBus.fire('spiff.service_tasks.returned', {
|
|
|
|
serviceTaskOperators: results,
|
|
|
|
});
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
const onServiceTasksRequested = (event: any) => {
|
|
|
|
HttpService.makeCallToBackend({
|
|
|
|
path: `/service_tasks`,
|
|
|
|
successCallback: makeApiHandler(event),
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2022-11-07 14:35:49 -05:00
|
|
|
const onJsonFilesRequested = (event: any) => {
|
|
|
|
if (processModel) {
|
|
|
|
const jsonFiles = processModel.files.filter((f) => f.type === 'json');
|
|
|
|
const options = jsonFiles.map((f) => {
|
|
|
|
return { label: f.name, value: f.name };
|
|
|
|
});
|
|
|
|
event.eventBus.fire('spiff.json_files.returned', { options });
|
|
|
|
} else {
|
2022-11-10 09:13:27 -05:00
|
|
|
console.error('There is no process Model.');
|
2022-11-07 14:35:49 -05:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const onDmnFilesRequested = (event: any) => {
|
|
|
|
if (processModel) {
|
|
|
|
const dmnFiles = processModel.files.filter((f) => f.type === 'dmn');
|
|
|
|
const options: any[] = [];
|
|
|
|
dmnFiles.forEach((file) => {
|
|
|
|
file.references.forEach((ref) => {
|
|
|
|
options.push({ label: ref.name, value: ref.id });
|
|
|
|
});
|
|
|
|
});
|
|
|
|
event.eventBus.fire('spiff.dmn_files.returned', { options });
|
|
|
|
} else {
|
2022-11-10 09:13:27 -05:00
|
|
|
console.error('There is no process model.');
|
2022-11-07 14:35:49 -05:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-10-12 10:21:49 -04:00
|
|
|
const getScriptUnitTestElements = (element: any) => {
|
|
|
|
const { extensionElements } = element.businessObject;
|
|
|
|
if (extensionElements && extensionElements.values.length > 0) {
|
|
|
|
const unitTestModdleElements = extensionElements
|
|
|
|
.get('values')
|
|
|
|
.filter(function getInstanceOfType(e: any) {
|
|
|
|
return e.$instanceOf('spiffworkflow:unitTests');
|
|
|
|
})[0];
|
|
|
|
if (unitTestModdleElements) {
|
|
|
|
return unitTestModdleElements.unitTests;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return [];
|
|
|
|
};
|
|
|
|
|
|
|
|
const setScriptUnitTestElementWithIndex = (
|
|
|
|
scriptIndex: number,
|
|
|
|
element: any = scriptElement
|
|
|
|
) => {
|
|
|
|
const unitTestsModdleElements = getScriptUnitTestElements(element);
|
|
|
|
if (unitTestsModdleElements.length > 0) {
|
|
|
|
setCurrentScriptUnitTest(unitTestsModdleElements[scriptIndex]);
|
|
|
|
setCurrentScriptUnitTestIndex(scriptIndex);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-10-31 16:51:27 -04:00
|
|
|
const onLaunchScriptEditor = (
|
|
|
|
element: any,
|
|
|
|
script: string,
|
2022-10-31 16:58:10 -04:00
|
|
|
scriptTypeString: string,
|
|
|
|
eventBus: any,
|
|
|
|
modeling: any
|
2022-10-31 16:51:27 -04:00
|
|
|
) => {
|
2022-10-31 16:58:10 -04:00
|
|
|
// TODO: modeling is only needed for script unit tests.
|
|
|
|
// we should update this to act like updating scripts
|
|
|
|
// where we pass an event to bpmn-js
|
2022-10-12 10:21:49 -04:00
|
|
|
setScriptModeling(modeling);
|
2022-10-31 16:51:27 -04:00
|
|
|
setScriptText(script || '');
|
2022-10-31 16:58:10 -04:00
|
|
|
setScriptType(scriptTypeString);
|
2022-10-31 16:51:27 -04:00
|
|
|
setScriptEventBus(eventBus);
|
2022-10-12 10:21:49 -04:00
|
|
|
setScriptElement(element);
|
|
|
|
setScriptUnitTestElementWithIndex(0, element);
|
|
|
|
handleShowScriptEditor();
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleScriptEditorClose = () => {
|
2022-11-07 14:35:49 -05:00
|
|
|
scriptEventBus.fire('spiff.script.update', {
|
2022-10-31 16:51:27 -04:00
|
|
|
scriptType,
|
|
|
|
script: scriptText,
|
|
|
|
element: scriptElement,
|
|
|
|
});
|
|
|
|
|
2022-10-12 10:21:49 -04:00
|
|
|
resetUnitTextResult();
|
|
|
|
setShowScriptEditor(false);
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleEditorScriptChange = (value: any) => {
|
|
|
|
setScriptText(value);
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleEditorScriptTestUnitInputChange = (value: any) => {
|
|
|
|
if (currentScriptUnitTest) {
|
|
|
|
currentScriptUnitTest.inputJson.value = value;
|
|
|
|
(scriptModeling as any).updateProperties(scriptElement, {});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleEditorScriptTestUnitOutputChange = (value: any) => {
|
|
|
|
if (currentScriptUnitTest) {
|
|
|
|
currentScriptUnitTest.expectedOutputJson.value = value;
|
|
|
|
(scriptModeling as any).updateProperties(scriptElement, {});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const generalEditorOptions = () => {
|
|
|
|
return {
|
|
|
|
glyphMargin: false,
|
|
|
|
folding: false,
|
|
|
|
lineNumbersMinChars: 0,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
const setPreviousScriptUnitTest = () => {
|
|
|
|
resetUnitTextResult();
|
|
|
|
const newScriptIndex = currentScriptUnitTestIndex - 1;
|
|
|
|
if (newScriptIndex >= 0) {
|
|
|
|
setScriptUnitTestElementWithIndex(newScriptIndex);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const setNextScriptUnitTest = () => {
|
|
|
|
resetUnitTextResult();
|
|
|
|
const newScriptIndex = currentScriptUnitTestIndex + 1;
|
|
|
|
const unitTestsModdleElements = getScriptUnitTestElements(scriptElement);
|
|
|
|
if (newScriptIndex < unitTestsModdleElements.length) {
|
|
|
|
setScriptUnitTestElementWithIndex(newScriptIndex);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const processScriptUnitTestRunResult = (result: any) => {
|
|
|
|
if ('result' in result) {
|
|
|
|
setScriptUnitTestResult(result);
|
|
|
|
if (
|
|
|
|
result.line_number &&
|
|
|
|
result.error &&
|
|
|
|
editorRef.current &&
|
|
|
|
monacoRef.current
|
|
|
|
) {
|
|
|
|
const currentClassName = `${failingScriptLineClassNamePrefix}_${makeid(
|
|
|
|
7
|
|
|
|
)}`;
|
|
|
|
|
|
|
|
// document.documentElement.style.setProperty causes the content property to go away
|
|
|
|
// so add the rule dynamically instead of changing a property variable
|
|
|
|
document.styleSheets[0].addRule(
|
|
|
|
`.${currentClassName}::after`,
|
|
|
|
`content: " # ${result.error.replaceAll('"', '')}"; color: red`
|
|
|
|
);
|
|
|
|
|
|
|
|
const lineLength =
|
|
|
|
scriptText.split('\n')[result.line_number - 1].length + 1;
|
|
|
|
|
|
|
|
const editorRefToUse = editorRef.current as any;
|
|
|
|
editorRefToUse.deltaDecorations(
|
|
|
|
[],
|
|
|
|
[
|
|
|
|
{
|
|
|
|
// Range(lineStart, column, lineEnd, column)
|
|
|
|
range: new (monacoRef.current as any).Range(
|
|
|
|
result.line_number,
|
|
|
|
lineLength
|
|
|
|
),
|
|
|
|
options: { afterContentClassName: currentClassName },
|
|
|
|
},
|
|
|
|
]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const runCurrentUnitTest = () => {
|
|
|
|
if (currentScriptUnitTest && scriptElement) {
|
|
|
|
resetUnitTextResult();
|
|
|
|
HttpService.makeCallToBackend({
|
2022-11-07 17:22:46 -05:00
|
|
|
path: `/process-models/${modifiedProcessModelId}/script-unit-tests/run`,
|
2022-10-12 10:21:49 -04:00
|
|
|
httpMethod: 'POST',
|
|
|
|
successCallback: processScriptUnitTestRunResult,
|
|
|
|
postBody: {
|
|
|
|
bpmn_task_identifier: (scriptElement as any).id,
|
|
|
|
python_script: scriptText,
|
|
|
|
input_json: JSON.parse(currentScriptUnitTest.inputJson.value),
|
|
|
|
expected_output_json: JSON.parse(
|
|
|
|
currentScriptUnitTest.expectedOutputJson.value
|
|
|
|
),
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const unitTestFailureElement = () => {
|
|
|
|
if (
|
|
|
|
scriptUnitTestResult &&
|
|
|
|
scriptUnitTestResult.result === false &&
|
|
|
|
!scriptUnitTestResult.line_number
|
|
|
|
) {
|
|
|
|
let errorStringElement = null;
|
|
|
|
if (scriptUnitTestResult.error) {
|
|
|
|
errorStringElement = (
|
|
|
|
<span>
|
|
|
|
Received error when running script:{' '}
|
|
|
|
{JSON.stringify(scriptUnitTestResult.error)}
|
|
|
|
</span>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
let errorContextElement = null;
|
|
|
|
if (scriptUnitTestResult.context) {
|
|
|
|
errorContextElement = (
|
|
|
|
<span>
|
|
|
|
Received unexpected output:{' '}
|
|
|
|
{JSON.stringify(scriptUnitTestResult.context)}
|
|
|
|
</span>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return (
|
|
|
|
<span style={{ color: 'red', fontSize: '1em' }}>
|
|
|
|
{errorStringElement}
|
|
|
|
{errorContextElement}
|
|
|
|
</span>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
};
|
|
|
|
|
|
|
|
const scriptUnitTestEditorElement = () => {
|
|
|
|
if (currentScriptUnitTest) {
|
|
|
|
let previousButtonDisable = true;
|
|
|
|
if (currentScriptUnitTestIndex > 0) {
|
|
|
|
previousButtonDisable = false;
|
|
|
|
}
|
|
|
|
let nextButtonDisable = true;
|
|
|
|
const unitTestsModdleElements = getScriptUnitTestElements(scriptElement);
|
|
|
|
if (currentScriptUnitTestIndex < unitTestsModdleElements.length - 1) {
|
|
|
|
nextButtonDisable = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// unset current unit test if all tests were deleted
|
|
|
|
if (unitTestsModdleElements.length < 1) {
|
|
|
|
setCurrentScriptUnitTest(null);
|
|
|
|
setCurrentScriptUnitTestIndex(-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
let scriptUnitTestResultBoolElement = null;
|
|
|
|
if (scriptUnitTestResult) {
|
|
|
|
scriptUnitTestResultBoolElement = (
|
|
|
|
<Col xs={1}>
|
|
|
|
{scriptUnitTestResult.result === true && (
|
|
|
|
<span style={{ color: 'green', fontSize: '3em' }}>✓</span>
|
|
|
|
)}
|
|
|
|
{scriptUnitTestResult.result === false && (
|
|
|
|
<span style={{ color: 'red', fontSize: '3em' }}>✘</span>
|
|
|
|
)}
|
|
|
|
</Col>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return (
|
|
|
|
<main>
|
2022-10-31 16:03:14 -04:00
|
|
|
<Content>
|
2022-10-12 10:21:49 -04:00
|
|
|
<Row>
|
|
|
|
<Col xs={8}>
|
|
|
|
<Button variant="link" disabled style={{ fontSize: '1.5em' }}>
|
|
|
|
Unit Test: {currentScriptUnitTest.id}
|
|
|
|
</Button>
|
|
|
|
</Col>
|
|
|
|
<Col xs={1}>
|
|
|
|
<Button
|
|
|
|
data-qa="unit-test-previous-button"
|
|
|
|
style={{ fontSize: '1.5em' }}
|
|
|
|
onClick={setPreviousScriptUnitTest}
|
|
|
|
variant="link"
|
|
|
|
disabled={previousButtonDisable}
|
|
|
|
>
|
|
|
|
«
|
|
|
|
</Button>
|
|
|
|
</Col>
|
|
|
|
<Col xs={1}>
|
|
|
|
<Button
|
|
|
|
data-qa="unit-test-next-button"
|
|
|
|
style={{ fontSize: '1.5em' }}
|
|
|
|
onClick={setNextScriptUnitTest}
|
|
|
|
variant="link"
|
|
|
|
disabled={nextButtonDisable}
|
|
|
|
>
|
|
|
|
»
|
|
|
|
</Button>
|
|
|
|
</Col>
|
|
|
|
<Col xs={1}>
|
|
|
|
<Button
|
|
|
|
className="justify-content-end"
|
|
|
|
data-qa="unit-test-run"
|
|
|
|
style={{ fontSize: '1.5em' }}
|
|
|
|
onClick={runCurrentUnitTest}
|
|
|
|
>
|
|
|
|
Run
|
|
|
|
</Button>
|
|
|
|
</Col>
|
|
|
|
<Col xs={1}>{scriptUnitTestResultBoolElement}</Col>
|
|
|
|
</Row>
|
2022-10-31 16:03:14 -04:00
|
|
|
</Content>
|
2022-11-04 18:27:10 -04:00
|
|
|
<Stack orientation="horizontal" gap={3}>
|
2022-10-12 10:21:49 -04:00
|
|
|
{unitTestFailureElement()}
|
|
|
|
</Stack>
|
2022-11-04 18:27:10 -04:00
|
|
|
<Stack orientation="horizontal" gap={3}>
|
2022-10-12 10:21:49 -04:00
|
|
|
<Stack>
|
|
|
|
<div>Input Json:</div>
|
|
|
|
<div>
|
|
|
|
<Editor
|
|
|
|
height={200}
|
|
|
|
defaultLanguage="json"
|
|
|
|
options={Object.assign(generalEditorOptions(), {
|
|
|
|
minimap: { enabled: false },
|
|
|
|
})}
|
|
|
|
value={currentScriptUnitTest.inputJson.value}
|
|
|
|
onChange={handleEditorScriptTestUnitInputChange}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</Stack>
|
|
|
|
<Stack>
|
|
|
|
<div>Expected Output Json:</div>
|
|
|
|
<div>
|
|
|
|
<Editor
|
|
|
|
height={200}
|
|
|
|
defaultLanguage="json"
|
|
|
|
options={Object.assign(generalEditorOptions(), {
|
|
|
|
minimap: { enabled: false },
|
|
|
|
})}
|
|
|
|
value={currentScriptUnitTest.expectedOutputJson.value}
|
|
|
|
onChange={handleEditorScriptTestUnitOutputChange}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</Stack>
|
|
|
|
</Stack>
|
|
|
|
</main>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
};
|
|
|
|
|
|
|
|
const scriptEditor = () => {
|
|
|
|
let scriptName = '';
|
|
|
|
if (scriptElement) {
|
|
|
|
scriptName = (scriptElement as any).di.bpmnElement.name;
|
|
|
|
}
|
2022-11-04 15:07:40 -04:00
|
|
|
|
2022-10-12 10:21:49 -04:00
|
|
|
return (
|
2022-11-04 15:07:40 -04:00
|
|
|
<Modal
|
|
|
|
open={showScriptEditor}
|
|
|
|
modalHeading={`Editing Script: ${scriptName}`}
|
|
|
|
primaryButtonText="Close"
|
|
|
|
onRequestSubmit={handleScriptEditorClose}
|
|
|
|
size="lg"
|
|
|
|
>
|
|
|
|
<Editor
|
|
|
|
height={500}
|
|
|
|
width="auto"
|
|
|
|
options={generalEditorOptions()}
|
|
|
|
defaultLanguage="python"
|
|
|
|
value={scriptText}
|
|
|
|
onChange={handleEditorScriptChange}
|
|
|
|
onMount={handleEditorDidMount}
|
|
|
|
/>
|
|
|
|
{scriptUnitTestEditorElement()}
|
2022-10-12 10:21:49 -04:00
|
|
|
</Modal>
|
|
|
|
);
|
|
|
|
};
|
2022-11-07 14:35:49 -05:00
|
|
|
const onLaunchMarkdownEditor = (
|
|
|
|
element: any,
|
|
|
|
markdown: string,
|
|
|
|
eventBus: any
|
|
|
|
) => {
|
|
|
|
setMarkdownText(markdown || '');
|
|
|
|
setMarkdownEventBus(eventBus);
|
|
|
|
handleShowMarkdownEditor();
|
|
|
|
};
|
|
|
|
const handleMarkdownEditorClose = () => {
|
|
|
|
markdownEventBus.fire('spiff.markdown.update', {
|
|
|
|
value: markdownText,
|
|
|
|
});
|
|
|
|
setShowMarkdownEditor(false);
|
|
|
|
};
|
|
|
|
|
|
|
|
const markdownEditor = () => {
|
|
|
|
return (
|
|
|
|
<Modal
|
2022-11-10 12:31:26 -05:00
|
|
|
open={showMarkdownEditor}
|
|
|
|
modalHeading={`Edit Markdown`}
|
|
|
|
primaryButtonText="Close"
|
|
|
|
onRequestSubmit={handleMarkdownEditorClose}
|
|
|
|
size="lg"
|
2022-11-07 14:35:49 -05:00
|
|
|
>
|
2022-11-10 12:31:26 -05:00
|
|
|
<MDEditor height={500} highlightEnable={false} value={markdownText} onChange={setMarkdownText} />
|
2022-11-07 14:35:49 -05:00
|
|
|
</Modal>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const findFileNameForReferenceId = (
|
|
|
|
id: string,
|
|
|
|
type: string
|
|
|
|
): ProcessFile | null => {
|
|
|
|
// Given a reference id (like a process_id, or decision_id) finds the file
|
|
|
|
// that contains that reference and returns it.
|
|
|
|
let matchFile = null;
|
|
|
|
if (processModel) {
|
|
|
|
const files = processModel.files.filter((f) => f.type === type);
|
|
|
|
files.some((file) => {
|
|
|
|
if (file.references.some((ref) => ref.id === id)) {
|
|
|
|
matchFile = file;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return matchFile;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* fixme: Not currently in use. This would only work for bpmn files within the process model. Which is right for DMN and json, but not right here. Need to merge in work on the nested process groups before tackling this.
|
|
|
|
* @param processId
|
|
|
|
*/
|
2022-11-09 13:19:01 -05:00
|
|
|
|
2022-11-10 12:31:26 -05:00
|
|
|
|
2022-11-09 13:19:01 -05:00
|
|
|
|
2022-11-07 14:35:49 -05:00
|
|
|
const onLaunchBpmnEditor = (processId: string) => {
|
|
|
|
const file = findFileNameForReferenceId(processId, 'bpmn');
|
|
|
|
if (file) {
|
2022-11-10 12:31:26 -05:00
|
|
|
const path = generatePath(
|
|
|
|
'/admin/process-models/:process_model_id/files/:file_name',
|
|
|
|
{
|
|
|
|
process_model_id: params.process_model_id,
|
|
|
|
file_name: file.name,
|
|
|
|
}
|
|
|
|
);
|
2022-11-07 14:35:49 -05:00
|
|
|
window.open(path);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
const onLaunchJsonEditor = (fileName: string) => {
|
2022-11-10 12:31:26 -05:00
|
|
|
const path = generatePath(
|
|
|
|
'/admin/process-models/:process_model_id/form/:file_name',
|
|
|
|
{
|
|
|
|
process_model_id: params.process_model_id,
|
|
|
|
file_name: fileName,
|
|
|
|
}
|
|
|
|
);
|
2022-11-07 14:35:49 -05:00
|
|
|
window.open(path);
|
|
|
|
};
|
|
|
|
const onLaunchDmnEditor = (processId: string) => {
|
|
|
|
const file = findFileNameForReferenceId(processId, 'dmn');
|
|
|
|
if (file) {
|
2022-11-10 12:31:26 -05:00
|
|
|
const path = generatePath(
|
|
|
|
'/admin/process-models/:process_model_id/files/:file_name',
|
|
|
|
{
|
|
|
|
process_model_id: params.process_model_id,
|
|
|
|
file_name: file.name,
|
|
|
|
}
|
|
|
|
);
|
2022-11-07 14:35:49 -05:00
|
|
|
window.open(path);
|
|
|
|
}
|
|
|
|
};
|
2022-10-12 10:21:49 -04:00
|
|
|
|
|
|
|
const isDmn = () => {
|
|
|
|
const fileName = params.file_name || '';
|
|
|
|
return searchParams.get('file_type') === 'dmn' || fileName.endsWith('.dmn');
|
|
|
|
};
|
|
|
|
|
|
|
|
const appropriateEditor = () => {
|
|
|
|
if (isDmn()) {
|
|
|
|
return (
|
|
|
|
<ReactDiagramEditor
|
|
|
|
processModelId={params.process_model_id || ''}
|
|
|
|
saveDiagram={saveDiagram}
|
|
|
|
onDeleteFile={onDeleteFile}
|
|
|
|
diagramXML={bpmnXmlForDiagramRendering}
|
|
|
|
fileName={params.file_name}
|
|
|
|
diagramType="dmn"
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
// let this be undefined (so we won't display the button) unless the
|
|
|
|
// current primary_file_name is different from the one we're looking at.
|
|
|
|
let onSetPrimaryFileCallback;
|
|
|
|
if (
|
|
|
|
processModel &&
|
|
|
|
params.file_name &&
|
|
|
|
params.file_name !== processModel.primary_file_name
|
|
|
|
) {
|
|
|
|
onSetPrimaryFileCallback = onSetPrimaryFile;
|
|
|
|
}
|
|
|
|
return (
|
|
|
|
<ReactDiagramEditor
|
|
|
|
processModelId={params.process_model_id || ''}
|
|
|
|
saveDiagram={saveDiagram}
|
|
|
|
onDeleteFile={onDeleteFile}
|
|
|
|
onSetPrimaryFile={onSetPrimaryFileCallback}
|
|
|
|
diagramXML={bpmnXmlForDiagramRendering}
|
|
|
|
fileName={params.file_name}
|
|
|
|
diagramType="bpmn"
|
|
|
|
onLaunchScriptEditor={onLaunchScriptEditor}
|
|
|
|
onServiceTasksRequested={onServiceTasksRequested}
|
2022-11-07 14:35:49 -05:00
|
|
|
onLaunchMarkdownEditor={onLaunchMarkdownEditor}
|
|
|
|
onLaunchBpmnEditor={onLaunchBpmnEditor}
|
|
|
|
onLaunchJsonEditor={onLaunchJsonEditor}
|
|
|
|
onJsonFilesRequested={onJsonFilesRequested}
|
|
|
|
onLaunchDmnEditor={onLaunchDmnEditor}
|
|
|
|
onDmnFilesRequested={onDmnFilesRequested}
|
2022-10-12 10:21:49 -04:00
|
|
|
/>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
// if a file name is not given then this is a new model and the ReactDiagramEditor component will handle it
|
2022-11-07 14:35:49 -05:00
|
|
|
if ((bpmnXmlForDiagramRendering || !params.file_name) && processModel) {
|
2022-11-09 10:12:13 -05:00
|
|
|
const processModelFileName = processModelFile ? processModelFile.name : '';
|
2022-10-12 10:21:49 -04:00
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<ProcessBreadcrumb
|
2022-11-08 17:20:17 -05:00
|
|
|
hotCrumbs={[
|
|
|
|
['Process Groups', '/admin'],
|
|
|
|
[
|
|
|
|
`Process Model: ${processModel.id}`,
|
|
|
|
`process_model:${processModel.id}:link`,
|
|
|
|
],
|
|
|
|
[processModelFileName],
|
|
|
|
]}
|
2022-10-12 10:21:49 -04:00
|
|
|
/>
|
|
|
|
<h2>
|
2022-11-09 10:12:13 -05:00
|
|
|
Process Model File{processModelFile ? ': ' : ''}
|
2022-11-08 17:20:17 -05:00
|
|
|
{processModelFileName}
|
2022-10-12 10:21:49 -04:00
|
|
|
</h2>
|
|
|
|
{appropriateEditor()}
|
|
|
|
{newFileNameBox()}
|
|
|
|
{scriptEditor()}
|
2022-11-07 14:35:49 -05:00
|
|
|
{markdownEditor()}
|
2022-10-12 10:21:49 -04:00
|
|
|
|
|
|
|
<div id="diagram-container" />
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|