Merge remote-tracking branch 'origin/main' into feature/home_page_filter_links
|
@ -519,16 +519,16 @@ paths:
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
|
|
||||||
/processes/{bpmn_process_identifier}/callers:
|
/processes/callers:
|
||||||
parameters:
|
parameters:
|
||||||
- name: bpmn_process_identifier
|
- name: bpmn_process_identifier
|
||||||
in: path
|
in: query
|
||||||
required: true
|
required: true
|
||||||
description: the modified process model id
|
description: the bpmn process identifier/id (not the name with spaces and not the process model identifier)
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
get:
|
get:
|
||||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_caller_lists
|
operationId: spiffworkflow_backend.routes.process_api_blueprint.process_caller_list
|
||||||
summary:
|
summary:
|
||||||
Return a list of information about all processes that call the provided process id
|
Return a list of information about all processes that call the provided process id
|
||||||
tags:
|
tags:
|
||||||
|
|
|
@ -78,7 +78,7 @@ def process_list() -> Any:
|
||||||
return SpecReferenceSchema(many=True).dump(references)
|
return SpecReferenceSchema(many=True).dump(references)
|
||||||
|
|
||||||
|
|
||||||
def process_caller_lists(bpmn_process_identifier: str) -> Any:
|
def process_caller_list(bpmn_process_identifier: str) -> Any:
|
||||||
callers = ProcessCallerService.callers(bpmn_process_identifier)
|
callers = ProcessCallerService.callers(bpmn_process_identifier)
|
||||||
references = (
|
references = (
|
||||||
SpecReferenceCache.query.filter_by(type="process").filter(SpecReferenceCache.identifier.in_(callers)).all()
|
SpecReferenceCache.query.filter_by(type="process").filter(SpecReferenceCache.identifier.in_(callers)).all()
|
||||||
|
|
|
@ -20,6 +20,7 @@ from flask import make_response
|
||||||
from flask import stream_with_context
|
from flask import stream_with_context
|
||||||
from flask.wrappers import Response
|
from flask.wrappers import Response
|
||||||
from jinja2 import TemplateSyntaxError
|
from jinja2 import TemplateSyntaxError
|
||||||
|
from SpiffWorkflow.bpmn.workflow import BpmnWorkflow # type: ignore
|
||||||
from SpiffWorkflow.exceptions import WorkflowTaskException # type: ignore
|
from SpiffWorkflow.exceptions import WorkflowTaskException # type: ignore
|
||||||
from SpiffWorkflow.task import Task as SpiffTask # type: ignore
|
from SpiffWorkflow.task import Task as SpiffTask # type: ignore
|
||||||
from SpiffWorkflow.task import TaskState
|
from SpiffWorkflow.task import TaskState
|
||||||
|
@ -385,47 +386,90 @@ def _render_instructions_for_end_user(task_model: TaskModel, extensions: Optiona
|
||||||
|
|
||||||
def _interstitial_stream(process_instance: ProcessInstanceModel) -> Generator[str, Optional[str], None]:
|
def _interstitial_stream(process_instance: ProcessInstanceModel) -> Generator[str, Optional[str], None]:
|
||||||
processor = ProcessInstanceProcessor(process_instance)
|
processor = ProcessInstanceProcessor(process_instance)
|
||||||
reported_ids = [] # bit of an issue with end tasks showing as getting completed twice.
|
reported_ids = [] # A list of all the ids reported by this endpoint so far.
|
||||||
spiff_task = processor.next_task()
|
|
||||||
task_model = TaskModel.query.filter_by(guid=str(spiff_task.id)).first()
|
def get_reportable_tasks() -> Any:
|
||||||
last_task = None
|
return processor.bpmn_process_instance.get_tasks(
|
||||||
while last_task != spiff_task:
|
TaskState.WAITING | TaskState.STARTED | TaskState.READY | TaskState.ERROR
|
||||||
task = ProcessInstanceService.spiff_task_to_api_task(processor, processor.next_task())
|
)
|
||||||
|
|
||||||
|
def render_instructions(spiff_task: SpiffTask) -> str:
|
||||||
|
task_model = TaskModel.query.filter_by(guid=str(spiff_task.id)).first()
|
||||||
extensions = TaskService.get_extensions_from_task_model(task_model)
|
extensions = TaskService.get_extensions_from_task_model(task_model)
|
||||||
instructions = _render_instructions_for_end_user(task_model, extensions)
|
return _render_instructions_for_end_user(task_model, extensions)
|
||||||
if instructions and spiff_task.id not in reported_ids:
|
|
||||||
reported_ids.append(spiff_task.id)
|
tasks = get_reportable_tasks()
|
||||||
task.properties = extensions
|
while True:
|
||||||
yield f"data: {current_app.json.dumps(task)} \n\n"
|
for spiff_task in tasks:
|
||||||
last_task = spiff_task
|
try:
|
||||||
|
instructions = render_instructions(spiff_task)
|
||||||
|
except Exception as e:
|
||||||
|
api_error = ApiError(
|
||||||
|
error_code="engine_steps_error",
|
||||||
|
message=f"Failed to complete an automated task. Error was: {str(e)}",
|
||||||
|
status_code=400,
|
||||||
|
)
|
||||||
|
yield f"data: {current_app.json.dumps(api_error)} \n\n"
|
||||||
|
raise e
|
||||||
|
if instructions and spiff_task.id not in reported_ids:
|
||||||
|
task = ProcessInstanceService.spiff_task_to_api_task(processor, spiff_task)
|
||||||
|
task.properties = {"instructionsForEndUser": instructions}
|
||||||
|
yield f"data: {current_app.json.dumps(task)} \n\n"
|
||||||
|
reported_ids.append(spiff_task.id)
|
||||||
|
if spiff_task.state == TaskState.READY:
|
||||||
|
try:
|
||||||
|
processor.do_engine_steps(execution_strategy_name="one_at_a_time")
|
||||||
|
processor.do_engine_steps(execution_strategy_name="run_until_user_message")
|
||||||
|
processor.save() # Fixme - maybe find a way not to do this on every loop?
|
||||||
|
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"
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
api_error = ApiError(
|
||||||
|
error_code="engine_steps_error",
|
||||||
|
message=f"Failed to complete an automated task. Error was: {str(e)}",
|
||||||
|
status_code=400,
|
||||||
|
)
|
||||||
|
yield f"data: {current_app.json.dumps(api_error)} \n\n"
|
||||||
|
return
|
||||||
|
processor.bpmn_process_instance.refresh_waiting_tasks()
|
||||||
|
ready_engine_task_count = get_ready_engine_step_count(processor.bpmn_process_instance)
|
||||||
|
tasks = get_reportable_tasks()
|
||||||
|
if ready_engine_task_count == 0:
|
||||||
|
break # No more tasks to report
|
||||||
|
|
||||||
|
spiff_task = processor.next_task()
|
||||||
|
task = ProcessInstanceService.spiff_task_to_api_task(processor, processor.next_task())
|
||||||
|
if task.id not in reported_ids:
|
||||||
try:
|
try:
|
||||||
processor.do_engine_steps(execution_strategy_name="one_at_a_time")
|
instructions = render_instructions(spiff_task)
|
||||||
processor.do_engine_steps(execution_strategy_name="run_until_user_message")
|
|
||||||
processor.save() # Fixme - maybe find a way not to do this on every loop?
|
|
||||||
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"
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
api_error = ApiError(
|
api_error = ApiError(
|
||||||
error_code="engine_steps_error",
|
error_code="engine_steps_error",
|
||||||
message=f"Failed complete an automated task. Error was: {str(e)}",
|
message=f"Failed to complete an automated task. Error was: {str(e)}",
|
||||||
status_code=400,
|
status_code=400,
|
||||||
)
|
)
|
||||||
yield f"data: {current_app.json.dumps(api_error)} \n\n"
|
yield f"data: {current_app.json.dumps(api_error)} \n\n"
|
||||||
|
raise e
|
||||||
# Note, this has to be done in case someone leaves the page,
|
task.properties = {"instructionsForEndUser": instructions}
|
||||||
# which can otherwise cancel this function and leave completed tasks un-registered.
|
|
||||||
spiff_task = processor.next_task()
|
|
||||||
task_model = TaskModel.query.filter_by(guid=str(spiff_task.id)).first()
|
|
||||||
|
|
||||||
# Always provide some response, in the event no instructions were provided.
|
|
||||||
if len(reported_ids) == 0:
|
|
||||||
task = ProcessInstanceService.spiff_task_to_api_task(processor, processor.next_task())
|
|
||||||
yield f"data: {current_app.json.dumps(task)} \n\n"
|
yield f"data: {current_app.json.dumps(task)} \n\n"
|
||||||
|
|
||||||
|
|
||||||
|
def get_ready_engine_step_count(bpmn_process_instance: BpmnWorkflow) -> int:
|
||||||
|
return len(
|
||||||
|
list(
|
||||||
|
[
|
||||||
|
t
|
||||||
|
for t in bpmn_process_instance.get_tasks(TaskState.READY)
|
||||||
|
if bpmn_process_instance._is_engine_task(t.task_spec)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _dequeued_interstitial_stream(process_instance_id: int) -> Generator[str, Optional[str], None]:
|
def _dequeued_interstitial_stream(process_instance_id: int) -> Generator[str, Optional[str], None]:
|
||||||
process_instance = _find_process_instance_by_id_or_raise(process_instance_id)
|
process_instance = _find_process_instance_by_id_or_raise(process_instance_id)
|
||||||
with ProcessInstanceQueueService.dequeued(process_instance):
|
with ProcessInstanceQueueService.dequeued(process_instance):
|
||||||
|
|
|
@ -572,6 +572,7 @@ class AuthorizationService:
|
||||||
permissions_to_assign: list[PermissionToAssign] = []
|
permissions_to_assign: list[PermissionToAssign] = []
|
||||||
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/process-instances/for-me"))
|
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/process-instances/for-me"))
|
||||||
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/processes"))
|
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/processes"))
|
||||||
|
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/processes/callers"))
|
||||||
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/service-tasks"))
|
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/service-tasks"))
|
||||||
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/user-groups/for-current-user"))
|
permissions_to_assign.append(PermissionToAssign(permission="read", target_uri="/user-groups/for-current-user"))
|
||||||
permissions_to_assign.append(PermissionToAssign(permission="create", target_uri="/users/exists/by-username"))
|
permissions_to_assign.append(PermissionToAssign(permission="create", target_uri="/users/exists/by-username"))
|
||||||
|
|
|
@ -220,14 +220,15 @@ class TaskService:
|
||||||
if task_model.state == "COMPLETED":
|
if task_model.state == "COMPLETED":
|
||||||
event_type = ProcessInstanceEventType.task_completed.value
|
event_type = ProcessInstanceEventType.task_completed.value
|
||||||
timestamp = task_model.end_in_seconds or task_model.start_in_seconds or time.time()
|
timestamp = task_model.end_in_seconds or task_model.start_in_seconds or time.time()
|
||||||
process_instance_event, _process_instance_error_detail = (
|
(
|
||||||
ProcessInstanceTmpService.add_event_to_process_instance(
|
process_instance_event,
|
||||||
self.process_instance,
|
_process_instance_error_detail,
|
||||||
event_type,
|
) = ProcessInstanceTmpService.add_event_to_process_instance(
|
||||||
task_guid=task_model.guid,
|
self.process_instance,
|
||||||
timestamp=timestamp,
|
event_type,
|
||||||
add_to_db_session=False,
|
task_guid=task_model.guid,
|
||||||
)
|
timestamp=timestamp,
|
||||||
|
add_to_db_session=False,
|
||||||
)
|
)
|
||||||
self.process_instance_events[task_model.guid] = process_instance_event
|
self.process_instance_events[task_model.guid] = process_instance_event
|
||||||
|
|
||||||
|
@ -454,7 +455,6 @@ class TaskService:
|
||||||
spiff_task,
|
spiff_task,
|
||||||
self.bpmn_definition_to_task_definitions_mappings,
|
self.bpmn_definition_to_task_definitions_mappings,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.update_task_model(task_model, spiff_task)
|
self.update_task_model(task_model, spiff_task)
|
||||||
self.task_models[task_model.guid] = task_model
|
self.task_models[task_model.guid] = task_model
|
||||||
|
|
||||||
|
|
|
@ -299,28 +299,28 @@ class RunUntilServiceTaskExecutionStrategy(ExecutionStrategy):
|
||||||
|
|
||||||
|
|
||||||
class RunUntilUserTaskOrMessageExecutionStrategy(ExecutionStrategy):
|
class RunUntilUserTaskOrMessageExecutionStrategy(ExecutionStrategy):
|
||||||
"""When you want to run tasks until you hit something to report to the end user."""
|
"""When you want to run tasks until you hit something to report to the end user.
|
||||||
|
|
||||||
def get_engine_steps(self, bpmn_process_instance: BpmnWorkflow) -> list[SpiffTask]:
|
Note that this will run at least one engine step if possible,
|
||||||
return list(
|
but will stop if it hits instructions after the first task.
|
||||||
[
|
"""
|
||||||
t
|
|
||||||
for t in bpmn_process_instance.get_tasks(TaskState.READY)
|
|
||||||
if t.task_spec.spec_type not in ["User Task", "Manual Task"]
|
|
||||||
and not (
|
|
||||||
hasattr(t.task_spec, "extensions") and t.task_spec.extensions.get("instructionsForEndUser", None)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
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:
|
||||||
engine_steps = self.get_engine_steps(bpmn_process_instance)
|
should_continue = True
|
||||||
while engine_steps:
|
bpmn_process_instance.refresh_waiting_tasks()
|
||||||
|
engine_steps = self.get_ready_engine_steps(bpmn_process_instance)
|
||||||
|
while engine_steps and should_continue:
|
||||||
for task in engine_steps:
|
for task in engine_steps:
|
||||||
|
if hasattr(task.task_spec, "extensions") and task.task_spec.extensions.get(
|
||||||
|
"instructionsForEndUser", None
|
||||||
|
):
|
||||||
|
should_continue = False
|
||||||
|
break
|
||||||
self.delegate.will_complete_task(task)
|
self.delegate.will_complete_task(task)
|
||||||
task.run()
|
task.run()
|
||||||
self.delegate.did_complete_task(task)
|
self.delegate.did_complete_task(task)
|
||||||
engine_steps = self.get_engine_steps(bpmn_process_instance)
|
bpmn_process_instance.refresh_waiting_tasks()
|
||||||
|
engine_steps = self.get_ready_engine_steps(bpmn_process_instance)
|
||||||
self.delegate.after_engine_steps(bpmn_process_instance)
|
self.delegate.after_engine_steps(bpmn_process_instance)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -591,7 +591,7 @@ class TestProcessApi(BaseTest):
|
||||||
|
|
||||||
# get the results
|
# get the results
|
||||||
response = client.get(
|
response = client.get(
|
||||||
"/v1.0/processes/Level2/callers",
|
"/v1.0/processes/callers?bpmn_process_identifier=Level2",
|
||||||
headers=self.logged_in_headers(with_super_admin_user),
|
headers=self.logged_in_headers(with_super_admin_user),
|
||||||
)
|
)
|
||||||
assert response.json is not None
|
assert response.json is not None
|
||||||
|
|
|
@ -296,6 +296,7 @@ class TestAuthorizationService(BaseTest):
|
||||||
("/process-instances/reports/*", "read"),
|
("/process-instances/reports/*", "read"),
|
||||||
("/process-instances/reports/*", "update"),
|
("/process-instances/reports/*", "update"),
|
||||||
("/processes", "read"),
|
("/processes", "read"),
|
||||||
|
("/processes/callers", "read"),
|
||||||
("/service-tasks", "read"),
|
("/service-tasks", "read"),
|
||||||
("/tasks/*", "create"),
|
("/tasks/*", "create"),
|
||||||
("/tasks/*", "delete"),
|
("/tasks/*", "delete"),
|
||||||
|
|
|
@ -348,8 +348,13 @@ class TestProcessInstanceProcessor(BaseTest):
|
||||||
assert len(process_instance.human_tasks) == 1
|
assert len(process_instance.human_tasks) == 1
|
||||||
|
|
||||||
spiff_manual_task = processor.bpmn_process_instance.get_task_from_id(UUID(human_task_one.task_id))
|
spiff_manual_task = processor.bpmn_process_instance.get_task_from_id(UUID(human_task_one.task_id))
|
||||||
|
assert len(process_instance.active_human_tasks) == 1, "expected 1 active human task"
|
||||||
|
|
||||||
ProcessInstanceService.complete_form_task(processor, spiff_manual_task, {}, initiator_user, human_task_one)
|
ProcessInstanceService.complete_form_task(processor, spiff_manual_task, {}, initiator_user, human_task_one)
|
||||||
assert len(process_instance.human_tasks) == 2, "expected 2 human tasks after first one is completed"
|
assert len(process_instance.human_tasks) == 2, "expected 2 human tasks after first one is completed"
|
||||||
|
assert (
|
||||||
|
len(process_instance.active_human_tasks) == 1
|
||||||
|
), "expected 1 active human tasks after 1st one is completed"
|
||||||
|
|
||||||
# unnecessary lookup just in case on windows
|
# unnecessary lookup just in case on windows
|
||||||
process_instance = ProcessInstanceModel.query.filter_by(id=process_instance.id).first()
|
process_instance = ProcessInstanceModel.query.filter_by(id=process_instance.id).first()
|
||||||
|
@ -359,8 +364,8 @@ class TestProcessInstanceProcessor(BaseTest):
|
||||||
ProcessInstanceService.complete_form_task(processor, spiff_manual_task, {}, initiator_user, human_task_one)
|
ProcessInstanceService.complete_form_task(processor, spiff_manual_task, {}, initiator_user, human_task_one)
|
||||||
import pdb; pdb.set_trace()
|
import pdb; pdb.set_trace()
|
||||||
assert (
|
assert (
|
||||||
len(process_instance.active_human_tasks) == 0
|
len(process_instance.active_human_tasks) == 1
|
||||||
), "expected 0 active human tasks after 2nd one is completed"
|
), "expected 1 active human tasks after 2nd one is completed, as we have looped back around."
|
||||||
|
|
||||||
processor.suspend()
|
processor.suspend()
|
||||||
|
|
||||||
|
@ -373,7 +378,7 @@ class TestProcessInstanceProcessor(BaseTest):
|
||||||
assert len(all_task_models_matching_top_level_subprocess_script) == 1
|
assert len(all_task_models_matching_top_level_subprocess_script) == 1
|
||||||
task_model_to_reset_to = all_task_models_matching_top_level_subprocess_script[0]
|
task_model_to_reset_to = all_task_models_matching_top_level_subprocess_script[0]
|
||||||
assert task_model_to_reset_to is not None
|
assert task_model_to_reset_to is not None
|
||||||
assert len(process_instance.human_tasks) == 2, "expected 2 human tasks before reset"
|
assert len(process_instance.human_tasks) == 3, "expected 3 human tasks before reset"
|
||||||
ProcessInstanceProcessor.reset_process(process_instance, task_model_to_reset_to.guid)
|
ProcessInstanceProcessor.reset_process(process_instance, task_model_to_reset_to.guid)
|
||||||
assert len(process_instance.human_tasks) == 2, "still expected 2 human tasks after reset"
|
assert len(process_instance.human_tasks) == 2, "still expected 2 human tasks after reset"
|
||||||
|
|
||||||
|
@ -381,7 +386,7 @@ class TestProcessInstanceProcessor(BaseTest):
|
||||||
db.session.expire_all()
|
db.session.expire_all()
|
||||||
assert (
|
assert (
|
||||||
len(process_instance.human_tasks) == 2
|
len(process_instance.human_tasks) == 2
|
||||||
), "still expected 2 human tasks after reset and session expire_all"
|
), "still expected 3 human tasks after reset and session expire_all"
|
||||||
|
|
||||||
process_instance = ProcessInstanceModel.query.filter_by(id=process_instance.id).first()
|
process_instance = ProcessInstanceModel.query.filter_by(id=process_instance.id).first()
|
||||||
processor = ProcessInstanceProcessor(process_instance)
|
processor = ProcessInstanceProcessor(process_instance)
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
"bootstrap": "^5.2.0",
|
"bootstrap": "^5.2.0",
|
||||||
"bpmn-js": "^9.3.2",
|
"bpmn-js": "^9.3.2",
|
||||||
"bpmn-js-properties-panel": "^1.10.0",
|
"bpmn-js-properties-panel": "^1.10.0",
|
||||||
"bpmn-js-spiffworkflow": "sartography/bpmn-js-spiffworkflow#main",
|
"bpmn-js-spiffworkflow": "github:sartography/bpmn-js-spiffworkflow#main",
|
||||||
"cookie": "^0.5.0",
|
"cookie": "^0.5.0",
|
||||||
"craco": "^0.0.3",
|
"craco": "^0.0.3",
|
||||||
"cypress-slow-down": "^1.2.1",
|
"cypress-slow-down": "^1.2.1",
|
||||||
|
@ -8343,7 +8343,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#5f2cb3d50b021dffce61fd6b2929ef5620dcdb2e",
|
"resolved": "git+ssh://git@github.com/sartography/bpmn-js-spiffworkflow.git#313969da1067fce0a51b152626a609a122697693",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"inherits": "^2.0.4",
|
"inherits": "^2.0.4",
|
||||||
|
@ -38215,8 +38215,8 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bpmn-js-spiffworkflow": {
|
"bpmn-js-spiffworkflow": {
|
||||||
"version": "git+ssh://git@github.com/sartography/bpmn-js-spiffworkflow.git#5f2cb3d50b021dffce61fd6b2929ef5620dcdb2e",
|
"version": "git+ssh://git@github.com/sartography/bpmn-js-spiffworkflow.git#313969da1067fce0a51b152626a609a122697693",
|
||||||
"from": "bpmn-js-spiffworkflow@sartography/bpmn-js-spiffworkflow#main",
|
"from": "bpmn-js-spiffworkflow@github:sartography/bpmn-js-spiffworkflow#main",
|
||||||
"requires": {
|
"requires": {
|
||||||
"inherits": "^2.0.4",
|
"inherits": "^2.0.4",
|
||||||
"inherits-browser": "^0.0.1",
|
"inherits-browser": "^0.0.1",
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
"bootstrap": "^5.2.0",
|
"bootstrap": "^5.2.0",
|
||||||
"bpmn-js": "^9.3.2",
|
"bpmn-js": "^9.3.2",
|
||||||
"bpmn-js-properties-panel": "^1.10.0",
|
"bpmn-js-properties-panel": "^1.10.0",
|
||||||
"bpmn-js-spiffworkflow": "sartography/bpmn-js-spiffworkflow#main",
|
"bpmn-js-spiffworkflow": "github:sartography/bpmn-js-spiffworkflow#main",
|
||||||
"cookie": "^0.5.0",
|
"cookie": "^0.5.0",
|
||||||
"craco": "^0.0.3",
|
"craco": "^0.0.3",
|
||||||
"cypress-slow-down": "^1.2.1",
|
"cypress-slow-down": "^1.2.1",
|
||||||
|
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 2.5 KiB |
|
@ -2,12 +2,19 @@ import React from 'react';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import MDEditor from '@uiw/react-md-editor';
|
import MDEditor from '@uiw/react-md-editor';
|
||||||
|
|
||||||
export default function InstructionsForEndUser({ task }: any) {
|
type OwnProps = {
|
||||||
|
task: any;
|
||||||
|
defaultMessage?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function InstructionsForEndUser({
|
||||||
|
task,
|
||||||
|
defaultMessage = '',
|
||||||
|
}: OwnProps) {
|
||||||
if (!task) {
|
if (!task) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
let instructions =
|
let instructions = defaultMessage;
|
||||||
'There is no additional instructions or information for this task.';
|
|
||||||
let { properties } = task;
|
let { properties } = task;
|
||||||
if (!properties) {
|
if (!properties) {
|
||||||
properties = task.extensions;
|
properties = task.extensions;
|
||||||
|
|
|
@ -1551,6 +1551,7 @@ export default function ProcessInstanceListTable({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
const buildTable = () => {
|
const buildTable = () => {
|
||||||
const headerLabels: Record<string, string> = {
|
const headerLabels: Record<string, string> = {
|
||||||
id: 'Id',
|
id: 'Id',
|
||||||
|
@ -1592,11 +1593,11 @@ export default function ProcessInstanceListTable({
|
||||||
buttonElement = (
|
buttonElement = (
|
||||||
<Button
|
<Button
|
||||||
kind={
|
kind={
|
||||||
hasAccessToCompleteTask && row.task_id ? 'secondary' : 'tertiary'
|
hasAccessToCompleteTask && row.task_id ? 'secondary' : 'ghost'
|
||||||
}
|
}
|
||||||
href={interstitialUrl}
|
href={interstitialUrl}
|
||||||
>
|
>
|
||||||
Go
|
{hasAccessToCompleteTask && row.task_id ? 'Go' : 'View'}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
currentRow.push(<td>{buttonElement}</td>);
|
currentRow.push(<td>{buttonElement}</td>);
|
||||||
|
|
|
@ -29,6 +29,10 @@
|
||||||
border-color: none;
|
border-color: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cds--loading__stroke {
|
||||||
|
stroke: gray;
|
||||||
|
}
|
||||||
|
|
||||||
/* make this a little less prominent so the actual human beings completing tasks stand out */
|
/* make this a little less prominent so the actual human beings completing tasks stand out */
|
||||||
.system-user-log-entry {
|
.system-user-log-entry {
|
||||||
color: #B0B0B0;
|
color: #B0B0B0;
|
||||||
|
@ -424,3 +428,24 @@ svg.notification-icon {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.user_instructions_0 {
|
||||||
|
filter: opacity(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user_instructions_1 {
|
||||||
|
filter: opacity(60%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user_instructions_2 {
|
||||||
|
filter: opacity(40%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user_instructions_3 {
|
||||||
|
filter: opacity(20%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user_instructions_4 {
|
||||||
|
filter: opacity(10%);
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import { fetchEventSource } from '@microsoft/fetch-event-source';
|
import { fetchEventSource } from '@microsoft/fetch-event-source';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { Loading, Grid, Column } from '@carbon/react';
|
import { Loading, Grid, Column, Button } from '@carbon/react';
|
||||||
import { BACKEND_BASE_URL } from '../config';
|
import { BACKEND_BASE_URL } from '../config';
|
||||||
import { getBasicHeaders } from '../services/HttpService';
|
import { getBasicHeaders } from '../services/HttpService';
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ export default function ProcessInterstitial() {
|
||||||
if ('error_code' in retValue) {
|
if ('error_code' in retValue) {
|
||||||
addError(retValue);
|
addError(retValue);
|
||||||
} else {
|
} else {
|
||||||
setData((prevData) => [...prevData, retValue]);
|
setData((prevData) => [retValue, ...prevData]);
|
||||||
setLastTask(retValue);
|
setLastTask(retValue);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -100,6 +100,33 @@ export default function ProcessInterstitial() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getReturnHomeButton = (index: number) => {
|
||||||
|
if (
|
||||||
|
index === 0 &&
|
||||||
|
state !== 'REDIRECTING' &&
|
||||||
|
['WAITING', 'ERROR', 'LOCKED', 'COMPLETED', 'READY'].includes(getStatus())
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
<div style={{ padding: '10px 0 0 0' }}>
|
||||||
|
<Button kind="secondary" onClick={() => navigate(`/tasks`)}>
|
||||||
|
Return to Home
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const getHr = (index: number) => {
|
||||||
|
if (index === 0) {
|
||||||
|
return (
|
||||||
|
<div style={{ padding: '10px 0 50px 0' }}>
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
function capitalize(str: string): string {
|
function capitalize(str: string): string {
|
||||||
if (str && str.length > 0) {
|
if (str && str.length > 0) {
|
||||||
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
||||||
|
@ -109,7 +136,15 @@ export default function ProcessInterstitial() {
|
||||||
|
|
||||||
const userMessage = (myTask: ProcessInstanceTask) => {
|
const userMessage = (myTask: ProcessInstanceTask) => {
|
||||||
if (!myTask.can_complete && userTasks.includes(myTask.type)) {
|
if (!myTask.can_complete && userTasks.includes(myTask.type)) {
|
||||||
return <div>This next task must be completed by a different person.</div>;
|
return (
|
||||||
|
<>
|
||||||
|
<h4 className="heading-compact-01">Waiting on Someone Else</h4>
|
||||||
|
<p>
|
||||||
|
This next task is assigned to a different person or team. There is
|
||||||
|
no action for you take at this time.
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (shouldRedirect(myTask)) {
|
if (shouldRedirect(myTask)) {
|
||||||
return <div>Redirecting you to the next task now ...</div>;
|
return <div>Redirecting you to the next task now ...</div>;
|
||||||
|
@ -120,7 +155,10 @@ export default function ProcessInterstitial() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<InstructionsForEndUser task={myTask} />
|
<InstructionsForEndUser
|
||||||
|
task={myTask}
|
||||||
|
defaultMessage="There are no additional instructions or information for this task."
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -147,7 +185,7 @@ export default function ProcessInterstitial() {
|
||||||
],
|
],
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
|
||||||
{getStatusImage()}
|
{getStatusImage()}
|
||||||
<div>
|
<div>
|
||||||
<h1 style={{ marginBottom: '0em' }}>
|
<h1 style={{ marginBottom: '0em' }}>
|
||||||
|
@ -159,13 +197,20 @@ export default function ProcessInterstitial() {
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
{data.map((d) => (
|
{data.map((d, index) => (
|
||||||
<Grid fullWidth style={{ marginBottom: '1em' }}>
|
<Grid fullWidth style={{ marginBottom: '1em' }}>
|
||||||
<Column md={2} lg={4} sm={2}>
|
<Column md={6} lg={8} sm={4}>
|
||||||
Task: <em>{d.title}</em>
|
<div
|
||||||
</Column>
|
className={
|
||||||
<Column md={6} lg={6} sm={4}>
|
index < 4
|
||||||
{userMessage(d)}
|
? `user_instructions_${index}`
|
||||||
|
: `user_instructions_4`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{userMessage(d)}
|
||||||
|
</div>
|
||||||
|
{getReturnHomeButton(index)}
|
||||||
|
{getHr(index)}
|
||||||
</Column>
|
</Column>
|
||||||
</Grid>
|
</Grid>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -168,7 +168,7 @@ export default function ProcessModelEditDiagram() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (processModel !== null) {
|
if (processModel !== null) {
|
||||||
HttpService.makeCallToBackend({
|
HttpService.makeCallToBackend({
|
||||||
path: `/processes/${processModel.primary_process_id}/callers`,
|
path: `/processes/callers?bpmn_process_identifier=${processModel.primary_process_id}`,
|
||||||
successCallback: setCallers,
|
successCallback: setCallers,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|