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:
parent
fe8c86e288
commit
8a6426efec
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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"),
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 = (
|
||||||
|
|
|
@ -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()}`,
|
||||||
|
|
Loading…
Reference in New Issue