mirror of
https://github.com/status-im/spiff-arena.git
synced 2025-01-30 19:56:20 +00:00
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:
commit
d5e4d3a4ee
@ -39,7 +39,9 @@ function run_autofixers() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
python_dirs=$(get_python_dirs)
|
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
|
ruff --fix $python_files
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,23 @@
|
|||||||
|
|
||||||
The follow is a list of enhancements we wish to do complete in the near (or even distant future)
|
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
|
## Performance Improvements
|
||||||
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.
|
|
||||||
|
### 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.
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1753,6 +1753,20 @@ class TestProcessApi(BaseTest):
|
|||||||
assert json_results[0]["task"]["title"] == "Please Approve"
|
assert json_results[0]["task"]["title"] == "Please Approve"
|
||||||
assert json_results[0]["task"]["properties"]["instructionsForEndUser"] == "I am a manual task in another lane"
|
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.
|
# Complete task as the finance user.
|
||||||
response = client.put(
|
response = client.put(
|
||||||
f"/v1.0/tasks/{process_instance_id}/{json_results[0]['task']['id']}",
|
f"/v1.0/tasks/{process_instance_id}/{json_results[0]['task']['id']}",
|
||||||
|
@ -15,12 +15,14 @@ type OwnProps = {
|
|||||||
processInstanceId: number;
|
processInstanceId: number;
|
||||||
processInstanceShowPageUrl: string;
|
processInstanceShowPageUrl: string;
|
||||||
allowRedirect: boolean;
|
allowRedirect: boolean;
|
||||||
|
smallSpinner?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ProcessInterstitial({
|
export default function ProcessInterstitial({
|
||||||
processInstanceId,
|
processInstanceId,
|
||||||
allowRedirect,
|
allowRedirect,
|
||||||
processInstanceShowPageUrl,
|
processInstanceShowPageUrl,
|
||||||
|
smallSpinner = false,
|
||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
const [data, setData] = useState<any[]>([]);
|
const [data, setData] = useState<any[]>([]);
|
||||||
const [lastTask, setLastTask] = useState<any>(null);
|
const [lastTask, setLastTask] = useState<any>(null);
|
||||||
@ -98,62 +100,82 @@ export default function ProcessInterstitial({
|
|||||||
shouldRedirectToProcessInstance,
|
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 = () => {
|
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 (
|
||||||
<Loading
|
<Loading
|
||||||
description="Active loading indicator"
|
description="Active loading indicator"
|
||||||
withOverlay={false}
|
withOverlay={false}
|
||||||
style={{ margin: '50px 0 50px 50px' }}
|
small={smallSpinner}
|
||||||
|
style={style}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
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) => {
|
const userMessage = (myTask: ProcessInstanceTask) => {
|
||||||
if (!processInstance || processInstance.status === 'completed') {
|
if (processInstance) {
|
||||||
if (!myTask.can_complete && userTasks.includes(myTask.type)) {
|
return userMessageForProcessInstance(processInstance, myTask);
|
||||||
return (
|
|
||||||
<p>
|
|
||||||
This next task is assigned to a different person or team. There is
|
|
||||||
no action for you to take at this time.
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (shouldRedirectToTask(myTask)) {
|
|
||||||
return <div>Redirecting you to the next task now ...</div>;
|
|
||||||
}
|
|
||||||
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 <div>{myTask.error_message}</div>;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let message =
|
if (!myTask.can_complete && userTasks.includes(myTask.type)) {
|
||||||
'There are no additional instructions or information for this task.';
|
return (
|
||||||
if (processInstance && processInstance.status !== 'completed') {
|
<p>
|
||||||
message = `The tasks cannot be completed on this instance because its status is "${processInstance.status}".`;
|
This next task is assigned to a different person or team. There is no
|
||||||
|
action for you to take at this time.
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (shouldRedirectToTask(myTask)) {
|
||||||
|
return <div>Redirecting you to the next task now ...</div>;
|
||||||
|
}
|
||||||
|
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 <div>{myTask.error_message}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<InstructionsForEndUser task={myTask} defaultMessage={message} />
|
<InstructionsForEndUser
|
||||||
|
task={myTask}
|
||||||
|
defaultMessage="There are no additional instructions or information for this task."
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -171,7 +193,7 @@ export default function ProcessInterstitial({
|
|||||||
|
|
||||||
if (lastTask) {
|
if (lastTask) {
|
||||||
return (
|
return (
|
||||||
<>
|
<div>
|
||||||
{getLoadingIcon()}
|
{getLoadingIcon()}
|
||||||
{displayableData.map((d, index) => (
|
{displayableData.map((d, index) => (
|
||||||
<div
|
<div
|
||||||
@ -182,8 +204,16 @@ export default function ProcessInterstitial({
|
|||||||
{userMessage(d)}
|
{userMessage(d)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
if (processInstance) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{getLoadingIcon()}
|
||||||
|
{userMessageForProcessInstance(processInstance)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <div>{getLoadingIcon()}</div>;
|
||||||
}
|
}
|
||||||
|
@ -1122,6 +1122,7 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
|
|||||||
processInstanceId={processInstance.id}
|
processInstanceId={processInstance.id}
|
||||||
processInstanceShowPageUrl={processInstanceShowPageBaseUrl}
|
processInstanceShowPageUrl={processInstanceShowPageBaseUrl}
|
||||||
allowRedirect={false}
|
allowRedirect={false}
|
||||||
|
smallSpinner
|
||||||
/>
|
/>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
|
Loading…
x
Reference in New Issue
Block a user