fixed test w/ burnettk

This commit is contained in:
jasquat 2023-04-19 15:52:11 -04:00
parent d73baedcbe
commit b1568fb472
13 changed files with 156 additions and 135 deletions

View File

@ -1,9 +1,7 @@
"""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
@ -14,10 +12,11 @@ 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, stream_with_context from flask import current_app
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
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.exceptions import WorkflowTaskException # type: ignore from SpiffWorkflow.exceptions import WorkflowTaskException # type: ignore
@ -85,7 +84,7 @@ class ReactJsonSchemaSelectOption(TypedDict):
def task_list_my_tasks( def task_list_my_tasks(
process_instance_id: Optional[int] = None, page: int = 1, per_page: int = 100 process_instance_id: Optional[int] = None, page: int = 1, per_page: int = 100
) -> flask.wrappers.Response: ) -> flask.wrappers.Response:
"""Task_list_my_tasks.""" """Task_list_my_tasks."""
principal = _find_principal_or_raise() principal = _find_principal_or_raise()
@ -166,7 +165,7 @@ def task_list_for_me(page: int = 1, per_page: int = 100) -> flask.wrappers.Respo
def task_list_for_my_groups( def task_list_for_my_groups(
user_group_identifier: Optional[str] = None, page: int = 1, per_page: int = 100 user_group_identifier: Optional[str] = None, page: int = 1, per_page: int = 100
) -> flask.wrappers.Response: ) -> flask.wrappers.Response:
"""Task_list_for_my_groups.""" """Task_list_for_my_groups."""
return _get_tasks( return _get_tasks(
@ -178,9 +177,9 @@ def task_list_for_my_groups(
def task_data_show( def task_data_show(
modified_process_model_identifier: str, modified_process_model_identifier: str,
process_instance_id: int, process_instance_id: int,
task_guid: str, task_guid: str,
) -> flask.wrappers.Response: ) -> flask.wrappers.Response:
task_model = _get_task_model_from_guid_or_raise(task_guid, process_instance_id) task_model = _get_task_model_from_guid_or_raise(task_guid, process_instance_id)
task_model.data = task_model.json_data() task_model.data = task_model.json_data()
@ -188,10 +187,10 @@ def task_data_show(
def task_data_update( def task_data_update(
process_instance_id: str, process_instance_id: str,
modified_process_model_identifier: str, modified_process_model_identifier: str,
task_guid: str, task_guid: str,
body: Dict, body: Dict,
) -> Response: ) -> Response:
"""Update task data.""" """Update task data."""
process_instance = ProcessInstanceModel.query.filter(ProcessInstanceModel.id == int(process_instance_id)).first() process_instance = ProcessInstanceModel.query.filter(ProcessInstanceModel.id == int(process_instance_id)).first()
@ -241,10 +240,10 @@ def task_data_update(
def manual_complete_task( def manual_complete_task(
modified_process_model_identifier: str, modified_process_model_identifier: str,
process_instance_id: str, process_instance_id: str,
task_guid: str, task_guid: str,
body: Dict, body: Dict,
) -> Response: ) -> Response:
"""Mark a task complete without executing it.""" """Mark a task complete without executing it."""
execute = body.get("execute", True) execute = body.get("execute", True)
@ -359,9 +358,7 @@ def _render_instructions_for_end_user(spiff_task: SpiffTask, task: Task):
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:
instructions = _render_jinja_template( instructions = _render_jinja_template(task.properties["instructionsForEndUser"], spiff_task)
task.properties["instructionsForEndUser"], spiff_task
)
task.properties["instructionsForEndUser"] = instructions task.properties["instructionsForEndUser"] = instructions
return instructions return instructions
except WorkflowTaskException as wfe: except WorkflowTaskException as wfe:
@ -371,9 +368,9 @@ def _render_instructions_for_end_user(spiff_task: SpiffTask, task: Task):
def process_data_show( def process_data_show(
process_instance_id: int, process_instance_id: int,
process_data_identifier: str, process_data_identifier: str,
modified_process_model_identifier: str, modified_process_model_identifier: str,
) -> flask.wrappers.Response: ) -> flask.wrappers.Response:
"""Process_data_show.""" """Process_data_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)
@ -408,7 +405,7 @@ def _interstitial_stream(process_instance_id: int):
instructions = _render_instructions_for_end_user(spiff_task, task) instructions = _render_instructions_for_end_user(spiff_task, task)
if instructions and spiff_task.id not in reported_ids: if instructions and spiff_task.id not in reported_ids:
reported_ids.append(spiff_task.id) reported_ids.append(spiff_task.id)
yield f'data: {current_app.json.dumps(task)} \n\n' yield f"data: {current_app.json.dumps(task)} \n\n"
last_task = spiff_task last_task = spiff_task
processor.do_engine_steps(execution_strategy_name="run_until_user_message") processor.do_engine_steps(execution_strategy_name="run_until_user_message")
processor.do_engine_steps(execution_strategy_name="one_at_a_time") processor.do_engine_steps(execution_strategy_name="one_at_a_time")
@ -420,15 +417,15 @@ def _interstitial_stream(process_instance_id: int):
def interstitial(process_instance_id: int): def interstitial(process_instance_id: int):
"""A Server Side Events Stream for watching the execution of engine tasks in a """A Server Side Events Stream for watching the execution of engine tasks in a
process instance. """ process instance."""
return Response(stream_with_context(_interstitial_stream(process_instance_id)), mimetype='text/event-stream') return Response(stream_with_context(_interstitial_stream(process_instance_id)), mimetype="text/event-stream")
def _task_submit_shared( def _task_submit_shared(
process_instance_id: int, process_instance_id: int,
task_guid: str, task_guid: str,
body: Dict[str, Any], body: Dict[str, Any],
save_as_draft: bool = False, save_as_draft: bool = False,
) -> flask.wrappers.Response: ) -> flask.wrappers.Response:
principal = _find_principal_or_raise() principal = _find_principal_or_raise()
process_instance = _find_process_instance_by_id_or_raise(process_instance_id) process_instance = _find_process_instance_by_id_or_raise(process_instance_id)
@ -509,18 +506,24 @@ def _task_submit_shared(
task = ProcessInstanceService.spiff_task_to_api_task(processor, processor.next_task()) task = ProcessInstanceService.spiff_task_to_api_task(processor, processor.next_task())
return make_response(jsonify(task), 200) return make_response(jsonify(task), 200)
return Response(json.dumps( return Response(
{"ok": True, json.dumps(
"process_model_identifier": process_instance.process_model_identifier, {
"process_instance_id": process_instance_id "ok": True,
}), status=202, mimetype="application/json") "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,
task_guid: str, task_guid: str,
body: Dict[str, Any], body: Dict[str, Any],
save_as_draft: bool = False, save_as_draft: bool = False,
) -> flask.wrappers.Response: ) -> flask.wrappers.Response:
"""Task_submit_user_data.""" """Task_submit_user_data."""
with sentry_sdk.start_span(op="controller_action", description="tasks_controller.task_submit"): with sentry_sdk.start_span(op="controller_action", description="tasks_controller.task_submit"):
@ -528,11 +531,11 @@ def task_submit(
def _get_tasks( def _get_tasks(
processes_started_by_user: bool = True, processes_started_by_user: bool = True,
has_lane_assignment_id: bool = True, has_lane_assignment_id: bool = True,
page: int = 1, page: int = 1,
per_page: int = 100, per_page: int = 100,
user_group_identifier: Optional[str] = None, user_group_identifier: Optional[str] = None,
) -> flask.wrappers.Response: ) -> flask.wrappers.Response:
"""Get_tasks.""" """Get_tasks."""
user_id = g.user.id user_id = g.user.id
@ -679,9 +682,9 @@ def _render_jinja_template(unprocessed_template: str, spiff_task: SpiffTask) ->
def _get_spiff_task_from_process_instance( def _get_spiff_task_from_process_instance(
task_guid: str, task_guid: str,
process_instance: ProcessInstanceModel, process_instance: ProcessInstanceModel,
processor: Union[ProcessInstanceProcessor, None] = None, processor: Union[ProcessInstanceProcessor, None] = None,
) -> SpiffTask: ) -> SpiffTask:
"""Get_spiff_task_from_process_instance.""" """Get_spiff_task_from_process_instance."""
if processor is None: if processor is None:
@ -737,8 +740,9 @@ def _update_form_schema_with_task_data_as_needed(in_dict: dict, task: Task, spif
select_options_from_task_data = task.data.get(task_data_var) select_options_from_task_data = task.data.get(task_data_var)
if isinstance(select_options_from_task_data, list): if isinstance(select_options_from_task_data, list):
if all("value" in d and "label" in d for d in select_options_from_task_data): if all("value" in d and "label" in d for d in select_options_from_task_data):
def map_function( def map_function(
task_data_select_option: TaskDataSelectOption, task_data_select_option: TaskDataSelectOption,
) -> ReactJsonSchemaSelectOption: ) -> ReactJsonSchemaSelectOption:
"""Map_function.""" """Map_function."""
return { return {
@ -776,9 +780,9 @@ def _get_potential_owner_usernames(assigned_user: AliasedClass) -> Any:
def _find_human_task_or_raise( def _find_human_task_or_raise(
process_instance_id: int, process_instance_id: int,
task_guid: str, task_guid: str,
only_tasks_that_can_be_completed: bool = False, only_tasks_that_can_be_completed: bool = False,
) -> HumanTaskModel: ) -> HumanTaskModel:
if only_tasks_that_can_be_completed: if only_tasks_that_can_be_completed:
human_task_query = HumanTaskModel.query.filter_by( human_task_query = HumanTaskModel.query.filter_by(

View File

@ -10,7 +10,8 @@ from typing import Tuple
from urllib.parse import unquote from urllib.parse import unquote
import sentry_sdk import sentry_sdk
from flask import current_app, g from flask import current_app
from flask import g
from SpiffWorkflow.bpmn.specs.events.IntermediateEvent import _BoundaryEventParent # type: ignore from SpiffWorkflow.bpmn.specs.events.IntermediateEvent import _BoundaryEventParent # type: ignore
from SpiffWorkflow.task import Task as SpiffTask # type: ignore from SpiffWorkflow.task import Task as SpiffTask # type: ignore
@ -26,8 +27,9 @@ from spiffworkflow_backend.models.process_instance_file_data import (
from spiffworkflow_backend.models.process_model import ProcessModelInfo from spiffworkflow_backend.models.process_model import ProcessModelInfo
from spiffworkflow_backend.models.task import Task from spiffworkflow_backend.models.task import Task
from spiffworkflow_backend.models.user import UserModel from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.services.authorization_service import AuthorizationService, HumanTaskNotFoundError, \ from spiffworkflow_backend.services.authorization_service import AuthorizationService
UserDoesNotHaveAccessToTaskError from spiffworkflow_backend.services.authorization_service import HumanTaskNotFoundError
from spiffworkflow_backend.services.authorization_service import UserDoesNotHaveAccessToTaskError
from spiffworkflow_backend.services.git_service import GitCommandError from spiffworkflow_backend.services.git_service import GitCommandError
from spiffworkflow_backend.services.git_service import GitService from spiffworkflow_backend.services.git_service import GitService
from spiffworkflow_backend.services.process_instance_processor import ( from spiffworkflow_backend.services.process_instance_processor import (
@ -427,12 +429,13 @@ class ProcessInstanceService:
# can complete it. # can complete it.
can_complete = False can_complete = False
try: try:
AuthorizationService.assert_user_can_complete_spiff_task(processor.process_instance_model.id, spiff_task, AuthorizationService.assert_user_can_complete_spiff_task(
g.user) processor.process_instance_model.id, spiff_task, g.user
)
can_complete = True can_complete = True
except HumanTaskNotFoundError as e: except HumanTaskNotFoundError:
can_complete = False can_complete = False
except UserDoesNotHaveAccessToTaskError as ude: except UserDoesNotHaveAccessToTaskError:
can_complete = False can_complete = False
if hasattr(spiff_task.task_spec, "spec"): if hasattr(spiff_task.task_spec, "spec"):

View File

@ -1,6 +1,7 @@
import copy import copy
import time import time
from typing import Callable, List from typing import Callable
from typing import 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
@ -50,11 +51,11 @@ class TaskModelSavingDelegate(EngineStepDelegate):
""" """
def __init__( def __init__(
self, self,
serializer: BpmnWorkflowSerializer, serializer: BpmnWorkflowSerializer,
process_instance: ProcessInstanceModel, process_instance: ProcessInstanceModel,
bpmn_definition_to_task_definitions_mappings: dict, bpmn_definition_to_task_definitions_mappings: dict,
secondary_engine_step_delegate: Optional[EngineStepDelegate] = None, secondary_engine_step_delegate: Optional[EngineStepDelegate] = None,
) -> None: ) -> None:
self.secondary_engine_step_delegate = secondary_engine_step_delegate self.secondary_engine_step_delegate = secondary_engine_step_delegate
self.process_instance = process_instance self.process_instance = process_instance
@ -132,12 +133,12 @@ class TaskModelSavingDelegate(EngineStepDelegate):
# excludes COMPLETED. the others were required to get PP1 to go to completion. # excludes COMPLETED. the others were required to get PP1 to go to completion.
# process FUTURE tasks because Boundary events are not processed otherwise. # process FUTURE tasks because Boundary events are not processed otherwise.
for waiting_spiff_task in bpmn_process_instance.get_tasks( for waiting_spiff_task in bpmn_process_instance.get_tasks(
TaskState.WAITING TaskState.WAITING
| TaskState.CANCELLED | TaskState.CANCELLED
| TaskState.READY | TaskState.READY
| TaskState.MAYBE | TaskState.MAYBE
| TaskState.LIKELY | TaskState.LIKELY
| TaskState.FUTURE | TaskState.FUTURE
): ):
# these will be removed from the parent and then ignored # these will be removed from the parent and then ignored
if waiting_spiff_task._has_state(TaskState.PREDICTED_MASK): if waiting_spiff_task._has_state(TaskState.PREDICTED_MASK):
@ -267,17 +268,21 @@ 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, or """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.""" until there are no other engine steps to complete."""
def get_engine_steps(self, bpmn_process_instance: BpmnWorkflow) -> List[SpiffTask]: def get_engine_steps(self, bpmn_process_instance: BpmnWorkflow) -> List[SpiffTask]:
return list([t for t in bpmn_process_instance.get_tasks(TaskState.READY) \ return list(
if t.task_spec.spec_type not in ["User Task", "Manual Task"] and [
not (hasattr(t.task_spec, "extensions") and t
t.task_spec.extensions.get("instructionsForEndUser", None)) 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) engine_steps = self.get_engine_steps(bpmn_process_instance)
while engine_steps: while engine_steps:
for task in engine_steps: for task in engine_steps:
@ -320,12 +325,12 @@ class WorkflowExecutionService:
"""Provides the driver code for workflow execution.""" """Provides the driver code for workflow execution."""
def __init__( def __init__(
self, self,
bpmn_process_instance: BpmnWorkflow, bpmn_process_instance: BpmnWorkflow,
process_instance_model: ProcessInstanceModel, process_instance_model: ProcessInstanceModel,
execution_strategy: ExecutionStrategy, execution_strategy: ExecutionStrategy,
process_instance_completer: ProcessInstanceCompleter, process_instance_completer: ProcessInstanceCompleter,
process_instance_saver: ProcessInstanceSaver, process_instance_saver: ProcessInstanceSaver,
): ):
"""__init__.""" """__init__."""
self.bpmn_process_instance = bpmn_process_instance self.bpmn_process_instance = bpmn_process_instance
@ -402,12 +407,12 @@ class WorkflowExecutionService:
for event in waiting_message_events: for event in waiting_message_events:
# Ensure we are only creating one message instance for each waiting message # Ensure we are only creating one message instance for each waiting message
if ( if (
MessageInstanceModel.query.filter_by( MessageInstanceModel.query.filter_by(
process_instance_id=self.process_instance_model.id, process_instance_id=self.process_instance_model.id,
message_type="receive", message_type="receive",
name=event["name"], name=event["name"],
).count() ).count()
> 0 > 0
): ):
continue continue

View File

@ -120,4 +120,4 @@
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
</bpmndi:BPMNPlane> </bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram> </bpmndi:BPMNDiagram>
</bpmn:definitions> </bpmn:definitions>

View File

@ -3,14 +3,13 @@ from typing import Any
from flask.app import Flask from flask.app import Flask
from flask.testing import FlaskClient from flask.testing import FlaskClient
from spiffworkflow_backend.routes.tasks_controller import _interstitial_stream
from tests.spiffworkflow_backend.helpers.base_test import BaseTest from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
from spiffworkflow_backend import db from spiffworkflow_backend import db
from spiffworkflow_backend.models.human_task import HumanTaskModel from spiffworkflow_backend.models.human_task import HumanTaskModel
from spiffworkflow_backend.models.user import UserModel from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.routes.tasks_controller import _interstitial_stream
class TestForGoodErrors(BaseTest): class TestForGoodErrors(BaseTest):
@ -22,7 +21,6 @@ class TestForGoodErrors(BaseTest):
client: FlaskClient, client: FlaskClient,
with_super_admin_user: UserModel, with_super_admin_user: UserModel,
) -> Any: ) -> Any:
# Call this to assure all engine-steps are fully processed before we search for human tasks. # Call this to assure all engine-steps are fully processed before we search for human tasks.
_interstitial_stream(process_instance_id) _interstitial_stream(process_instance_id)

View File

@ -10,8 +10,6 @@ import pytest
from flask.app import Flask from flask.app import Flask
from flask.testing import FlaskClient from flask.testing import FlaskClient
from SpiffWorkflow.task import TaskState # type: ignore from SpiffWorkflow.task import TaskState # type: ignore
from spiffworkflow_backend.routes.tasks_controller import _interstitial_stream
from tests.spiffworkflow_backend.helpers.base_test import BaseTest from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
@ -35,6 +33,7 @@ from spiffworkflow_backend.models.process_model import ProcessModelInfoSchema
from spiffworkflow_backend.models.spec_reference import SpecReferenceCache from spiffworkflow_backend.models.spec_reference import SpecReferenceCache
from spiffworkflow_backend.models.task import TaskModel # noqa: F401 from spiffworkflow_backend.models.task import TaskModel # noqa: F401
from spiffworkflow_backend.models.user import UserModel from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.routes.tasks_controller import _interstitial_stream
from spiffworkflow_backend.services.authorization_service import AuthorizationService from spiffworkflow_backend.services.authorization_service import AuthorizationService
from spiffworkflow_backend.services.file_system_service import FileSystemService from spiffworkflow_backend.services.file_system_service import FileSystemService
from spiffworkflow_backend.services.process_instance_processor import ( from spiffworkflow_backend.services.process_instance_processor import (
@ -1615,13 +1614,12 @@ class TestProcessApi(BaseTest):
} }
def test_interstitial_page( def test_interstitial_page(
self, self,
app: Flask, app: Flask,
client: FlaskClient, client: FlaskClient,
with_db_and_bpmn_file_cleanup: None, with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel, with_super_admin_user: UserModel,
) -> None: ) -> None:
process_group_id = "my_process_group" process_group_id = "my_process_group"
process_model_id = "interstitial" process_model_id = "interstitial"
bpmn_file_location = "interstitial" bpmn_file_location = "interstitial"
@ -1649,23 +1647,24 @@ class TestProcessApi(BaseTest):
assert response.json is not None assert response.json is not None
assert response.json["next_task"] is not None assert response.json["next_task"] is not None
assert response.json["next_task"]["state"] == 'READY' assert response.json["next_task"]["state"] == "READY"
assert response.json["next_task"]["title"] == 'Script Task #2' assert response.json["next_task"]["title"] == "Script Task #2"
# Rather that call the API and deal with the Server Side Events, call the loop directly and covert it to # Rather that call the API and deal with the Server Side Events, call the loop directly and covert it to
# a list. It tests all of our code. No reason to test Flasks SSE support. # a list. It tests all of our code. No reason to test Flasks SSE support.
results = list(_interstitial_stream(process_instance_id)) results = list(_interstitial_stream(process_instance_id))
json_results = list(map(lambda x: json.loads(x[5:]), results)) # strip the "data:" prefix and convert remaining string to dict. # strip the "data:" prefix and convert remaining string to dict.
json_results = list(map(lambda x: json.loads(x[5:]), results))
# There should be 2 results back - # There should be 2 results back -
# the first script task should not be returned (it contains no end user instructions) # the first script task should not be returned (it contains no end user instructions)
# The second script task should produce rendered jinja text # The second script task should produce rendered jinja text
# The Manual Task should then return a message as well. # The Manual Task should then return a message as well.
assert len(results) == 2 assert len(results) == 2
assert json_results[0]["state"] == 'READY' assert json_results[0]["state"] == "READY"
assert json_results[0]["title"] == 'Script Task #2' assert json_results[0]["title"] == "Script Task #2"
assert json_results[0]["properties"]["instructionsForEndUser"] == 'I am Script Task 2' assert json_results[0]["properties"]["instructionsForEndUser"] == "I am Script Task 2"
assert json_results[1]["state"] == 'READY' assert json_results[1]["state"] == "READY"
assert json_results[1]["title"] == 'Manual Task' assert json_results[1]["title"] == "Manual Task"
response = client.put( response = client.put(
f"/v1.0/tasks/{process_instance_id}/{json_results[1]['id']}", f"/v1.0/tasks/{process_instance_id}/{json_results[1]['id']}",
@ -1678,10 +1677,10 @@ class TestProcessApi(BaseTest):
results = list(_interstitial_stream(process_instance_id)) results = list(_interstitial_stream(process_instance_id))
json_results = list(map(lambda x: json.loads(x[5:]), results)) json_results = list(map(lambda x: json.loads(x[5:]), results))
assert len(results) == 1 assert len(results) == 1
assert json_results[0]["state"] == 'READY' assert json_results[0]["state"] == "READY"
assert json_results[0]["can_complete"] == False assert json_results[0]["can_complete"] == False
assert json_results[0]["title"] == 'Please Approve' assert json_results[0]["title"] == "Please Approve"
assert json_results[0]["properties"]["instructionsForEndUser"] == 'I am a manual task in another lane' assert json_results[0]["properties"]["instructionsForEndUser"] == "I am a manual task in another lane"
# Complete task as the finance user. # Complete task as the finance user.
response = client.put( response = client.put(
@ -1690,13 +1689,13 @@ class TestProcessApi(BaseTest):
) )
# We should now be on the end task with a valid message, even after loading it many times. # We should now be on the end task with a valid message, even after loading it many times.
results_1 = list(_interstitial_stream(process_instance_id)) list(_interstitial_stream(process_instance_id))
results_2 = list(_interstitial_stream(process_instance_id)) list(_interstitial_stream(process_instance_id))
results = list(_interstitial_stream(process_instance_id)) results = list(_interstitial_stream(process_instance_id))
json_results = list(map(lambda x: json.loads(x[5:]), results)) json_results = list(map(lambda x: json.loads(x[5:]), results))
assert len(json_results) == 1 assert len(json_results) == 1
assert json_results[0]["state"] == 'COMPLETED' assert json_results[0]["state"] == "COMPLETED"
assert json_results[0]["properties"]["instructionsForEndUser"] == 'I am the end task' assert json_results[0]["properties"]["instructionsForEndUser"] == "I am the end task"
def test_process_instance_list_with_default_list( def test_process_instance_list_with_default_list(
self, self,

View File

@ -405,7 +405,7 @@ class TestProcessInstanceProcessor(BaseTest):
process_model=process_model, user=initiator_user process_model=process_model, user=initiator_user
) )
processor = ProcessInstanceProcessor(process_instance) processor = ProcessInstanceProcessor(process_instance)
processor.do_engine_steps(save=True) processor.do_engine_steps(save=True, execution_strategy_name="greedy")
assert len(process_instance.active_human_tasks) == 1 assert len(process_instance.active_human_tasks) == 1
initial_human_task_id = process_instance.active_human_tasks[0].id initial_human_task_id = process_instance.active_human_tasks[0].id
@ -436,7 +436,8 @@ class TestProcessInstanceProcessor(BaseTest):
# recreate variables to ensure all bpmn json was recreated from scratch from the db # recreate variables to ensure all bpmn json was recreated from scratch from the db
process_instance_relookup = ProcessInstanceModel.query.filter_by(id=process_instance.id).first() process_instance_relookup = ProcessInstanceModel.query.filter_by(id=process_instance.id).first()
processor_final = ProcessInstanceProcessor(process_instance_relookup) processor_final = ProcessInstanceProcessor(process_instance_relookup)
processor.do_engine_steps(save=True, execution_strategy_name="greedy") processor_final.do_engine_steps(save=True, execution_strategy_name="greedy")
assert process_instance_relookup.status == "complete" assert process_instance_relookup.status == "complete"
data_set_1 = {"set_in_top_level_script": 1} data_set_1 = {"set_in_top_level_script": 1}
@ -548,7 +549,6 @@ class TestProcessInstanceProcessor(BaseTest):
# assert task_model.python_env_data() == expected_python_env_data, message # assert task_model.python_env_data() == expected_python_env_data, message
assert task_model.json_data() == expected_python_env_data, message assert task_model.json_data() == expected_python_env_data, message
processor_final.do_engine_steps(save=True, execution_strategy_name="greedy")
all_spiff_tasks = processor_final.bpmn_process_instance.get_tasks() all_spiff_tasks = processor_final.bpmn_process_instance.get_tasks()
assert len(all_spiff_tasks) > 1 assert len(all_spiff_tasks) > 1
for spiff_task in all_spiff_tasks: for spiff_task in all_spiff_tasks:
@ -607,7 +607,7 @@ class TestProcessInstanceProcessor(BaseTest):
) )
assert task_models_that_are_predicted_count == 0 assert task_models_that_are_predicted_count == 0
assert processor.get_data() == data_set_7 assert processor_final.get_data() == data_set_7
def test_does_not_recreate_human_tasks_on_multiple_saves( def test_does_not_recreate_human_tasks_on_multiple_saves(
self, self,

View File

@ -4477,11 +4477,6 @@
"@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/@lezer/markdown": { "node_modules/@lezer/markdown": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.0.2.tgz", "resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.0.2.tgz",
@ -4492,6 +4487,11 @@
"@lezer/highlight": "^1.0.0" "@lezer/highlight": "^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.3", "version": "1.3.3",
"resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.3.3.tgz", "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.3.3.tgz",
@ -35359,11 +35359,6 @@
"@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=="
},
"@lezer/markdown": { "@lezer/markdown": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.0.2.tgz", "resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.0.2.tgz",
@ -35374,6 +35369,11 @@
"@lezer/highlight": "^1.0.0" "@lezer/highlight": "^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.3", "version": "1.3.3",
"resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.3.3.tgz", "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.3.3.tgz",

View File

@ -7,7 +7,7 @@ export default function InstructionsForEndUser({ task }: any) {
return null; return null;
} }
let instructions = ''; let instructions = '';
console.log("I was passed a task: ", task); console.log('I was passed a task: ', task);
const { properties } = task; const { properties } = task;
const { instructionsForEndUser } = properties; const { instructionsForEndUser } = properties;
if (instructionsForEndUser) { if (instructionsForEndUser) {

View File

@ -1455,7 +1455,8 @@ export default function ProcessInstanceListTable({
if (showActionsColumn) { if (showActionsColumn) {
let buttonElement = null; let buttonElement = null;
const interstitialUrl = `/process/${modifyProcessIdentifierForPathParam( const interstitialUrl = `/process/${modifyProcessIdentifierForPathParam(
row.process_model_identifier)}/${row.id}/interstitial` row.process_model_identifier
)}/${row.id}/interstitial`;
const regex = new RegExp(`\\b(${preferredUsername}|${userEmail})\\b`); const regex = new RegExp(`\\b(${preferredUsername}|${userEmail})\\b`);
let hasAccessToCompleteTask = false; let hasAccessToCompleteTask = false;
if ( if (

View File

@ -56,7 +56,10 @@ 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="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

@ -75,7 +75,12 @@ export default function ProcessInterstitial() {
case 'COMPLETED': case 'COMPLETED':
return <img src="/interstitial/checkmark.png" alt="Completed" />; return <img src="/interstitial/checkmark.png" alt="Completed" />;
case 'LOCKED': case 'LOCKED':
return <img src="/interstitial/lock.png" alt="Locked, Waiting on someone else." />; return (
<img
src="/interstitial/lock.png"
alt="Locked, Waiting on someone else."
/>
);
default: default:
return null; return null;
} }
@ -86,11 +91,13 @@ export default function ProcessInterstitial() {
!myTask.can_complete && !myTask.can_complete &&
['User Task', 'Manual Task'].includes(myTask.type) ['User Task', 'Manual Task'].includes(myTask.type)
) { ) {
return ( return <div>This next task must be completed by a different person.</div>;
<div>This next task must be completed by a different person.</div>
);
} }
return <div><InstructionsForEndUser task={myTask} /></div>; return (
<div>
<InstructionsForEndUser task={myTask} />
</div>
);
}; };
if (lastTask) { if (lastTask) {
@ -116,7 +123,9 @@ export default function ProcessInterstitial() {
<Column md={6} lg={8} sm={4}> <Column md={6} lg={8} sm={4}>
{data && {data &&
data.map((d) => ( data.map((d) => (
<div style={{ display: 'flex', alignItems: 'center', gap: '2em' }}> <div
style={{ display: 'flex', alignItems: 'center', gap: '2em' }}
>
<div> <div>
Task: <em>{d.title}</em> Task: <em>{d.title}</em>
</div> </div>

View File

@ -126,7 +126,6 @@ export default function TaskShow() {
); );
}; };
useEffect(() => { useEffect(() => {
const processResult = (result: ProcessInstanceTask) => { const processResult = (result: ProcessInstanceTask) => {
setTask(result); setTask(result);