drop completed_tasks from the task_api object

Show the "next" task if no task is provided on the task_show api endpoint
Adding interstitial endpoint
Rename run to run_and_save
Remove repeated code from execution strategy
Adding interstital frontend page
This commit is contained in:
Dan 2023-04-14 15:44:59 -04:00
parent fe8c86e288
commit 8a6426efec
12 changed files with 151 additions and 59 deletions

View File

@ -1817,6 +1817,27 @@ paths:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/ServiceTask" $ref: "#/components/schemas/ServiceTask"
/tasks/{process_instance_id}:
parameters:
- name: process_instance_id
in: path
required: true
description: The unique id of an existing process instance.
schema:
type: integer
get:
tags:
- Tasks
operationId: spiffworkflow_backend.routes.tasks_controller.interstitial
summary: An SSE (Server Sent Events) endpoint that returns what tasks are currently active (running, waiting, or the final END event)
responses:
"200":
description: One task
content:
application/json:
schema:
$ref: "#/components/schemas/Task"
/tasks/{process_instance_id}/{task_guid}: /tasks/{process_instance_id}/{task_guid}:
parameters: parameters:

View File

@ -205,7 +205,6 @@ class ProcessInstanceApi:
next_task: Task | None, next_task: Task | None,
process_model_identifier: str, process_model_identifier: str,
process_model_display_name: str, process_model_display_name: str,
completed_tasks: int,
updated_at_in_seconds: int, updated_at_in_seconds: int,
) -> None: ) -> None:
"""__init__.""" """__init__."""
@ -214,7 +213,6 @@ class ProcessInstanceApi:
self.next_task = next_task # The next task that requires user input. self.next_task = next_task # The next task that requires user input.
self.process_model_identifier = process_model_identifier self.process_model_identifier = process_model_identifier
self.process_model_display_name = process_model_display_name self.process_model_display_name = process_model_display_name
self.completed_tasks = completed_tasks
self.updated_at_in_seconds = updated_at_in_seconds self.updated_at_in_seconds = updated_at_in_seconds

View File

