Refactor bpmn diagram component (#1984)
* some initial refactoring to react diagram editor for pi show page w/ burnettk
* pi show and pm file show are now working w/ burnettk
* some minor tweaks w/ burnettk
* pi show is mostly working - need to handle future states w/ burnettk
* pass the task into the call activity overly so we can check the state w/ burnettk
* removed commented out code w/ burnettk
* fixed issue with process model new file not showing w/ burnettk
* fixed endless reload new bpmn file w/ burnettk
* do not bother zooming dmn tables w/ burnettk
* use a linke instead of a button to open the call activity so it can be opened in a new tab if desired w/ burnettk
* Revert "use a linke instead of a button to open the call activity so it can be opened in a new tab if desired w/ burnettk"
This reverts commit 1c12725cd3
.
* listen for auxclick to know if call activity should be opened in new tab w/ burnettk
---------
Co-authored-by: jasquat <jasquat@users.noreply.github.com>
This commit is contained in:
parent
01deb74680
commit
6f1af2132e
|
@ -111,29 +111,31 @@ describe('process-instances', () => {
|
|||
cy.contains(originalDmnOutputForKevin).should('not.exist');
|
||||
cy.runPrimaryBpmnFile();
|
||||
|
||||
const processModelFiles = 'process-model-files';
|
||||
|
||||
// Change dmn
|
||||
cy.getBySel('process-model-files').click();
|
||||
cy.getBySel(processModelFiles).click();
|
||||
cy.getBySel(`edit-file-${dmnFile.replace('.', '-')}`).click();
|
||||
updateDmnText(originalDmnOutputForKevin, newDmnOutputForKevin);
|
||||
|
||||
cy.contains(acceptanceTestOneDisplayName).click();
|
||||
cy.runPrimaryBpmnFile();
|
||||
|
||||
cy.getBySel('process-model-files').click();
|
||||
cy.getBySel(processModelFiles).click();
|
||||
cy.getBySel(`edit-file-${dmnFile.replace('.', '-')}`).click();
|
||||
updateDmnText(newDmnOutputForKevin, originalDmnOutputForKevin);
|
||||
cy.contains(acceptanceTestOneDisplayName).click();
|
||||
cy.runPrimaryBpmnFile();
|
||||
|
||||
// Change bpmn
|
||||
cy.getBySel('process-model-files').click();
|
||||
cy.getBySel(processModelFiles).click();
|
||||
cy.getBySel(`edit-file-${bpmnFile.replace('.', '-')}`).click();
|
||||
cy.contains(`Process Model File: ${bpmnFile}`);
|
||||
updateBpmnPythonScript(newPythonScript);
|
||||
cy.contains(acceptanceTestOneDisplayName).click();
|
||||
cy.runPrimaryBpmnFile();
|
||||
|
||||
cy.getBySel('process-model-files').click();
|
||||
cy.getBySel(processModelFiles).click();
|
||||
cy.getBySel(`edit-file-${bpmnFile.replace('.', '-')}`).click();
|
||||
updateBpmnPythonScript(originalPythonScript);
|
||||
cy.contains(acceptanceTestOneDisplayName).click();
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
// @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'dmn-... Remove this comment to see the full error message
|
||||
} from 'dmn-js-properties-panel';
|
||||
|
||||
import React, { useRef, useEffect, useState, useCallback } from 'react';
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
// @ts-ignore
|
||||
import { Button, ButtonSet, Modal, UnorderedList, Link } from '@carbon/react';
|
||||
|
||||
|
@ -77,6 +77,7 @@ type OwnProps = {
|
|||
disableSaveButton?: boolean;
|
||||
fileName?: string;
|
||||
isPrimaryFile?: boolean;
|
||||
onCallActivityOverlayClick?: (..._args: any[]) => any;
|
||||
onDataStoresRequested?: (..._args: any[]) => any;
|
||||
onDeleteFile?: (..._args: any[]) => any;
|
||||
onDmnFilesRequested?: (..._args: any[]) => any;
|
||||
|
@ -109,6 +110,7 @@ export default function ReactDiagramEditor({
|
|||
disableSaveButton,
|
||||
fileName,
|
||||
isPrimaryFile,
|
||||
onCallActivityOverlayClick,
|
||||
onDataStoresRequested,
|
||||
onDeleteFile,
|
||||
onDmnFilesRequested,
|
||||
|
@ -131,11 +133,9 @@ export default function ReactDiagramEditor({
|
|||
url,
|
||||
}: OwnProps) {
|
||||
const [diagramXMLString, setDiagramXMLString] = useState('');
|
||||
const [diagramModelerState, setDiagramModelerState] = useState(null);
|
||||
const [diagramModelerState, setDiagramModelerState] = useState<any>(null);
|
||||
const [performingXmlUpdates, setPerformingXmlUpdates] = useState(false);
|
||||
|
||||
const alreadyImportedXmlRef = useRef(false);
|
||||
|
||||
const { targetUris } = useUriListForPermissions();
|
||||
const permissionRequestData: PermissionsToCheck = {};
|
||||
|
||||
|
@ -203,11 +203,8 @@ export default function ReactDiagramEditor({
|
|||
});
|
||||
};
|
||||
|
||||
// get the xml and set the modeler
|
||||
useEffect(() => {
|
||||
if (diagramModelerState) {
|
||||
return;
|
||||
}
|
||||
|
||||
let canvasClass = 'diagram-editor-canvas';
|
||||
if (diagramType === 'readonly') {
|
||||
canvasClass = 'diagram-viewer-canvas';
|
||||
|
@ -436,7 +433,6 @@ export default function ReactDiagramEditor({
|
|||
}
|
||||
});
|
||||
}, [
|
||||
diagramModelerState,
|
||||
diagramType,
|
||||
onDataStoresRequested,
|
||||
onDmnFilesRequested,
|
||||
|
@ -447,13 +443,26 @@ export default function ReactDiagramEditor({
|
|||
onLaunchDmnEditor,
|
||||
onLaunchJsonSchemaEditor,
|
||||
onLaunchMarkdownEditor,
|
||||
onLaunchScriptEditor,
|
||||
onLaunchMessageEditor,
|
||||
onLaunchScriptEditor,
|
||||
onMessagesRequested,
|
||||
onSearchProcessModels,
|
||||
onServiceTasksRequested,
|
||||
]);
|
||||
|
||||
// display the diagram
|
||||
useEffect(() => {
|
||||
if (!diagramXMLString || !diagramModelerState) {
|
||||
return;
|
||||
}
|
||||
diagramModelerState.importXML(diagramXMLString);
|
||||
zoom(0);
|
||||
if (diagramType !== 'dmn') {
|
||||
fixUnresolvedReferences(diagramModelerState);
|
||||
}
|
||||
}, [diagramXMLString, diagramModelerState, diagramType, zoom]);
|
||||
|
||||
// import done operations
|
||||
useEffect(() => {
|
||||
// These seem to be system tasks that cannot be highlighted
|
||||
const taskSpecsThatCannotBeHighlighted = ['Root', 'Start', 'End'];
|
||||
|
@ -507,6 +516,63 @@ export default function ReactDiagramEditor({
|
|||
}
|
||||
}
|
||||
|
||||
function addOverlayOnCallActivity(
|
||||
task: Task,
|
||||
bpmnProcessIdentifiers: string[],
|
||||
) {
|
||||
if (
|
||||
!onCallActivityOverlayClick ||
|
||||
diagramType !== 'readonly' ||
|
||||
!diagramModelerState
|
||||
) {
|
||||
return;
|
||||
}
|
||||
function domify(htmlString: string) {
|
||||
const template = document.createElement('template');
|
||||
template.innerHTML = htmlString.trim();
|
||||
return template.content.firstChild;
|
||||
}
|
||||
const createCallActivityOverlay = () => {
|
||||
const overlays = diagramModelerState.get('overlays');
|
||||
const ARROW_DOWN_SVG =
|
||||
'<svg width="20" height="20" viewBox="0 0 24.00 24.00" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="#ffffff"> <g id="SVGRepo_bgCarrier" stroke-width="0"> <rect x="0" y="0" width="24.00" height="24.00" rx="0" fill="#2196f3" strokewidth="0"/> </g> <g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round" stroke="#CCCCCC" stroke-width="0.048"/> <g id="SVGRepo_iconCarrier"> <path d="M7 17L17 7M17 7H8M17 7V16" stroke="#ffffff" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/> </g> </svg>';
|
||||
const button: any = domify(
|
||||
`<button class="bjs-drilldown">${ARROW_DOWN_SVG}</button>`,
|
||||
);
|
||||
button.addEventListener('click', (newEvent: any) => {
|
||||
onCallActivityOverlayClick(task, newEvent);
|
||||
});
|
||||
button.addEventListener('auxclick', (newEvent: any) => {
|
||||
onCallActivityOverlayClick(task, newEvent);
|
||||
});
|
||||
overlays.add(task.bpmn_identifier, 'drilldown', {
|
||||
position: {
|
||||
bottom: -10,
|
||||
right: -8,
|
||||
},
|
||||
html: button,
|
||||
});
|
||||
};
|
||||
try {
|
||||
if (
|
||||
bpmnProcessIdentifiers.includes(
|
||||
task.bpmn_process_definition_identifier,
|
||||
)
|
||||
) {
|
||||
createCallActivityOverlay();
|
||||
}
|
||||
} catch (bpmnIoError: any) {
|
||||
// the task list also contains task for processes called from call activities which will
|
||||
// not exist in this diagram so just ignore them for now.
|
||||
if (
|
||||
bpmnIoError.message !==
|
||||
"Cannot read properties of undefined (reading 'id')"
|
||||
) {
|
||||
throw bpmnIoError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onImportDone(event: any) {
|
||||
const { error } = event;
|
||||
|
||||
|
@ -515,12 +581,11 @@ export default function ReactDiagramEditor({
|
|||
return;
|
||||
}
|
||||
|
||||
let modeler = diagramModelerState;
|
||||
if (diagramType === 'dmn') {
|
||||
modeler = (diagramModelerState as any).getActiveViewer();
|
||||
return;
|
||||
}
|
||||
|
||||
const canvas = (modeler as any).get('canvas');
|
||||
const canvas = diagramModelerState.get('canvas');
|
||||
canvas.zoom(FitViewport, 'auto'); // Concerned this might bug out somehow.
|
||||
|
||||
// highlighting a field
|
||||
|
@ -549,25 +614,16 @@ export default function ReactDiagramEditor({
|
|||
bpmnProcessIdentifiers,
|
||||
);
|
||||
}
|
||||
if (
|
||||
task.typename === 'CallActivity' &&
|
||||
!['FUTURE', 'LIKELY', 'MAYBE'].includes(task.state)
|
||||
) {
|
||||
addOverlayOnCallActivity(task, bpmnProcessIdentifiers);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function displayDiagram(
|
||||
diagramModelerToUse: any,
|
||||
diagramXMLToDisplay: any,
|
||||
) {
|
||||
if (alreadyImportedXmlRef.current) {
|
||||
return;
|
||||
}
|
||||
diagramModelerToUse.importXML(diagramXMLToDisplay);
|
||||
zoom(0);
|
||||
if (diagramType !== 'dmn') {
|
||||
fixUnresolvedReferences(diagramModelerToUse);
|
||||
}
|
||||
alreadyImportedXmlRef.current = true;
|
||||
}
|
||||
|
||||
function dmnTextHandler(text: string) {
|
||||
const decisionId = `decision_${makeid(7)}`;
|
||||
const newText = text.replaceAll('{{DECISION_ID}}', decisionId);
|
||||
|
@ -586,7 +642,7 @@ export default function ReactDiagramEditor({
|
|||
) {
|
||||
fetch(urlToUse)
|
||||
.then((response) => response.text())
|
||||
.then(textHandler ?? bpmnTextHandler)
|
||||
.then(textHandler)
|
||||
.catch((err) => handleError(err));
|
||||
}
|
||||
|
||||
|
@ -602,17 +658,12 @@ export default function ReactDiagramEditor({
|
|||
}
|
||||
(diagramModelerState as any).on('import.done', onImportDone);
|
||||
|
||||
const diagramXMLToUse = diagramXML || diagramXMLString;
|
||||
if (diagramXMLToUse) {
|
||||
if (!diagramXMLString) {
|
||||
setDiagramXMLString(diagramXMLToUse);
|
||||
}
|
||||
displayDiagram(diagramModelerState, diagramXMLToUse);
|
||||
|
||||
if (diagramXML) {
|
||||
setDiagramXMLString(diagramXML);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!diagramXMLString) {
|
||||
if (!diagramXML) {
|
||||
if (url) {
|
||||
fetchDiagramFromURL(url);
|
||||
return undefined;
|
||||
|
@ -622,7 +673,7 @@ export default function ReactDiagramEditor({
|
|||
return undefined;
|
||||
}
|
||||
let newDiagramFileName = 'new_bpmn_diagram.bpmn';
|
||||
let textHandler;
|
||||
let textHandler = bpmnTextHandler;
|
||||
if (diagramType === 'dmn') {
|
||||
newDiagramFileName = 'new_dmn_diagram.dmn';
|
||||
textHandler = dmnTextHandler;
|
||||
|
@ -638,13 +689,12 @@ export default function ReactDiagramEditor({
|
|||
diagramModelerState,
|
||||
diagramType,
|
||||
diagramXML,
|
||||
diagramXMLString,
|
||||
fileName,
|
||||
tasks,
|
||||
onCallActivityOverlayClick,
|
||||
performingXmlUpdates,
|
||||
processModelId,
|
||||
tasks,
|
||||
url,
|
||||
zoom,
|
||||
]);
|
||||
|
||||
function handleSave() {
|
||||
|
|
|
@ -68,7 +68,6 @@ import {
|
|||
ProcessInstance,
|
||||
ProcessModel,
|
||||
Task,
|
||||
TaskDefinitionPropertiesJson,
|
||||
User,
|
||||
} from '../interfaces';
|
||||
import { usePermissionFetcher } from '../hooks/PermissionService';
|
||||
|
@ -707,21 +706,24 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
|||
return <div />;
|
||||
};
|
||||
|
||||
const initializeTaskInstancesToDisplay = (task: Task | null) => {
|
||||
if (!task) {
|
||||
return;
|
||||
}
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/tasks/${params.process_instance_id}/${task.guid}/task-instances`,
|
||||
httpMethod: 'GET',
|
||||
// reverse operates on self as well as return the new ordered array so reverse it right away
|
||||
successCallback: (results: Task[]) =>
|
||||
setTaskInstancesToDisplay(results.reverse()),
|
||||
failureCallback: (error: any) => {
|
||||
setTaskDataToDisplay(`ERROR: ${error.message}`);
|
||||
},
|
||||
});
|
||||
};
|
||||
const initializeTaskInstancesToDisplay = useCallback(
|
||||
(task: Task | null) => {
|
||||
if (!task) {
|
||||
return;
|
||||
}
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/tasks/${params.process_instance_id}/${task.guid}/task-instances`,
|
||||
httpMethod: 'GET',
|
||||
// reverse operates on self as well as return the new ordered array so reverse it right away
|
||||
successCallback: (results: Task[]) =>
|
||||
setTaskInstancesToDisplay(results.reverse()),
|
||||
failureCallback: (error: any) => {
|
||||
setTaskDataToDisplay(`ERROR: ${error.message}`);
|
||||
},
|
||||
});
|
||||
},
|
||||
[params.process_instance_id],
|
||||
);
|
||||
|
||||
const processTaskResult = (result: Task) => {
|
||||
if (result == null) {
|
||||
|
@ -732,26 +734,29 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
|||
setShowTaskDataLoading(false);
|
||||
};
|
||||
|
||||
const initializeTaskDataToDisplay = (task: Task | null) => {
|
||||
if (
|
||||
task &&
|
||||
['COMPLETED', 'ERROR', 'READY'].includes(task.state) &&
|
||||
ability.can('GET', targetUris.processInstanceTaskDataPath)
|
||||
) {
|
||||
setShowTaskDataLoading(true);
|
||||
HttpService.makeCallToBackend({
|
||||
path: `${targetUris.processInstanceTaskDataPath}/${task.guid}`,
|
||||
httpMethod: 'GET',
|
||||
successCallback: processTaskResult,
|
||||
failureCallback: (error: any) => {
|
||||
setTaskDataToDisplay(`ERROR: ${error.message}`);
|
||||
setShowTaskDataLoading(false);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
setTaskDataToDisplay('');
|
||||
}
|
||||
};
|
||||
const initializeTaskDataToDisplay = useCallback(
|
||||
(task: Task | null) => {
|
||||
if (
|
||||
task &&
|
||||
['COMPLETED', 'ERROR', 'READY'].includes(task.state) &&
|
||||
ability.can('GET', targetUris.processInstanceTaskDataPath)
|
||||
) {
|
||||
setShowTaskDataLoading(true);
|
||||
HttpService.makeCallToBackend({
|
||||
path: `${targetUris.processInstanceTaskDataPath}/${task.guid}`,
|
||||
httpMethod: 'GET',
|
||||
successCallback: processTaskResult,
|
||||
failureCallback: (error: any) => {
|
||||
setTaskDataToDisplay(`ERROR: ${error.message}`);
|
||||
setShowTaskDataLoading(false);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
setTaskDataToDisplay('');
|
||||
}
|
||||
},
|
||||
[ability, targetUris.processInstanceTaskDataPath],
|
||||
);
|
||||
|
||||
const handleProcessDataDisplayClose = () => {
|
||||
setProcessDataToDisplay(null);
|
||||
|
@ -807,72 +812,117 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
|||
setProcessDataToDisplay(processData);
|
||||
};
|
||||
|
||||
const makeProcessDataCallFromShapeElement = (shapeElement: any) => {
|
||||
const { dataObjectRef } = shapeElement.businessObject;
|
||||
let category = 'default';
|
||||
if ('extensionElements' in dataObjectRef) {
|
||||
const categoryExtension = dataObjectRef.extensionElements.values.find(
|
||||
(extension: any) => {
|
||||
return extension.$type === 'spiffworkflow:category';
|
||||
},
|
||||
);
|
||||
if (categoryExtension) {
|
||||
category = categoryExtension.$body;
|
||||
}
|
||||
}
|
||||
const dataObjectIdentifer = dataObjectRef.id;
|
||||
const parentProcess = shapeElement.businessObject.$parent;
|
||||
const parentProcessIdentifier = parentProcess.id;
|
||||
|
||||
let additionalParams = '';
|
||||
if (tasks) {
|
||||
const matchingTask: Task | undefined = tasks.find((task: Task) => {
|
||||
return task.bpmn_identifier === parentProcessIdentifier;
|
||||
});
|
||||
if (matchingTask) {
|
||||
additionalParams = `?process_identifier=${parentProcessIdentifier}&bpmn_process_guid=${matchingTask.guid}`;
|
||||
} else if (
|
||||
searchParams.get('process_identifier') &&
|
||||
searchParams.get('bpmn_process_guid')
|
||||
) {
|
||||
additionalParams = `?process_identifier=${searchParams.get(
|
||||
'process_identifier',
|
||||
)}&bpmn_process_guid=${searchParams.get('bpmn_process_guid')}`;
|
||||
}
|
||||
}
|
||||
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-data/${category}/${params.process_model_id}/${dataObjectIdentifer}/${params.process_instance_id}${additionalParams}`,
|
||||
httpMethod: 'GET',
|
||||
successCallback: handleProcessDataShowResponse,
|
||||
failureCallback: addError,
|
||||
onUnauthorized: (result: any) =>
|
||||
handleProcessDataShowReponseUnauthorized(dataObjectIdentifer, result),
|
||||
});
|
||||
};
|
||||
|
||||
const handleClickedDiagramTask = (
|
||||
shapeElement: any,
|
||||
bpmnProcessIdentifiers: any,
|
||||
) => {
|
||||
if (shapeElement.type === 'bpmn:DataObjectReference') {
|
||||
makeProcessDataCallFromShapeElement(shapeElement);
|
||||
} else if (tasks) {
|
||||
const matchingTask: Task | undefined = tasks.find((task: Task) => {
|
||||
return (
|
||||
task.bpmn_identifier === shapeElement.id &&
|
||||
bpmnProcessIdentifiers.includes(
|
||||
task.bpmn_process_definition_identifier,
|
||||
)
|
||||
const makeProcessDataCallFromShapeElement = useCallback(
|
||||
(shapeElement: any) => {
|
||||
const { dataObjectRef } = shapeElement.businessObject;
|
||||
let category = 'default';
|
||||
if ('extensionElements' in dataObjectRef) {
|
||||
const categoryExtension = dataObjectRef.extensionElements.values.find(
|
||||
(extension: any) => {
|
||||
return extension.$type === 'spiffworkflow:category';
|
||||
},
|
||||
);
|
||||
});
|
||||
if (matchingTask) {
|
||||
setTaskToDisplay(matchingTask);
|
||||
initializeTaskDataToDisplay(matchingTask);
|
||||
initializeTaskInstancesToDisplay(matchingTask);
|
||||
if (categoryExtension) {
|
||||
category = categoryExtension.$body;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const dataObjectIdentifer = dataObjectRef.id;
|
||||
const parentProcess = shapeElement.businessObject.$parent;
|
||||
const parentProcessIdentifier = parentProcess.id;
|
||||
|
||||
let additionalParams = '';
|
||||
if (tasks) {
|
||||
const matchingTask: Task | undefined = tasks.find((task: Task) => {
|
||||
return task.bpmn_identifier === parentProcessIdentifier;
|
||||
});
|
||||
if (matchingTask) {
|
||||
additionalParams = `?process_identifier=${parentProcessIdentifier}&bpmn_process_guid=${matchingTask.guid}`;
|
||||
} else if (processIdentifier && bpmnProcessGuid) {
|
||||
additionalParams = `?process_identifier=${processIdentifier}&bpmn_process_guid=${bpmnProcessGuid}`;
|
||||
}
|
||||
}
|
||||
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/process-data/${category}/${params.process_model_id}/${dataObjectIdentifer}/${params.process_instance_id}${additionalParams}`,
|
||||
httpMethod: 'GET',
|
||||
successCallback: handleProcessDataShowResponse,
|
||||
failureCallback: addError,
|
||||
onUnauthorized: (result: any) =>
|
||||
handleProcessDataShowReponseUnauthorized(dataObjectIdentifer, result),
|
||||
});
|
||||
},
|
||||
[
|
||||
addError,
|
||||
params.process_instance_id,
|
||||
params.process_model_id,
|
||||
tasks,
|
||||
bpmnProcessGuid,
|
||||
processIdentifier,
|
||||
],
|
||||
);
|
||||
|
||||
const findMatchingTaskFromShapeElement = useCallback(
|
||||
(shapeElement: any, bpmnProcessIdentifiers: any) => {
|
||||
if (tasks) {
|
||||
const matchingTask: Task | undefined = tasks.find((task: Task) => {
|
||||
return (
|
||||
task.bpmn_identifier === shapeElement.id &&
|
||||
bpmnProcessIdentifiers.includes(
|
||||
task.bpmn_process_definition_identifier,
|
||||
)
|
||||
);
|
||||
});
|
||||
return matchingTask;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
[tasks],
|
||||
);
|
||||
|
||||
const handleCallActivityNavigate = useCallback(
|
||||
(task: Task, event: any) => {
|
||||
if (
|
||||
task &&
|
||||
task.typename === 'CallActivity' &&
|
||||
!['FUTURE', 'LIKELY', 'MAYBE'].includes(task.state)
|
||||
) {
|
||||
const processIdentifierToUse =
|
||||
task.task_definition_properties_json.spec;
|
||||
const url = `${window.location.pathname}?process_identifier=${processIdentifierToUse}&bpmn_process_guid=${task.guid}`;
|
||||
if (event.type === 'auxclick') {
|
||||
window.open(url);
|
||||
} else {
|
||||
navigate(url);
|
||||
}
|
||||
}
|
||||
},
|
||||
[navigate],
|
||||
);
|
||||
|
||||
const handleClickedDiagramTask = useCallback(
|
||||
(shapeElement: any, bpmnProcessIdentifiers: any) => {
|
||||
if (shapeElement.type === 'bpmn:DataObjectReference') {
|
||||
makeProcessDataCallFromShapeElement(shapeElement);
|
||||
} else if (tasks) {
|
||||
const matchingTask = findMatchingTaskFromShapeElement(
|
||||
shapeElement,
|
||||
bpmnProcessIdentifiers,
|
||||
);
|
||||
if (matchingTask) {
|
||||
setTaskToDisplay(matchingTask);
|
||||
initializeTaskDataToDisplay(matchingTask);
|
||||
initializeTaskInstancesToDisplay(matchingTask);
|
||||
}
|
||||
}
|
||||
},
|
||||
[
|
||||
findMatchingTaskFromShapeElement,
|
||||
initializeTaskDataToDisplay,
|
||||
initializeTaskInstancesToDisplay,
|
||||
makeProcessDataCallFromShapeElement,
|
||||
tasks,
|
||||
],
|
||||
);
|
||||
|
||||
const resetTaskActionDetails = () => {
|
||||
setEditingTaskData(false);
|
||||
|
@ -1125,23 +1175,6 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
|||
);
|
||||
}
|
||||
|
||||
if (
|
||||
task.typename === 'CallActivity' &&
|
||||
!['FUTURE', 'LIKELY', 'MAYBE'].includes(task.state)
|
||||
) {
|
||||
const taskDefinitionPropertiesJson: TaskDefinitionPropertiesJson =
|
||||
task.task_definition_properties_json;
|
||||
buttons.push(
|
||||
<Link
|
||||
data-qa="go-to-call-activity-result"
|
||||
to={`${window.location.pathname}?process_identifier=${taskDefinitionPropertiesJson.spec}&bpmn_process_guid=${task.guid}`}
|
||||
target="_blank"
|
||||
>
|
||||
View Call Activity Diagram
|
||||
</Link>,
|
||||
);
|
||||
}
|
||||
|
||||
if (canEditTaskData(task)) {
|
||||
buttons.push(
|
||||
<Button
|
||||
|
@ -1723,6 +1756,7 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
|||
tasks={tasks}
|
||||
diagramType="readonly"
|
||||
onElementClick={handleClickedDiagramTask}
|
||||
onCallActivityOverlayClick={handleCallActivityNavigate}
|
||||
/>
|
||||
<div id="diagram-container" />
|
||||
</>
|
||||
|
|
|
@ -295,9 +295,9 @@ export default function ProcessModelEditDiagram() {
|
|||
setDiagramHasChanges(false);
|
||||
};
|
||||
|
||||
const onElementsChanged = () => {
|
||||
const onElementsChanged = useCallback(() => {
|
||||
setDiagramHasChanges(true);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onDeleteFile = (fileName = params.file_name) => {
|
||||
const url = `/process-models/${modifiedProcessModelId}/files/${fileName}`;
|
||||
|
@ -414,51 +414,60 @@ export default function ProcessModelEditDiagram() {
|
|||
};
|
||||
};
|
||||
|
||||
const onServiceTasksRequested = (event: any) => {
|
||||
const onServiceTasksRequested = useCallback((event: any) => {
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/service-tasks`,
|
||||
successCallback: makeApiHandler(event),
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onDataStoresRequested = (event: any) => {
|
||||
const processGroupIdentifier =
|
||||
processModel?.parent_groups?.slice(-1).pop()?.id ?? '';
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/data-stores?upsearch=true&process_group_identifier=${processGroupIdentifier}`,
|
||||
successCallback: makeDataStoresApiHandler(event),
|
||||
});
|
||||
};
|
||||
|
||||
const onJsonSchemaFilesRequested = (event: any) => {
|
||||
setFileEventBus(event.eventBus);
|
||||
const re = /.*[-.]schema.json/;
|
||||
if (processModel) {
|
||||
const jsonFiles = processModel.files.filter((f) => f.name.match(re));
|
||||
const options = jsonFiles.map((f) => {
|
||||
return { label: f.name, value: f.name };
|
||||
const onDataStoresRequested = useCallback(
|
||||
(event: any) => {
|
||||
const processGroupIdentifier =
|
||||
processModel?.parent_groups?.slice(-1).pop()?.id ?? '';
|
||||
HttpService.makeCallToBackend({
|
||||
path: `/data-stores?upsearch=true&process_group_identifier=${processGroupIdentifier}`,
|
||||
successCallback: makeDataStoresApiHandler(event),
|
||||
});
|
||||
event.eventBus.fire('spiff.json_schema_files.returned', { options });
|
||||
} else {
|
||||
console.error('There is no process Model.');
|
||||
}
|
||||
};
|
||||
},
|
||||
[processModel?.parent_groups],
|
||||
);
|
||||
|
||||
const onDmnFilesRequested = (event: any) => {
|
||||
setFileEventBus(event.eventBus);
|
||||
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.display_name, value: ref.identifier });
|
||||
const onJsonSchemaFilesRequested = useCallback(
|
||||
(event: any) => {
|
||||
setFileEventBus(event.eventBus);
|
||||
const re = /.*[-.]schema.json/;
|
||||
if (processModel) {
|
||||
const jsonFiles = processModel.files.filter((f) => f.name.match(re));
|
||||
const options = jsonFiles.map((f) => {
|
||||
return { label: f.name, value: f.name };
|
||||
});
|
||||
});
|
||||
event.eventBus.fire('spiff.dmn_files.returned', { options });
|
||||
} else {
|
||||
console.error('There is no process model.');
|
||||
}
|
||||
};
|
||||
event.eventBus.fire('spiff.json_schema_files.returned', { options });
|
||||
} else {
|
||||
console.error('There is no process Model.');
|
||||
}
|
||||
},
|
||||
[processModel],
|
||||
);
|
||||
|
||||
const onDmnFilesRequested = useCallback(
|
||||
(event: any) => {
|
||||
setFileEventBus(event.eventBus);
|
||||
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.display_name, value: ref.identifier });
|
||||
});
|
||||
});
|
||||
event.eventBus.fire('spiff.dmn_files.returned', { options });
|
||||
} else {
|
||||
console.error('There is no process model.');
|
||||
}
|
||||
},
|
||||
[processModel],
|
||||
);
|
||||
|
||||
const makeMessagesRequestedHandler = (event: any) => {
|
||||
return function fireEvent(results: any) {
|
||||
|
@ -467,20 +476,23 @@ export default function ProcessModelEditDiagram() {
|
|||
});
|
||||
};
|
||||
};
|
||||
const onMessagesRequested = (event: any) => {
|
||||
// it is perfectly reasonable to access the edit diagram page in read only mode when you actually don't have access to edit.
|
||||
// this is awkward in terms of functionality like this, where we are fetching the relevant list of messages to show in the
|
||||
// properties panel. since message_model_list is a different permission, you may not have access to it even though you have
|
||||
// access to the read the process model. we also considered automatically giving you access to read message_model_list
|
||||
// when you have read access to the process model, but this seemed easier and more in line with the current backend permission system,
|
||||
// where we normally only pork barrel permissions on top of "start" and "all."
|
||||
if (ability.can('GET', targetUris.messageModelListPath)) {
|
||||
HttpService.makeCallToBackend({
|
||||
path: targetUris.messageModelListPath,
|
||||
successCallback: makeMessagesRequestedHandler(event),
|
||||
});
|
||||
}
|
||||
};
|
||||
const onMessagesRequested = useCallback(
|
||||
(event: any) => {
|
||||
// it is perfectly reasonable to access the edit diagram page in read only mode when you actually don't have access to edit.
|
||||
// this is awkward in terms of functionality like this, where we are fetching the relevant list of messages to show in the
|
||||
// properties panel. since message_model_list is a different permission, you may not have access to it even though you have
|
||||
// access to the read the process model. we also considered automatically giving you access to read message_model_list
|
||||
// when you have read access to the process model, but this seemed easier and more in line with the current backend permission system,
|
||||
// where we normally only pork barrel permissions on top of "start" and "all."
|
||||
if (ability.can('GET', targetUris.messageModelListPath)) {
|
||||
HttpService.makeCallToBackend({
|
||||
path: targetUris.messageModelListPath,
|
||||
successCallback: makeMessagesRequestedHandler(event),
|
||||
});
|
||||
}
|
||||
},
|
||||
[ability, targetUris.messageModelListPath],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const updateDiagramFiles = (pm: ProcessModel) => {
|
||||
|
@ -518,35 +530,38 @@ export default function ProcessModelEditDiagram() {
|
|||
return [];
|
||||
};
|
||||
|
||||
const setScriptUnitTestElementWithIndex = (
|
||||
scriptIndex: number,
|
||||
element: any = scriptElement,
|
||||
) => {
|
||||
const unitTestsModdleElements = getScriptUnitTestElements(element);
|
||||
if (unitTestsModdleElements.length > 0) {
|
||||
setCurrentScriptUnitTest(unitTestsModdleElements[scriptIndex]);
|
||||
setCurrentScriptUnitTestIndex(scriptIndex);
|
||||
}
|
||||
};
|
||||
const setScriptUnitTestElementWithIndex = useCallback(
|
||||
(scriptIndex: number, element: any = scriptElement) => {
|
||||
const unitTestsModdleElements = getScriptUnitTestElements(element);
|
||||
if (unitTestsModdleElements.length > 0) {
|
||||
setCurrentScriptUnitTest(unitTestsModdleElements[scriptIndex]);
|
||||
setCurrentScriptUnitTestIndex(scriptIndex);
|
||||
}
|
||||
},
|
||||
[scriptElement],
|
||||
);
|
||||
|
||||
const onLaunchScriptEditor = (
|
||||
element: any,
|
||||
script: string,
|
||||
scriptTypeString: string,
|
||||
eventBus: any,
|
||||
modeling: any,
|
||||
) => {
|
||||
// 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
|
||||
setScriptModeling(modeling);
|
||||
setScriptText(script || '');
|
||||
setScriptType(scriptTypeString);
|
||||
setScriptEventBus(eventBus);
|
||||
setScriptElement(element);
|
||||
setScriptUnitTestElementWithIndex(0, element);
|
||||
handleShowScriptEditor();
|
||||
};
|
||||
const onLaunchScriptEditor = useCallback(
|
||||
(
|
||||
element: any,
|
||||
script: string,
|
||||
scriptTypeString: string,
|
||||
eventBus: any,
|
||||
modeling: any,
|
||||
) => {
|
||||
// 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
|
||||
setScriptModeling(modeling);
|
||||
setScriptText(script || '');
|
||||
setScriptType(scriptTypeString);
|
||||
setScriptEventBus(eventBus);
|
||||
setScriptElement(element);
|
||||
setScriptUnitTestElementWithIndex(0, element);
|
||||
handleShowScriptEditor();
|
||||
},
|
||||
[setScriptUnitTestElementWithIndex],
|
||||
);
|
||||
|
||||
const handleScriptEditorClose = () => {
|
||||
scriptEventBus.fire('spiff.script.update', {
|
||||
|
@ -1045,15 +1060,16 @@ export default function ProcessModelEditDiagram() {
|
|||
</Modal>
|
||||
);
|
||||
};
|
||||
const onLaunchMarkdownEditor = (
|
||||
_element: any,
|
||||
markdown: string,
|
||||
eventBus: any,
|
||||
) => {
|
||||
setMarkdownText(markdown || '');
|
||||
setMarkdownEventBus(eventBus);
|
||||
handleShowMarkdownEditor();
|
||||
};
|
||||
|
||||
const onLaunchMarkdownEditor = useCallback(
|
||||
(_element: any, markdown: string, eventBus: any) => {
|
||||
setMarkdownText(markdown || '');
|
||||
setMarkdownEventBus(eventBus);
|
||||
handleShowMarkdownEditor();
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleMarkdownEditorClose = () => {
|
||||
markdownEventBus.fire('spiff.markdown.update', {
|
||||
value: markdownText,
|
||||
|
@ -1083,13 +1099,14 @@ export default function ProcessModelEditDiagram() {
|
|||
);
|
||||
};
|
||||
|
||||
const onLaunchMessageEditor = (event: any) => {
|
||||
const onLaunchMessageEditor = useCallback((event: any) => {
|
||||
setMessageEvent(event);
|
||||
setMessageId(event.value.messageId);
|
||||
setElementId(event.value.elementId);
|
||||
setCorrelationProperties(event.value.correlation_properties);
|
||||
handleShowMessageEditor();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleMessageEditorClose = () => {
|
||||
setShowMessageEditor(false);
|
||||
onMessagesRequested(messageEvent);
|
||||
|
@ -1132,15 +1149,14 @@ export default function ProcessModelEditDiagram() {
|
|||
);
|
||||
};
|
||||
|
||||
const onSearchProcessModels = (
|
||||
_processId: string,
|
||||
eventBus: any,
|
||||
element: any,
|
||||
) => {
|
||||
setProcessSearchEventBus(eventBus);
|
||||
setProcessSearchElement(element);
|
||||
setShowProcessSearch(true);
|
||||
};
|
||||
const onSearchProcessModels = useCallback(
|
||||
(_processId: string, eventBus: any, element: any) => {
|
||||
setProcessSearchEventBus(eventBus);
|
||||
setProcessSearchElement(element);
|
||||
setShowProcessSearch(true);
|
||||
},
|
||||
[],
|
||||
);
|
||||
const processSearchOnClose = (selection: CarbonComboBoxProcessSelection) => {
|
||||
const selectedProcessModel = selection.selectedItem;
|
||||
if (selectedProcessModel) {
|
||||
|
@ -1172,79 +1188,81 @@ export default function ProcessModelEditDiagram() {
|
|||
);
|
||||
};
|
||||
|
||||
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.identifier === id)) {
|
||||
matchFile = file;
|
||||
return true;
|
||||
const findFileNameForReferenceId = useCallback(
|
||||
(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.identifier === id)) {
|
||||
matchFile = file;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
return matchFile;
|
||||
},
|
||||
[processModel],
|
||||
);
|
||||
|
||||
const onLaunchBpmnEditor = useCallback(
|
||||
(processId: string) => {
|
||||
const openProcessModelFileInNewTab = (
|
||||
processReference: ProcessReference,
|
||||
) => {
|
||||
const path = generatePath(
|
||||
'/editor/process-models/:process_model_path/files/:file_name',
|
||||
{
|
||||
process_model_path: modifyProcessIdentifierForPathParam(
|
||||
processReference.relative_location,
|
||||
),
|
||||
file_name: processReference.file_name,
|
||||
},
|
||||
);
|
||||
window.open(path);
|
||||
};
|
||||
|
||||
const openFileNameForProcessId = (
|
||||
processesReferences: ProcessReference[],
|
||||
) => {
|
||||
const processRef = processesReferences.find((p) => {
|
||||
return p.identifier === processId;
|
||||
});
|
||||
if (processRef) {
|
||||
openProcessModelFileInNewTab(processRef);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// using the "setState" method with a function gives us access to the
|
||||
// most current state of processes. Otherwise it uses the stale state
|
||||
// when passing the callback to a non-React component like bpmn-js:
|
||||
// https://stackoverflow.com/a/60643670/6090676
|
||||
setProcesses((upToDateProcesses: ProcessReference[]) => {
|
||||
const processRef = upToDateProcesses.find((p) => {
|
||||
return p.identifier === processId;
|
||||
});
|
||||
if (!processRef) {
|
||||
getProcessesCallback(openFileNameForProcessId);
|
||||
} else {
|
||||
openProcessModelFileInNewTab(processRef);
|
||||
}
|
||||
return upToDateProcesses;
|
||||
});
|
||||
}
|
||||
return matchFile;
|
||||
};
|
||||
},
|
||||
[getProcessesCallback],
|
||||
);
|
||||
|
||||
const onLaunchBpmnEditor = (processId: string) => {
|
||||
const openProcessModelFileInNewTab = (
|
||||
processReference: ProcessReference,
|
||||
) => {
|
||||
const path = generatePath(
|
||||
'/editor/process-models/:process_model_path/files/:file_name',
|
||||
{
|
||||
process_model_path: modifyProcessIdentifierForPathParam(
|
||||
processReference.relative_location,
|
||||
),
|
||||
file_name: processReference.file_name,
|
||||
},
|
||||
);
|
||||
window.open(path);
|
||||
};
|
||||
|
||||
const openFileNameForProcessId = (
|
||||
processesReferences: ProcessReference[],
|
||||
) => {
|
||||
const processRef = processesReferences.find((p) => {
|
||||
return p.identifier === processId;
|
||||
});
|
||||
if (processRef) {
|
||||
openProcessModelFileInNewTab(processRef);
|
||||
}
|
||||
};
|
||||
|
||||
// using the "setState" method with a function gives us access to the
|
||||
// most current state of processes. Otherwise it uses the stale state
|
||||
// when passing the callback to a non-React component like bpmn-js:
|
||||
// https://stackoverflow.com/a/60643670/6090676
|
||||
setProcesses((upToDateProcesses: ProcessReference[]) => {
|
||||
const processRef = upToDateProcesses.find((p) => {
|
||||
return p.identifier === processId;
|
||||
});
|
||||
if (!processRef) {
|
||||
getProcessesCallback(openFileNameForProcessId);
|
||||
} else {
|
||||
openProcessModelFileInNewTab(processRef);
|
||||
}
|
||||
return upToDateProcesses;
|
||||
});
|
||||
};
|
||||
|
||||
const onLaunchJsonSchemaEditor = (
|
||||
_element: any,
|
||||
fileName: string,
|
||||
eventBus: any,
|
||||
) => {
|
||||
setFileEventBus(eventBus);
|
||||
setJsonScehmaFileName(fileName);
|
||||
setShowJsonSchemaEditor(true);
|
||||
};
|
||||
const onLaunchJsonSchemaEditor = useCallback(
|
||||
(_element: any, fileName: string, eventBus: any) => {
|
||||
setFileEventBus(eventBus);
|
||||
setJsonScehmaFileName(fileName);
|
||||
setShowJsonSchemaEditor(true);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleJsonScehmaEditorClose = () => {
|
||||
fileEventBus.fire('spiff.jsonSchema.update', {
|
||||
|
@ -1283,28 +1301,31 @@ export default function ProcessModelEditDiagram() {
|
|||
);
|
||||
};
|
||||
|
||||
const onLaunchDmnEditor = (processId: string) => {
|
||||
const file = findFileNameForReferenceId(processId, 'dmn');
|
||||
let path = '';
|
||||
if (file) {
|
||||
path = generatePath(
|
||||
'/editor/process-models/:process_model_id/files/:file_name',
|
||||
{
|
||||
process_model_id: params.process_model_id || null,
|
||||
file_name: file.name,
|
||||
},
|
||||
);
|
||||
const onLaunchDmnEditor = useCallback(
|
||||
(processId: string) => {
|
||||
const file = findFileNameForReferenceId(processId, 'dmn');
|
||||
let path = '';
|
||||
if (file) {
|
||||
path = generatePath(
|
||||
'/editor/process-models/:process_model_id/files/:file_name',
|
||||
{
|
||||
process_model_id: params.process_model_id || null,
|
||||
file_name: file.name,
|
||||
},
|
||||
);
|
||||
window.open(path);
|
||||
} else {
|
||||
path = generatePath(
|
||||
'/editor/process-models/:process_model_id/files?file_type=dmn',
|
||||
{
|
||||
process_model_id: params.process_model_id || null,
|
||||
},
|
||||
);
|
||||
}
|
||||
window.open(path);
|
||||
} else {
|
||||
path = generatePath(
|
||||
'/editor/process-models/:process_model_id/files?file_type=dmn',
|
||||
{
|
||||
process_model_id: params.process_model_id || null,
|
||||
},
|
||||
);
|
||||
}
|
||||
window.open(path);
|
||||
};
|
||||
},
|
||||
[findFileNameForReferenceId, params.process_model_id],
|
||||
);
|
||||
|
||||
const isDmn = () => {
|
||||
const fileName = params.file_name || '';
|
||||
|
@ -1315,12 +1336,12 @@ export default function ProcessModelEditDiagram() {
|
|||
if (isDmn()) {
|
||||
return (
|
||||
<ReactDiagramEditor
|
||||
processModelId={params.process_model_id || ''}
|
||||
saveDiagram={saveDiagram}
|
||||
onDeleteFile={onDeleteFile}
|
||||
diagramType="dmn"
|
||||
diagramXML={bpmnXmlForDiagramRendering}
|
||||
fileName={params.file_name}
|
||||
diagramType="dmn"
|
||||
onDeleteFile={onDeleteFile}
|
||||
processModelId={params.process_model_id || ''}
|
||||
saveDiagram={saveDiagram}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -1352,8 +1373,8 @@ export default function ProcessModelEditDiagram() {
|
|||
onLaunchDmnEditor={onLaunchDmnEditor}
|
||||
onLaunchJsonSchemaEditor={onLaunchJsonSchemaEditor}
|
||||
onLaunchMarkdownEditor={onLaunchMarkdownEditor}
|
||||
onLaunchScriptEditor={onLaunchScriptEditor}
|
||||
onLaunchMessageEditor={onLaunchMessageEditor}
|
||||
onLaunchScriptEditor={onLaunchScriptEditor}
|
||||
onMessagesRequested={onMessagesRequested}
|
||||
onSearchProcessModels={onSearchProcessModels}
|
||||
onServiceTasksRequested={onServiceTasksRequested}
|
||||
|
|
Loading…
Reference in New Issue