* Scroll to the top on each update of a process result.

* When displaying an error, scroll to the top of the page.
* Rename a few of the filter titles to better match the titles of the table on the process instances list page.
* Add error_message to the task object - so if a task is in an error state and has a message, we send it to the front end.
* Handle errors on the Interstitial page - so if an error happens during execution it is diplayed, and if an error happened previously, we show it.
* Return the last Errored Tasks if no READY or WAITING tasks exists when calling the Interstitial endpoint.
This commit is contained in:
Dan 2023-04-22 15:38:29 -04:00
parent 0c089ec39d
commit e9aae6123e
11 changed files with 52 additions and 17 deletions

View File

@ -110,6 +110,7 @@ class Task:
event_definition: Union[dict[str, Any], None] = None,
call_activity_process_identifier: Optional[str] = None,
calling_subprocess_task_id: Optional[str] = None,
error_message: Optional[str] = None,
):
"""__init__."""
self.id = id
@ -147,6 +148,7 @@ class Task:
self.properties = properties # Arbitrary extension properties from BPMN editor.
if self.properties is None:
self.properties = {}
self.error_message = error_message
@property
def serialized(self) -> dict[str, Any]:
@ -183,6 +185,7 @@ class Task:
"event_definition": self.event_definition,
"call_activity_process_identifier": self.call_activity_process_identifier,
"calling_subprocess_task_id": self.calling_subprocess_task_id,
"error_message": self.error_message,
}
@classmethod

View File