@ -1,7 +1,9 @@
"""APIs for dealing with process groups, process models, and process instances.""" """APIs for dealing with process groups, process models, and process instances."""
import json import json
import os import os
import time
import uuid import uuid
from datetime import datetime
from sys import exc_info from sys import exc_info
from typing import Any from typing import Any
from typing import Dict from typing import Dict
@ -12,7 +14,7 @@ from typing import Union
import flask.wrappers import flask.wrappers
import jinja2 import jinja2
import sentry_sdk import sentry_sdk
from flask import current_app from flask import current_app, stream_with_context
from flask import g from flask import g
from flask import jsonify from flask import jsonify
from flask import make_response from flask import make_response
@ -262,7 +264,7 @@ def manual_complete_task(
) )
def task_show(process_instance_id: int, task_guid: str) -> flask.wrappers.Response: def task_show(process_instance_id: int, task_guid: str = "next") -> flask.wrappers.Response:
"""Task_show.""" """Task_show."""
process_instance = _find_process_instance_by_id_or_raise(process_instance_id) process_instance = _find_process_instance_by_id_or_raise(process_instance_id)
@ -277,12 +279,16 @@ def task_show(process_instance_id: int, task_guid: str) -> flask.wrappers.Respon
process_instance.process_model_identifier, process_instance.process_model_identifier,
) )
_find_human_task_or_raise(process_instance_id, task_guid) # _find_human_task_or_raise(process_instance_id, task_guid)
form_schema_file_name = "" form_schema_file_name = ""
form_ui_schema_file_name = "" form_ui_schema_file_name = ""
processor = ProcessInstanceProcessor(process_instance) processor = ProcessInstanceProcessor(process_instance)
spiff_task = _get_spiff_task_from_process_instance(task_guid, process_instance, processor=processor) if task_guid == "next":
spiff_task = processor.next_task()
task_guid = spiff_task.id
else:
spiff_task = _get_spiff_task_from_process_instance(task_guid, process_instance, processor=processor)
extensions = spiff_task.task_spec.extensions extensions = spiff_task.task_spec.extensions
if "properties" in extensions: if "properties" in extensions:
@ -344,7 +350,11 @@ def task_show(process_instance_id: int, task_guid: str) -> flask.wrappers.Respon
task.form_ui_schema = ui_form_contents task.form_ui_schema = ui_form_contents
_munge_form_ui_schema_based_on_hidden_fields_in_task_data(task) _munge_form_ui_schema_based_on_hidden_fields_in_task_data(task)
_render_instructions_for_end_user(spiff_task, task)
return make_response(jsonify(task), 200)
def _render_instructions_for_end_user(spiff_task: SpiffTask, task: Task):
"""Assure any instructions for end user are processed for jinja syntax."""
if task.properties and "instructionsForEndUser" in task.properties: if task.properties and "instructionsForEndUser" in task.properties:
if task.properties["instructionsForEndUser"]: if task.properties["instructionsForEndUser"]:
try: try:
@ -354,7 +364,7 @@ def task_show(process_instance_id: int, task_guid: str) -> flask.wrappers.Respon
except WorkflowTaskException as wfe: except WorkflowTaskException as wfe:
wfe.add_note("Failed to render instructions for end user.") wfe.add_note("Failed to render instructions for end user.")
raise ApiError.from_workflow_exception("instructions_error", str(wfe), exp=wfe) from wfe raise ApiError.from_workflow_exception("instructions_error", str(wfe), exp=wfe) from wfe
return make_response(jsonify(task), 200) return ""
def process_data_show( def process_data_show(
@ -380,6 +390,24 @@ def process_data_show(
200, 200,
) )
def interstitial(process_instance_id: int):
process_instance = _find_process_instance_by_id_or_raise(process_instance_id)
processor = ProcessInstanceProcessor(process_instance)
def get_data():
spiff_task = processor.next_task()
last_task = None
while last_task != spiff_task:
task = ProcessInstanceService.spiff_task_to_api_task(processor, processor.next_task())
_render_instructions_for_end_user(spiff_task, task)
yield f'data: {current_app.json.dumps(task)} \n\n'
last_task = spiff_task
processor.do_engine_steps(execution_strategy_name="one_at_a_time")
spiff_task = processor.next_task()
return
# return Response(get_data(), mimetype='text/event-stream')
return Response(stream_with_context(get_data()), mimetype='text/event-stream')
def _task_submit_shared( def _task_submit_shared(
process_instance_id: int, process_instance_id: int,
@ -462,9 +490,15 @@ def _task_submit_shared(
) )
if next_human_task_assigned_to_me: if next_human_task_assigned_to_me:
return make_response(jsonify(HumanTaskModel.to_task(next_human_task_assigned_to_me)), 200) return make_response(jsonify(HumanTaskModel.to_task(next_human_task_assigned_to_me)), 200)
elif processor.next_task():
task = ProcessInstanceService.spiff_task_to_api_task(processor, processor.next_task())
return make_response(jsonify(task), 200)
return Response(json.dumps({"ok": True}), status=202, mimetype="application/json") return Response(json.dumps(
{"ok": True,
"process_model_identifier": process_instance.process_model_identifier,
"process_instance_id": process_instance_id
}), status=202, mimetype="application/json")
def task_submit( def task_submit(
process_instance_id: int, process_instance_id: int,

View File

@ -1642,7 +1642,7 @@ class ProcessInstanceProcessor:
self.save, self.save,
) )
try: try:
execution_service.run(exit_at, save) execution_service.run_and_save(exit_at, save)
finally: finally:
# clear out failling spiff tasks here since the ProcessInstanceProcessor creates an instance of the # clear out failling spiff tasks here since the ProcessInstanceProcessor creates an instance of the
# script engine on a class variable. # script engine on a class variable.

View File

@ -155,7 +155,6 @@ class ProcessInstanceService:
next_task=None, next_task=None,
process_model_identifier=processor.process_model_identifier, process_model_identifier=processor.process_model_identifier,
process_model_display_name=processor.process_model_display_name, process_model_display_name=processor.process_model_display_name,
completed_tasks=processor.process_instance_model.completed_tasks,
updated_at_in_seconds=processor.process_instance_model.updated_at_in_seconds, updated_at_in_seconds=processor.process_instance_model.updated_at_in_seconds,
) )
@ -442,6 +441,8 @@ class ProcessInstanceService:
spiff_task.get_state_name(), spiff_task.get_state_name(),
lane=lane, lane=lane,
process_identifier=spiff_task.task_spec._wf_spec.name, process_identifier=spiff_task.task_spec._wf_spec.name,
process_instance_id=processor.process_instance_model.id,
process_model_identifier=processor.process_model_identifier,
properties=props, properties=props,
parent=parent_id, parent=parent_id,
event_definition=serialized_task_spec.get("event_definition"), event_definition=serialized_task_spec.get("event_definition"),

