From 47a3332ed3937fdca908b06cc736f326bbc1df9c Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 17 Apr 2023 14:43:30 -0400 Subject: [PATCH] Assure we save after every chunk of tasks get completed on the interstital endpoint. Fix a merge screwup in the workflow execution service Add an instructions for end user component so we can reuse the logic in multiple places. Split up the intersitital page so that it clears out the interval timer. --- .../routes/tasks_controller.py | 3 ++ .../services/workflow_execution_service.py | 23 ++++++-- .../src/components/InstructionsForEndUser.tsx | 27 ++++++++++ .../src/routes/ProcessInterstitial.tsx | 53 +++++++++++++++++-- .../src/routes/TaskShow.tsx | 24 +-------- 5 files changed, 101 insertions(+), 29 deletions(-) create mode 100644 spiffworkflow-frontend/src/components/InstructionsForEndUser.tsx diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py index 4486d91ae..53b62bfb4 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py @@ -404,6 +404,9 @@ def interstitial(process_instance_id: int): last_task = spiff_task processor.do_engine_steps(execution_strategy_name="one_at_a_time") spiff_task = processor.next_task() + # Note, this has to be done in case someone leaves the page, + # which can cancel this function before saving. + processor.save() # Fixme - maybe find a way not to do this on every method? return # return Response(get_data(), mimetype='text/event-stream') diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/workflow_execution_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/workflow_execution_service.py index 4ef3e0133..6158d1dfd 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/workflow_execution_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/workflow_execution_service.py @@ -331,6 +331,15 @@ class WorkflowExecutionService: # execution_strategy.spiff_run # spiff.[some_run_task_method] def run_and_save(self, exit_at: None = None, save: bool = False) -> None: + """Do_engine_steps.""" + with safe_assertion(ProcessInstanceLockService.has_lock(self.process_instance_model.id)) as tripped: + if tripped: + raise AssertionError( + "The current thread has not obtained a lock for this process" + f" instance ({self.process_instance_model.id})." + ) + + try: self.bpmn_process_instance.refresh_waiting_tasks() # TODO: implicit re-entrant locks here `with_dequeued` @@ -339,9 +348,17 @@ class WorkflowExecutionService: if self.bpmn_process_instance.is_completed(): self.process_instance_completer(self.bpmn_process_instance) - self.execution_strategy.spiff_run(self.bpmn_process_instance, exit_at) - if self.bpmn_process_instance.is_completed(): - self.process_instance_completer(self.bpmn_process_instance) + self.process_bpmn_messages() + self.queue_waiting_receive_messages() + except SpiffWorkflowException as swe: + raise ApiError.from_workflow_exception("task_error", str(swe), swe) from swe + + finally: + self.execution_strategy.save(self.bpmn_process_instance) + db.session.commit() + + if save: + self.process_instance_saver() def process_bpmn_messages(self) -> None: """Process_bpmn_messages.""" diff --git a/spiffworkflow-frontend/src/components/InstructionsForEndUser.tsx b/spiffworkflow-frontend/src/components/InstructionsForEndUser.tsx new file mode 100644 index 000000000..a53f6c556 --- /dev/null +++ b/spiffworkflow-frontend/src/components/InstructionsForEndUser.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +// @ts-ignore +import MDEditor from '@uiw/react-md-editor'; + +export default function InstructionsForEndUser({ task }: any) { + if (!task) { + return null; + } + let instructions = ''; + console.log("I was passed a task: ", task); + const { properties } = task; + const { instructionsForEndUser } = properties; + if (instructionsForEndUser) { + instructions = instructionsForEndUser; + } + return ( +
+ {/* + https://www.npmjs.com/package/@uiw/react-md-editor switches to dark mode by default by respecting @media (prefers-color-scheme: dark) + This makes it look like our site is broken, so until the rest of the site supports dark mode, turn off dark mode for this component. + */} +
+ +
+
+ ); +} diff --git a/spiffworkflow-frontend/src/routes/ProcessInterstitial.tsx b/spiffworkflow-frontend/src/routes/ProcessInterstitial.tsx index ea6d61d61..9f8a94bd7 100644 --- a/spiffworkflow-frontend/src/routes/ProcessInterstitial.tsx +++ b/spiffworkflow-frontend/src/routes/ProcessInterstitial.tsx @@ -1,12 +1,16 @@ import { useEffect, useState } from 'react'; -import { useParams } from 'react-router-dom'; +import {useNavigate, useParams} from 'react-router-dom'; import { fetchEventSource } from '@microsoft/fetch-event-source'; import { BACKEND_BASE_URL } from '../config'; import { getBasicHeaders } from '../services/HttpService'; +import {Task} from '../interfaces'; +import InstructionsForEndUser from '../components/InstructionsForEndUser'; export default function ProcessInterstitial() { const [data, setData] = useState([]); + const [lastTask, setLastTask] = useState(null); const params = useParams(); + const navigate = useNavigate(); useEffect(() => { fetchEventSource( @@ -15,12 +19,53 @@ export default function ProcessInterstitial() { headers: getBasicHeaders(), onmessage(ev) { console.log(data, ev.data); - const parsedData = JSON.parse(ev.data); - setData((data) => [...data, parsedData]); + const task = JSON.parse(ev.data); + setData((prevData) => [...prevData, task]); + setLastTask(task); + }, + onclose() { + console.log("Connection Closed by the Server"); }, } ); }, []); - return
The last streamed item was: {data}
; + useEffect(() => { + // Added this seperate use effect so that the timer interval will be cleared if + // we end up redirecting back to the TaskShow page. + if (lastTask && ['User Task', 'Manual Task'].includes(lastTask.type)) { + const timerId = setInterval(() => { + navigate(`/tasks/${lastTask.process_instance_id}/${lastTask.id}`); + }, 1000); + return () => clearInterval(timerId); + } + return undefined; + }, [lastTask]); + + + return ( +
+

React - Display a list of items

+ + + + + + + + {data && + data.map((d) => ( + + + + + + + ))} + +
Task Title
{d.title}{d.state}{d.type} + +
+
+ ); } diff --git a/spiffworkflow-frontend/src/routes/TaskShow.tsx b/spiffworkflow-frontend/src/routes/TaskShow.tsx index 5561efada..de15aa3c2 100644 --- a/spiffworkflow-frontend/src/routes/TaskShow.tsx +++ b/spiffworkflow-frontend/src/routes/TaskShow.tsx @@ -23,6 +23,7 @@ import useAPIError from '../hooks/UseApiError'; import { modifyProcessIdentifierForPathParam } from '../helpers'; import { ProcessInstanceTask } from '../interfaces'; import ProcessBreadcrumb from '../components/ProcessBreadcrumb'; +import InstructionsForEndUser from '../components/InstructionsForEndUser'; // TODO: move this somewhere else function TypeAheadWidget({ @@ -402,27 +403,6 @@ export default function TaskShow() { ); }; - const instructionsElement = () => { - if (!task) { - return null; - } - let instructions = ''; - if (task.properties.instructionsForEndUser) { - instructions = task.properties.instructionsForEndUser; - } - return ( -
- {/* - https://www.npmjs.com/package/@uiw/react-md-editor switches to dark mode by default by respecting @media (prefers-color-scheme: dark) - This makes it look like our site is broken, so until the rest of the site supports dark mode, turn off dark mode for this component. - */} -
- -
-
- ); - }; - if (task) { let statusString = ''; if (task.state !== 'READY') { @@ -446,7 +426,7 @@ export default function TaskShow() {

Task: {task.title} ({task.process_model_display_name}){statusString}

- {instructionsElement()} + {formElement()} );