@ -405,14 +405,19 @@ def _interstitial_stream(process_instance_id: int) -> Generator[str, Optional[st
reported_ids.append(spiff_task.id)
yield f"data: {current_app.json.dumps(task)} \n\n"
last_task = spiff_task
processor.do_engine_steps(execution_strategy_name="run_until_user_message")
processor.do_engine_steps(execution_strategy_name="one_at_a_time")
spiff_task = processor.next_task()
try:
processor.do_engine_steps(execution_strategy_name="run_until_user_message")
processor.do_engine_steps(execution_strategy_name="one_at_a_time")
except WorkflowTaskException as wfe:
api_error = ApiError.from_workflow_exception("engine_steps_error", "Failed complete an automated task.", exp=wfe)
yield f"data: {current_app.json.dumps(api_error)} \n\n"
# Note, this has to be done in case someone leaves the page,
# which can otherwise cancel this function and leave completed tasks un-registered.
processor.save() # Fixme - maybe find a way not to do this on every loop?
spiff_task = processor.next_task()
# Always provide some response, in the event no instructions were provided.
if len(reported_ids) == 0:
# Always provide some response, in the event no instructions were provided.
task = ProcessInstanceService.spiff_task_to_api_task(processor, processor.next_task())
yield f"data: {current_app.json.dumps(task)} \n\n"

View File

@ -1741,8 +1741,8 @@ class ProcessInstanceProcessor:
def next_task(self) -> SpiffTask:
"""Returns the next task that should be completed even if there are parallel tasks and multiple options are available.
If the process_instance is complete
it will return the final end task.
If the process_instance is complete it will return the final end task.
If the process_instance is in an error state it will return the task that is erroring.
"""
# If the whole blessed mess is done, return the end_event task in the tree
# This was failing in the case of a call activity where we have an intermediate EndEvent
@ -1769,8 +1769,13 @@ class ProcessInstanceProcessor:
waiting_tasks = self.bpmn_process_instance.get_tasks(TaskState.WAITING)
if len(waiting_tasks) > 0:
return waiting_tasks[0]
else:
return # We have not tasks to return.
# If there are no ready tasks, and not waiting tasks, return the latest error.
error_task = None
for task in SpiffTask.Iterator(self.bpmn_process_instance.task_tree, TaskState.ERROR):
error_task = task
return error_task
# Get a list of all completed user tasks (Non engine tasks)
completed_user_tasks = self.completed_user_tasks()
@ -1798,6 +1803,7 @@ class ProcessInstanceProcessor:
next_task = task
return next_task
def completed_user_tasks(self) -> List[SpiffTask]:
"""Completed_user_tasks."""
user_tasks = self.bpmn_process_instance.get_tasks(TaskState.COMPLETED)

View File

@ -462,6 +462,12 @@ class ProcessInstanceService:
serialized_task_spec = processor.serialize_task_spec(spiff_task.task_spec)
# Grab the last error message.
error_message = None
for event in processor.process_instance_model.process_instance_events:
for detail in event.error_details:
error_message = detail.message
task = Task(
spiff_task.id,
spiff_task.task_spec.name,
@ -479,6 +485,7 @@ class ProcessInstanceService:
event_definition=serialized_task_spec.get("event_definition"),
call_activity_process_identifier=call_activity_process_identifier,
calling_subprocess_task_id=calling_subprocess_task_id,
error_message=error_message
)
return task

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -109,6 +109,7 @@ export default function ErrorDisplay() {
if (errorObject) {
const title = 'Error:';
window.scrollTo(0, 0); // Scroll back to the top of the page
errorTag = (
<Notification title={title} onClose={() => removeError()} type="error">

View File

@ -1190,7 +1190,7 @@ export default function ProcessInstanceListTable({
return null;
}}
placeholder="Start typing username"
titleText="Process Initiator"
titleText="Started By"
selectedItem={processInitiatorSelection}
/>
);
@ -1199,7 +1199,7 @@ export default function ProcessInstanceListTable({
<TextInput
id="process-instance-initiator-search"
placeholder="Enter username"
labelText="Process Initiator"
labelText="Started By"
invalid={processInitiatorNotFoundErrorText !== ''}
invalidText={processInitiatorNotFoundErrorText}
onChange={(event: any) => {

View File

@ -15,7 +15,7 @@ export default function ProcessModelSearch({
processModels,
selectedItem,
onChange,
titleText = 'Process model',
titleText = 'Process',
}: OwnProps) {
const getParentGroupsDisplayName = (processModel: ProcessModel) => {
if (processModel.parent_groups) {
@ -65,7 +65,7 @@ export default function ProcessModelSearch({
return null;
}}
shouldFilterItem={shouldFilterProcessModel}
placeholder="Choose a process model"
placeholder="Choose a process"
titleText={titleText}
selectedItem={selectedItem}
/>

View File

@ -81,6 +81,7 @@ export interface ProcessInstanceTask {
potential_owner_usernames?: string;
assigned_user_group_identifier?: string;
error_message?: string;
}
export interface ProcessReference {

View File

@ -10,6 +10,7 @@ import { getBasicHeaders } from '../services/HttpService';
import InstructionsForEndUser from '../components/InstructionsForEndUser';
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import { ProcessInstanceTask } from '../interfaces';
import useAPIError from '../hooks/UseApiError';
export default function ProcessInterstitial() {
const [data, setData] = useState<any[]>([]);
@ -20,6 +21,7 @@ export default function ProcessInterstitial() {
const userTasks = useMemo(() => {
return ['User Task', 'Manual Task'];
}, []);
const { addError, removeError } = useAPIError();
useEffect(() => {
fetchEventSource(
@ -27,9 +29,13 @@ export default function ProcessInterstitial() {
{
headers: getBasicHeaders(),
onmessage(ev) {
const task = JSON.parse(ev.data);
setData((prevData) => [...prevData, task]);
setLastTask(task);
const retValue = JSON.parse(ev.data);
if ('error_code' in retValue) {
addError(retValue);
} else {
setData((prevData) => [...prevData, retValue]);
setLastTask(retValue);
}
},
onclose() {
setState('CLOSED');
@ -85,6 +91,8 @@ export default function ProcessInterstitial() {
return <img src="/interstitial/waiting.png" alt="Waiting ...." />;
case 'COMPLETED':
return <img src="/interstitial/completed.png" alt="Completed" />;
case 'ERROR':
return <img src="/interstitial/errored.png" alt="Errored" />;
default:
return getStatus();
}
@ -104,6 +112,10 @@ export default function ProcessInterstitial() {
if (shouldRedirect(myTask)) {
return <div>Redirecting you to the next task now ...</div>;
}
if (myTask.error_message) {
return <div>{myTask.error_message}</div>
}
return (
<div>
<InstructionsForEndUser task={myTask} />
@ -147,7 +159,7 @@ export default function ProcessInterstitial() {
<Column md={2} lg={4} sm={2}>
Task: <em>{d.title}</em>
</Column>
<Column md={6} lg={8} sm={4}>
<Column md={6} lg={6} sm={4}>
{userMessage(d)}
</Column>
</Grid>

View File

@ -117,10 +117,10 @@ export default function TaskShow() {
const processResult = (result: ProcessInstanceTask) => {
setTask(result);
setDisabled(false);
if (!result.can_complete) {
navigateToInterstitial(result);
}
window.scrollTo(0, 0); // Scroll back to the top of the page
/* Disable call to load previous tasks -- do not display menu.
const url = `/v1.0/process-instances/for-me/${modifyProcessIdentifierForPathParam(