View File

@ -1,6 +1,6 @@
import copy import copy
import time import time
from typing import Callable from typing import Callable, List
from typing import Optional from typing import Optional
from typing import Set from typing import Set
from uuid import UUID from uuid import UUID
@ -206,6 +206,15 @@ class ExecutionStrategy:
def save(self, bpmn_process_instance: BpmnWorkflow) -> None: def save(self, bpmn_process_instance: BpmnWorkflow) -> None:
self.delegate.save(bpmn_process_instance) self.delegate.save(bpmn_process_instance)
def get_ready_engine_steps(self, bpmn_process_instance: BpmnWorkflow) -> List[SpiffTask]:
return list(
[
t
for t in bpmn_process_instance.get_tasks(TaskState.READY)
if bpmn_process_instance._is_engine_task(t.task_spec)
]
)
class GreedyExecutionStrategy(ExecutionStrategy): class GreedyExecutionStrategy(ExecutionStrategy):
"""The common execution strategy. This will greedily run all engine steps without stopping.""" """The common execution strategy. This will greedily run all engine steps without stopping."""
@ -234,7 +243,6 @@ class GreedyExecutionStrategy(ExecutionStrategy):
if non_human_waiting_task is not None: if non_human_waiting_task is not None:
self.run_until_user_input_required(exit_at) self.run_until_user_input_required(exit_at)
class RunUntilServiceTaskExecutionStrategy(ExecutionStrategy): class RunUntilServiceTaskExecutionStrategy(ExecutionStrategy):
"""For illustration purposes, not currently integrated. """For illustration purposes, not currently integrated.
@ -243,30 +251,45 @@ class RunUntilServiceTaskExecutionStrategy(ExecutionStrategy):
""" """
def spiff_run(self, bpmn_process_instance: BpmnWorkflow, exit_at: None = None) -> None: def spiff_run(self, bpmn_process_instance: BpmnWorkflow, exit_at: None = None) -> None:
self.bpmn_process_instance = bpmn_process_instance engine_steps = self.get_ready_engine_steps(bpmn_process_instance)
engine_steps = list(
[
t
for t in bpmn_process_instance.get_tasks(TaskState.READY)
if bpmn_process_instance._is_engine_task(t.task_spec)
]
)
while engine_steps: while engine_steps:
for spiff_task in engine_steps: for spiff_task in engine_steps:
if spiff_task.task_spec.spec_type == "Service Task": if spiff_task.task_spec.spec_type == "Service Task":
return return
self.delegate.will_complete_task(spiff_task) self.delegate.will_complete_task(spiff_task)
spiff_task.complete() spiff_task.run()
self.delegate.did_complete_task(spiff_task) self.delegate.did_complete_task(spiff_task)
bpmn_process_instance.refresh_waiting_tasks()
engine_steps = self.get_ready_engine_steps(bpmn_process_instance)
self.delegate.after_engine_steps(bpmn_process_instance)
engine_steps = list(
[
t
for t in bpmn_process_instance.get_tasks(TaskState.READY)
if bpmn_process_instance._is_engine_task(t.task_spec)
]
)
class RunUntilUserMessageExecutionStrategy(ExecutionStrategy):
"""When you want to run tasks until you hit something to report to the end user, or
until there are no other engine steps to complete."""
def spiff_run(self, bpmn_process_instance: BpmnWorkflow, exit_at: None = None) -> None:
engine_steps = self.get_ready_engine_steps(bpmn_process_instance)
while engine_steps:
for spiff_task in engine_steps:
self.delegate.will_complete_task(spiff_task)
spiff_task.run()
self.delegate.did_complete_task(spiff_task)
if spiff_task.task_spec.properties.get("instructionsForEndUser", None) is not None:
break
engine_steps = self.get_ready_engine_steps(bpmn_process_instance)
self.delegate.after_engine_steps(bpmn_process_instance)
class OneAtATimeExecutionStrategy(ExecutionStrategy):
"""When you want to run only one engine step at a time."""
def spiff_run(self, bpmn_process_instance: BpmnWorkflow, exit_at: None = None) -> None:
engine_steps = self.get_ready_engine_steps(bpmn_process_instance)
if len(engine_steps) > 0:
spiff_task = engine_steps[0]
self.delegate.will_complete_task(spiff_task)
spiff_task.run()
self.delegate.did_complete_task(spiff_task)
self.delegate.after_engine_steps(bpmn_process_instance) self.delegate.after_engine_steps(bpmn_process_instance)
@ -274,6 +297,8 @@ def execution_strategy_named(name: str, delegate: EngineStepDelegate) -> Executi
cls = { cls = {
"greedy": GreedyExecutionStrategy, "greedy": GreedyExecutionStrategy,
"run_until_service_task": RunUntilServiceTaskExecutionStrategy, "run_until_service_task": RunUntilServiceTaskExecutionStrategy,
"run_until_user_message": RunUntilUserMessageExecutionStrategy,
"one_at_a_time": OneAtATimeExecutionStrategy,
}[name] }[name]
return cls(delegate) return cls(delegate)
@ -282,7 +307,6 @@ def execution_strategy_named(name: str, delegate: EngineStepDelegate) -> Executi
ProcessInstanceCompleter = Callable[[BpmnWorkflow], None] ProcessInstanceCompleter = Callable[[BpmnWorkflow], None]
ProcessInstanceSaver = Callable[[], None] ProcessInstanceSaver = Callable[[], None]
class WorkflowExecutionService: class WorkflowExecutionService:
"""Provides the driver code for workflow execution.""" """Provides the driver code for workflow execution."""
@ -306,16 +330,7 @@ class WorkflowExecutionService:
# run # run
# execution_strategy.spiff_run # execution_strategy.spiff_run
# spiff.[some_run_task_method] # spiff.[some_run_task_method]
def run(self, exit_at: None = None, save: bool = False) -> None: 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() self.bpmn_process_instance.refresh_waiting_tasks()
# TODO: implicit re-entrant locks here `with_dequeued` # TODO: implicit re-entrant locks here `with_dequeued`
@ -324,17 +339,9 @@ class WorkflowExecutionService:
if self.bpmn_process_instance.is_completed(): if self.bpmn_process_instance.is_completed():
self.process_instance_completer(self.bpmn_process_instance) self.process_instance_completer(self.bpmn_process_instance)
self.process_bpmn_messages() self.execution_strategy.spiff_run(self.bpmn_process_instance, exit_at)
self.queue_waiting_receive_messages() if self.bpmn_process_instance.is_completed():
except SpiffWorkflowException as swe: self.process_instance_completer(self.bpmn_process_instance)
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: def process_bpmn_messages(self) -> None:
"""Process_bpmn_messages.""" """Process_bpmn_messages."""
@ -407,11 +414,11 @@ class WorkflowExecutionService:
class ProfiledWorkflowExecutionService(WorkflowExecutionService): class ProfiledWorkflowExecutionService(WorkflowExecutionService):
"""A profiled version of the workflow execution service.""" """A profiled version of the workflow execution service."""
def run(self, exit_at: None = None, save: bool = False) -> None: def run_and_save(self, exit_at: None = None, save: bool = False) -> None:
"""__do_engine_steps.""" """__do_engine_steps."""
import cProfile import cProfile
from pstats import SortKey from pstats import SortKey
with cProfile.Profile() as pr: with cProfile.Profile() as pr:
super().run(exit_at=exit_at, save=save) super().run_and_save(exit_at=exit_at, save=save)
pr.print_stats(sort=SortKey.CUMULATIVE) pr.print_stats(sort=SortKey.CUMULATIVE)

