moved more api methods to own controllers w/ burnettk
This commit is contained in:
parent
05417039fe
commit
1c3da92f81
|
@ -1244,7 +1244,7 @@ paths:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Tasks
|
- Tasks
|
||||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.task_list_my_tasks
|
operationId: spiffworkflow_backend.routes.tasks_controller.task_list_my_tasks
|
||||||
summary: returns the list of ready or waiting tasks for a user
|
summary: returns the list of ready or waiting tasks for a user
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
|
@ -1273,7 +1273,7 @@ paths:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Process Instances
|
- Process Instances
|
||||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.task_list_for_my_open_processes
|
operationId: spiffworkflow_backend.routes.tasks_controller.task_list_for_my_open_processes
|
||||||
summary: returns the list of tasks for given user's open process instances
|
summary: returns the list of tasks for given user's open process instances
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
|
@ -1302,7 +1302,7 @@ paths:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Process Instances
|
- Process Instances
|
||||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.task_list_for_me
|
operationId: spiffworkflow_backend.routes.tasks_controller.task_list_for_me
|
||||||
summary: returns the list of tasks for given user's open process instances
|
summary: returns the list of tasks for given user's open process instances
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
|
@ -1337,7 +1337,7 @@ paths:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Process Instances
|
- Process Instances
|
||||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.task_list_for_my_groups
|
operationId: spiffworkflow_backend.routes.tasks_controller.task_list_for_my_groups
|
||||||
summary: returns the list of tasks for given user's open process instances
|
summary: returns the list of tasks for given user's open process instances
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
|
@ -1562,7 +1562,7 @@ paths:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Tasks
|
- Tasks
|
||||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.task_show
|
operationId: spiffworkflow_backend.routes.tasks_controller.task_show
|
||||||
summary: Gets one task that a user wants to complete
|
summary: Gets one task that a user wants to complete
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
|
@ -1574,7 +1574,7 @@ paths:
|
||||||
put:
|
put:
|
||||||
tags:
|
tags:
|
||||||
- Tasks
|
- Tasks
|
||||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.task_submit
|
operationId: spiffworkflow_backend.routes.tasks_controller.task_submit
|
||||||
summary: Update the form data for a tasks
|
summary: Update the form data for a tasks
|
||||||
requestBody:
|
requestBody:
|
||||||
content:
|
content:
|
||||||
|
@ -1618,7 +1618,7 @@ paths:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Messages
|
- Messages
|
||||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.message_instance_list
|
operationId: spiffworkflow_backend.routes.messages_controller.message_instance_list
|
||||||
summary: Get a list of message instances
|
summary: Get a list of message instances
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
|
@ -1639,7 +1639,7 @@ paths:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
- Messages
|
- Messages
|
||||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.message_start
|
operationId: spiffworkflow_backend.routes.messages_controller.message_start
|
||||||
summary: Instantiate and run a given process model with a message start event matching given identifier
|
summary: Instantiate and run a given process model with a message start event matching given identifier
|
||||||
requestBody:
|
requestBody:
|
||||||
content:
|
content:
|
||||||
|
@ -1708,7 +1708,7 @@ paths:
|
||||||
schema:
|
schema:
|
||||||
type: integer
|
type: integer
|
||||||
post:
|
post:
|
||||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.secret_create
|
operationId: spiffworkflow_backend.routes.secrets_controller.secret_create
|
||||||
summary: Create a secret for a key and value
|
summary: Create a secret for a key and value
|
||||||
tags:
|
tags:
|
||||||
- Secrets
|
- Secrets
|
||||||
|
@ -1725,7 +1725,7 @@ paths:
|
||||||
schema:
|
schema:
|
||||||
type: number
|
type: number
|
||||||
get:
|
get:
|
||||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.secret_list
|
operationId: spiffworkflow_backend.routes.secrets_controller.secret_list
|
||||||
summary: Return list of all secrets
|
summary: Return list of all secrets
|
||||||
tags:
|
tags:
|
||||||
- Secrets
|
- Secrets
|
||||||
|
@ -1746,7 +1746,7 @@ paths:
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
get:
|
get:
|
||||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.get_secret
|
operationId: spiffworkflow_backend.routes.secrets_controller.secret_show
|
||||||
summary: Return a secret value for a key
|
summary: Return a secret value for a key
|
||||||
tags:
|
tags:
|
||||||
- Secrets
|
- Secrets
|
||||||
|
@ -1758,7 +1758,7 @@ paths:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/Secret"
|
$ref: "#/components/schemas/Secret"
|
||||||
delete:
|
delete:
|
||||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.secret_delete
|
operationId: spiffworkflow_backend.routes.secrets_controller.secret_delete
|
||||||
summary: Delete an existing secret
|
summary: Delete an existing secret
|
||||||
tags:
|
tags:
|
||||||
- Secrets
|
- Secrets
|
||||||
|
@ -1770,7 +1770,7 @@ paths:
|
||||||
"404":
|
"404":
|
||||||
description: Secret does not exist
|
description: Secret does not exist
|
||||||
put:
|
put:
|
||||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.secret_update
|
operationId: spiffworkflow_backend.routes.secrets_controller.secret_update
|
||||||
summary: Modify an existing secret
|
summary: Modify an existing secret
|
||||||
tags:
|
tags:
|
||||||
- Secrets
|
- Secrets
|
||||||
|
|
|
@ -0,0 +1,167 @@
|
||||||
|
"""APIs for dealing with process groups, process models, and process instances."""
|
||||||
|
import json
|
||||||
|
from typing import Any
|
||||||
|
from typing import Dict
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import flask.wrappers
|
||||||
|
from flask import g
|
||||||
|
from flask import jsonify
|
||||||
|
from flask import make_response
|
||||||
|
from flask.wrappers import Response
|
||||||
|
from flask_bpmn.api.api_error import ApiError
|
||||||
|
|
||||||
|
from spiffworkflow_backend.models.message_correlation import MessageCorrelationModel
|
||||||
|
from spiffworkflow_backend.models.message_instance import MessageInstanceModel
|
||||||
|
from spiffworkflow_backend.models.message_model import MessageModel
|
||||||
|
from spiffworkflow_backend.models.message_triggerable_process_model import (
|
||||||
|
MessageTriggerableProcessModel,
|
||||||
|
)
|
||||||
|
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
||||||
|
from spiffworkflow_backend.models.process_instance import ProcessInstanceModelSchema
|
||||||
|
from spiffworkflow_backend.services.message_service import MessageService
|
||||||
|
|
||||||
|
|
||||||
|
def message_instance_list(
|
||||||
|
process_instance_id: Optional[int] = None,
|
||||||
|
page: int = 1,
|
||||||
|
per_page: int = 100,
|
||||||
|
) -> flask.wrappers.Response:
|
||||||
|
"""Message_instance_list."""
|
||||||
|
# to make sure the process instance exists
|
||||||
|
message_instances_query = MessageInstanceModel.query
|
||||||
|
|
||||||
|
if process_instance_id:
|
||||||
|
message_instances_query = message_instances_query.filter_by(
|
||||||
|
process_instance_id=process_instance_id
|
||||||
|
)
|
||||||
|
|
||||||
|
message_instances = (
|
||||||
|
message_instances_query.order_by(
|
||||||
|
MessageInstanceModel.created_at_in_seconds.desc(), # type: ignore
|
||||||
|
MessageInstanceModel.id.desc(), # type: ignore
|
||||||
|
)
|
||||||
|
.join(MessageModel, MessageModel.id == MessageInstanceModel.message_model_id)
|
||||||
|
.join(ProcessInstanceModel)
|
||||||
|
.add_columns(
|
||||||
|
MessageModel.identifier.label("message_identifier"),
|
||||||
|
ProcessInstanceModel.process_model_identifier,
|
||||||
|
ProcessInstanceModel.process_model_display_name,
|
||||||
|
)
|
||||||
|
.paginate(page=page, per_page=per_page, error_out=False)
|
||||||
|
)
|
||||||
|
|
||||||
|
for message_instance in message_instances:
|
||||||
|
message_correlations: dict = {}
|
||||||
|
for (
|
||||||
|
mcmi
|
||||||
|
) in (
|
||||||
|
message_instance.MessageInstanceModel.message_correlations_message_instances
|
||||||
|
):
|
||||||
|
mc = MessageCorrelationModel.query.filter_by(
|
||||||
|
id=mcmi.message_correlation_id
|
||||||
|
).all()
|
||||||
|
for m in mc:
|
||||||
|
if m.name not in message_correlations:
|
||||||
|
message_correlations[m.name] = {}
|
||||||
|
message_correlations[m.name][
|
||||||
|
m.message_correlation_property.identifier
|
||||||
|
] = m.value
|
||||||
|
message_instance.MessageInstanceModel.message_correlations = (
|
||||||
|
message_correlations
|
||||||
|
)
|
||||||
|
|
||||||
|
response_json = {
|
||||||
|
"results": message_instances.items,
|
||||||
|
"pagination": {
|
||||||
|
"count": len(message_instances.items),
|
||||||
|
"total": message_instances.total,
|
||||||
|
"pages": message_instances.pages,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return make_response(jsonify(response_json), 200)
|
||||||
|
|
||||||
|
|
||||||
|
# body: {
|
||||||
|
# payload: dict,
|
||||||
|
# process_instance_id: Optional[int],
|
||||||
|
# }
|
||||||
|
def message_start(
|
||||||
|
message_identifier: str,
|
||||||
|
body: Dict[str, Any],
|
||||||
|
) -> flask.wrappers.Response:
|
||||||
|
"""Message_start."""
|
||||||
|
message_model = MessageModel.query.filter_by(identifier=message_identifier).first()
|
||||||
|
if message_model is None:
|
||||||
|
raise (
|
||||||
|
ApiError(
|
||||||
|
error_code="unknown_message",
|
||||||
|
message=f"Could not find message with identifier: {message_identifier}",
|
||||||
|
status_code=404,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if "payload" not in body:
|
||||||
|
raise (
|
||||||
|
ApiError(
|
||||||
|
error_code="missing_payload",
|
||||||
|
message="Body is missing payload.",
|
||||||
|
status_code=400,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
process_instance = None
|
||||||
|
if "process_instance_id" in body:
|
||||||
|
# to make sure we have a valid process_instance_id
|
||||||
|
process_instance = _find_process_instance_by_id_or_raise(
|
||||||
|
body["process_instance_id"]
|
||||||
|
)
|
||||||
|
|
||||||
|
message_instance = MessageInstanceModel.query.filter_by(
|
||||||
|
process_instance_id=process_instance.id,
|
||||||
|
message_model_id=message_model.id,
|
||||||
|
message_type="receive",
|
||||||
|
status="ready",
|
||||||
|
).first()
|
||||||
|
if message_instance is None:
|
||||||
|
raise (
|
||||||
|
ApiError(
|
||||||
|
error_code="cannot_find_waiting_message",
|
||||||
|
message=f"Could not find waiting message for identifier {message_identifier} "
|
||||||
|
f"and process instance {process_instance.id}",
|
||||||
|
status_code=400,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
MessageService.process_message_receive(
|
||||||
|
message_instance, message_model.name, body["payload"]
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
message_triggerable_process_model = (
|
||||||
|
MessageTriggerableProcessModel.query.filter_by(
|
||||||
|
message_model_id=message_model.id
|
||||||
|
).first()
|
||||||
|
)
|
||||||
|
|
||||||
|
if message_triggerable_process_model is None:
|
||||||
|
raise (
|
||||||
|
ApiError(
|
||||||
|
error_code="cannot_start_message",
|
||||||
|
message=f"Message with identifier cannot be start with message: {message_identifier}",
|
||||||
|
status_code=400,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
process_instance = MessageService.process_message_triggerable_process_model(
|
||||||
|
message_triggerable_process_model,
|
||||||
|
message_model.name,
|
||||||
|
body["payload"],
|
||||||
|
g.user,
|
||||||
|
)
|
||||||
|
|
||||||
|
return Response(
|
||||||
|
json.dumps(ProcessInstanceModelSchema().dump(process_instance)),
|
||||||
|
status=200,
|
||||||
|
mimetype="application/json",
|
||||||
|
)
|
|
@ -158,6 +158,14 @@ def permissions_check(body: Dict[str, Dict[str, list[str]]]) -> flask.wrappers.R
|
||||||
return make_response(jsonify({"results": response_dict}), 200)
|
return make_response(jsonify({"results": response_dict}), 200)
|
||||||
|
|
||||||
|
|
||||||
|
def user_group_list_for_current_user() -> flask.wrappers.Response:
|
||||||
|
"""User_group_list_for_current_user."""
|
||||||
|
groups = g.user.groups
|
||||||
|
# TODO: filter out the default group and have a way to know what is the default group
|
||||||
|
group_identifiers = [i.identifier for i in groups if i.identifier != "everybody"]
|
||||||
|
return make_response(jsonify(sorted(group_identifiers)), 200)
|
||||||
|
|
||||||
|
|
||||||
def process_list() -> Any:
|
def process_list() -> Any:
|
||||||
"""Returns a list of all known processes.
|
"""Returns a list of all known processes.
|
||||||
|
|
||||||
|
@ -168,202 +176,6 @@ def process_list() -> Any:
|
||||||
return SpecReferenceSchema(many=True).dump(references)
|
return SpecReferenceSchema(many=True).dump(references)
|
||||||
|
|
||||||
|
|
||||||
def message_instance_list(
|
|
||||||
process_instance_id: Optional[int] = None,
|
|
||||||
page: int = 1,
|
|
||||||
per_page: int = 100,
|
|
||||||
) -> flask.wrappers.Response:
|
|
||||||
"""Message_instance_list."""
|
|
||||||
# to make sure the process instance exists
|
|
||||||
message_instances_query = MessageInstanceModel.query
|
|
||||||
|
|
||||||
if process_instance_id:
|
|
||||||
message_instances_query = message_instances_query.filter_by(
|
|
||||||
process_instance_id=process_instance_id
|
|
||||||
)
|
|
||||||
|
|
||||||
message_instances = (
|
|
||||||
message_instances_query.order_by(
|
|
||||||
MessageInstanceModel.created_at_in_seconds.desc(), # type: ignore
|
|
||||||
MessageInstanceModel.id.desc(), # type: ignore
|
|
||||||
)
|
|
||||||
.join(MessageModel, MessageModel.id == MessageInstanceModel.message_model_id)
|
|
||||||
.join(ProcessInstanceModel)
|
|
||||||
.add_columns(
|
|
||||||
MessageModel.identifier.label("message_identifier"),
|
|
||||||
ProcessInstanceModel.process_model_identifier,
|
|
||||||
ProcessInstanceModel.process_model_display_name,
|
|
||||||
)
|
|
||||||
.paginate(page=page, per_page=per_page, error_out=False)
|
|
||||||
)
|
|
||||||
|
|
||||||
for message_instance in message_instances:
|
|
||||||
message_correlations: dict = {}
|
|
||||||
for (
|
|
||||||
mcmi
|
|
||||||
) in (
|
|
||||||
message_instance.MessageInstanceModel.message_correlations_message_instances
|
|
||||||
):
|
|
||||||
mc = MessageCorrelationModel.query.filter_by(
|
|
||||||
id=mcmi.message_correlation_id
|
|
||||||
).all()
|
|
||||||
for m in mc:
|
|
||||||
if m.name not in message_correlations:
|
|
||||||
message_correlations[m.name] = {}
|
|
||||||
message_correlations[m.name][
|
|
||||||
m.message_correlation_property.identifier
|
|
||||||
] = m.value
|
|
||||||
message_instance.MessageInstanceModel.message_correlations = (
|
|
||||||
message_correlations
|
|
||||||
)
|
|
||||||
|
|
||||||
response_json = {
|
|
||||||
"results": message_instances.items,
|
|
||||||
"pagination": {
|
|
||||||
"count": len(message_instances.items),
|
|
||||||
"total": message_instances.total,
|
|
||||||
"pages": message_instances.pages,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return make_response(jsonify(response_json), 200)
|
|
||||||
|
|
||||||
|
|
||||||
# body: {
|
|
||||||
# payload: dict,
|
|
||||||
# process_instance_id: Optional[int],
|
|
||||||
# }
|
|
||||||
def message_start(
|
|
||||||
message_identifier: str,
|
|
||||||
body: Dict[str, Any],
|
|
||||||
) -> flask.wrappers.Response:
|
|
||||||
"""Message_start."""
|
|
||||||
message_model = MessageModel.query.filter_by(identifier=message_identifier).first()
|
|
||||||
if message_model is None:
|
|
||||||
raise (
|
|
||||||
ApiError(
|
|
||||||
error_code="unknown_message",
|
|
||||||
message=f"Could not find message with identifier: {message_identifier}",
|
|
||||||
status_code=404,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if "payload" not in body:
|
|
||||||
raise (
|
|
||||||
ApiError(
|
|
||||||
error_code="missing_payload",
|
|
||||||
message="Body is missing payload.",
|
|
||||||
status_code=400,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
process_instance = None
|
|
||||||
if "process_instance_id" in body:
|
|
||||||
# to make sure we have a valid process_instance_id
|
|
||||||
process_instance = _find_process_instance_by_id_or_raise(
|
|
||||||
body["process_instance_id"]
|
|
||||||
)
|
|
||||||
|
|
||||||
message_instance = MessageInstanceModel.query.filter_by(
|
|
||||||
process_instance_id=process_instance.id,
|
|
||||||
message_model_id=message_model.id,
|
|
||||||
message_type="receive",
|
|
||||||
status="ready",
|
|
||||||
).first()
|
|
||||||
if message_instance is None:
|
|
||||||
raise (
|
|
||||||
ApiError(
|
|
||||||
error_code="cannot_find_waiting_message",
|
|
||||||
message=f"Could not find waiting message for identifier {message_identifier} "
|
|
||||||
f"and process instance {process_instance.id}",
|
|
||||||
status_code=400,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
MessageService.process_message_receive(
|
|
||||||
message_instance, message_model.name, body["payload"]
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
message_triggerable_process_model = (
|
|
||||||
MessageTriggerableProcessModel.query.filter_by(
|
|
||||||
message_model_id=message_model.id
|
|
||||||
).first()
|
|
||||||
)
|
|
||||||
|
|
||||||
if message_triggerable_process_model is None:
|
|
||||||
raise (
|
|
||||||
ApiError(
|
|
||||||
error_code="cannot_start_message",
|
|
||||||
message=f"Message with identifier cannot be start with message: {message_identifier}",
|
|
||||||
status_code=400,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
process_instance = MessageService.process_message_triggerable_process_model(
|
|
||||||
message_triggerable_process_model,
|
|
||||||
message_model.name,
|
|
||||||
body["payload"],
|
|
||||||
g.user,
|
|
||||||
)
|
|
||||||
|
|
||||||
return Response(
|
|
||||||
json.dumps(ProcessInstanceModelSchema().dump(process_instance)),
|
|
||||||
status=200,
|
|
||||||
mimetype="application/json",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_process_instance(
|
|
||||||
modified_process_model_identifier: str,
|
|
||||||
process_instance: ProcessInstanceModel,
|
|
||||||
process_identifier: Optional[str] = None,
|
|
||||||
) -> flask.wrappers.Response:
|
|
||||||
"""_get_process_instance."""
|
|
||||||
process_model_identifier = modified_process_model_identifier.replace(":", "/")
|
|
||||||
try:
|
|
||||||
current_version_control_revision = GitService.get_current_revision()
|
|
||||||
except GitCommandError:
|
|
||||||
current_version_control_revision = ""
|
|
||||||
|
|
||||||
process_model_with_diagram = None
|
|
||||||
name_of_file_with_diagram = None
|
|
||||||
if process_identifier:
|
|
||||||
spec_reference = SpecReferenceCache.query.filter_by(
|
|
||||||
identifier=process_identifier, type="process"
|
|
||||||
).first()
|
|
||||||
if spec_reference is None:
|
|
||||||
raise SpecReferenceNotFoundError(
|
|
||||||
f"Could not find given process identifier in the cache: {process_identifier}"
|
|
||||||
)
|
|
||||||
|
|
||||||
process_model_with_diagram = ProcessModelService.get_process_model(
|
|
||||||
spec_reference.process_model_id
|
|
||||||
)
|
|
||||||
name_of_file_with_diagram = spec_reference.file_name
|
|
||||||
else:
|
|
||||||
process_model_with_diagram = _get_process_model(process_model_identifier)
|
|
||||||
if process_model_with_diagram.primary_file_name:
|
|
||||||
name_of_file_with_diagram = process_model_with_diagram.primary_file_name
|
|
||||||
|
|
||||||
if process_model_with_diagram and name_of_file_with_diagram:
|
|
||||||
if (
|
|
||||||
process_instance.bpmn_version_control_identifier
|
|
||||||
== current_version_control_revision
|
|
||||||
):
|
|
||||||
bpmn_xml_file_contents = SpecFileService.get_data(
|
|
||||||
process_model_with_diagram, name_of_file_with_diagram
|
|
||||||
).decode("utf-8")
|
|
||||||
else:
|
|
||||||
bpmn_xml_file_contents = GitService.get_instance_file_contents_for_revision(
|
|
||||||
process_model_with_diagram,
|
|
||||||
process_instance.bpmn_version_control_identifier,
|
|
||||||
file_name=name_of_file_with_diagram,
|
|
||||||
)
|
|
||||||
process_instance.bpmn_xml_file_contents = bpmn_xml_file_contents
|
|
||||||
|
|
||||||
return make_response(jsonify(process_instance), 200)
|
|
||||||
|
|
||||||
|
|
||||||
def service_task_list() -> flask.wrappers.Response:
|
def service_task_list() -> flask.wrappers.Response:
|
||||||
"""Service_task_list."""
|
"""Service_task_list."""
|
||||||
available_connectors = ServiceTaskService.available_connectors()
|
available_connectors = ServiceTaskService.available_connectors()
|
||||||
|
@ -399,268 +211,6 @@ def authentication_callback(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# TODO: see comment for before_request
|
|
||||||
# @process_api_blueprint.route("/v1.0/tasks", methods=["GET"])
|
|
||||||
def task_list_my_tasks(page: int = 1, per_page: int = 100) -> flask.wrappers.Response:
|
|
||||||
"""Task_list_my_tasks."""
|
|
||||||
principal = find_principal_or_raise()
|
|
||||||
human_tasks = (
|
|
||||||
HumanTaskModel.query.order_by(desc(HumanTaskModel.id)) # type: ignore
|
|
||||||
.join(ProcessInstanceModel)
|
|
||||||
.join(HumanTaskUserModel)
|
|
||||||
.filter_by(user_id=principal.user_id)
|
|
||||||
.filter(HumanTaskModel.completed == False) # noqa: E712
|
|
||||||
# just need this add_columns to add the process_model_identifier. Then add everything back that was removed.
|
|
||||||
.add_columns(
|
|
||||||
ProcessInstanceModel.process_model_identifier,
|
|
||||||
ProcessInstanceModel.process_model_display_name,
|
|
||||||
ProcessInstanceModel.status,
|
|
||||||
HumanTaskModel.task_name,
|
|
||||||
HumanTaskModel.task_title,
|
|
||||||
HumanTaskModel.task_type,
|
|
||||||
HumanTaskModel.task_status,
|
|
||||||
HumanTaskModel.task_id,
|
|
||||||
HumanTaskModel.id,
|
|
||||||
HumanTaskModel.process_model_display_name,
|
|
||||||
HumanTaskModel.process_instance_id,
|
|
||||||
)
|
|
||||||
.paginate(page=page, per_page=per_page, error_out=False)
|
|
||||||
)
|
|
||||||
tasks = [HumanTaskModel.to_task(human_task) for human_task in human_tasks.items]
|
|
||||||
|
|
||||||
response_json = {
|
|
||||||
"results": tasks,
|
|
||||||
"pagination": {
|
|
||||||
"count": len(human_tasks.items),
|
|
||||||
"total": human_tasks.total,
|
|
||||||
"pages": human_tasks.pages,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return make_response(jsonify(response_json), 200)
|
|
||||||
|
|
||||||
|
|
||||||
def task_list_for_my_open_processes(
|
|
||||||
page: int = 1, per_page: int = 100
|
|
||||||
) -> flask.wrappers.Response:
|
|
||||||
"""Task_list_for_my_open_processes."""
|
|
||||||
return get_tasks(page=page, per_page=per_page)
|
|
||||||
|
|
||||||
|
|
||||||
def task_list_for_me(page: int = 1, per_page: int = 100) -> flask.wrappers.Response:
|
|
||||||
"""Task_list_for_me."""
|
|
||||||
return get_tasks(
|
|
||||||
processes_started_by_user=False,
|
|
||||||
has_lane_assignment_id=False,
|
|
||||||
page=page,
|
|
||||||
per_page=per_page,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def task_list_for_my_groups(
|
|
||||||
user_group_identifier: Optional[str] = None, page: int = 1, per_page: int = 100
|
|
||||||
) -> flask.wrappers.Response:
|
|
||||||
"""Task_list_for_my_groups."""
|
|
||||||
return get_tasks(
|
|
||||||
user_group_identifier=user_group_identifier,
|
|
||||||
processes_started_by_user=False,
|
|
||||||
page=page,
|
|
||||||
per_page=per_page,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def user_group_list_for_current_user() -> flask.wrappers.Response:
|
|
||||||
"""User_group_list_for_current_user."""
|
|
||||||
groups = g.user.groups
|
|
||||||
# TODO: filter out the default group and have a way to know what is the default group
|
|
||||||
group_identifiers = [i.identifier for i in groups if i.identifier != "everybody"]
|
|
||||||
return make_response(jsonify(sorted(group_identifiers)), 200)
|
|
||||||
|
|
||||||
|
|
||||||
def get_tasks(
|
|
||||||
processes_started_by_user: bool = True,
|
|
||||||
has_lane_assignment_id: bool = True,
|
|
||||||
page: int = 1,
|
|
||||||
per_page: int = 100,
|
|
||||||
user_group_identifier: Optional[str] = None,
|
|
||||||
) -> flask.wrappers.Response:
|
|
||||||
"""Get_tasks."""
|
|
||||||
user_id = g.user.id
|
|
||||||
|
|
||||||
# use distinct to ensure we only get one row per human task otherwise
|
|
||||||
# we can get back multiple for the same human task row which throws off
|
|
||||||
# pagination later on
|
|
||||||
# https://stackoverflow.com/q/34582014/6090676
|
|
||||||
human_tasks_query = (
|
|
||||||
HumanTaskModel.query.distinct()
|
|
||||||
.outerjoin(GroupModel, GroupModel.id == HumanTaskModel.lane_assignment_id)
|
|
||||||
.join(ProcessInstanceModel)
|
|
||||||
.join(UserModel, UserModel.id == ProcessInstanceModel.process_initiator_id)
|
|
||||||
.filter(HumanTaskModel.completed == False) # noqa: E712
|
|
||||||
)
|
|
||||||
|
|
||||||
if processes_started_by_user:
|
|
||||||
human_tasks_query = human_tasks_query.filter(
|
|
||||||
ProcessInstanceModel.process_initiator_id == user_id
|
|
||||||
).outerjoin(
|
|
||||||
HumanTaskUserModel,
|
|
||||||
and_(
|
|
||||||
HumanTaskUserModel.user_id == user_id,
|
|
||||||
HumanTaskModel.id == HumanTaskUserModel.human_task_id,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
human_tasks_query = human_tasks_query.filter(
|
|
||||||
ProcessInstanceModel.process_initiator_id != user_id
|
|
||||||
).join(
|
|
||||||
HumanTaskUserModel,
|
|
||||||
and_(
|
|
||||||
HumanTaskUserModel.user_id == user_id,
|
|
||||||
HumanTaskModel.id == HumanTaskUserModel.human_task_id,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
if has_lane_assignment_id:
|
|
||||||
if user_group_identifier:
|
|
||||||
human_tasks_query = human_tasks_query.filter(
|
|
||||||
GroupModel.identifier == user_group_identifier
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
human_tasks_query = human_tasks_query.filter(
|
|
||||||
HumanTaskModel.lane_assignment_id.is_not(None) # type: ignore
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
human_tasks_query = human_tasks_query.filter(HumanTaskModel.lane_assignment_id.is_(None)) # type: ignore
|
|
||||||
|
|
||||||
human_tasks = (
|
|
||||||
human_tasks_query.add_columns(
|
|
||||||
ProcessInstanceModel.process_model_identifier,
|
|
||||||
ProcessInstanceModel.status.label("process_instance_status"), # type: ignore
|
|
||||||
ProcessInstanceModel.updated_at_in_seconds,
|
|
||||||
ProcessInstanceModel.created_at_in_seconds,
|
|
||||||
UserModel.username,
|
|
||||||
GroupModel.identifier.label("user_group_identifier"),
|
|
||||||
HumanTaskModel.task_name,
|
|
||||||
HumanTaskModel.task_title,
|
|
||||||
HumanTaskModel.process_model_display_name,
|
|
||||||
HumanTaskModel.process_instance_id,
|
|
||||||
HumanTaskUserModel.user_id.label("current_user_is_potential_owner"),
|
|
||||||
)
|
|
||||||
.order_by(desc(HumanTaskModel.id)) # type: ignore
|
|
||||||
.paginate(page=page, per_page=per_page, error_out=False)
|
|
||||||
)
|
|
||||||
|
|
||||||
response_json = {
|
|
||||||
"results": human_tasks.items,
|
|
||||||
"pagination": {
|
|
||||||
"count": len(human_tasks.items),
|
|
||||||
"total": human_tasks.total,
|
|
||||||
"pages": human_tasks.pages,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return make_response(jsonify(response_json), 200)
|
|
||||||
|
|
||||||
|
|
||||||
def task_show(process_instance_id: int, task_id: str) -> flask.wrappers.Response:
|
|
||||||
"""Task_show."""
|
|
||||||
process_instance = _find_process_instance_by_id_or_raise(process_instance_id)
|
|
||||||
|
|
||||||
if process_instance.status == ProcessInstanceStatus.suspended.value:
|
|
||||||
raise ApiError(
|
|
||||||
error_code="error_suspended",
|
|
||||||
message="The process instance is suspended",
|
|
||||||
status_code=400,
|
|
||||||
)
|
|
||||||
|
|
||||||
process_model = _get_process_model(
|
|
||||||
process_instance.process_model_identifier,
|
|
||||||
)
|
|
||||||
|
|
||||||
form_schema_file_name = ""
|
|
||||||
form_ui_schema_file_name = ""
|
|
||||||
spiff_task = get_spiff_task_from_process_instance(task_id, process_instance)
|
|
||||||
extensions = spiff_task.task_spec.extensions
|
|
||||||
|
|
||||||
if "properties" in extensions:
|
|
||||||
properties = extensions["properties"]
|
|
||||||
if "formJsonSchemaFilename" in properties:
|
|
||||||
form_schema_file_name = properties["formJsonSchemaFilename"]
|
|
||||||
if "formUiSchemaFilename" in properties:
|
|
||||||
form_ui_schema_file_name = properties["formUiSchemaFilename"]
|
|
||||||
task = ProcessInstanceService.spiff_task_to_api_task(spiff_task)
|
|
||||||
task.data = spiff_task.data
|
|
||||||
task.process_model_display_name = process_model.display_name
|
|
||||||
task.process_model_identifier = process_model.id
|
|
||||||
|
|
||||||
process_model_with_form = process_model
|
|
||||||
refs = SpecFileService.get_references_for_process(process_model_with_form)
|
|
||||||
all_processes = [i.identifier for i in refs]
|
|
||||||
if task.process_identifier not in all_processes:
|
|
||||||
bpmn_file_full_path = (
|
|
||||||
ProcessInstanceProcessor.bpmn_file_full_path_from_bpmn_process_identifier(
|
|
||||||
task.process_identifier
|
|
||||||
)
|
|
||||||
)
|
|
||||||
relative_path = os.path.relpath(
|
|
||||||
bpmn_file_full_path, start=FileSystemService.root_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
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if task.type == "User Task":
|
|
||||||
if not form_schema_file_name:
|
|
||||||
raise (
|
|
||||||
ApiError(
|
|
||||||
error_code="missing_form_file",
|
|
||||||
message=f"Cannot find a form file for process_instance_id: {process_instance_id}, task_id: {task_id}",
|
|
||||||
status_code=400,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
form_contents = prepare_form_data(
|
|
||||||
form_schema_file_name,
|
|
||||||
task.data,
|
|
||||||
process_model_with_form,
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
# form_contents is a str
|
|
||||||
form_dict = json.loads(form_contents)
|
|
||||||
except Exception as exception:
|
|
||||||
raise (
|
|
||||||
ApiError(
|
|
||||||
error_code="error_loading_form",
|
|
||||||
message=f"Could not load form schema from: {form_schema_file_name}. Error was: {str(exception)}",
|
|
||||||
status_code=400,
|
|
||||||
)
|
|
||||||
) from exception
|
|
||||||
|
|
||||||
if task.data:
|
|
||||||
_update_form_schema_with_task_data_as_needed(form_dict, task.data)
|
|
||||||
|
|
||||||
if form_contents:
|
|
||||||
task.form_schema = form_dict
|
|
||||||
|
|
||||||
if form_ui_schema_file_name:
|
|
||||||
ui_form_contents = prepare_form_data(
|
|
||||||
form_ui_schema_file_name,
|
|
||||||
task.data,
|
|
||||||
process_model_with_form,
|
|
||||||
)
|
|
||||||
if ui_form_contents:
|
|
||||||
task.form_ui_schema = ui_form_contents
|
|
||||||
|
|
||||||
if task.properties and task.data and "instructionsForEndUser" in task.properties:
|
|
||||||
if task.properties["instructionsForEndUser"]:
|
|
||||||
task.properties["instructionsForEndUser"] = render_jinja_template(
|
|
||||||
task.properties["instructionsForEndUser"], task.data
|
|
||||||
)
|
|
||||||
return make_response(jsonify(task), 200)
|
|
||||||
|
|
||||||
|
|
||||||
def process_data_show(
|
def process_data_show(
|
||||||
process_instance_id: int,
|
process_instance_id: int,
|
||||||
process_data_identifier: str,
|
process_data_identifier: str,
|
||||||
|
@ -685,90 +235,6 @@ def process_data_show(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def task_submit(
|
|
||||||
process_instance_id: int,
|
|
||||||
task_id: str,
|
|
||||||
body: Dict[str, Any],
|
|
||||||
terminate_loop: bool = False,
|
|
||||||
) -> flask.wrappers.Response:
|
|
||||||
"""Task_submit_user_data."""
|
|
||||||
principal = find_principal_or_raise()
|
|
||||||
process_instance = _find_process_instance_by_id_or_raise(process_instance_id)
|
|
||||||
if not process_instance.can_submit_task():
|
|
||||||
raise ApiError(
|
|
||||||
error_code="process_instance_not_runnable",
|
|
||||||
message=f"Process Instance ({process_instance.id}) has status "
|
|
||||||
f"{process_instance.status} which does not allow tasks to be submitted.",
|
|
||||||
status_code=400,
|
|
||||||
)
|
|
||||||
|
|
||||||
processor = ProcessInstanceProcessor(process_instance)
|
|
||||||
spiff_task = get_spiff_task_from_process_instance(
|
|
||||||
task_id, process_instance, processor=processor
|
|
||||||
)
|
|
||||||
AuthorizationService.assert_user_can_complete_spiff_task(
|
|
||||||
process_instance.id, spiff_task, principal.user
|
|
||||||
)
|
|
||||||
|
|
||||||
if spiff_task.state != TaskState.READY:
|
|
||||||
raise (
|
|
||||||
ApiError(
|
|
||||||
error_code="invalid_state",
|
|
||||||
message="You may not update a task unless it is in the READY state.",
|
|
||||||
status_code=400,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if terminate_loop and spiff_task.is_looping():
|
|
||||||
spiff_task.terminate_loop()
|
|
||||||
|
|
||||||
human_task = HumanTaskModel.query.filter_by(
|
|
||||||
process_instance_id=process_instance_id, task_id=task_id, completed=False
|
|
||||||
).first()
|
|
||||||
if human_task is None:
|
|
||||||
raise (
|
|
||||||
ApiError(
|
|
||||||
error_code="no_human_task",
|
|
||||||
message="Cannot find an human task with task id '{task_id}' for process instance {process_instance_id}.",
|
|
||||||
status_code=500,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
ProcessInstanceService.complete_form_task(
|
|
||||||
processor=processor,
|
|
||||||
spiff_task=spiff_task,
|
|
||||||
data=body,
|
|
||||||
user=g.user,
|
|
||||||
human_task=human_task,
|
|
||||||
)
|
|
||||||
|
|
||||||
# If we need to update all tasks, then get the next ready task and if it a multi-instance with the same
|
|
||||||
# task spec, complete that form as well.
|
|
||||||
# if update_all:
|
|
||||||
# last_index = spiff_task.task_info()["mi_index"]
|
|
||||||
# next_task = processor.next_task()
|
|
||||||
# while next_task and next_task.task_info()["mi_index"] > last_index:
|
|
||||||
# __update_task(processor, next_task, form_data, user)
|
|
||||||
# last_index = next_task.task_info()["mi_index"]
|
|
||||||
# next_task = processor.next_task()
|
|
||||||
|
|
||||||
next_human_task_assigned_to_me = (
|
|
||||||
HumanTaskModel.query.filter_by(
|
|
||||||
process_instance_id=process_instance_id, completed=False
|
|
||||||
)
|
|
||||||
.order_by(asc(HumanTaskModel.id)) # type: ignore
|
|
||||||
.join(HumanTaskUserModel)
|
|
||||||
.filter_by(user_id=principal.user_id)
|
|
||||||
.first()
|
|
||||||
)
|
|
||||||
if next_human_task_assigned_to_me:
|
|
||||||
return make_response(
|
|
||||||
jsonify(HumanTaskModel.to_task(next_human_task_assigned_to_me)), 200
|
|
||||||
)
|
|
||||||
|
|
||||||
return Response(json.dumps({"ok": True}), status=202, mimetype="application/json")
|
|
||||||
|
|
||||||
|
|
||||||
def script_unit_test_create(
|
def script_unit_test_create(
|
||||||
modified_process_model_identifier: str, body: Dict[str, Union[str, bool, int]]
|
modified_process_model_identifier: str, body: Dict[str, Union[str, bool, int]]
|
||||||
) -> flask.wrappers.Response:
|
) -> flask.wrappers.Response:
|
||||||
|
@ -878,38 +344,7 @@ def script_unit_test_run(
|
||||||
return make_response(jsonify(result), 200)
|
return make_response(jsonify(result), 200)
|
||||||
|
|
||||||
|
|
||||||
def _get_file_from_request() -> Any:
|
def _find_principal_or_raise() -> PrincipalModel:
|
||||||
"""Get_file_from_request."""
|
|
||||||
request_file = connexion.request.files.get("file")
|
|
||||||
if not request_file:
|
|
||||||
raise ApiError(
|
|
||||||
error_code="no_file_given",
|
|
||||||
message="Given request does not contain a file",
|
|
||||||
status_code=400,
|
|
||||||
)
|
|
||||||
return request_file
|
|
||||||
|
|
||||||
|
|
||||||
# process_model_id uses forward slashes on all OSes
|
|
||||||
# this seems to return an object where process_model.id has backslashes on windows
|
|
||||||
def _get_process_model(process_model_id: str) -> ProcessModelInfo:
|
|
||||||
"""Get_process_model."""
|
|
||||||
process_model = None
|
|
||||||
try:
|
|
||||||
process_model = ProcessModelService.get_process_model(process_model_id)
|
|
||||||
except ProcessEntityNotFoundError as exception:
|
|
||||||
raise (
|
|
||||||
ApiError(
|
|
||||||
error_code="process_model_cannot_be_found",
|
|
||||||
message=f"Process model cannot be found: {process_model_id}",
|
|
||||||
status_code=400,
|
|
||||||
)
|
|
||||||
) from exception
|
|
||||||
|
|
||||||
return process_model
|
|
||||||
|
|
||||||
|
|
||||||
def find_principal_or_raise() -> PrincipalModel:
|
|
||||||
"""Find_principal_or_raise."""
|
"""Find_principal_or_raise."""
|
||||||
principal = PrincipalModel.query.filter_by(user_id=g.user.id).first()
|
principal = PrincipalModel.query.filter_by(user_id=g.user.id).first()
|
||||||
if principal is None:
|
if principal is None:
|
||||||
|
@ -923,33 +358,6 @@ def find_principal_or_raise() -> PrincipalModel:
|
||||||
return principal # type: ignore
|
return principal # type: ignore
|
||||||
|
|
||||||
|
|
||||||
def _find_process_instance_by_id_or_raise(
|
|
||||||
process_instance_id: int,
|
|
||||||
) -> ProcessInstanceModel:
|
|
||||||
"""Find_process_instance_by_id_or_raise."""
|
|
||||||
process_instance_query = ProcessInstanceModel.query.filter_by(
|
|
||||||
id=process_instance_id
|
|
||||||
)
|
|
||||||
|
|
||||||
# we had a frustrating session trying to do joins and access columns from two tables. here's some notes for our future selves:
|
|
||||||
# this returns an object that allows you to do: process_instance.UserModel.username
|
|
||||||
# process_instance = db.session.query(ProcessInstanceModel, UserModel).filter_by(id=process_instance_id).first()
|
|
||||||
# you can also use splat with add_columns, but it still didn't ultimately give us access to the process instance
|
|
||||||
# attributes or username like we wanted:
|
|
||||||
# process_instance_query.join(UserModel).add_columns(*ProcessInstanceModel.__table__.columns, UserModel.username)
|
|
||||||
|
|
||||||
process_instance = process_instance_query.first()
|
|
||||||
if process_instance is None:
|
|
||||||
raise (
|
|
||||||
ApiError(
|
|
||||||
error_code="process_instance_cannot_be_found",
|
|
||||||
message=f"Process instance cannot be found: {process_instance_id}",
|
|
||||||
status_code=400,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return process_instance # type: ignore
|
|
||||||
|
|
||||||
|
|
||||||
def get_value_from_array_with_index(array: list, index: int) -> Any:
|
def get_value_from_array_with_index(array: list, index: int) -> Any:
|
||||||
"""Get_value_from_array_with_index."""
|
"""Get_value_from_array_with_index."""
|
||||||
if index < 0:
|
if index < 0:
|
||||||
|
@ -981,28 +389,6 @@ def render_jinja_template(unprocessed_template: str, data: dict[str, Any]) -> st
|
||||||
return template.render(**data)
|
return template.render(**data)
|
||||||
|
|
||||||
|
|
||||||
def get_spiff_task_from_process_instance(
|
|
||||||
task_id: str,
|
|
||||||
process_instance: ProcessInstanceModel,
|
|
||||||
processor: Union[ProcessInstanceProcessor, None] = None,
|
|
||||||
) -> SpiffTask:
|
|
||||||
"""Get_spiff_task_from_process_instance."""
|
|
||||||
if processor is None:
|
|
||||||
processor = ProcessInstanceProcessor(process_instance)
|
|
||||||
task_uuid = uuid.UUID(task_id)
|
|
||||||
spiff_task = processor.bpmn_process_instance.get_task(task_uuid)
|
|
||||||
|
|
||||||
if spiff_task is None:
|
|
||||||
raise (
|
|
||||||
ApiError(
|
|
||||||
error_code="empty_task",
|
|
||||||
message="Processor failed to obtain task.",
|
|
||||||
status_code=500,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return spiff_task
|
|
||||||
|
|
||||||
|
|
||||||
# sample body:
|
# sample body:
|
||||||
# {"ref": "refs/heads/main", "repository": {"name": "sample-process-models",
|
# {"ref": "refs/heads/main", "repository": {"name": "sample-process-models",
|
||||||
# "full_name": "sartography/sample-process-models", "private": False .... }}
|
# "full_name": "sartography/sample-process-models", "private": False .... }}
|
||||||
|
@ -1018,63 +404,6 @@ def github_webhook_receive(body: Dict) -> Response:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Methods for secrets CRUD - maybe move somewhere else:
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
def get_secret(key: str) -> Optional[str]:
|
|
||||||
"""Get_secret."""
|
|
||||||
return SecretService.get_secret(key)
|
|
||||||
|
|
||||||
|
|
||||||
def secret_list(
|
|
||||||
page: int = 1,
|
|
||||||
per_page: int = 100,
|
|
||||||
) -> Response:
|
|
||||||
"""Secret_list."""
|
|
||||||
secrets = (
|
|
||||||
SecretModel.query.order_by(SecretModel.key)
|
|
||||||
.join(UserModel)
|
|
||||||
.add_columns(
|
|
||||||
UserModel.username,
|
|
||||||
)
|
|
||||||
.paginate(page=page, per_page=per_page, error_out=False)
|
|
||||||
)
|
|
||||||
response_json = {
|
|
||||||
"results": secrets.items,
|
|
||||||
"pagination": {
|
|
||||||
"count": len(secrets.items),
|
|
||||||
"total": secrets.total,
|
|
||||||
"pages": secrets.pages,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return make_response(jsonify(response_json), 200)
|
|
||||||
|
|
||||||
|
|
||||||
def secret_create(body: Dict) -> Response:
|
|
||||||
"""Add secret."""
|
|
||||||
secret_model = SecretService().add_secret(body["key"], body["value"], g.user.id)
|
|
||||||
return Response(
|
|
||||||
json.dumps(SecretModelSchema().dump(secret_model)),
|
|
||||||
status=201,
|
|
||||||
mimetype="application/json",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def secret_update(key: str, body: dict) -> Response:
|
|
||||||
"""Update secret."""
|
|
||||||
SecretService().update_secret(key, body["value"], g.user.id)
|
|
||||||
return Response(json.dumps({"ok": True}), status=200, mimetype="application/json")
|
|
||||||
|
|
||||||
|
|
||||||
def secret_delete(key: str) -> Response:
|
|
||||||
"""Delete secret."""
|
|
||||||
current_user = UserService.current_user()
|
|
||||||
SecretService.delete_secret(key, current_user.id)
|
|
||||||
return Response(json.dumps({"ok": True}), status=200, mimetype="application/json")
|
|
||||||
|
|
||||||
|
|
||||||
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,
|
||||||
|
@ -1249,3 +578,134 @@ def _find_process_instance_for_me_or_raise(
|
||||||
def _un_modify_modified_process_model_id(modified_process_model_identifier: str) -> str:
|
def _un_modify_modified_process_model_id(modified_process_model_identifier: str) -> str:
|
||||||
"""Un_modify_modified_process_model_id."""
|
"""Un_modify_modified_process_model_id."""
|
||||||
return modified_process_model_identifier.replace(":", "/")
|
return modified_process_model_identifier.replace(":", "/")
|
||||||
|
|
||||||
|
|
||||||
|
def _find_process_instance_by_id_or_raise(
|
||||||
|
process_instance_id: int,
|
||||||
|
) -> ProcessInstanceModel:
|
||||||
|
"""Find_process_instance_by_id_or_raise."""
|
||||||
|
process_instance_query = ProcessInstanceModel.query.filter_by(
|
||||||
|
id=process_instance_id
|
||||||
|
)
|
||||||
|
|
||||||
|
# we had a frustrating session trying to do joins and access columns from two tables. here's some notes for our future selves:
|
||||||
|
# this returns an object that allows you to do: process_instance.UserModel.username
|
||||||
|
# process_instance = db.session.query(ProcessInstanceModel, UserModel).filter_by(id=process_instance_id).first()
|
||||||
|
# you can also use splat with add_columns, but it still didn't ultimately give us access to the process instance
|
||||||
|
# attributes or username like we wanted:
|
||||||
|
# process_instance_query.join(UserModel).add_columns(*ProcessInstanceModel.__table__.columns, UserModel.username)
|
||||||
|
|
||||||
|
process_instance = process_instance_query.first()
|
||||||
|
if process_instance is None:
|
||||||
|
raise (
|
||||||
|
ApiError(
|
||||||
|
error_code="process_instance_cannot_be_found",
|
||||||
|
message=f"Process instance cannot be found: {process_instance_id}",
|
||||||
|
status_code=400,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return process_instance # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
def _get_process_instance(
|
||||||
|
modified_process_model_identifier: str,
|
||||||
|
process_instance: ProcessInstanceModel,
|
||||||
|
process_identifier: Optional[str] = None,
|
||||||
|
) -> flask.wrappers.Response:
|
||||||
|
"""_get_process_instance."""
|
||||||
|
process_model_identifier = modified_process_model_identifier.replace(":", "/")
|
||||||
|
try:
|
||||||
|
current_version_control_revision = GitService.get_current_revision()
|
||||||
|
except GitCommandError:
|
||||||
|
current_version_control_revision = ""
|
||||||
|
|
||||||
|
process_model_with_diagram = None
|
||||||
|
name_of_file_with_diagram = None
|
||||||
|
if process_identifier:
|
||||||
|
spec_reference = SpecReferenceCache.query.filter_by(
|
||||||
|
identifier=process_identifier, type="process"
|
||||||
|
).first()
|
||||||
|
if spec_reference is None:
|
||||||
|
raise SpecReferenceNotFoundError(
|
||||||
|
f"Could not find given process identifier in the cache: {process_identifier}"
|
||||||
|
)
|
||||||
|
|
||||||
|
process_model_with_diagram = ProcessModelService.get_process_model(
|
||||||
|
spec_reference.process_model_id
|
||||||
|
)
|
||||||
|
name_of_file_with_diagram = spec_reference.file_name
|
||||||
|
else:
|
||||||
|
process_model_with_diagram = _get_process_model(process_model_identifier)
|
||||||
|
if process_model_with_diagram.primary_file_name:
|
||||||
|
name_of_file_with_diagram = process_model_with_diagram.primary_file_name
|
||||||
|
|
||||||
|
if process_model_with_diagram and name_of_file_with_diagram:
|
||||||
|
if (
|
||||||
|
process_instance.bpmn_version_control_identifier
|
||||||
|
== current_version_control_revision
|
||||||
|
):
|
||||||
|
bpmn_xml_file_contents = SpecFileService.get_data(
|
||||||
|
process_model_with_diagram, name_of_file_with_diagram
|
||||||
|
).decode("utf-8")
|
||||||
|
else:
|
||||||
|
bpmn_xml_file_contents = GitService.get_instance_file_contents_for_revision(
|
||||||
|
process_model_with_diagram,
|
||||||
|
process_instance.bpmn_version_control_identifier,
|
||||||
|
file_name=name_of_file_with_diagram,
|
||||||
|
)
|
||||||
|
process_instance.bpmn_xml_file_contents = bpmn_xml_file_contents
|
||||||
|
|
||||||
|
return make_response(jsonify(process_instance), 200)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_file_from_request() -> Any:
|
||||||
|
"""Get_file_from_request."""
|
||||||
|
request_file = connexion.request.files.get("file")
|
||||||
|
if not request_file:
|
||||||
|
raise ApiError(
|
||||||
|
error_code="no_file_given",
|
||||||
|
message="Given request does not contain a file",
|
||||||
|
status_code=400,
|
||||||
|
)
|
||||||
|
return request_file
|
||||||
|
|
||||||
|
|
||||||
|
# process_model_id uses forward slashes on all OSes
|
||||||
|
# this seems to return an object where process_model.id has backslashes on windows
|
||||||
|
def _get_process_model(process_model_id: str) -> ProcessModelInfo:
|
||||||
|
"""Get_process_model."""
|
||||||
|
process_model = None
|
||||||
|
try:
|
||||||
|
process_model = ProcessModelService.get_process_model(process_model_id)
|
||||||
|
except ProcessEntityNotFoundError as exception:
|
||||||
|
raise (
|
||||||
|
ApiError(
|
||||||
|
error_code="process_model_cannot_be_found",
|
||||||
|
message=f"Process model cannot be found: {process_model_id}",
|
||||||
|
status_code=400,
|
||||||
|
)
|
||||||
|
) from exception
|
||||||
|
|
||||||
|
return process_model
|
||||||
|
|
||||||
|
|
||||||
|
def _get_spiff_task_from_process_instance(
|
||||||
|
task_id: str,
|
||||||
|
process_instance: ProcessInstanceModel,
|
||||||
|
processor: Union[ProcessInstanceProcessor, None] = None,
|
||||||
|
) -> SpiffTask:
|
||||||
|
"""Get_spiff_task_from_process_instance."""
|
||||||
|
if processor is None:
|
||||||
|
processor = ProcessInstanceProcessor(process_instance)
|
||||||
|
task_uuid = uuid.UUID(task_id)
|
||||||
|
spiff_task = processor.bpmn_process_instance.get_task(task_uuid)
|
||||||
|
|
||||||
|
if spiff_task is None:
|
||||||
|
raise (
|
||||||
|
ApiError(
|
||||||
|
error_code="empty_task",
|
||||||
|
message="Processor failed to obtain task.",
|
||||||
|
status_code=500,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return spiff_task
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
"""APIs for dealing with process groups, process models, and process instances."""
|
||||||
|
import json
|
||||||
|
from typing import Dict
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from flask import g
|
||||||
|
from flask import jsonify
|
||||||
|
from flask import make_response
|
||||||
|
from flask.wrappers import Response
|
||||||
|
|
||||||
|
from spiffworkflow_backend.models.secret_model import SecretModel
|
||||||
|
from spiffworkflow_backend.models.secret_model import SecretModelSchema
|
||||||
|
from spiffworkflow_backend.models.user import UserModel
|
||||||
|
from spiffworkflow_backend.services.secret_service import SecretService
|
||||||
|
from spiffworkflow_backend.services.user_service import UserService
|
||||||
|
|
||||||
|
|
||||||
|
def secret_show(key: str) -> Optional[str]:
|
||||||
|
return SecretService.get_secret(key)
|
||||||
|
|
||||||
|
|
||||||
|
def secret_list(
|
||||||
|
page: int = 1,
|
||||||
|
per_page: int = 100,
|
||||||
|
) -> Response:
|
||||||
|
"""Secret_list."""
|
||||||
|
secrets = (
|
||||||
|
SecretModel.query.order_by(SecretModel.key)
|
||||||
|
.join(UserModel)
|
||||||
|
.add_columns(
|
||||||
|
UserModel.username,
|
||||||
|
)
|
||||||
|
.paginate(page=page, per_page=per_page, error_out=False)
|
||||||
|
)
|
||||||
|
response_json = {
|
||||||
|
"results": secrets.items,
|
||||||
|
"pagination": {
|
||||||
|
"count": len(secrets.items),
|
||||||
|
"total": secrets.total,
|
||||||
|
"pages": secrets.pages,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return make_response(jsonify(response_json), 200)
|
||||||
|
|
||||||
|
|
||||||
|
def secret_create(body: Dict) -> Response:
|
||||||
|
"""Add secret."""
|
||||||
|
secret_model = SecretService().add_secret(body["key"], body["value"], g.user.id)
|
||||||
|
return Response(
|
||||||
|
json.dumps(SecretModelSchema().dump(secret_model)),
|
||||||
|
status=201,
|
||||||
|
mimetype="application/json",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def secret_update(key: str, body: dict) -> Response:
|
||||||
|
"""Update secret."""
|
||||||
|
SecretService().update_secret(key, body["value"], g.user.id)
|
||||||
|
return Response(json.dumps({"ok": True}), status=200, mimetype="application/json")
|
||||||
|
|
||||||
|
|
||||||
|
def secret_delete(key: str) -> Response:
|
||||||
|
"""Delete secret."""
|
||||||
|
current_user = UserService.current_user()
|
||||||
|
SecretService.delete_secret(key, current_user.id)
|
||||||
|
return Response(json.dumps({"ok": True}), status=200, mimetype="application/json")
|
|
@ -0,0 +1,396 @@
|
||||||
|
"""APIs for dealing with process groups, process models, and process instances."""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from typing import Any
|
||||||
|
from typing import Dict
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import flask.wrappers
|
||||||
|
from flask import g
|
||||||
|
from flask import jsonify
|
||||||
|
from flask import make_response
|
||||||
|
from flask.wrappers import Response
|
||||||
|
from flask_bpmn.api.api_error import ApiError
|
||||||
|
from SpiffWorkflow.task import TaskState
|
||||||
|
from sqlalchemy import and_
|
||||||
|
from sqlalchemy import asc
|
||||||
|
from sqlalchemy import desc
|
||||||
|
|
||||||
|
from spiffworkflow_backend.models.group import GroupModel
|
||||||
|
from spiffworkflow_backend.models.human_task import HumanTaskModel
|
||||||
|
from spiffworkflow_backend.models.human_task_user import HumanTaskUserModel
|
||||||
|
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
||||||
|
from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus
|
||||||
|
from spiffworkflow_backend.models.user import UserModel
|
||||||
|
from spiffworkflow_backend.services.authorization_service import AuthorizationService
|
||||||
|
from spiffworkflow_backend.services.file_system_service import FileSystemService
|
||||||
|
from spiffworkflow_backend.services.process_instance_processor import (
|
||||||
|
ProcessInstanceProcessor,
|
||||||
|
)
|
||||||
|
from spiffworkflow_backend.services.process_instance_service import (
|
||||||
|
ProcessInstanceService,
|
||||||
|
)
|
||||||
|
from spiffworkflow_backend.services.process_model_service import ProcessModelService
|
||||||
|
from spiffworkflow_backend.services.spec_file_service import SpecFileService
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: see comment for before_request
|
||||||
|
# @process_api_blueprint.route("/v1.0/tasks", methods=["GET"])
|
||||||
|
def task_list_my_tasks(page: int = 1, per_page: int = 100) -> flask.wrappers.Response:
|
||||||
|
"""Task_list_my_tasks."""
|
||||||
|
principal = _find_principal_or_raise()
|
||||||
|
human_tasks = (
|
||||||
|
HumanTaskModel.query.order_by(desc(HumanTaskModel.id)) # type: ignore
|
||||||
|
.join(ProcessInstanceModel)
|
||||||
|
.join(HumanTaskUserModel)
|
||||||
|
.filter_by(user_id=principal.user_id)
|
||||||
|
.filter(HumanTaskModel.completed == False) # noqa: E712
|
||||||
|
# just need this add_columns to add the process_model_identifier. Then add everything back that was removed.
|
||||||
|
.add_columns(
|
||||||
|
ProcessInstanceModel.process_model_identifier,
|
||||||
|
ProcessInstanceModel.process_model_display_name,
|
||||||
|
ProcessInstanceModel.status,
|
||||||
|
HumanTaskModel.task_name,
|
||||||
|
HumanTaskModel.task_title,
|
||||||
|
HumanTaskModel.task_type,
|
||||||
|
HumanTaskModel.task_status,
|
||||||
|
HumanTaskModel.task_id,
|
||||||
|
HumanTaskModel.id,
|
||||||
|
HumanTaskModel.process_model_display_name,
|
||||||
|
HumanTaskModel.process_instance_id,
|
||||||
|
)
|
||||||
|
.paginate(page=page, per_page=per_page, error_out=False)
|
||||||
|
)
|
||||||
|
tasks = [HumanTaskModel.to_task(human_task) for human_task in human_tasks.items]
|
||||||
|
|
||||||
|
response_json = {
|
||||||
|
"results": tasks,
|
||||||
|
"pagination": {
|
||||||
|
"count": len(human_tasks.items),
|
||||||
|
"total": human_tasks.total,
|
||||||
|
"pages": human_tasks.pages,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return make_response(jsonify(response_json), 200)
|
||||||
|
|
||||||
|
|
||||||
|
def task_list_for_my_open_processes(
|
||||||
|
page: int = 1, per_page: int = 100
|
||||||
|
) -> flask.wrappers.Response:
|
||||||
|
"""Task_list_for_my_open_processes."""
|
||||||
|
return _get_tasks(page=page, per_page=per_page)
|
||||||
|
|
||||||
|
|
||||||
|
def task_list_for_me(page: int = 1, per_page: int = 100) -> flask.wrappers.Response:
|
||||||
|
"""Task_list_for_me."""
|
||||||
|
return _get_tasks(
|
||||||
|
processes_started_by_user=False,
|
||||||
|
has_lane_assignment_id=False,
|
||||||
|
page=page,
|
||||||
|
per_page=per_page,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def task_list_for_my_groups(
|
||||||
|
user_group_identifier: Optional[str] = None, page: int = 1, per_page: int = 100
|
||||||
|
) -> flask.wrappers.Response:
|
||||||
|
"""Task_list_for_my_groups."""
|
||||||
|
return _get_tasks(
|
||||||
|
user_group_identifier=user_group_identifier,
|
||||||
|
processes_started_by_user=False,
|
||||||
|
page=page,
|
||||||
|
per_page=per_page,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def task_show(process_instance_id: int, task_id: str) -> flask.wrappers.Response:
|
||||||
|
"""Task_show."""
|
||||||
|
process_instance = _find_process_instance_by_id_or_raise(process_instance_id)
|
||||||
|
|
||||||
|
if process_instance.status == ProcessInstanceStatus.suspended.value:
|
||||||
|
raise ApiError(
|
||||||
|
error_code="error_suspended",
|
||||||
|
message="The process instance is suspended",
|
||||||
|
status_code=400,
|
||||||
|
)
|
||||||
|
|
||||||
|
process_model = _get_process_model(
|
||||||
|
process_instance.process_model_identifier,
|
||||||
|
)
|
||||||
|
|
||||||
|
form_schema_file_name = ""
|
||||||
|
form_ui_schema_file_name = ""
|
||||||
|
spiff_task = _get_spiff_task_from_process_instance(task_id, process_instance)
|
||||||
|
extensions = spiff_task.task_spec.extensions
|
||||||
|
|
||||||
|
if "properties" in extensions:
|
||||||
|
properties = extensions["properties"]
|
||||||
|
if "formJsonSchemaFilename" in properties:
|
||||||
|
form_schema_file_name = properties["formJsonSchemaFilename"]
|
||||||
|
if "formUiSchemaFilename" in properties:
|
||||||
|
form_ui_schema_file_name = properties["formUiSchemaFilename"]
|
||||||
|
task = ProcessInstanceService.spiff_task_to_api_task(spiff_task)
|
||||||
|
task.data = spiff_task.data
|
||||||
|
task.process_model_display_name = process_model.display_name
|
||||||
|
task.process_model_identifier = process_model.id
|
||||||
|
|
||||||
|
process_model_with_form = process_model
|
||||||
|
refs = SpecFileService.get_references_for_process(process_model_with_form)
|
||||||
|
all_processes = [i.identifier for i in refs]
|
||||||
|
if task.process_identifier not in all_processes:
|
||||||
|
bpmn_file_full_path = (
|
||||||
|
ProcessInstanceProcessor.bpmn_file_full_path_from_bpmn_process_identifier(
|
||||||
|
task.process_identifier
|
||||||
|
)
|
||||||
|
)
|
||||||
|
relative_path = os.path.relpath(
|
||||||
|
bpmn_file_full_path, start=FileSystemService.root_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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if task.type == "User Task":
|
||||||
|
if not form_schema_file_name:
|
||||||
|
raise (
|
||||||
|
ApiError(
|
||||||
|
error_code="missing_form_file",
|
||||||
|
message=f"Cannot find a form file for process_instance_id: {process_instance_id}, task_id: {task_id}",
|
||||||
|
status_code=400,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
form_contents = prepare_form_data(
|
||||||
|
form_schema_file_name,
|
||||||
|
task.data,
|
||||||
|
process_model_with_form,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# form_contents is a str
|
||||||
|
form_dict = json.loads(form_contents)
|
||||||
|
except Exception as exception:
|
||||||
|
raise (
|
||||||
|
ApiError(
|
||||||
|
error_code="error_loading_form",
|
||||||
|
message=f"Could not load form schema from: {form_schema_file_name}. Error was: {str(exception)}",
|
||||||
|
status_code=400,
|
||||||
|
)
|
||||||
|
) from exception
|
||||||
|
|
||||||
|
if task.data:
|
||||||
|
_update_form_schema_with_task_data_as_needed(form_dict, task.data)
|
||||||
|
|
||||||
|
if form_contents:
|
||||||
|
task.form_schema = form_dict
|
||||||
|
|
||||||
|
if form_ui_schema_file_name:
|
||||||
|
ui_form_contents = prepare_form_data(
|
||||||
|
form_ui_schema_file_name,
|
||||||
|
task.data,
|
||||||
|
process_model_with_form,
|
||||||
|
)
|
||||||
|
if ui_form_contents:
|
||||||
|
task.form_ui_schema = ui_form_contents
|
||||||
|
|
||||||
|
if task.properties and task.data and "instructionsForEndUser" in task.properties:
|
||||||
|
if task.properties["instructionsForEndUser"]:
|
||||||
|
task.properties["instructionsForEndUser"] = render_jinja_template(
|
||||||
|
task.properties["instructionsForEndUser"], task.data
|
||||||
|
)
|
||||||
|
return make_response(jsonify(task), 200)
|
||||||
|
|
||||||
|
|
||||||
|
def process_data_show(
|
||||||
|
process_instance_id: int,
|
||||||
|
process_data_identifier: str,
|
||||||
|
modified_process_model_identifier: str,
|
||||||
|
) -> flask.wrappers.Response:
|
||||||
|
"""Process_data_show."""
|
||||||
|
process_instance = _find_process_instance_by_id_or_raise(process_instance_id)
|
||||||
|
processor = ProcessInstanceProcessor(process_instance)
|
||||||
|
all_process_data = processor.get_data()
|
||||||
|
process_data_value = None
|
||||||
|
if process_data_identifier in all_process_data:
|
||||||
|
process_data_value = all_process_data[process_data_identifier]
|
||||||
|
|
||||||
|
return make_response(
|
||||||
|
jsonify(
|
||||||
|
{
|
||||||
|
"process_data_identifier": process_data_identifier,
|
||||||
|
"process_data_value": process_data_value,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
200,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def task_submit(
|
||||||
|
process_instance_id: int,
|
||||||
|
task_id: str,
|
||||||
|
body: Dict[str, Any],
|
||||||
|
terminate_loop: bool = False,
|
||||||
|
) -> flask.wrappers.Response:
|
||||||
|
"""Task_submit_user_data."""
|
||||||
|
principal = _find_principal_or_raise()
|
||||||
|
process_instance = _find_process_instance_by_id_or_raise(process_instance_id)
|
||||||
|
if not process_instance.can_submit_task():
|
||||||
|
raise ApiError(
|
||||||
|
error_code="process_instance_not_runnable",
|
||||||
|
message=f"Process Instance ({process_instance.id}) has status "
|
||||||
|
f"{process_instance.status} which does not allow tasks to be submitted.",
|
||||||
|
status_code=400,
|
||||||
|
)
|
||||||
|
|
||||||
|
processor = ProcessInstanceProcessor(process_instance)
|
||||||
|
spiff_task = _get_spiff_task_from_process_instance(
|
||||||
|
task_id, process_instance, processor=processor
|
||||||
|
)
|
||||||
|
AuthorizationService.assert_user_can_complete_spiff_task(
|
||||||
|
process_instance.id, spiff_task, principal.user
|
||||||
|
)
|
||||||
|
|
||||||
|
if spiff_task.state != TaskState.READY:
|
||||||
|
raise (
|
||||||
|
ApiError(
|
||||||
|
error_code="invalid_state",
|
||||||
|
message="You may not update a task unless it is in the READY state.",
|
||||||
|
status_code=400,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if terminate_loop and spiff_task.is_looping():
|
||||||
|
spiff_task.terminate_loop()
|
||||||
|
|
||||||
|
human_task = HumanTaskModel.query.filter_by(
|
||||||
|
process_instance_id=process_instance_id, task_id=task_id, completed=False
|
||||||
|
).first()
|
||||||
|
if human_task is None:
|
||||||
|
raise (
|
||||||
|
ApiError(
|
||||||
|
error_code="no_human_task",
|
||||||
|
message="Cannot find an human task with task id '{task_id}' for process instance {process_instance_id}.",
|
||||||
|
status_code=500,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
ProcessInstanceService.complete_form_task(
|
||||||
|
processor=processor,
|
||||||
|
spiff_task=spiff_task,
|
||||||
|
data=body,
|
||||||
|
user=g.user,
|
||||||
|
human_task=human_task,
|
||||||
|
)
|
||||||
|
|
||||||
|
# If we need to update all tasks, then get the next ready task and if it a multi-instance with the same
|
||||||
|
# task spec, complete that form as well.
|
||||||
|
# if update_all:
|
||||||
|
# last_index = spiff_task.task_info()["mi_index"]
|
||||||
|
# next_task = processor.next_task()
|
||||||
|
# while next_task and next_task.task_info()["mi_index"] > last_index:
|
||||||
|
# __update_task(processor, next_task, form_data, user)
|
||||||
|
# last_index = next_task.task_info()["mi_index"]
|
||||||
|
# next_task = processor.next_task()
|
||||||
|
|
||||||
|
next_human_task_assigned_to_me = (
|
||||||
|
HumanTaskModel.query.filter_by(
|
||||||
|
process_instance_id=process_instance_id, completed=False
|
||||||
|
)
|
||||||
|
.order_by(asc(HumanTaskModel.id)) # type: ignore
|
||||||
|
.join(HumanTaskUserModel)
|
||||||
|
.filter_by(user_id=principal.user_id)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
if next_human_task_assigned_to_me:
|
||||||
|
return make_response(
|
||||||
|
jsonify(HumanTaskModel.to_task(next_human_task_assigned_to_me)), 200
|
||||||
|
)
|
||||||
|
|
||||||
|
return Response(json.dumps({"ok": True}), status=202, mimetype="application/json")
|
||||||
|
|
||||||
|
|
||||||
|
def _get_tasks(
|
||||||
|
processes_started_by_user: bool = True,
|
||||||
|
has_lane_assignment_id: bool = True,
|
||||||
|
page: int = 1,
|
||||||
|
per_page: int = 100,
|
||||||
|
user_group_identifier: Optional[str] = None,
|
||||||
|
) -> flask.wrappers.Response:
|
||||||
|
"""Get_tasks."""
|
||||||
|
user_id = g.user.id
|
||||||
|
|
||||||
|
# use distinct to ensure we only get one row per human task otherwise
|
||||||
|
# we can get back multiple for the same human task row which throws off
|
||||||
|
# pagination later on
|
||||||
|
# https://stackoverflow.com/q/34582014/6090676
|
||||||
|
human_tasks_query = (
|
||||||
|
HumanTaskModel.query.distinct()
|
||||||
|
.outerjoin(GroupModel, GroupModel.id == HumanTaskModel.lane_assignment_id)
|
||||||
|
.join(ProcessInstanceModel)
|
||||||
|
.join(UserModel, UserModel.id == ProcessInstanceModel.process_initiator_id)
|
||||||
|
.filter(HumanTaskModel.completed == False) # noqa: E712
|
||||||
|
)
|
||||||
|
|
||||||
|
if processes_started_by_user:
|
||||||
|
human_tasks_query = human_tasks_query.filter(
|
||||||
|
ProcessInstanceModel.process_initiator_id == user_id
|
||||||
|
).outerjoin(
|
||||||
|
HumanTaskUserModel,
|
||||||
|
and_(
|
||||||
|
HumanTaskUserModel.user_id == user_id,
|
||||||
|
HumanTaskModel.id == HumanTaskUserModel.human_task_id,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
human_tasks_query = human_tasks_query.filter(
|
||||||
|
ProcessInstanceModel.process_initiator_id != user_id
|
||||||
|
).join(
|
||||||
|
HumanTaskUserModel,
|
||||||
|
and_(
|
||||||
|
HumanTaskUserModel.user_id == user_id,
|
||||||
|
HumanTaskModel.id == HumanTaskUserModel.human_task_id,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if has_lane_assignment_id:
|
||||||
|
if user_group_identifier:
|
||||||
|
human_tasks_query = human_tasks_query.filter(
|
||||||
|
GroupModel.identifier == user_group_identifier
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
human_tasks_query = human_tasks_query.filter(
|
||||||
|
HumanTaskModel.lane_assignment_id.is_not(None) # type: ignore
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
human_tasks_query = human_tasks_query.filter(HumanTaskModel.lane_assignment_id.is_(None)) # type: ignore
|
||||||
|
|
||||||
|
human_tasks = (
|
||||||
|
human_tasks_query.add_columns(
|
||||||
|
ProcessInstanceModel.process_model_identifier,
|
||||||
|
ProcessInstanceModel.status.label("process_instance_status"), # type: ignore
|
||||||
|
ProcessInstanceModel.updated_at_in_seconds,
|
||||||
|
ProcessInstanceModel.created_at_in_seconds,
|
||||||
|
UserModel.username,
|
||||||
|
GroupModel.identifier.label("user_group_identifier"),
|
||||||
|
HumanTaskModel.task_name,
|
||||||
|
HumanTaskModel.task_title,
|
||||||
|
HumanTaskModel.process_model_display_name,
|
||||||
|
HumanTaskModel.process_instance_id,
|
||||||
|
HumanTaskUserModel.user_id.label("current_user_is_potential_owner"),
|
||||||
|
)
|
||||||
|
.order_by(desc(HumanTaskModel.id)) # type: ignore
|
||||||
|
.paginate(page=page, per_page=per_page, error_out=False)
|
||||||
|
)
|
||||||
|
|
||||||
|
response_json = {
|
||||||
|
"results": human_tasks.items,
|
||||||
|
"pagination": {
|
||||||
|
"count": len(human_tasks.items),
|
||||||
|
"total": human_tasks.total,
|
||||||
|
"pages": human_tasks.pages,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return make_response(jsonify(response_json), 200)
|
Loading…
Reference in New Issue