Merge pull request #296 from sartography/feature/better_interstitial_spinner_and_suspend_message

Feature/better interstitial spinner and suspend message
This commit is contained in:
Dan Funk 2023-06-01 15:47:10 -04:00 committed by GitHub
commit 37668b14e1
5 changed files with 108 additions and 45 deletions

View File

@ -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
}

View File

@ -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

View File

@ -1753,6 +1753,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']}",

View File

@ -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<any[]>([]);
const [lastTask, setLastTask] = useState<any>(null);
@ -98,39 +100,64 @@ 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 (
<Loading
description="Active loading indicator"
withOverlay={false}
style={{ margin: '50px 0 50px 50px' }}
small={smallSpinner}
style={style}
/>
);
}
return null;
};
const userMessageForProcessInstance = (
pi: ProcessInstance,
myTask: ProcessInstanceTask | null = null
) => {
if (['terminated', 'suspended'].includes(pi.status)) {
return (
<p>
This process instance was {pi.status} by an administrator. Please get
in touch with them for more information.
</p>
);
}
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 <p>{errMessage}</p>;
}
// 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 (
<InstructionsForEndUser task={myTask} defaultMessage={defaultMsg} />
);
}
return <p>{defaultMsg}</p>;
};
const userMessage = (myTask: ProcessInstanceTask) => {
if (!processInstance || processInstance.status === 'completed') {
if (processInstance) {
return userMessageForProcessInstance(processInstance, myTask);
}
if (!myTask.can_complete && userTasks.includes(myTask.type)) {
return (
<p>
This next task is assigned to a different person or team. There is
no action for you to take at this time.
This next task is assigned to a different person or team. There is no
action for you to take at this time.
</p>
);
}
@ -143,17 +170,12 @@ export default function ProcessInterstitial({
if (myTask.error_message) {
return <div>{myTask.error_message}</div>;
}
}
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}".`;
}
return (
<div>
<InstructionsForEndUser task={myTask} defaultMessage={message} />
<InstructionsForEndUser
task={myTask}
defaultMessage="There are no additional instructions or information for this task."
/>
</div>
);
};
@ -171,7 +193,7 @@ export default function ProcessInterstitial({
if (lastTask) {
return (
<>
<div>
{getLoadingIcon()}
{displayableData.map((d, index) => (
<div
@ -182,8 +204,16 @@ export default function ProcessInterstitial({
{userMessage(d)}
</div>
))}
</>
</div>
);
}
return null;
if (processInstance) {
return (
<div>
{getLoadingIcon()}
{userMessageForProcessInstance(processInstance)}
</div>
);
}
return <div>{getLoadingIcon()}</div>;
}

View File

@ -1122,6 +1122,7 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
processInstanceId={processInstance.id}
processInstanceShowPageUrl={processInstanceShowPageBaseUrl}
allowRedirect={false}
smallSpinner
/>
<br />
<br />