mirror of
https://github.com/status-im/spiff-arena.git
synced 2025-03-03 19:00:32 +00:00
Revert "some updates so task_show no longer needs the processor. i think it mostly works w/ burnettk"
This reverts commit 5a561fb83319db0f4c1269b3f3fd550bc27d0deb.
This commit is contained in:
parent
5a561fb833
commit
09d5ba8409
@ -69,17 +69,6 @@ class TaskModel(SpiffworkflowBaseDBModel):
|
|||||||
|
|
||||||
data: Optional[dict] = None
|
data: Optional[dict] = None
|
||||||
|
|
||||||
# these are here to be compatible with task api
|
|
||||||
form_schema: Optional[dict] = None
|
|
||||||
form_ui_schema: Optional[dict] = None
|
|
||||||
process_model_display_name: Optional[str] = None
|
|
||||||
process_model_identifier: Optional[str] = None
|
|
||||||
type: Optional[str] = None
|
|
||||||
can_complete: Optional[bool] = None
|
|
||||||
|
|
||||||
def get_data(self) -> dict:
|
|
||||||
return {**self.python_env_data(), **self.json_data()}
|
|
||||||
|
|
||||||
def python_env_data(self) -> dict:
|
def python_env_data(self) -> dict:
|
||||||
return JsonDataModel.find_data_dict_by_hash(self.python_env_data_hash)
|
return JsonDataModel.find_data_dict_by_hash(self.python_env_data_hash)
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
"""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
|
||||||
from spiffworkflow_backend.services.authorization_service import UserDoesNotHaveAccessToTaskError
|
|
||||||
from spiffworkflow_backend.services.authorization_service import HumanTaskNotFoundError
|
|
||||||
import os
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
from sys import exc_info
|
from sys import exc_info
|
||||||
@ -285,67 +283,39 @@ def task_show(process_instance_id: int, task_guid: str = "next") -> flask.wrappe
|
|||||||
|
|
||||||
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)
|
||||||
|
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)
|
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
|
||||||
|
|
||||||
task_model = _get_task_model_from_guid_or_raise(task_guid, process_instance_id)
|
|
||||||
# extensions = task_model.properties_json['extensions'] if 'extensions' in task_model.properties_json else {}
|
|
||||||
|
|
||||||
if "properties" in extensions:
|
if "properties" in extensions:
|
||||||
properties = extensions["properties"]
|
properties = extensions["properties"]
|
||||||
if "formJsonSchemaFilename" in properties:
|
if "formJsonSchemaFilename" in properties:
|
||||||
form_schema_file_name = properties["formJsonSchemaFilename"]
|
form_schema_file_name = properties["formJsonSchemaFilename"]
|
||||||
if "formUiSchemaFilename" in properties:
|
if "formUiSchemaFilename" in properties:
|
||||||
form_ui_schema_file_name = properties["formUiSchemaFilename"]
|
form_ui_schema_file_name = properties["formUiSchemaFilename"]
|
||||||
|
|
||||||
can_complete = False
|
|
||||||
try:
|
|
||||||
AuthorizationService.assert_user_can_complete_task(
|
|
||||||
process_instance.id, task_model.task_definition.bpmn_identifier, g.user
|
|
||||||
)
|
|
||||||
can_complete = True
|
|
||||||
except HumanTaskNotFoundError:
|
|
||||||
can_complete = False
|
|
||||||
except UserDoesNotHaveAccessToTaskError:
|
|
||||||
can_complete = False
|
|
||||||
|
|
||||||
task = ProcessInstanceService.spiff_task_to_api_task(processor, spiff_task)
|
task = ProcessInstanceService.spiff_task_to_api_task(processor, spiff_task)
|
||||||
task.data = spiff_task.data
|
task.data = spiff_task.data
|
||||||
task.process_model_display_name = process_model.display_name
|
task.process_model_display_name = process_model.display_name
|
||||||
task.process_model_identifier = process_model.id
|
task.process_model_identifier = process_model.id
|
||||||
|
|
||||||
# task.data
|
|
||||||
# task.form_schema
|
|
||||||
# task.form_ui_schema
|
|
||||||
# task.id
|
|
||||||
# task.process_model_display_name
|
|
||||||
# task.process_model_identifier
|
|
||||||
# task.state
|
|
||||||
# task.type
|
|
||||||
|
|
||||||
task_model.data = task_model.get_data()
|
|
||||||
task_model.process_model_display_name = process_model.display_name
|
|
||||||
task_model.process_model_identifier = process_model.id
|
|
||||||
task_model.type = task_model.task_definition.typename
|
|
||||||
task_model.can_complete = can_complete
|
|
||||||
task_process_identifier = task_model.bpmn_process.bpmn_process_definition.bpmn_identifier
|
|
||||||
|
|
||||||
process_model_with_form = process_model
|
process_model_with_form = process_model
|
||||||
|
|
||||||
refs = SpecFileService.get_references_for_process(process_model_with_form)
|
refs = SpecFileService.get_references_for_process(process_model_with_form)
|
||||||
all_processes = [i.identifier for i in refs]
|
all_processes = [i.identifier for i in refs]
|
||||||
if task_process_identifier not in all_processes:
|
if task.process_identifier not in all_processes:
|
||||||
top_bpmn_process = TaskService.bpmn_process_for_called_activity_or_top_level_process(task_model)
|
top_process_name = processor.find_process_model_process_name_by_task_name(task.process_identifier)
|
||||||
bpmn_file_full_path = ProcessInstanceProcessor.bpmn_file_full_path_from_bpmn_process_identifier(
|
bpmn_file_full_path = ProcessInstanceProcessor.bpmn_file_full_path_from_bpmn_process_identifier(
|
||||||
top_bpmn_process.bpmn_process_definition.bpmn_identifier
|
top_process_name
|
||||||
)
|
)
|
||||||
relative_path = os.path.relpath(bpmn_file_full_path, start=FileSystemService.root_path())
|
relative_path = os.path.relpath(bpmn_file_full_path, start=FileSystemService.root_path())
|
||||||
process_model_relative_path = os.path.dirname(relative_path)
|
process_model_relative_path = os.path.dirname(relative_path)
|
||||||
process_model_with_form = ProcessModelService.get_process_model_from_relative_path(process_model_relative_path)
|
process_model_with_form = ProcessModelService.get_process_model_from_relative_path(process_model_relative_path)
|
||||||
|
|
||||||
if task_model.task_definition.typename == "UserTask":
|
if task.type == "User Task":
|
||||||
if not form_schema_file_name:
|
if not form_schema_file_name:
|
||||||
raise (
|
raise (
|
||||||
ApiError(
|
ApiError(
|
||||||
@ -360,38 +330,37 @@ def task_show(process_instance_id: int, task_guid: str = "next") -> flask.wrappe
|
|||||||
|
|
||||||
form_dict = _prepare_form_data(
|
form_dict = _prepare_form_data(
|
||||||
form_schema_file_name,
|
form_schema_file_name,
|
||||||
task_model,
|
spiff_task,
|
||||||
process_model_with_form,
|
process_model_with_form,
|
||||||
)
|
)
|
||||||
|
|
||||||
if task_model.data:
|
if task.data:
|
||||||
_update_form_schema_with_task_data_as_needed(form_dict, task_model)
|
_update_form_schema_with_task_data_as_needed(form_dict, task, spiff_task)
|
||||||
|
|
||||||
if form_dict:
|
if form_dict:
|
||||||
task_model.form_schema = form_dict
|
task.form_schema = form_dict
|
||||||
|
|
||||||
if form_ui_schema_file_name:
|
if form_ui_schema_file_name:
|
||||||
ui_form_contents = _prepare_form_data(
|
ui_form_contents = _prepare_form_data(
|
||||||
form_ui_schema_file_name,
|
form_ui_schema_file_name,
|
||||||
task_model,
|
task,
|
||||||
process_model_with_form,
|
process_model_with_form,
|
||||||
)
|
)
|
||||||
if ui_form_contents:
|
if ui_form_contents:
|
||||||
task_model.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_model)
|
_munge_form_ui_schema_based_on_hidden_fields_in_task_data(task)
|
||||||
_render_instructions_for_end_user(task_model)
|
_render_instructions_for_end_user(spiff_task, task)
|
||||||
return make_response(jsonify(task_model), 200)
|
return make_response(jsonify(task), 200)
|
||||||
|
|
||||||
|
|
||||||
def _render_instructions_for_end_user(task_model: TaskModel) -> str:
|
def _render_instructions_for_end_user(spiff_task: SpiffTask, task: Task) -> str:
|
||||||
"""Assure any instructions for end user are processed for jinja syntax."""
|
"""Assure any instructions for end user are processed for jinja syntax."""
|
||||||
extensions = task_model.properties_json['extensions'] if 'extensions' in task_model.properties_json else {}
|
if task.properties and "instructionsForEndUser" in task.properties:
|
||||||
if extensions and "instructionsForEndUser" in extensions:
|
if task.properties["instructionsForEndUser"]:
|
||||||
if extensions["instructionsForEndUser"]:
|
|
||||||
try:
|
try:
|
||||||
instructions = _render_jinja_template(extensions["instructionsForEndUser"], task_model)
|
instructions = _render_jinja_template(task.properties["instructionsForEndUser"], spiff_task)
|
||||||
extensions["instructionsForEndUser"] = instructions
|
task.properties["instructionsForEndUser"] = instructions
|
||||||
return instructions
|
return instructions
|
||||||
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.")
|
||||||
@ -428,12 +397,10 @@ def _interstitial_stream(process_instance_id: int) -> Generator[str, Optional[st
|
|||||||
processor = ProcessInstanceProcessor(process_instance)
|
processor = ProcessInstanceProcessor(process_instance)
|
||||||
reported_ids = [] # bit of an issue with end tasks showing as getting completed twice.
|
reported_ids = [] # bit of an issue with end tasks showing as getting completed twice.
|
||||||
spiff_task = processor.next_task()
|
spiff_task = processor.next_task()
|
||||||
task_model = TaskModel.query.filter_by(guid=str(spiff_task.id)).first()
|
|
||||||
last_task = None
|
last_task = None
|
||||||
while last_task != spiff_task:
|
while last_task != spiff_task:
|
||||||
# import pdb; pdb.set_trace()
|
|
||||||
task = ProcessInstanceService.spiff_task_to_api_task(processor, processor.next_task())
|
task = ProcessInstanceService.spiff_task_to_api_task(processor, processor.next_task())
|
||||||
instructions = _render_instructions_for_end_user(task_model)
|
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"
|
||||||
@ -441,9 +408,6 @@ def _interstitial_stream(process_instance_id: int) -> Generator[str, Optional[st
|
|||||||
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")
|
||||||
spiff_task = processor.next_task()
|
spiff_task = processor.next_task()
|
||||||
task_model = TaskModel.query.filter_by(guid=str(spiff_task.id)).first()
|
|
||||||
# print(f"spiff_task: {spiff_task}")
|
|
||||||
# print(f"last_task: {last_task}")
|
|
||||||
# Note, this has to be done in case someone leaves the page,
|
# Note, this has to be done in case someone leaves the page,
|
||||||
# which can otherwise cancel this function and leave completed tasks un-registered.
|
# which can otherwise cancel this function and leave completed tasks un-registered.
|
||||||
processor.save() # Fixme - maybe find a way not to do this on every loop?
|
processor.save() # Fixme - maybe find a way not to do this on every loop?
|
||||||
@ -482,7 +446,7 @@ def _task_submit_shared(
|
|||||||
|
|
||||||
processor = ProcessInstanceProcessor(process_instance)
|
processor = ProcessInstanceProcessor(process_instance)
|
||||||
spiff_task = _get_spiff_task_from_process_instance(task_guid, process_instance, processor=processor)
|
spiff_task = _get_spiff_task_from_process_instance(task_guid, process_instance, processor=processor)
|
||||||
AuthorizationService.assert_user_can_complete_task(process_instance.id, spiff_task.task_spec.name, principal.user)
|
AuthorizationService.assert_user_can_complete_spiff_task(process_instance.id, spiff_task, principal.user)
|
||||||
|
|
||||||
if spiff_task.state != TaskState.READY:
|
if spiff_task.state != TaskState.READY:
|
||||||
raise (
|
raise (
|
||||||
@ -670,14 +634,14 @@ def _get_tasks(
|
|||||||
return make_response(jsonify(response_json), 200)
|
return make_response(jsonify(response_json), 200)
|
||||||
|
|
||||||
|
|
||||||
def _prepare_form_data(form_file: str, task_model: TaskModel, process_model: ProcessModelInfo) -> dict:
|
def _prepare_form_data(form_file: str, spiff_task: SpiffTask, process_model: ProcessModelInfo) -> dict:
|
||||||
"""Prepare_form_data."""
|
"""Prepare_form_data."""
|
||||||
if task_model.data is None:
|
if spiff_task.data is None:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
file_contents = SpecFileService.get_data(process_model, form_file).decode("utf-8")
|
file_contents = SpecFileService.get_data(process_model, form_file).decode("utf-8")
|
||||||
try:
|
try:
|
||||||
form_contents = _render_jinja_template(file_contents, task_model)
|
form_contents = _render_jinja_template(file_contents, spiff_task)
|
||||||
try:
|
try:
|
||||||
# form_contents is a str
|
# form_contents is a str
|
||||||
hot_dict: dict = json.loads(form_contents)
|
hot_dict: dict = json.loads(form_contents)
|
||||||
@ -697,14 +661,14 @@ def _prepare_form_data(form_file: str, task_model: TaskModel, process_model: Pro
|
|||||||
raise api_error
|
raise api_error
|
||||||
|
|
||||||
|
|
||||||
def _render_jinja_template(unprocessed_template: str, task_model: TaskModel) -> str:
|
def _render_jinja_template(unprocessed_template: str, spiff_task: SpiffTask) -> str:
|
||||||
"""Render_jinja_template."""
|
"""Render_jinja_template."""
|
||||||
jinja_environment = jinja2.Environment(autoescape=True, lstrip_blocks=True, trim_blocks=True)
|
jinja_environment = jinja2.Environment(autoescape=True, lstrip_blocks=True, trim_blocks=True)
|
||||||
try:
|
try:
|
||||||
template = jinja_environment.from_string(unprocessed_template)
|
template = jinja_environment.from_string(unprocessed_template)
|
||||||
return template.render(**(task_model.data or {}))
|
return template.render(**spiff_task.data)
|
||||||
except jinja2.exceptions.TemplateError as template_error:
|
except jinja2.exceptions.TemplateError as template_error:
|
||||||
wfe = WorkflowTaskException(str(template_error), task=task_model, exception=template_error)
|
wfe = WorkflowTaskException(str(template_error), task=spiff_task, exception=template_error)
|
||||||
if isinstance(template_error, TemplateSyntaxError):
|
if isinstance(template_error, TemplateSyntaxError):
|
||||||
wfe.line_number = template_error.lineno
|
wfe.line_number = template_error.lineno
|
||||||
wfe.error_line = template_error.source.split("\n")[template_error.lineno - 1]
|
wfe.error_line = template_error.source.split("\n")[template_error.lineno - 1]
|
||||||
@ -712,7 +676,7 @@ def _render_jinja_template(unprocessed_template: str, task_model: TaskModel) ->
|
|||||||
raise wfe from template_error
|
raise wfe from template_error
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
_type, _value, tb = exc_info()
|
_type, _value, tb = exc_info()
|
||||||
wfe = WorkflowTaskException(str(error), task=task_model, exception=error)
|
wfe = WorkflowTaskException(str(error), task=spiff_task, exception=error)
|
||||||
while tb:
|
while tb:
|
||||||
if tb.tb_frame.f_code.co_filename == "<template>":
|
if tb.tb_frame.f_code.co_filename == "<template>":
|
||||||
wfe.line_number = tb.tb_lineno
|
wfe.line_number = tb.tb_lineno
|
||||||
@ -745,9 +709,9 @@ def _get_spiff_task_from_process_instance(
|
|||||||
|
|
||||||
|
|
||||||
# originally from: https://bitcoden.com/answers/python-nested-dictionary-update-value-where-any-nested-key-matches
|
# originally from: https://bitcoden.com/answers/python-nested-dictionary-update-value-where-any-nested-key-matches
|
||||||
def _update_form_schema_with_task_data_as_needed(in_dict: dict, task_model: TaskModel) -> None:
|
def _update_form_schema_with_task_data_as_needed(in_dict: dict, task: Task, spiff_task: SpiffTask) -> None:
|
||||||
"""Update_nested."""
|
"""Update_nested."""
|
||||||
if task_model.data is None:
|
if task.data is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
for k, value in in_dict.items():
|
for k, value in in_dict.items():
|
||||||
@ -760,18 +724,25 @@ def _update_form_schema_with_task_data_as_needed(in_dict: dict, task_model: Task
|
|||||||
if first_element_in_value_list.startswith("options_from_task_data_var:"):
|
if first_element_in_value_list.startswith("options_from_task_data_var:"):
|
||||||
task_data_var = first_element_in_value_list.replace("options_from_task_data_var:", "")
|
task_data_var = first_element_in_value_list.replace("options_from_task_data_var:", "")
|
||||||
|
|
||||||
if task_data_var not in task_model.data:
|
if task_data_var not in task.data:
|
||||||
message = (
|
wte = WorkflowTaskException(
|
||||||
"Error building form. Attempting to create a selection list with options from variable"
|
(
|
||||||
f" '{task_data_var}' but it doesn't exist in the Task Data."
|
"Error building form. Attempting to create a"
|
||||||
|
" selection list with options from variable"
|
||||||
|
f" '{task_data_var}' but it doesn't exist in"
|
||||||
|
" the Task Data."
|
||||||
|
),
|
||||||
|
task=spiff_task,
|
||||||
)
|
)
|
||||||
raise ApiError(
|
raise (
|
||||||
|
ApiError.from_workflow_exception(
|
||||||
error_code="missing_task_data_var",
|
error_code="missing_task_data_var",
|
||||||
message=message,
|
message=str(wte),
|
||||||
status_code=500,
|
exp=wte,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
select_options_from_task_data = task_model.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):
|
||||||
|
|
||||||
@ -791,11 +762,11 @@ def _update_form_schema_with_task_data_as_needed(in_dict: dict, task_model: Task
|
|||||||
|
|
||||||
in_dict[k] = options_for_react_json_schema_form
|
in_dict[k] = options_for_react_json_schema_form
|
||||||
elif isinstance(value, dict):
|
elif isinstance(value, dict):
|
||||||
_update_form_schema_with_task_data_as_needed(value, task_model)
|
_update_form_schema_with_task_data_as_needed(value, task, spiff_task)
|
||||||
elif isinstance(value, list):
|
elif isinstance(value, list):
|
||||||
for o in value:
|
for o in value:
|
||||||
if isinstance(o, dict):
|
if isinstance(o, dict):
|
||||||
_update_form_schema_with_task_data_as_needed(o, task_model)
|
_update_form_schema_with_task_data_as_needed(o, task, spiff_task)
|
||||||
|
|
||||||
|
|
||||||
def _get_potential_owner_usernames(assigned_user: AliasedClass) -> Any:
|
def _get_potential_owner_usernames(assigned_user: AliasedClass) -> Any:
|
||||||
@ -840,15 +811,15 @@ def _find_human_task_or_raise(
|
|||||||
return human_task
|
return human_task
|
||||||
|
|
||||||
|
|
||||||
def _munge_form_ui_schema_based_on_hidden_fields_in_task_data(task_model: TaskModel) -> None:
|
def _munge_form_ui_schema_based_on_hidden_fields_in_task_data(task: Task) -> None:
|
||||||
if task_model.form_ui_schema is None:
|
if task.form_ui_schema is None:
|
||||||
task_model.form_ui_schema = {}
|
task.form_ui_schema = {}
|
||||||
|
|
||||||
if task_model.data and "form_ui_hidden_fields" in task_model.data:
|
if task.data and "form_ui_hidden_fields" in task.data:
|
||||||
hidden_fields = task_model.data["form_ui_hidden_fields"]
|
hidden_fields = task.data["form_ui_hidden_fields"]
|
||||||
for hidden_field in hidden_fields:
|
for hidden_field in hidden_fields:
|
||||||
hidden_field_parts = hidden_field.split(".")
|
hidden_field_parts = hidden_field.split(".")
|
||||||
relevant_depth_of_ui_schema = task_model.form_ui_schema
|
relevant_depth_of_ui_schema = task.form_ui_schema
|
||||||
for ii, hidden_field_part in enumerate(hidden_field_parts):
|
for ii, hidden_field_part in enumerate(hidden_field_parts):
|
||||||
if hidden_field_part not in relevant_depth_of_ui_schema:
|
if hidden_field_part not in relevant_depth_of_ui_schema:
|
||||||
relevant_depth_of_ui_schema[hidden_field_part] = {}
|
relevant_depth_of_ui_schema[hidden_field_part] = {}
|
||||||
|
@ -412,27 +412,27 @@ class AuthorizationService:
|
|||||||
) from exception
|
) from exception
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def assert_user_can_complete_task(
|
def assert_user_can_complete_spiff_task(
|
||||||
process_instance_id: int,
|
process_instance_id: int,
|
||||||
task_bpmn_identifier: str,
|
spiff_task: SpiffTask,
|
||||||
user: UserModel,
|
user: UserModel,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Assert_user_can_complete_spiff_task."""
|
"""Assert_user_can_complete_spiff_task."""
|
||||||
human_task = HumanTaskModel.query.filter_by(
|
human_task = HumanTaskModel.query.filter_by(
|
||||||
task_name=task_bpmn_identifier,
|
task_name=spiff_task.task_spec.name,
|
||||||
process_instance_id=process_instance_id,
|
process_instance_id=process_instance_id,
|
||||||
completed=False,
|
completed=False,
|
||||||
).first()
|
).first()
|
||||||
if human_task is None:
|
if human_task is None:
|
||||||
raise HumanTaskNotFoundError(
|
raise HumanTaskNotFoundError(
|
||||||
f"Could find an human task with task name '{task_bpmn_identifier}'"
|
f"Could find an human task with task name '{spiff_task.task_spec.name}'"
|
||||||
f" for process instance '{process_instance_id}'"
|
f" for process instance '{process_instance_id}'"
|
||||||
)
|
)
|
||||||
|
|
||||||
if user not in human_task.potential_owners:
|
if user not in human_task.potential_owners:
|
||||||
raise UserDoesNotHaveAccessToTaskError(
|
raise UserDoesNotHaveAccessToTaskError(
|
||||||
f"User {user.username} does not have access to update"
|
f"User {user.username} does not have access to update"
|
||||||
f" task'{task_bpmn_identifier}' for process instance"
|
f" task'{spiff_task.task_spec.name}' for process instance"
|
||||||
f" '{process_instance_id}'"
|
f" '{process_instance_id}'"
|
||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
|
@ -1793,10 +1793,10 @@ class ProcessInstanceProcessor:
|
|||||||
|
|
||||||
# If there are no ready tasks, but the thing isn't complete yet, find the first non-complete task
|
# If there are no ready tasks, but the thing isn't complete yet, find the first non-complete task
|
||||||
# and return that
|
# and return that
|
||||||
next_task_to_return = None
|
next_task = None
|
||||||
for task in SpiffTask.Iterator(self.bpmn_process_instance.task_tree, TaskState.NOT_FINISHED_MASK):
|
for task in SpiffTask.Iterator(self.bpmn_process_instance.task_tree, TaskState.NOT_FINISHED_MASK):
|
||||||
next_task_to_return = task
|
next_task = task
|
||||||
return next_task_to_return
|
return next_task
|
||||||
|
|
||||||
def completed_user_tasks(self) -> List[SpiffTask]:
|
def completed_user_tasks(self) -> List[SpiffTask]:
|
||||||
"""Completed_user_tasks."""
|
"""Completed_user_tasks."""
|
||||||
|
@ -344,7 +344,7 @@ class ProcessInstanceService:
|
|||||||
data: dict[str, Any],
|
data: dict[str, Any],
|
||||||
user: UserModel,
|
user: UserModel,
|
||||||
) -> None:
|
) -> None:
|
||||||
AuthorizationService.assert_user_can_complete_task(process_instance.id, spiff_task.task_spec.name, user)
|
AuthorizationService.assert_user_can_complete_spiff_task(process_instance.id, spiff_task, user)
|
||||||
cls.save_file_data_and_replace_with_digest_references(
|
cls.save_file_data_and_replace_with_digest_references(
|
||||||
data,
|
data,
|
||||||
process_instance.id,
|
process_instance.id,
|
||||||
@ -442,8 +442,8 @@ class ProcessInstanceService:
|
|||||||
# can complete it.
|
# can complete it.
|
||||||
can_complete = False
|
can_complete = False
|
||||||
try:
|
try:
|
||||||
AuthorizationService.assert_user_can_complete_task(
|
AuthorizationService.assert_user_can_complete_spiff_task(
|
||||||
processor.process_instance_model.id, spiff_task.task_spec.name, g.user
|
processor.process_instance_model.id, spiff_task, g.user
|
||||||
)
|
)
|
||||||
can_complete = True
|
can_complete = True
|
||||||
except HumanTaskNotFoundError:
|
except HumanTaskNotFoundError:
|
||||||
|
@ -488,17 +488,6 @@ class TaskService:
|
|||||||
setattr(task_model, task_model_data_column, task_data_hash)
|
setattr(task_model, task_model_data_column, task_data_hash)
|
||||||
return json_data_dict
|
return json_data_dict
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def bpmn_process_for_called_activity_or_top_level_process(cls, task_model: TaskModel) -> BpmnProcessModel:
|
|
||||||
"""Returns either the bpmn process for the call activity calling the process or the top level bpmn process.
|
|
||||||
|
|
||||||
For example, process_modelA has processA which has a call activity that calls processB which is inside of process_modelB.
|
|
||||||
processB has subprocessA which has taskA. Using taskA this method should return processB and then that can be used with
|
|
||||||
the spec reference cache to find process_modelB.
|
|
||||||
"""
|
|
||||||
(bpmn_processes, _task_models) = TaskService.task_models_of_parent_bpmn_processes(task_model, stop_on_first_call_activity=True)
|
|
||||||
return bpmn_processes[0]
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def bpmn_process_and_descendants(cls, bpmn_processes: list[BpmnProcessModel]) -> list[BpmnProcessModel]:
|
def bpmn_process_and_descendants(cls, bpmn_processes: list[BpmnProcessModel]) -> list[BpmnProcessModel]:
|
||||||
bpmn_process_ids = [p.id for p in bpmn_processes]
|
bpmn_process_ids = [p.id for p in bpmn_processes]
|
||||||
@ -511,51 +500,27 @@ class TaskService:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def task_models_of_parent_bpmn_processes(
|
def task_models_of_parent_bpmn_processes(
|
||||||
cls, task_model: TaskModel, stop_on_first_call_activity: Optional[bool] = False
|
cls, task_model: TaskModel
|
||||||
) -> Tuple[list[BpmnProcessModel], list[TaskModel]]:
|
) -> Tuple[list[BpmnProcessModel], list[TaskModel]]:
|
||||||
"""Returns the list of task models that are associated with the paren bpmn process.
|
|
||||||
|
|
||||||
Example: TopLevelProcess has SubprocessTaskA which has CallActivityTaskA which has ScriptTaskA.
|
|
||||||
SubprocessTaskA corresponds to SpiffSubprocess1.
|
|
||||||
CallActivityTaskA corresponds to SpiffSubprocess2.
|
|
||||||
Using ScriptTaskA this will return:
|
|
||||||
(
|
|
||||||
[TopLevelProcess, SpiffSubprocess1, SpiffSubprocess2],
|
|
||||||
[SubprocessTaskA, CallActivityTaskA]
|
|
||||||
)
|
|
||||||
|
|
||||||
If stop_on_first_call_activity it will stop when it reaches the first task model with a type of 'CallActivity'.
|
|
||||||
This will change the return value in the example to:
|
|
||||||
(
|
|
||||||
[SpiffSubprocess2],
|
|
||||||
[CallActivityTaskA]
|
|
||||||
)
|
|
||||||
"""
|
|
||||||
bpmn_process = task_model.bpmn_process
|
bpmn_process = task_model.bpmn_process
|
||||||
task_models: list[TaskModel] = []
|
task_models: list[TaskModel] = []
|
||||||
bpmn_processes: list[BpmnProcessModel] = [bpmn_process]
|
bpmn_processes: list[BpmnProcessModel] = [bpmn_process]
|
||||||
if bpmn_process.guid is not None:
|
if bpmn_process.guid is not None:
|
||||||
parent_task_model = TaskModel.query.filter_by(guid=bpmn_process.guid).first()
|
parent_task_model = TaskModel.query.filter_by(guid=bpmn_process.guid).first()
|
||||||
task_models.append(parent_task_model)
|
|
||||||
if not stop_on_first_call_activity or parent_task_model.task_definition.typename != 'CallActivity':
|
|
||||||
if parent_task_model is not None:
|
if parent_task_model is not None:
|
||||||
b, t = cls.task_models_of_parent_bpmn_processes(parent_task_model, stop_on_first_call_activity=stop_on_first_call_activity)
|
b, t = cls.task_models_of_parent_bpmn_processes(parent_task_model)
|
||||||
return (b + bpmn_processes, t + task_models)
|
return (bpmn_processes + b, [parent_task_model] + t)
|
||||||
return (bpmn_processes, task_models)
|
return (bpmn_processes, task_models)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def full_bpmn_process_path(cls, bpmn_process: BpmnProcessModel) -> list[str]:
|
def full_bpmn_process_path(cls, bpmn_process: BpmnProcessModel) -> list[str]:
|
||||||
"""Returns a list of bpmn process identifiers pointing the given bpmn_process."""
|
"""Returns a list of bpmn process identifiers pointing the given bpmn_process."""
|
||||||
bpmn_process_identifiers: list[str] = []
|
bpmn_process_identifiers: list[str] = [bpmn_process.bpmn_process_definition.bpmn_identifier]
|
||||||
if bpmn_process.guid:
|
if bpmn_process.direct_parent_process_id is not None:
|
||||||
task_model = TaskModel.query.filter_by(guid=bpmn_process.guid).first()
|
parent_bpmn_process = BpmnProcessModel.query.filter_by(id=bpmn_process.direct_parent_process_id).first()
|
||||||
(
|
if parent_bpmn_process is not None:
|
||||||
parent_bpmn_processes,
|
# always prepend new identifiers since they come first in the path
|
||||||
_task_models_of_parent_bpmn_processes,
|
bpmn_process_identifiers = cls.full_bpmn_process_path(parent_bpmn_process) + bpmn_process_identifiers
|
||||||
) = TaskService.task_models_of_parent_bpmn_processes(task_model)
|
|
||||||
for parent_bpmn_process in parent_bpmn_processes:
|
|
||||||
bpmn_process_identifiers.append(parent_bpmn_process.bpmn_process_definition.bpmn_identifier)
|
|
||||||
bpmn_process_identifiers.append(bpmn_process.bpmn_process_definition.bpmn_identifier)
|
|
||||||
return bpmn_process_identifiers
|
return bpmn_process_identifiers
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -4,88 +4,40 @@
|
|||||||
<bpmn:startEvent id="StartEvent_1">
|
<bpmn:startEvent id="StartEvent_1">
|
||||||
<bpmn:outgoing>Flow_1g3dpd7</bpmn:outgoing>
|
<bpmn:outgoing>Flow_1g3dpd7</bpmn:outgoing>
|
||||||
</bpmn:startEvent>
|
</bpmn:startEvent>
|
||||||
<bpmn:sequenceFlow id="Flow_1g3dpd7" sourceRef="StartEvent_1" targetRef="level_2b_script_task" />
|
<bpmn:sequenceFlow id="Flow_1g3dpd7" sourceRef="StartEvent_1" targetRef="do_nothing" />
|
||||||
<bpmn:endEvent id="Event_18dla68">
|
<bpmn:endEvent id="Event_18dla68">
|
||||||
<bpmn:documentation># Main Workflow
|
<bpmn:documentation># Main Workflow
|
||||||
Hello {{my_other_var}}
|
Hello {{my_other_var}}
|
||||||
|
|
||||||
</bpmn:documentation>
|
</bpmn:documentation>
|
||||||
<bpmn:incoming>Flow_0wt4dbv</bpmn:incoming>
|
<bpmn:incoming>Flow_0l0w6u9</bpmn:incoming>
|
||||||
</bpmn:endEvent>
|
</bpmn:endEvent>
|
||||||
<bpmn:scriptTask id="level_2b_script_task" name="level_2b_script_task">
|
<bpmn:sequenceFlow id="Flow_0l0w6u9" sourceRef="do_nothing" targetRef="Event_18dla68" />
|
||||||
|
<bpmn:scriptTask id="do_nothing" name="Do Nothing">
|
||||||
<bpmn:incoming>Flow_1g3dpd7</bpmn:incoming>
|
<bpmn:incoming>Flow_1g3dpd7</bpmn:incoming>
|
||||||
<bpmn:outgoing>Flow_1mvoqe4</bpmn:outgoing>
|
<bpmn:outgoing>Flow_0l0w6u9</bpmn:outgoing>
|
||||||
<bpmn:script>a = 1</bpmn:script>
|
<bpmn:script>a = 1</bpmn:script>
|
||||||
</bpmn:scriptTask>
|
</bpmn:scriptTask>
|
||||||
<bpmn:sequenceFlow id="Flow_1mvoqe4" sourceRef="level_2b_script_task" targetRef="level_2b_subprocess" />
|
|
||||||
<bpmn:subProcess id="level_2b_subprocess" name="level_2b_subprocess">
|
|
||||||
<bpmn:incoming>Flow_1mvoqe4</bpmn:incoming>
|
|
||||||
<bpmn:outgoing>Flow_0wt4dbv</bpmn:outgoing>
|
|
||||||
<bpmn:startEvent id="Event_0fpb33c">
|
|
||||||
<bpmn:outgoing>Flow_18nmqzh</bpmn:outgoing>
|
|
||||||
</bpmn:startEvent>
|
|
||||||
<bpmn:sequenceFlow id="Flow_18nmqzh" sourceRef="Event_0fpb33c" targetRef="level_2b_subprocess_script_task" />
|
|
||||||
<bpmn:endEvent id="Event_1x11xe3">
|
|
||||||
<bpmn:incoming>Flow_1srjuev</bpmn:incoming>
|
|
||||||
</bpmn:endEvent>
|
|
||||||
<bpmn:sequenceFlow id="Flow_1srjuev" sourceRef="level_2b_subprocess_script_task" targetRef="Event_1x11xe3" />
|
|
||||||
<bpmn:scriptTask id="level_2b_subprocess_script_task" name="level_2b_subprocess_script_task">
|
|
||||||
<bpmn:incoming>Flow_18nmqzh</bpmn:incoming>
|
|
||||||
<bpmn:outgoing>Flow_1srjuev</bpmn:outgoing>
|
|
||||||
<bpmn:script>z = 1</bpmn:script>
|
|
||||||
</bpmn:scriptTask>
|
|
||||||
</bpmn:subProcess>
|
|
||||||
<bpmn:sequenceFlow id="Flow_0wt4dbv" sourceRef="level_2b_subprocess" targetRef="Event_18dla68" />
|
|
||||||
</bpmn:process>
|
</bpmn:process>
|
||||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Level2b">
|
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Level2b">
|
||||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||||
<dc:Bounds x="179" y="99" width="36" height="36" />
|
<dc:Bounds x="179" y="99" width="36" height="36" />
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNShape>
|
||||||
<bpmndi:BPMNShape id="Activity_1reqred_di" bpmnElement="level_2b_script_task">
|
|
||||||
<dc:Bounds x="260" y="77" width="100" height="80" />
|
|
||||||
<bpmndi:BPMNLabel />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="Event_18dla68_di" bpmnElement="Event_18dla68">
|
<bpmndi:BPMNShape id="Event_18dla68_di" bpmnElement="Event_18dla68">
|
||||||
<dc:Bounds x="592" y="99" width="36" height="36" />
|
<dc:Bounds x="432" y="99" width="36" height="36" />
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNShape>
|
||||||
<bpmndi:BPMNShape id="Activity_1u9mmh7_di" bpmnElement="level_2b_subprocess">
|
<bpmndi:BPMNShape id="Activity_1reqred_di" bpmnElement="do_nothing">
|
||||||
<dc:Bounds x="410" y="77" width="100" height="80" />
|
<dc:Bounds x="260" y="77" width="100" height="80" />
|
||||||
<bpmndi:BPMNLabel />
|
<bpmndi:BPMNLabel />
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNShape>
|
||||||
<bpmndi:BPMNEdge id="Flow_1g3dpd7_di" bpmnElement="Flow_1g3dpd7">
|
<bpmndi:BPMNEdge id="Flow_1g3dpd7_di" bpmnElement="Flow_1g3dpd7">
|
||||||
<di:waypoint x="215" y="117" />
|
<di:waypoint x="215" y="117" />
|
||||||
<di:waypoint x="260" y="117" />
|
<di:waypoint x="260" y="117" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNEdge id="Flow_1mvoqe4_di" bpmnElement="Flow_1mvoqe4">
|
<bpmndi:BPMNEdge id="Flow_0l0w6u9_di" bpmnElement="Flow_0l0w6u9">
|
||||||
<di:waypoint x="360" y="117" />
|
<di:waypoint x="360" y="117" />
|
||||||
<di:waypoint x="410" y="117" />
|
<di:waypoint x="432" y="117" />
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_0wt4dbv_di" bpmnElement="Flow_0wt4dbv">
|
|
||||||
<di:waypoint x="510" y="117" />
|
|
||||||
<di:waypoint x="592" y="117" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
</bpmndi:BPMNPlane>
|
|
||||||
</bpmndi:BPMNDiagram>
|
|
||||||
<bpmndi:BPMNDiagram id="BPMNDiagram_14p97s9">
|
|
||||||
<bpmndi:BPMNPlane id="BPMNPlane_1qs3lh3" bpmnElement="level_2b_subprocess">
|
|
||||||
<bpmndi:BPMNShape id="Event_0fpb33c_di" bpmnElement="Event_0fpb33c">
|
|
||||||
<dc:Bounds x="332" y="212" width="36" height="36" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="Event_1x11xe3_di" bpmnElement="Event_1x11xe3">
|
|
||||||
<dc:Bounds x="572" y="212" width="36" height="36" />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNShape id="Activity_0oiioqq_di" bpmnElement="level_2b_subprocess_script_task">
|
|
||||||
<dc:Bounds x="420" y="190" width="100" height="80" />
|
|
||||||
<bpmndi:BPMNLabel />
|
|
||||||
</bpmndi:BPMNShape>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_18nmqzh_di" bpmnElement="Flow_18nmqzh">
|
|
||||||
<di:waypoint x="368" y="230" />
|
|
||||||
<di:waypoint x="420" y="230" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_1srjuev_di" bpmnElement="Flow_1srjuev">
|
|
||||||
<di:waypoint x="520" y="230" />
|
|
||||||
<di:waypoint x="572" y="230" />
|
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
</bpmndi:BPMNPlane>
|
</bpmndi:BPMNPlane>
|
||||||
</bpmndi:BPMNDiagram>
|
</bpmndi:BPMNDiagram>
|
||||||
|
@ -4,8 +4,8 @@
|
|||||||
<bpmn:startEvent id="StartEvent_1">
|
<bpmn:startEvent id="StartEvent_1">
|
||||||
<bpmn:outgoing>Flow_1g3dpd7</bpmn:outgoing>
|
<bpmn:outgoing>Flow_1g3dpd7</bpmn:outgoing>
|
||||||
</bpmn:startEvent>
|
</bpmn:startEvent>
|
||||||
<bpmn:sequenceFlow id="Flow_1g3dpd7" sourceRef="StartEvent_1" targetRef="level_3_script_task" />
|
<bpmn:sequenceFlow id="Flow_1g3dpd7" sourceRef="StartEvent_1" targetRef="do_nothing" />
|
||||||
<bpmn:sequenceFlow id="Flow_0qdgvah" sourceRef="level_3_script_task" targetRef="Event_18dla68" />
|
<bpmn:sequenceFlow id="Flow_0qdgvah" sourceRef="do_nothing" targetRef="Event_18dla68" />
|
||||||
<bpmn:endEvent id="Event_18dla68">
|
<bpmn:endEvent id="Event_18dla68">
|
||||||
<bpmn:documentation># Main Workflow
|
<bpmn:documentation># Main Workflow
|
||||||
Hello {{my_other_var}}
|
Hello {{my_other_var}}
|
||||||
@ -13,7 +13,7 @@ Hello {{my_other_var}}
|
|||||||
</bpmn:documentation>
|
</bpmn:documentation>
|
||||||
<bpmn:incoming>Flow_0qdgvah</bpmn:incoming>
|
<bpmn:incoming>Flow_0qdgvah</bpmn:incoming>
|
||||||
</bpmn:endEvent>
|
</bpmn:endEvent>
|
||||||
<bpmn:scriptTask id="level_3_script_task" name="Do Nothing">
|
<bpmn:scriptTask id="do_nothing" name="Do Nothing">
|
||||||
<bpmn:incoming>Flow_1g3dpd7</bpmn:incoming>
|
<bpmn:incoming>Flow_1g3dpd7</bpmn:incoming>
|
||||||
<bpmn:outgoing>Flow_0qdgvah</bpmn:outgoing>
|
<bpmn:outgoing>Flow_0qdgvah</bpmn:outgoing>
|
||||||
<bpmn:script>a = 3</bpmn:script>
|
<bpmn:script>a = 3</bpmn:script>
|
||||||
@ -27,7 +27,7 @@ Hello {{my_other_var}}
|
|||||||
<bpmndi:BPMNShape id="Event_18dla68_di" bpmnElement="Event_18dla68">
|
<bpmndi:BPMNShape id="Event_18dla68_di" bpmnElement="Event_18dla68">
|
||||||
<dc:Bounds x="432" y="99" width="36" height="36" />
|
<dc:Bounds x="432" y="99" width="36" height="36" />
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNShape>
|
||||||
<bpmndi:BPMNShape id="Activity_1po21cu_di" bpmnElement="level_3_script_task">
|
<bpmndi:BPMNShape id="Activity_1po21cu_di" bpmnElement="do_nothing">
|
||||||
<dc:Bounds x="280" y="77" width="100" height="80" />
|
<dc:Bounds x="280" y="77" width="100" height="80" />
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNShape>
|
||||||
<bpmndi:BPMNEdge id="Flow_1g3dpd7_di" bpmnElement="Flow_1g3dpd7">
|
<bpmndi:BPMNEdge id="Flow_1g3dpd7_di" bpmnElement="Flow_1g3dpd7">
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"description": "",
|
|
||||||
"display_name": "call activity with nested calls",
|
|
||||||
"display_order": 0,
|
|
||||||
"exception_notification_addresses": [],
|
|
||||||
"fault_or_suspend_on_exception": "fault",
|
|
||||||
"files": [],
|
|
||||||
"metadata_extraction_paths": null,
|
|
||||||
"primary_file_name": "call_activity_nested.bpmn",
|
|
||||||
"primary_process_id": "Level1"
|
|
||||||
}
|
|
@ -0,0 +1 @@
|
|||||||
|
{}
|
@ -29,9 +29,12 @@ class TestProcessModel(BaseTest):
|
|||||||
def test_can_run_process_model_with_call_activities_when_in_same_process_model_directory(
|
def test_can_run_process_model_with_call_activities_when_in_same_process_model_directory(
|
||||||
self,
|
self,
|
||||||
app: Flask,
|
app: Flask,
|
||||||
|
client: FlaskClient,
|
||||||
with_db_and_bpmn_file_cleanup: None,
|
with_db_and_bpmn_file_cleanup: None,
|
||||||
|
with_super_admin_user: UserModel,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test_can_run_process_model_with_call_activities."""
|
"""Test_can_run_process_model_with_call_activities."""
|
||||||
|
self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group")
|
||||||
process_model = load_test_spec(
|
process_model = load_test_spec(
|
||||||
"test_group/call_activity_test",
|
"test_group/call_activity_test",
|
||||||
# bpmn_file_name="call_activity_test.bpmn",
|
# bpmn_file_name="call_activity_test.bpmn",
|
||||||
@ -46,9 +49,12 @@ class TestProcessModel(BaseTest):
|
|||||||
def test_can_run_process_model_with_call_activities_when_not_in_same_directory(
|
def test_can_run_process_model_with_call_activities_when_not_in_same_directory(
|
||||||
self,
|
self,
|
||||||
app: Flask,
|
app: Flask,
|
||||||
|
client: FlaskClient,
|
||||||
with_db_and_bpmn_file_cleanup: None,
|
with_db_and_bpmn_file_cleanup: None,
|
||||||
|
with_super_admin_user: UserModel,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test_can_run_process_model_with_call_activities."""
|
"""Test_can_run_process_model_with_call_activities."""
|
||||||
|
self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group")
|
||||||
process_model = load_test_spec(
|
process_model = load_test_spec(
|
||||||
"test_group/call_activity_nested",
|
"test_group/call_activity_nested",
|
||||||
process_model_source_directory="call_activity_nested",
|
process_model_source_directory="call_activity_nested",
|
||||||
@ -74,9 +80,12 @@ class TestProcessModel(BaseTest):
|
|||||||
def test_can_run_process_model_with_call_activities_when_process_identifier_is_not_in_database(
|
def test_can_run_process_model_with_call_activities_when_process_identifier_is_not_in_database(
|
||||||
self,
|
self,
|
||||||
app: Flask,
|
app: Flask,
|
||||||
|
client: FlaskClient,
|
||||||
with_db_and_bpmn_file_cleanup: None,
|
with_db_and_bpmn_file_cleanup: None,
|
||||||
|
with_super_admin_user: UserModel,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test_can_run_process_model_with_call_activities."""
|
"""Test_can_run_process_model_with_call_activities."""
|
||||||
|
self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group")
|
||||||
process_model = load_test_spec(
|
process_model = load_test_spec(
|
||||||
"test_group/call_activity_nested",
|
"test_group/call_activity_nested",
|
||||||
process_model_source_directory="call_activity_nested",
|
process_model_source_directory="call_activity_nested",
|
||||||
@ -107,7 +116,9 @@ class TestProcessModel(BaseTest):
|
|||||||
def test_extract_metadata(
|
def test_extract_metadata(
|
||||||
self,
|
self,
|
||||||
app: Flask,
|
app: Flask,
|
||||||
|
client: FlaskClient,
|
||||||
with_db_and_bpmn_file_cleanup: None,
|
with_db_and_bpmn_file_cleanup: None,
|
||||||
|
with_super_admin_user: UserModel,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test_can_run_process_model_with_call_activities."""
|
"""Test_can_run_process_model_with_call_activities."""
|
||||||
process_model = self.create_process_model_with_metadata()
|
process_model = self.create_process_model_with_metadata()
|
||||||
|
@ -14,8 +14,12 @@ class TestProcessModelService(BaseTest):
|
|||||||
def test_can_update_specified_attributes(
|
def test_can_update_specified_attributes(
|
||||||
self,
|
self,
|
||||||
app: Flask,
|
app: Flask,
|
||||||
|
client: FlaskClient,
|
||||||
with_db_and_bpmn_file_cleanup: None,
|
with_db_and_bpmn_file_cleanup: None,
|
||||||
|
with_super_admin_user: UserModel,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
"""Test_can_update_specified_attributes."""
|
||||||
|
self.create_process_group_with_api(client, with_super_admin_user, "test_group", "test_group")
|
||||||
process_model = load_test_spec(
|
process_model = load_test_spec(
|
||||||
"test_group/hello_world",
|
"test_group/hello_world",
|
||||||
bpmn_file_name="hello_world.bpmn",
|
bpmn_file_name="hello_world.bpmn",
|
||||||
|
@ -1,153 +0,0 @@
|
|||||||
from flask import Flask
|
|
||||||
from spiffworkflow_backend.models.task_definition import TaskDefinitionModel
|
|
||||||
from spiffworkflow_backend.models.task import TaskModel # noqa: F401
|
|
||||||
from spiffworkflow_backend.models.bpmn_process import BpmnProcessModel
|
|
||||||
from flask.testing import FlaskClient
|
|
||||||
from spiffworkflow_backend.models.bpmn_process_definition import BpmnProcessDefinitionModel
|
|
||||||
from spiffworkflow_backend.services.process_instance_processor import ProcessInstanceProcessor
|
|
||||||
from spiffworkflow_backend.services.task_service import TaskService
|
|
||||||
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
|
|
||||||
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
|
|
||||||
|
|
||||||
from spiffworkflow_backend.models.user import UserModel
|
|
||||||
|
|
||||||
|
|
||||||
class TestTaskService(BaseTest):
|
|
||||||
|
|
||||||
def test_can_get_full_bpmn_process_path(
|
|
||||||
self,
|
|
||||||
app: Flask,
|
|
||||||
with_db_and_bpmn_file_cleanup: None,
|
|
||||||
) -> None:
|
|
||||||
process_model = load_test_spec(
|
|
||||||
"test_group/call_activity_nested",
|
|
||||||
process_model_source_directory="call_activity_nested",
|
|
||||||
bpmn_file_name="call_activity_nested",
|
|
||||||
)
|
|
||||||
|
|
||||||
bpmn_file_names = [
|
|
||||||
"call_activity_level_2b",
|
|
||||||
"call_activity_level_2",
|
|
||||||
"call_activity_level_3",
|
|
||||||
]
|
|
||||||
for bpmn_file_name in bpmn_file_names:
|
|
||||||
load_test_spec(
|
|
||||||
f"test_group/{bpmn_file_name}",
|
|
||||||
process_model_source_directory="call_activity_nested",
|
|
||||||
bpmn_file_name=bpmn_file_name,
|
|
||||||
)
|
|
||||||
process_instance = self.create_process_instance_from_process_model(process_model)
|
|
||||||
processor = ProcessInstanceProcessor(process_instance)
|
|
||||||
processor.do_engine_steps(save=True, execution_strategy_name="greedy")
|
|
||||||
assert process_instance.status == "complete"
|
|
||||||
|
|
||||||
bpmn_process_level_2b = (
|
|
||||||
BpmnProcessModel.query
|
|
||||||
.join(BpmnProcessDefinitionModel)
|
|
||||||
.filter(BpmnProcessDefinitionModel.bpmn_identifier == 'Level2b').first()
|
|
||||||
)
|
|
||||||
assert bpmn_process_level_2b is not None
|
|
||||||
full_bpnmn_process_path = TaskService.full_bpmn_process_path(bpmn_process_level_2b)
|
|
||||||
assert full_bpnmn_process_path == ['Level1', 'Level2', 'Level2b']
|
|
||||||
|
|
||||||
bpmn_process_level_3 = (
|
|
||||||
BpmnProcessModel.query
|
|
||||||
.join(BpmnProcessDefinitionModel)
|
|
||||||
.filter(BpmnProcessDefinitionModel.bpmn_identifier == 'Level3').first()
|
|
||||||
)
|
|
||||||
assert bpmn_process_level_3 is not None
|
|
||||||
full_bpnmn_process_path = TaskService.full_bpmn_process_path(bpmn_process_level_3)
|
|
||||||
assert full_bpnmn_process_path == ['Level1', 'Level2', 'Level3']
|
|
||||||
|
|
||||||
def test_task_models_of_parent_bpmn_processes_stop_on_first_call_activity(
|
|
||||||
self,
|
|
||||||
app: Flask,
|
|
||||||
with_db_and_bpmn_file_cleanup: None,
|
|
||||||
) -> None:
|
|
||||||
process_model = load_test_spec(
|
|
||||||
"test_group/call_activity_nested",
|
|
||||||
process_model_source_directory="call_activity_nested",
|
|
||||||
bpmn_file_name="call_activity_nested",
|
|
||||||
)
|
|
||||||
|
|
||||||
bpmn_file_names = [
|
|
||||||
"call_activity_level_2b",
|
|
||||||
"call_activity_level_2",
|
|
||||||
"call_activity_level_3",
|
|
||||||
]
|
|
||||||
for bpmn_file_name in bpmn_file_names:
|
|
||||||
load_test_spec(
|
|
||||||
f"test_group/{bpmn_file_name}",
|
|
||||||
process_model_source_directory="call_activity_nested",
|
|
||||||
bpmn_file_name=bpmn_file_name,
|
|
||||||
)
|
|
||||||
process_instance = self.create_process_instance_from_process_model(process_model)
|
|
||||||
processor = ProcessInstanceProcessor(process_instance)
|
|
||||||
processor.do_engine_steps(save=True, execution_strategy_name="greedy")
|
|
||||||
assert process_instance.status == "complete"
|
|
||||||
|
|
||||||
task_model_level_2b = (
|
|
||||||
TaskModel.query.join(TaskDefinitionModel)
|
|
||||||
.filter(TaskDefinitionModel.bpmn_identifier == 'level_2b_subprocess_script_task').first()
|
|
||||||
)
|
|
||||||
assert task_model_level_2b is not None
|
|
||||||
(bpmn_processes, task_models) = TaskService.task_models_of_parent_bpmn_processes(task_model_level_2b, stop_on_first_call_activity=True)
|
|
||||||
assert len(bpmn_processes) == 2
|
|
||||||
assert len(task_models) == 2
|
|
||||||
assert bpmn_processes[0].bpmn_process_definition.bpmn_identifier == 'Level2b'
|
|
||||||
assert task_models[0].task_definition.bpmn_identifier == 'level2b_second_call'
|
|
||||||
|
|
||||||
task_model_level_3 = (
|
|
||||||
TaskModel.query.join(TaskDefinitionModel)
|
|
||||||
.filter(TaskDefinitionModel.bpmn_identifier == 'level_3_script_task').first()
|
|
||||||
)
|
|
||||||
assert task_model_level_3 is not None
|
|
||||||
(bpmn_processes, task_models) = TaskService.task_models_of_parent_bpmn_processes(task_model_level_3, stop_on_first_call_activity=True)
|
|
||||||
assert len(bpmn_processes) == 1
|
|
||||||
assert len(task_models) == 1
|
|
||||||
assert bpmn_processes[0].bpmn_process_definition.bpmn_identifier == 'Level3'
|
|
||||||
assert task_models[0].task_definition.bpmn_identifier == 'level3'
|
|
||||||
|
|
||||||
def test_bpmn_process_for_called_activity_or_top_level_process(
|
|
||||||
self,
|
|
||||||
app: Flask,
|
|
||||||
with_db_and_bpmn_file_cleanup: None,
|
|
||||||
) -> None:
|
|
||||||
process_model = load_test_spec(
|
|
||||||
"test_group/call_activity_nested",
|
|
||||||
process_model_source_directory="call_activity_nested",
|
|
||||||
bpmn_file_name="call_activity_nested",
|
|
||||||
)
|
|
||||||
|
|
||||||
bpmn_file_names = [
|
|
||||||
"call_activity_level_2b",
|
|
||||||
"call_activity_level_2",
|
|
||||||
"call_activity_level_3",
|
|
||||||
]
|
|
||||||
for bpmn_file_name in bpmn_file_names:
|
|
||||||
load_test_spec(
|
|
||||||
f"test_group/{bpmn_file_name}",
|
|
||||||
process_model_source_directory="call_activity_nested",
|
|
||||||
bpmn_file_name=bpmn_file_name,
|
|
||||||
)
|
|
||||||
process_instance = self.create_process_instance_from_process_model(process_model)
|
|
||||||
processor = ProcessInstanceProcessor(process_instance)
|
|
||||||
processor.do_engine_steps(save=True, execution_strategy_name="greedy")
|
|
||||||
assert process_instance.status == "complete"
|
|
||||||
|
|
||||||
task_model_level_2b = (
|
|
||||||
TaskModel.query.join(TaskDefinitionModel)
|
|
||||||
.filter(TaskDefinitionModel.bpmn_identifier == 'level_2b_subprocess_script_task').first()
|
|
||||||
)
|
|
||||||
assert task_model_level_2b is not None
|
|
||||||
bpmn_process = TaskService.bpmn_process_for_called_activity_or_top_level_process(task_model_level_2b)
|
|
||||||
assert bpmn_process is not None
|
|
||||||
assert bpmn_process.bpmn_process_definition.bpmn_identifier == 'Level2b'
|
|
||||||
|
|
||||||
task_model_level_3 = (
|
|
||||||
TaskModel.query.join(TaskDefinitionModel)
|
|
||||||
.filter(TaskDefinitionModel.bpmn_identifier == 'level_3_script_task').first()
|
|
||||||
)
|
|
||||||
assert task_model_level_3 is not None
|
|
||||||
bpmn_process = TaskService.bpmn_process_for_called_activity_or_top_level_process(task_model_level_3)
|
|
||||||
assert bpmn_process.bpmn_process_definition.bpmn_identifier == 'Level3'
|
|
@ -8,10 +8,7 @@ export default function InstructionsForEndUser({ task }: any) {
|
|||||||
}
|
}
|
||||||
let instructions =
|
let instructions =
|
||||||
'There is no additional instructions or information for this task.';
|
'There is no additional instructions or information for this task.';
|
||||||
let { properties } = task;
|
const { properties } = task;
|
||||||
if (!properties) {
|
|
||||||
properties = task.properties_json;
|
|
||||||
}
|
|
||||||
const { instructionsForEndUser } = properties;
|
const { instructionsForEndUser } = properties;
|
||||||
if (instructionsForEndUser) {
|
if (instructionsForEndUser) {
|
||||||
instructions = instructionsForEndUser;
|
instructions = instructionsForEndUser;
|
||||||
|
@ -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,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="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>
|
||||||
|
@ -21,8 +21,6 @@ export default function ProcessInterstitial() {
|
|||||||
return ['User Task', 'Manual Task'];
|
return ['User Task', 'Manual Task'];
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const processInstanceShowPageBaseUrl = `/admin/process-instances/for-me/${params.process_model_id}`;
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchEventSource(
|
fetchEventSource(
|
||||||
`${BACKEND_BASE_URL}/tasks/${params.process_instance_id}`,
|
`${BACKEND_BASE_URL}/tasks/${params.process_instance_id}`,
|
||||||
@ -129,10 +127,7 @@ export default function ProcessInterstitial() {
|
|||||||
entityType: 'process-model-id',
|
entityType: 'process-model-id',
|
||||||
linkLastItem: true,
|
linkLastItem: true,
|
||||||
},
|
},
|
||||||
[
|
[`Process Instance Id: ${lastTask.process_instance_id}`],
|
||||||
`Process Instance: ${params.process_instance_id}`,
|
|
||||||
`${processInstanceShowPageBaseUrl}/${params.process_instance_id}`,
|
|
||||||
],
|
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
@ -6,7 +6,7 @@ export default function ProcessRoutes() {
|
|||||||
return (
|
return (
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route
|
<Route
|
||||||
path=":process_model_id/:process_instance_id/interstitial"
|
path=":process_model_identifier/:process_instance_id/interstitial"
|
||||||
element={<ProcessInterstitial />}
|
element={<ProcessInterstitial />}
|
||||||
/>
|
/>
|
||||||
</Routes>
|
</Routes>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user