From 6e9a5bd590c6fbeffc1a3312d06f095d037686a2 Mon Sep 17 00:00:00 2001 From: danfunk Date: Thu, 1 Jun 2023 15:05:59 -0400 Subject: [PATCH 1/2] 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 />

From c7db6f9bdd6e0e5ba2adf25dcb20700fdcbff521 Mon Sep 17 00:00:00 2001 From: danfunk Date: Thu, 1 Jun 2023 15:08:55 -0400 Subject: [PATCH 2/2] updaing the wishlist. --- docs/wish_list/wish_list.md | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/docs/wish_list/wish_list.md b/docs/wish_list/wish_list.md index 9236940dd..7298b55ad 100644 --- a/docs/wish_list/wish_list.md +++ b/docs/wish_list/wish_list.md @@ -2,7 +2,23 @@ The follow is a list of enhancements we wish to do complete in the near (or even distant future) -## BPMN Definitions at save time vs run time -Improve performance by pre-processesing the BPMN Specification and generating the internal JSON resprestation so we no longer create the expense of doing this on a per-process basis. +## Performance Improvements + +### BPMN Definitions at save time vs run time +Improve performance by pre-processing the BPMN Specification and generating the internal JSON representation so we no longer incur the expense of doing this on a per-process basis. This will also allow us to do some early and deep validation as well. +## End User Experience + +### Administrator / Support Contact Information +Allow defining contact information at the process group and process model level, perhaps at some very top level as well - which can be inherited unless overridden. +This information could then be displayed when a process is in a non-functional state - such an error, suspended, or terminiated state. +It might also be available in the footer or under a help icon when displaying a process instance. + +## Modeler Experience + +### Form Builder +Let's invest in a much better Form Builder experience, so that it is trivial to build new forms or modify existing forms + + +