View File

@ -17,6 +17,7 @@
"@casl/ability": "^6.3.2", "@casl/ability": "^6.3.2",
"@casl/react": "^3.1.0", "@casl/react": "^3.1.0",
"@ginkgo-bioworks/react-json-schema-form-builder": "^2.9.0", "@ginkgo-bioworks/react-json-schema-form-builder": "^2.9.0",
"@microsoft/fetch-event-source": "^2.0.1",
"@monaco-editor/react": "^4.4.5", "@monaco-editor/react": "^4.4.5",
"@mui/material": "^5.10.14", "@mui/material": "^5.10.14",
"@react-icons/all-files": "^4.1.0", "@react-icons/all-files": "^4.1.0",
@ -4473,6 +4474,11 @@
"@lezer/common": "^1.0.0" "@lezer/common": "^1.0.0"
} }
}, },
"node_modules/@microsoft/fetch-event-source": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz",
"integrity": "sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA=="
},
"node_modules/@monaco-editor/loader": { "node_modules/@monaco-editor/loader": {
"version": "1.3.2", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.3.2.tgz", "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.3.2.tgz",
@ -8066,7 +8072,7 @@
}, },
"node_modules/bpmn-js-spiffworkflow": { "node_modules/bpmn-js-spiffworkflow": {
"version": "0.0.8", "version": "0.0.8",
"resolved": "git+ssh://git@github.com/sartography/bpmn-js-spiffworkflow.git#24a71ec5e2cbbefce58be4b4610151db4a55a8e1", "resolved": "git+ssh://git@github.com/sartography/bpmn-js-spiffworkflow.git#69135655f8a5282bcdaef82705c3d522ef5b4464",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"inherits": "^2.0.4", "inherits": "^2.0.4",
@ -35584,6 +35590,11 @@
"@lezer/common": "^1.0.0" "@lezer/common": "^1.0.0"
} }
}, },
"@microsoft/fetch-event-source": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz",
"integrity": "sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA=="
},
"@monaco-editor/loader": { "@monaco-editor/loader": {
"version": "1.3.2", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.3.2.tgz", "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.3.2.tgz",
@ -38227,7 +38238,7 @@
} }
}, },
"bpmn-js-spiffworkflow": { "bpmn-js-spiffworkflow": {
"version": "git+ssh://git@github.com/sartography/bpmn-js-spiffworkflow.git#24a71ec5e2cbbefce58be4b4610151db4a55a8e1", "version": "git+ssh://git@github.com/sartography/bpmn-js-spiffworkflow.git#69135655f8a5282bcdaef82705c3d522ef5b4464",
"from": "bpmn-js-spiffworkflow@sartography/bpmn-js-spiffworkflow#main", "from": "bpmn-js-spiffworkflow@sartography/bpmn-js-spiffworkflow#main",
"requires": { "requires": {
"inherits": "^2.0.4", "inherits": "^2.0.4",

View File

@ -12,6 +12,7 @@
"@casl/ability": "^6.3.2", "@casl/ability": "^6.3.2",
"@casl/react": "^3.1.0", "@casl/react": "^3.1.0",
"@ginkgo-bioworks/react-json-schema-form-builder": "^2.9.0", "@ginkgo-bioworks/react-json-schema-form-builder": "^2.9.0",
"@microsoft/fetch-event-source": "^2.0.1",
"@monaco-editor/react": "^4.4.5", "@monaco-editor/react": "^4.4.5",
"@mui/material": "^5.10.14", "@mui/material": "^5.10.14",
"@react-icons/all-files": "^4.1.0", "@react-icons/all-files": "^4.1.0",

View File

@ -8,6 +8,7 @@ import NavigationBar from './components/NavigationBar';
import HomePageRoutes from './routes/HomePageRoutes'; import HomePageRoutes from './routes/HomePageRoutes';
import ErrorBoundary from './components/ErrorBoundary'; import ErrorBoundary from './components/ErrorBoundary';
import AdminRoutes from './routes/AdminRoutes'; import AdminRoutes from './routes/AdminRoutes';
import ProcessRoutes from './routes/ProcessRoutes';
import { AbilityContext } from './contexts/Can'; import { AbilityContext } from './contexts/Can';
import UserService from './services/UserService'; import UserService from './services/UserService';
@ -35,6 +36,7 @@ export default function App() {
<Routes> <Routes>
<Route path="/*" element={<HomePageRoutes />} /> <Route path="/*" element={<HomePageRoutes />} />
<Route path="/tasks/*" element={<HomePageRoutes />} /> <Route path="/tasks/*" element={<HomePageRoutes />} />
<Route path="/process/*" element={<ProcessRoutes />} />
<Route path="/admin/*" element={<AdminRoutes />} /> <Route path="/admin/*" element={<AdminRoutes />} />
</Routes> </Routes>
</ErrorBoundary> </ErrorBoundary>

View File

@ -7,6 +7,7 @@ import MyTasks from './MyTasks';
import CompletedInstances from './CompletedInstances'; import CompletedInstances from './CompletedInstances';
import CreateNewInstance from './CreateNewInstance'; import CreateNewInstance from './CreateNewInstance';
import InProgressInstances from './InProgressInstances'; import InProgressInstances from './InProgressInstances';
import ProcessInterstitial from './ProcessInterstitial';
export default function HomePageRoutes() { export default function HomePageRoutes() {
const location = useLocation(); const location = useLocation();
@ -55,6 +56,7 @@ export default function HomePageRoutes() {
<Route path="my-tasks" element={<MyTasks />} /> <Route path="my-tasks" element={<MyTasks />} />
<Route path=":process_instance_id/:task_id" element={<TaskShow />} /> <Route path=":process_instance_id/:task_id" element={<TaskShow />} />
<Route path="grouped" element={<InProgressInstances />} /> <Route path="grouped" element={<InProgressInstances />} />
<Route path="process/:process_instance_id/interstitial" element={<ProcessInterstitial />} />
<Route path="completed-instances" element={<CompletedInstances />} /> <Route path="completed-instances" element={<CompletedInstances />} />
<Route path="create-new-instance" element={<CreateNewInstance />} /> <Route path="create-new-instance" element={<CreateNewInstance />} />
</Routes> </Routes>

View File

@ -17,6 +17,7 @@ import {
import MDEditor from '@uiw/react-md-editor'; import MDEditor from '@uiw/react-md-editor';
// eslint-disable-next-line import/no-named-as-default // eslint-disable-next-line import/no-named-as-default
import Form from '../themes/carbon'; import Form from '../themes/carbon';
import Loading from '../themes/carbon';
import HttpService from '../services/HttpService'; import HttpService from '../services/HttpService';
import useAPIError from '../hooks/UseApiError'; import useAPIError from '../hooks/UseApiError';
import { modifyProcessIdentifierForPathParam } from '../helpers'; import { modifyProcessIdentifierForPathParam } from '../helpers';
@ -90,6 +91,8 @@ function TypeAheadWidget({
); );
} }
class UnexpectedHumanTaskType extends Error { class UnexpectedHumanTaskType extends Error {
constructor(message: string) { constructor(message: string) {
super(message); super(message);
@ -108,6 +111,7 @@ export default function TaskShow() {
const params = useParams(); const params = useParams();
const navigate = useNavigate(); const navigate = useNavigate();
const [disabled, setDisabled] = useState(false); const [disabled, setDisabled] = useState(false);
const [refreshSeconds, setRefreshSeconds] = useState(0);
// save current form data so that we can avoid validations in certain situations // save current form data so that we can avoid validations in certain situations
const [currentFormObject, setCurrentFormObject] = useState<any>({}); const [currentFormObject, setCurrentFormObject] = useState<any>({});
@ -117,6 +121,7 @@ export default function TaskShow() {
// eslint-disable-next-line sonarjs/no-duplicate-string // eslint-disable-next-line sonarjs/no-duplicate-string
const supportedHumanTaskTypes = ['User Task', 'Manual Task']; const supportedHumanTaskTypes = ['User Task', 'Manual Task'];
useEffect(() => { useEffect(() => {
const processResult = (result: ProcessInstanceTask) => { const processResult = (result: ProcessInstanceTask) => {
setTask(result); setTask(result);
@ -157,7 +162,15 @@ export default function TaskShow() {
if (result.ok) { if (result.ok) {
navigate(`/tasks`); navigate(`/tasks`);
} else if (result.process_instance_id) { } else if (result.process_instance_id) {
navigate(`/tasks/${result.process_instance_id}/${result.id}`); if (result.type in supportedHumanTaskTypes) {
navigate(`/tasks/${result.process_instance_id}/${result.id}`);
} else {
navigate(
`/process/${modifyProcessIdentifierForPathParam(
result.process_model_identifier
)}/${result.process_instance_id}/interstitial`
);
}
} else { } else {
addError(result); addError(result);
} }
@ -344,8 +357,10 @@ export default function TaskShow() {
</Button> </Button>
); );
} else { } else {
throw new UnexpectedHumanTaskType( return (
`Invalid task type given: ${task.type}. Only supported types: ${supportedHumanTaskTypes}` <p>
<i>Page will refresh in {refreshSeconds} seconds.</i>
</p>
); );
} }
reactFragmentToHideSubmitButton = ( reactFragmentToHideSubmitButton = (

View File

@ -8,7 +8,7 @@ const HttpMethods = {
DELETE: 'DELETE', DELETE: 'DELETE',
}; };
const getBasicHeaders = (): object => { export const getBasicHeaders = (): Record<string, string> => {
if (UserService.isLoggedIn()) { if (UserService.isLoggedIn()) {
return { return {
Authorization: `Bearer ${UserService.getAccessToken()}`, Authorization: `Bearer ${UserService.getAccessToken()}`,