From 9aa52bcd27b2c74ae7b6548b0a294803dcd393d6 Mon Sep 17 00:00:00 2001 From: danfunk Date: Thu, 1 Jun 2023 15:05:59 -0400 Subject: [PATCH] Introduced a small spinner to the interstitial view on the Process Instance Show page. Display reasonable messages when a processes is suspended or errored. Can't do much with terminated. Show the spinner more frequently and consistently. When running ruff, ignore everything in .gitignore --- bin/run_pyl | 4 +- .../integration/test_process_api.py | 14 +++ .../src/components/ProcessInterstitial.tsx | 114 +++++++++++------- .../src/routes/ProcessInstanceShow.tsx | 1 + 4 files changed, 90 insertions(+), 43 deletions(-) diff --git a/bin/run_pyl b/bin/run_pyl index d3a45ec8a..65f629efe 100755 --- a/bin/run_pyl +++ b/bin/run_pyl @@ -39,7 +39,9 @@ function run_autofixers() { fi python_dirs=$(get_python_dirs) - python_files=$(find $python_dirs -type f -name "*.py" ! -name '.null-ls*' ! -name '_null-ls*') + # python_files=$(find $python_dirs -type f -name "*.py" ! -name '.null-ls*' ! -name '_null-ls*') + # Don't check things in git-ignore: + python_files=$(git ls-files $python_dirs | grep .py$) ruff --fix $python_files } diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py index 028a7e0b3..6dce8345f 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py @@ -1785,6 +1785,20 @@ class TestProcessApi(BaseTest): assert json_results[0]["task"]["title"] == "Please Approve" assert json_results[0]["task"]["properties"]["instructionsForEndUser"] == "I am a manual task in another lane" + # Suspending the task should still report that the user can not complete the task. + process_instance = ProcessInstanceModel.query.filter_by(id=process_instance_id).first() + processor = ProcessInstanceProcessor(process_instance) + processor.suspend() + processor.save() + + results = list(_dequeued_interstitial_stream(process_instance_id)) + json_results = list(map(lambda x: json.loads(x[5:]), results)) # type: ignore + assert len(results) == 1 + assert json_results[0]["task"]["state"] == "READY" + assert json_results[0]["task"]["can_complete"] is False + assert json_results[0]["task"]["title"] == "Please Approve" + assert json_results[0]["task"]["properties"]["instructionsForEndUser"] == "I am a manual task in another lane" + # Complete task as the finance user. response = client.put( f"/v1.0/tasks/{process_instance_id}/{json_results[0]['task']['id']}", diff --git a/spiffworkflow-frontend/src/components/ProcessInterstitial.tsx b/spiffworkflow-frontend/src/components/ProcessInterstitial.tsx index 2face3311..512a86dcf 100644 --- a/spiffworkflow-frontend/src/components/ProcessInterstitial.tsx +++ b/spiffworkflow-frontend/src/components/ProcessInterstitial.tsx @@ -15,12 +15,14 @@ type OwnProps = { processInstanceId: number; processInstanceShowPageUrl: string; allowRedirect: boolean; + smallSpinner?: boolean; }; export default function ProcessInterstitial({ processInstanceId, allowRedirect, processInstanceShowPageUrl, + smallSpinner = false, }: OwnProps) { const [data, setData] = useState([]); const [lastTask, setLastTask] = useState(null); @@ -98,62 +100,82 @@ export default function ProcessInterstitial({ shouldRedirectToProcessInstance, ]); - const getStatus = (): string => { - if (processInstance) { - return 'LOCKED'; - } - if (!lastTask.can_complete && userTasks.includes(lastTask.type)) { - return 'LOCKED'; - } - if (state === 'CLOSED') { - return lastTask.state; - } - return state; - }; - const getLoadingIcon = () => { - if (getStatus() === 'RUNNING') { + if (state === 'RUNNING') { + let style = { margin: '50px 0 50px 50px' }; + if (smallSpinner) { + style = { margin: '2x 5px 2px 2px' }; + } return ( ); } return null; }; + const userMessageForProcessInstance = ( + pi: ProcessInstance, + myTask: ProcessInstanceTask | null = null + ) => { + if (['terminated', 'suspended'].includes(pi.status)) { + return ( +

+ This process instance was {pi.status} by an administrator. Please get + in touch with them for more information. +

+ ); + } + if (pi.status === 'error') { + let errMessage = `This process instance experienced an unexpected error and can not continue. Please get in touch with an administrator for more information and next steps. `; + if (myTask && myTask.error_message) { + errMessage = errMessage.concat(myTask.error_message); + } + return

{errMessage}

; + } + // Otherwise we are not started, waiting, complete, or user_input_required + const defaultMsg = + 'There are no additional instructions or information for this process.'; + if (myTask) { + return ( + + ); + } + return

{defaultMsg}

; + }; + const userMessage = (myTask: ProcessInstanceTask) => { - if (!processInstance || processInstance.status === 'completed') { - if (!myTask.can_complete && userTasks.includes(myTask.type)) { - return ( -

- This next task is assigned to a different person or team. There is - no action for you to take at this time. -

- ); - } - if (shouldRedirectToTask(myTask)) { - return
Redirecting you to the next task now ...
; - } - if (myTask && myTask.can_complete && userTasks.includes(myTask.type)) { - return `The task ${myTask.title} is ready for you to complete.`; - } - if (myTask.error_message) { - return
{myTask.error_message}
; - } + if (processInstance) { + return userMessageForProcessInstance(processInstance, myTask); } - let message = - 'There are no additional instructions or information for this task.'; - if (processInstance && processInstance.status !== 'completed') { - message = `The tasks cannot be completed on this instance because its status is "${processInstance.status}".`; + if (!myTask.can_complete && userTasks.includes(myTask.type)) { + return ( +

+ This next task is assigned to a different person or team. There is no + action for you to take at this time. +

+ ); + } + if (shouldRedirectToTask(myTask)) { + return
Redirecting you to the next task now ...
; + } + if (myTask && myTask.can_complete && userTasks.includes(myTask.type)) { + return `The task ${myTask.title} is ready for you to complete.`; + } + if (myTask.error_message) { + return
{myTask.error_message}
; } - return (
- +
); }; @@ -171,7 +193,7 @@ export default function ProcessInterstitial({ if (lastTask) { return ( - <> +
{getLoadingIcon()} {displayableData.map((d, index) => (
))} - +
); } - return null; + if (processInstance) { + return ( +
+ {getLoadingIcon()} + {userMessageForProcessInstance(processInstance)} +
+ ); + } + return
{getLoadingIcon()}
; } diff --git a/spiffworkflow-frontend/src/routes/ProcessInstanceShow.tsx b/spiffworkflow-frontend/src/routes/ProcessInstanceShow.tsx index 4e6fee340..46febee31 100644 --- a/spiffworkflow-frontend/src/routes/ProcessInstanceShow.tsx +++ b/spiffworkflow-frontend/src/routes/ProcessInstanceShow.tsx @@ -1122,6 +1122,7 @@ export default function ProcessInstanceShow({ variant }: OwnProps) { processInstanceId={processInstance.id} processInstanceShowPageUrl={processInstanceShowPageBaseUrl} allowRedirect={false} + smallSpinner />