diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py index 4486d91a..53b62bfb 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 4ef3e013..6158d1df 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 00000000..a53f6c55 --- /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 ea6d61d6..9f8a94bd 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 5561efad..de15aa3c 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()} );