mirror of
https://github.com/sartography/spiff-arena.git
synced 2025-01-11 18:14:20 +00:00
Merge remote-tracking branch 'origin/main' into feature/multiple_editor_users
This commit is contained in:
commit
8655ca0cd0
10
spiffworkflow-backend/.snyk
Normal file
10
spiffworkflow-backend/.snyk
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
|
||||||
|
version: v1.25.0
|
||||||
|
# ignores vulnerabilities until expiry date; change duration by modifying expiry date
|
||||||
|
ignore:
|
||||||
|
SNYK-PYTHON-FLASK-5490129:
|
||||||
|
- '*':
|
||||||
|
reason: Filed ticket to upgrade flask
|
||||||
|
expires: 2024-06-02T14:48:14.372Z
|
||||||
|
created: 2023-05-03T14:48:14.379Z
|
||||||
|
patch: {}
|
41
spiffworkflow-backend/bin/run_message_start_event_with_api
Executable file
41
spiffworkflow-backend/bin/run_message_start_event_with_api
Executable file
@ -0,0 +1,41 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
function error_handler() {
|
||||||
|
>&2 echo "Exited with BAD EXIT CODE '${2}' in ${0} script at line: ${1}."
|
||||||
|
exit "$2"
|
||||||
|
}
|
||||||
|
trap 'error_handler ${LINENO} $?' ERR
|
||||||
|
set -o errtrace -o errexit -o nounset -o pipefail
|
||||||
|
|
||||||
|
script_dir="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
|
||||||
|
|
||||||
|
if [[ -z "${KEYCLOAK_BASE_URL:-}" ]]; then
|
||||||
|
export KEYCLOAK_BASE_URL=http://localhost:7002
|
||||||
|
fi
|
||||||
|
if [[ -z "${BACKEND_BASE_URL:-}" ]]; then
|
||||||
|
export BACKEND_BASE_URL=http://localhost:7000
|
||||||
|
fi
|
||||||
|
|
||||||
|
message_identifier="${1:-start_test}"
|
||||||
|
username="${2:-admin}"
|
||||||
|
password="${3:-admin}"
|
||||||
|
realm_name="${4:-spiffworkflow}"
|
||||||
|
if [[ -z "${message_identifier}" ]]; then
|
||||||
|
>&2 echo "usage: $(basename "$0") [message_identifier] [username: OPTONAL] [password: OPTONAL] [realm_name: OPTONAL]"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
function check_result_for_error() {
|
||||||
|
local result="$1"
|
||||||
|
error_code=$(jq '.error_code' <<<"$result")
|
||||||
|
if [[ -n "$error_code" && "$error_code" != "null" ]]; then
|
||||||
|
>&2 echo "ERROR: Failed to run process instance. Received error: $result"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
access_token=$("${script_dir}/get_token" "$username" "$password" "$realm_name")
|
||||||
|
curl --silent -X POST "${BACKEND_BASE_URL}/v1.0/login_with_access_token?access_token=${access_token}" -H "Authorization: Bearer $access_token" >/dev/null
|
||||||
|
result=$(curl --silent -X POST "${BACKEND_BASE_URL}/v1.0/messages/${message_identifier}" -H "Authorization: Bearer $access_token" -d '{"payload": {"email": "HEY@example.com"}}' -H 'Content-type: application/json')
|
||||||
|
check_result_for_error "$result"
|
||||||
|
echo "$result"
|
@ -2,6 +2,7 @@
|
|||||||
import enum
|
import enum
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
from typing import List
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from typing import Union
|
from typing import Union
|
||||||
@ -85,6 +86,7 @@ class TaskModel(SpiffworkflowBaseDBModel):
|
|||||||
can_complete: Optional[bool] = None
|
can_complete: Optional[bool] = None
|
||||||
extensions: Optional[dict] = None
|
extensions: Optional[dict] = None
|
||||||
name_for_display: Optional[str] = None
|
name_for_display: Optional[str] = None
|
||||||
|
signal_buttons: Optional[List[dict]] = None
|
||||||
|
|
||||||
def get_data(self) -> dict:
|
def get_data(self) -> dict:
|
||||||
return {**self.python_env_data(), **self.json_data()}
|
return {**self.python_env_data(), **self.json_data()}
|
||||||
|
@ -69,7 +69,6 @@ def message_send(
|
|||||||
message_name: str,
|
message_name: str,
|
||||||
body: Dict[str, Any],
|
body: Dict[str, Any],
|
||||||
) -> flask.wrappers.Response:
|
) -> flask.wrappers.Response:
|
||||||
"""Message_start."""
|
|
||||||
if "payload" not in body:
|
if "payload" not in body:
|
||||||
raise (
|
raise (
|
||||||
ApiError(
|
ApiError(
|
||||||
|
@ -18,7 +18,6 @@ from spiffworkflow_backend.exceptions.process_entity_not_found_error import (
|
|||||||
)
|
)
|
||||||
from spiffworkflow_backend.models.principal import PrincipalModel
|
from spiffworkflow_backend.models.principal import PrincipalModel
|
||||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
|
||||||
from spiffworkflow_backend.models.process_instance import ProcessInstanceModelSchema
|
|
||||||
from spiffworkflow_backend.models.process_instance_file_data import (
|
from spiffworkflow_backend.models.process_instance_file_data import (
|
||||||
ProcessInstanceFileDataModel,
|
ProcessInstanceFileDataModel,
|
||||||
)
|
)
|
||||||
@ -32,6 +31,7 @@ from spiffworkflow_backend.services.process_caller_service import ProcessCallerS
|
|||||||
from spiffworkflow_backend.services.process_instance_processor import (
|
from spiffworkflow_backend.services.process_instance_processor import (
|
||||||
ProcessInstanceProcessor,
|
ProcessInstanceProcessor,
|
||||||
)
|
)
|
||||||
|
from spiffworkflow_backend.services.process_instance_service import ProcessInstanceService
|
||||||
from spiffworkflow_backend.services.process_model_service import ProcessModelService
|
from spiffworkflow_backend.services.process_model_service import ProcessModelService
|
||||||
|
|
||||||
|
|
||||||
@ -199,16 +199,13 @@ def send_bpmn_event(
|
|||||||
if process_instance:
|
if process_instance:
|
||||||
processor = ProcessInstanceProcessor(process_instance)
|
processor = ProcessInstanceProcessor(process_instance)
|
||||||
processor.send_bpmn_event(body)
|
processor.send_bpmn_event(body)
|
||||||
|
task = ProcessInstanceService.spiff_task_to_api_task(processor, processor.next_task())
|
||||||
|
return make_response(jsonify(task), 200)
|
||||||
else:
|
else:
|
||||||
raise ApiError(
|
raise ApiError(
|
||||||
error_code="send_bpmn_event_error",
|
error_code="send_bpmn_event_error",
|
||||||
message=f"Could not send event to Instance: {process_instance_id}",
|
message=f"Could not send event to Instance: {process_instance_id}",
|
||||||
)
|
)
|
||||||
return Response(
|
|
||||||
json.dumps(ProcessInstanceModelSchema().dump(process_instance)),
|
|
||||||
status=200,
|
|
||||||
mimetype="application/json",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _commit_and_push_to_git(message: str) -> None:
|
def _commit_and_push_to_git(message: str) -> None:
|
||||||
|
@ -289,6 +289,7 @@ def task_show(process_instance_id: int, task_guid: str = "next") -> flask.wrappe
|
|||||||
task_model = _get_task_model_from_guid_or_raise(task_guid, process_instance_id)
|
task_model = _get_task_model_from_guid_or_raise(task_guid, process_instance_id)
|
||||||
task_definition = task_model.task_definition
|
task_definition = task_model.task_definition
|
||||||
extensions = TaskService.get_extensions_from_task_model(task_model)
|
extensions = TaskService.get_extensions_from_task_model(task_model)
|
||||||
|
task_model.signal_buttons = TaskService.get_ready_signals_with_button_labels(process_instance_id)
|
||||||
|
|
||||||
if "properties" in extensions:
|
if "properties" in extensions:
|
||||||
properties = extensions["properties"]
|
properties = extensions["properties"]
|
||||||
|
@ -7,7 +7,7 @@ from spiffworkflow_backend.models.script_attributes_context import (
|
|||||||
from spiffworkflow_backend.scripts.script import Script
|
from spiffworkflow_backend.scripts.script import Script
|
||||||
|
|
||||||
|
|
||||||
class GetProcessInfo(Script):
|
class GetToplevelProcessInfo(Script):
|
||||||
"""GetProcessInfo."""
|
"""GetProcessInfo."""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
@ -34,7 +34,11 @@ from spiffworkflow_backend.services.process_model_service import ProcessModelSer
|
|||||||
|
|
||||||
|
|
||||||
class ProcessInstanceReportNotFoundError(Exception):
|
class ProcessInstanceReportNotFoundError(Exception):
|
||||||
"""ProcessInstanceReportNotFoundError."""
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ProcessInstanceReportMetadataInvalidError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ProcessInstanceReportService:
|
class ProcessInstanceReportService:
|
||||||
@ -53,15 +57,15 @@ class ProcessInstanceReportService:
|
|||||||
}
|
}
|
||||||
system_report_completed_instances_initiated_by_me: ReportMetadata = {
|
system_report_completed_instances_initiated_by_me: ReportMetadata = {
|
||||||
"columns": [
|
"columns": [
|
||||||
{"Header": "id", "accessor": "id", "filterable": False},
|
{"Header": "Id", "accessor": "id", "filterable": False},
|
||||||
{
|
{
|
||||||
"Header": "process_model_display_name",
|
"Header": "Process",
|
||||||
"accessor": "process_model_display_name",
|
"accessor": "process_model_display_name",
|
||||||
"filterable": False,
|
"filterable": False,
|
||||||
},
|
},
|
||||||
{"Header": "start_in_seconds", "accessor": "start_in_seconds", "filterable": False},
|
{"Header": "Start Time", "accessor": "start_in_seconds", "filterable": False},
|
||||||
{"Header": "end_in_seconds", "accessor": "end_in_seconds", "filterable": False},
|
{"Header": "End Time", "accessor": "end_in_seconds", "filterable": False},
|
||||||
{"Header": "status", "accessor": "status", "filterable": False},
|
{"Header": "Status", "accessor": "status", "filterable": False},
|
||||||
],
|
],
|
||||||
"filter_by": [
|
"filter_by": [
|
||||||
{"field_name": "initiated_by_me", "field_value": True, "operator": "equals"},
|
{"field_name": "initiated_by_me", "field_value": True, "operator": "equals"},
|
||||||
@ -72,7 +76,7 @@ class ProcessInstanceReportService:
|
|||||||
system_report_completed_instances_with_tasks_completed_by_me: ReportMetadata = {
|
system_report_completed_instances_with_tasks_completed_by_me: ReportMetadata = {
|
||||||
"columns": cls.builtin_column_options(),
|
"columns": cls.builtin_column_options(),
|
||||||
"filter_by": [
|
"filter_by": [
|
||||||
{"field_name": "with_tasks_completed_by_me", "field_value": True, "operator": "equals"},
|
{"field_name": "instances_with_tasks_completed_by_me", "field_value": True, "operator": "equals"},
|
||||||
{"field_name": "process_status", "field_value": terminal_status_values, "operator": "equals"},
|
{"field_name": "process_status", "field_value": terminal_status_values, "operator": "equals"},
|
||||||
],
|
],
|
||||||
"order_by": ["-start_in_seconds", "-id"],
|
"order_by": ["-start_in_seconds", "-id"],
|
||||||
@ -86,9 +90,9 @@ class ProcessInstanceReportService:
|
|||||||
}
|
}
|
||||||
system_report_in_progress_instances_initiated_by_me: ReportMetadata = {
|
system_report_in_progress_instances_initiated_by_me: ReportMetadata = {
|
||||||
"columns": [
|
"columns": [
|
||||||
{"Header": "id", "accessor": "id", "filterable": False},
|
{"Header": "Id", "accessor": "id", "filterable": False},
|
||||||
{
|
{
|
||||||
"Header": "process_model_display_name",
|
"Header": "Process",
|
||||||
"accessor": "process_model_display_name",
|
"accessor": "process_model_display_name",
|
||||||
"filterable": False,
|
"filterable": False,
|
||||||
},
|
},
|
||||||
@ -96,7 +100,7 @@ class ProcessInstanceReportService:
|
|||||||
{"Header": "Waiting For", "accessor": "waiting_for", "filterable": False},
|
{"Header": "Waiting For", "accessor": "waiting_for", "filterable": False},
|
||||||
{"Header": "Started", "accessor": "start_in_seconds", "filterable": False},
|
{"Header": "Started", "accessor": "start_in_seconds", "filterable": False},
|
||||||
{"Header": "Last Updated", "accessor": "task_updated_at_in_seconds", "filterable": False},
|
{"Header": "Last Updated", "accessor": "task_updated_at_in_seconds", "filterable": False},
|
||||||
{"Header": "status", "accessor": "status", "filterable": False},
|
{"Header": "Status", "accessor": "status", "filterable": False},
|
||||||
],
|
],
|
||||||
"filter_by": [
|
"filter_by": [
|
||||||
{"field_name": "initiated_by_me", "field_value": True, "operator": "equals"},
|
{"field_name": "initiated_by_me", "field_value": True, "operator": "equals"},
|
||||||
@ -111,9 +115,9 @@ class ProcessInstanceReportService:
|
|||||||
}
|
}
|
||||||
system_report_in_progress_instances_with_tasks_for_me: ReportMetadata = {
|
system_report_in_progress_instances_with_tasks_for_me: ReportMetadata = {
|
||||||
"columns": [
|
"columns": [
|
||||||
{"Header": "id", "accessor": "id", "filterable": False},
|
{"Header": "Id", "accessor": "id", "filterable": False},
|
||||||
{
|
{
|
||||||
"Header": "process_model_display_name",
|
"Header": "Process",
|
||||||
"accessor": "process_model_display_name",
|
"accessor": "process_model_display_name",
|
||||||
"filterable": False,
|
"filterable": False,
|
||||||
},
|
},
|
||||||
@ -123,7 +127,7 @@ class ProcessInstanceReportService:
|
|||||||
{"Header": "Last Updated", "accessor": "task_updated_at_in_seconds", "filterable": False},
|
{"Header": "Last Updated", "accessor": "task_updated_at_in_seconds", "filterable": False},
|
||||||
],
|
],
|
||||||
"filter_by": [
|
"filter_by": [
|
||||||
{"field_name": "with_tasks_i_can_complete", "field_value": True, "operator": "equals"},
|
{"field_name": "instances_with_tasks_waiting_for_me", "field_value": True, "operator": "equals"},
|
||||||
{"field_name": "process_status", "field_value": active_status_values, "operator": "equals"},
|
{"field_name": "process_status", "field_value": active_status_values, "operator": "equals"},
|
||||||
{
|
{
|
||||||
"field_name": "with_oldest_open_task",
|
"field_name": "with_oldest_open_task",
|
||||||
@ -135,9 +139,9 @@ class ProcessInstanceReportService:
|
|||||||
}
|
}
|
||||||
system_report_in_progress_instances_with_tasks: ReportMetadata = {
|
system_report_in_progress_instances_with_tasks: ReportMetadata = {
|
||||||
"columns": [
|
"columns": [
|
||||||
{"Header": "id", "accessor": "id", "filterable": False},
|
{"Header": "Id", "accessor": "id", "filterable": False},
|
||||||
{
|
{
|
||||||
"Header": "process_model_display_name",
|
"Header": "Process",
|
||||||
"accessor": "process_model_display_name",
|
"accessor": "process_model_display_name",
|
||||||
"filterable": False,
|
"filterable": False,
|
||||||
},
|
},
|
||||||
@ -414,17 +418,17 @@ class ProcessInstanceReportService:
|
|||||||
process_initiator_id = initiator.id
|
process_initiator_id = initiator.id
|
||||||
process_instance_query = process_instance_query.filter_by(process_initiator_id=process_initiator_id)
|
process_instance_query = process_instance_query.filter_by(process_initiator_id=process_initiator_id)
|
||||||
|
|
||||||
with_tasks_completed_by_me = cls.get_filter_value(filters, "with_tasks_completed_by_me")
|
instances_with_tasks_completed_by_me = cls.get_filter_value(filters, "instances_with_tasks_completed_by_me")
|
||||||
with_tasks_i_can_complete = cls.get_filter_value(filters, "with_tasks_i_can_complete")
|
instances_with_tasks_waiting_for_me = cls.get_filter_value(filters, "instances_with_tasks_waiting_for_me")
|
||||||
user_group_identifier = cls.get_filter_value(filters, "user_group_identifier")
|
user_group_identifier = cls.get_filter_value(filters, "user_group_identifier")
|
||||||
|
|
||||||
# builtin only - for the for-me paths
|
# builtin only - for the for-me paths
|
||||||
with_relation_to_me = cls.get_filter_value(filters, "with_relation_to_me")
|
with_relation_to_me = cls.get_filter_value(filters, "with_relation_to_me")
|
||||||
|
|
||||||
if (
|
if (
|
||||||
not with_tasks_completed_by_me
|
not instances_with_tasks_completed_by_me
|
||||||
and not user_group_identifier
|
and not user_group_identifier
|
||||||
and not with_tasks_i_can_complete
|
and not instances_with_tasks_waiting_for_me
|
||||||
and with_relation_to_me is True
|
and with_relation_to_me is True
|
||||||
):
|
):
|
||||||
process_instance_query = process_instance_query.outerjoin(HumanTaskModel).outerjoin(
|
process_instance_query = process_instance_query.outerjoin(HumanTaskModel).outerjoin(
|
||||||
@ -441,7 +445,16 @@ class ProcessInstanceReportService:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if with_tasks_completed_by_me is True:
|
if instances_with_tasks_completed_by_me is True and instances_with_tasks_waiting_for_me is True:
|
||||||
|
raise ProcessInstanceReportMetadataInvalidError(
|
||||||
|
"Cannot set both 'instances_with_tasks_completed_by_me' and 'instances_with_tasks_waiting_for_me' to"
|
||||||
|
" true. You must choose one."
|
||||||
|
)
|
||||||
|
|
||||||
|
# ensure we only join with HumanTaskModel once
|
||||||
|
human_task_already_joined = False
|
||||||
|
|
||||||
|
if instances_with_tasks_completed_by_me is True:
|
||||||
process_instance_query = process_instance_query.filter(
|
process_instance_query = process_instance_query.filter(
|
||||||
ProcessInstanceModel.process_initiator_id != user.id
|
ProcessInstanceModel.process_initiator_id != user.id
|
||||||
)
|
)
|
||||||
@ -452,10 +465,11 @@ class ProcessInstanceReportService:
|
|||||||
HumanTaskModel.completed_by_user_id == user.id,
|
HumanTaskModel.completed_by_user_id == user.id,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
human_task_already_joined = True
|
||||||
|
|
||||||
# this excludes some tasks you can complete, because that's the way the requirements were described.
|
# this excludes some tasks you can complete, because that's the way the requirements were described.
|
||||||
# if it's assigned to one of your groups, it does not get returned by this query.
|
# if it's assigned to one of your groups, it does not get returned by this query.
|
||||||
if with_tasks_i_can_complete is True:
|
if instances_with_tasks_waiting_for_me is True:
|
||||||
process_instance_query = process_instance_query.filter(
|
process_instance_query = process_instance_query.filter(
|
||||||
ProcessInstanceModel.process_initiator_id != user.id
|
ProcessInstanceModel.process_initiator_id != user.id
|
||||||
)
|
)
|
||||||
@ -470,13 +484,15 @@ class ProcessInstanceReportService:
|
|||||||
HumanTaskUserModel,
|
HumanTaskUserModel,
|
||||||
and_(HumanTaskUserModel.human_task_id == HumanTaskModel.id, HumanTaskUserModel.user_id == user.id),
|
and_(HumanTaskUserModel.human_task_id == HumanTaskModel.id, HumanTaskUserModel.user_id == user.id),
|
||||||
)
|
)
|
||||||
|
human_task_already_joined = True
|
||||||
|
|
||||||
if user_group_identifier is not None:
|
if user_group_identifier is not None:
|
||||||
group_model_join_conditions = [GroupModel.id == HumanTaskModel.lane_assignment_id]
|
group_model_join_conditions = [GroupModel.id == HumanTaskModel.lane_assignment_id]
|
||||||
if user_group_identifier:
|
if user_group_identifier:
|
||||||
group_model_join_conditions.append(GroupModel.identifier == user_group_identifier)
|
group_model_join_conditions.append(GroupModel.identifier == user_group_identifier)
|
||||||
|
|
||||||
process_instance_query = process_instance_query.join(HumanTaskModel)
|
if human_task_already_joined is False:
|
||||||
|
process_instance_query = process_instance_query.join(HumanTaskModel)
|
||||||
if process_status is not None:
|
if process_status is not None:
|
||||||
non_active_statuses = [
|
non_active_statuses = [
|
||||||
s for s in process_status.split(",") if s not in ProcessInstanceModel.active_statuses()
|
s for s in process_status.split(",") if s not in ProcessInstanceModel.active_statuses()
|
||||||
|
@ -2,6 +2,7 @@ import copy
|
|||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
|
from typing import List
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
from typing import TypedDict
|
from typing import TypedDict
|
||||||
@ -607,6 +608,28 @@ class TaskService:
|
|||||||
)
|
)
|
||||||
return extensions
|
return extensions
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_ready_signals_with_button_labels(cls, process_instance_id: int) -> list[dict]:
|
||||||
|
waiting_tasks: List[TaskModel] = TaskModel.query.filter_by(
|
||||||
|
state="WAITING", process_instance_id=process_instance_id
|
||||||
|
).all()
|
||||||
|
result = []
|
||||||
|
for task_model in waiting_tasks:
|
||||||
|
task_definition = task_model.task_definition
|
||||||
|
extensions: dict = (
|
||||||
|
task_definition.properties_json["extensions"]
|
||||||
|
if "extensions" in task_definition.properties_json
|
||||||
|
else {}
|
||||||
|
)
|
||||||
|
event_definition: dict = (
|
||||||
|
task_definition.properties_json["event_definition"]
|
||||||
|
if "event_definition" in task_definition.properties_json
|
||||||
|
else {}
|
||||||
|
)
|
||||||
|
if "signalButtonLabel" in extensions and "name" in event_definition:
|
||||||
|
result.append({"event": event_definition, "label": extensions["signalButtonLabel"]})
|
||||||
|
return result
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_spec_reference_from_bpmn_process(cls, bpmn_process: BpmnProcessModel) -> SpecReferenceCache:
|
def get_spec_reference_from_bpmn_process(cls, bpmn_process: BpmnProcessModel) -> SpecReferenceCache:
|
||||||
"""Get the bpmn file for a given task model.
|
"""Get the bpmn file for a given task model.
|
||||||
|
@ -0,0 +1,86 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:spiffworkflow="http://spiffworkflow.org/bpmn/schema/1.0/core" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_96f6665" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.0.0-dev">
|
||||||
|
<bpmn:process id="SpiffCatchEventExtensions" isExecutable="true">
|
||||||
|
<bpmn:startEvent id="StartEvent_1">
|
||||||
|
<bpmn:outgoing>Flow_0elszck</bpmn:outgoing>
|
||||||
|
</bpmn:startEvent>
|
||||||
|
<bpmn:sequenceFlow id="Flow_0elszck" sourceRef="StartEvent_1" targetRef="Activity_0cmmlen" />
|
||||||
|
<bpmn:endEvent id="Event_1mjvim4">
|
||||||
|
<bpmn:incoming>Flow_1akz8b3</bpmn:incoming>
|
||||||
|
</bpmn:endEvent>
|
||||||
|
<bpmn:sequenceFlow id="Flow_1akz8b3" sourceRef="Activity_0cmmlen" targetRef="Event_1mjvim4" />
|
||||||
|
<bpmn:sequenceFlow id="Flow_0uenxs3" sourceRef="SpamEvent" targetRef="Activity_1u4om4i" />
|
||||||
|
<bpmn:endEvent id="Event_1dvll15">
|
||||||
|
<bpmn:incoming>Flow_16bzuvz</bpmn:incoming>
|
||||||
|
</bpmn:endEvent>
|
||||||
|
<bpmn:sequenceFlow id="Flow_16bzuvz" sourceRef="Activity_1u4om4i" targetRef="Event_1dvll15" />
|
||||||
|
<bpmn:manualTask id="Activity_0cmmlen" name="My Manual Task">
|
||||||
|
<bpmn:extensionElements>
|
||||||
|
<spiffworkflow:instructionsForEndUser># Welcome
|
||||||
|
This manual task has Two Buttons! The first is standard submit button that will take you to the end. The second button will fire a signal event and take you to a different manual task.</spiffworkflow:instructionsForEndUser>
|
||||||
|
</bpmn:extensionElements>
|
||||||
|
<bpmn:incoming>Flow_0elszck</bpmn:incoming>
|
||||||
|
<bpmn:outgoing>Flow_1akz8b3</bpmn:outgoing>
|
||||||
|
</bpmn:manualTask>
|
||||||
|
<bpmn:manualTask id="Activity_1u4om4i" name="Spam Message">
|
||||||
|
<bpmn:extensionElements>
|
||||||
|
<spiffworkflow:instructionsForEndUser># Spam Eaten!
|
||||||
|
Congratulations! You have selected the Eat Additional Spam option, which opens up new doors to vast previously uncharted culinary eating experiences! Oh the Joy! Oh the Reward! Sweet savory wonderful Spam! </spiffworkflow:instructionsForEndUser>
|
||||||
|
</bpmn:extensionElements>
|
||||||
|
<bpmn:incoming>Flow_0uenxs3</bpmn:incoming>
|
||||||
|
<bpmn:outgoing>Flow_16bzuvz</bpmn:outgoing>
|
||||||
|
</bpmn:manualTask>
|
||||||
|
<bpmn:boundaryEvent id="SpamEvent" name="Spam Event" attachedToRef="Activity_0cmmlen">
|
||||||
|
<bpmn:extensionElements>
|
||||||
|
<spiffworkflow:signalButtonLabel>Eat Spam</spiffworkflow:signalButtonLabel>
|
||||||
|
</bpmn:extensionElements>
|
||||||
|
<bpmn:outgoing>Flow_0uenxs3</bpmn:outgoing>
|
||||||
|
<bpmn:signalEventDefinition id="SignalEventDefinition_11tlwya" signalRef="Signal_17t90lm" />
|
||||||
|
</bpmn:boundaryEvent>
|
||||||
|
</bpmn:process>
|
||||||
|
<bpmn:signal id="Signal_17t90lm" name="eat_spam" />
|
||||||
|
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||||
|
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_z1jgvu5">
|
||||||
|
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||||
|
<dc:Bounds x="179" y="159" width="36" height="36" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Event_1mjvim4_di" bpmnElement="Event_1mjvim4">
|
||||||
|
<dc:Bounds x="432" y="159" width="36" height="36" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Event_1dvll15_di" bpmnElement="Event_1dvll15">
|
||||||
|
<dc:Bounds x="562" y="282" width="36" height="36" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Activity_0zxmtux_di" bpmnElement="Activity_0cmmlen">
|
||||||
|
<dc:Bounds x="270" y="137" width="100" height="80" />
|
||||||
|
<bpmndi:BPMNLabel />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Activity_0tll58x_di" bpmnElement="Activity_1u4om4i">
|
||||||
|
<dc:Bounds x="410" y="260" width="100" height="80" />
|
||||||
|
<bpmndi:BPMNLabel />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Event_0vnraxp_di" bpmnElement="SpamEvent">
|
||||||
|
<dc:Bounds x="322" y="199" width="36" height="36" />
|
||||||
|
<bpmndi:BPMNLabel>
|
||||||
|
<dc:Bounds x="311" y="242" width="61" height="14" />
|
||||||
|
</bpmndi:BPMNLabel>
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_0elszck_di" bpmnElement="Flow_0elszck">
|
||||||
|
<di:waypoint x="215" y="177" />
|
||||||
|
<di:waypoint x="270" y="177" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_1akz8b3_di" bpmnElement="Flow_1akz8b3">
|
||||||
|
<di:waypoint x="370" y="177" />
|
||||||
|
<di:waypoint x="432" y="177" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_0uenxs3_di" bpmnElement="Flow_0uenxs3">
|
||||||
|
<di:waypoint x="340" y="235" />
|
||||||
|
<di:waypoint x="340" y="300" />
|
||||||
|
<di:waypoint x="410" y="300" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_16bzuvz_di" bpmnElement="Flow_16bzuvz">
|
||||||
|
<di:waypoint x="510" y="300" />
|
||||||
|
<di:waypoint x="562" y="300" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
</bpmndi:BPMNPlane>
|
||||||
|
</bpmndi:BPMNDiagram>
|
||||||
|
</bpmn:definitions>
|
@ -2649,7 +2649,8 @@ class TestProcessApi(BaseTest):
|
|||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json is not None
|
assert response.json is not None
|
||||||
assert response.json["status"] == "complete"
|
assert response.json["type"] == "End Event"
|
||||||
|
assert response.json["state"] == "COMPLETED"
|
||||||
|
|
||||||
response = client.get(
|
response = client.get(
|
||||||
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/task-info?all_tasks=true",
|
f"/v1.0/process-instances/{self.modify_process_identifier_for_path_param(process_model_identifier)}/{process_instance_id}/task-info?all_tasks=true",
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Test_process_instance_report_service."""
|
"""Test_process_instance_report_service."""
|
||||||
|
import pytest
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
from flask.testing import FlaskClient
|
from flask.testing import FlaskClient
|
||||||
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
|
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
|
||||||
@ -7,9 +8,9 @@ from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
|
|||||||
from spiffworkflow_backend.models.db import db
|
from spiffworkflow_backend.models.db import db
|
||||||
from spiffworkflow_backend.models.group import GroupModel
|
from spiffworkflow_backend.models.group import GroupModel
|
||||||
from spiffworkflow_backend.models.human_task import HumanTaskModel
|
from spiffworkflow_backend.models.human_task import HumanTaskModel
|
||||||
from spiffworkflow_backend.services.process_instance_report_service import (
|
from spiffworkflow_backend.models.process_instance_report import ReportMetadata
|
||||||
ProcessInstanceReportService,
|
from spiffworkflow_backend.services.process_instance_report_service import ProcessInstanceReportMetadataInvalidError
|
||||||
)
|
from spiffworkflow_backend.services.process_instance_report_service import ProcessInstanceReportService
|
||||||
from spiffworkflow_backend.services.user_service import UserService
|
from spiffworkflow_backend.services.user_service import UserService
|
||||||
|
|
||||||
|
|
||||||
@ -52,6 +53,62 @@ class TestProcessInstanceReportService(BaseTest):
|
|||||||
assert response_json["results"][0]["status"] == "complete"
|
assert response_json["results"][0]["status"] == "complete"
|
||||||
assert response_json["results"][1]["status"] == "complete"
|
assert response_json["results"][1]["status"] == "complete"
|
||||||
|
|
||||||
|
def test_raises_if_filtering_with_both_task_i_can_complete_and_tasks_completed_by_me(
|
||||||
|
self,
|
||||||
|
app: Flask,
|
||||||
|
client: FlaskClient,
|
||||||
|
with_db_and_bpmn_file_cleanup: None,
|
||||||
|
) -> None:
|
||||||
|
user_one = self.find_or_create_user(username="user_one")
|
||||||
|
report_metadata: ReportMetadata = {
|
||||||
|
"columns": [],
|
||||||
|
"filter_by": [
|
||||||
|
{"field_name": "instances_with_tasks_waiting_for_me", "field_value": True, "operator": "equals"},
|
||||||
|
{"field_name": "instances_with_tasks_completed_by_me", "field_value": True, "operator": "equals"},
|
||||||
|
],
|
||||||
|
"order_by": [],
|
||||||
|
}
|
||||||
|
with pytest.raises(ProcessInstanceReportMetadataInvalidError):
|
||||||
|
ProcessInstanceReportService.run_process_instance_report(
|
||||||
|
report_metadata=report_metadata,
|
||||||
|
user=user_one,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_with_group_identifier_does_not_conflict_with_system_filters(
|
||||||
|
self,
|
||||||
|
app: Flask,
|
||||||
|
client: FlaskClient,
|
||||||
|
with_db_and_bpmn_file_cleanup: None,
|
||||||
|
) -> None:
|
||||||
|
user_one = self.find_or_create_user(username="user_one")
|
||||||
|
report_metadata: ReportMetadata = {
|
||||||
|
"columns": [],
|
||||||
|
"filter_by": [
|
||||||
|
{"field_name": "instances_with_tasks_waiting_for_me", "field_value": True, "operator": "equals"},
|
||||||
|
{"field_name": "user_group_identifier", "field_value": "group_one", "operator": "equals"},
|
||||||
|
],
|
||||||
|
"order_by": [],
|
||||||
|
}
|
||||||
|
result = ProcessInstanceReportService.run_process_instance_report(
|
||||||
|
report_metadata=report_metadata,
|
||||||
|
user=user_one,
|
||||||
|
)
|
||||||
|
assert result is not None
|
||||||
|
|
||||||
|
report_metadata = {
|
||||||
|
"columns": [],
|
||||||
|
"filter_by": [
|
||||||
|
{"field_name": "instances_with_tasks_completed_by_me", "field_value": True, "operator": "equals"},
|
||||||
|
{"field_name": "user_group_identifier", "field_value": "group_one", "operator": "equals"},
|
||||||
|
],
|
||||||
|
"order_by": [],
|
||||||
|
}
|
||||||
|
result = ProcessInstanceReportService.run_process_instance_report(
|
||||||
|
report_metadata=report_metadata,
|
||||||
|
user=user_one,
|
||||||
|
)
|
||||||
|
assert result is not None
|
||||||
|
|
||||||
def test_can_filter_by_completed_instances_with_tasks_completed_by_me(
|
def test_can_filter_by_completed_instances_with_tasks_completed_by_me(
|
||||||
self,
|
self,
|
||||||
app: Flask,
|
app: Flask,
|
||||||
|
@ -156,3 +156,30 @@ class TestTaskService(BaseTest):
|
|||||||
assert task_model_level_3 is not None
|
assert task_model_level_3 is not None
|
||||||
bpmn_process = TaskService.bpmn_process_for_called_activity_or_top_level_process(task_model_level_3)
|
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"
|
assert bpmn_process.bpmn_process_definition.bpmn_identifier == "Level3"
|
||||||
|
|
||||||
|
def test_get_button_labels_for_waiting_signal_event_tasks(
|
||||||
|
self,
|
||||||
|
app: Flask,
|
||||||
|
with_db_and_bpmn_file_cleanup: None,
|
||||||
|
) -> None:
|
||||||
|
process_model = load_test_spec(
|
||||||
|
"test_group/signal_event_extensions",
|
||||||
|
process_model_source_directory="signal_event_extensions",
|
||||||
|
bpmn_file_name="signal_event_extensions",
|
||||||
|
)
|
||||||
|
load_test_spec(
|
||||||
|
"test_group/SpiffCatchEventExtensions",
|
||||||
|
process_model_source_directory="call_activity_nested",
|
||||||
|
bpmn_file_name="SpiffCatchEventExtensions",
|
||||||
|
)
|
||||||
|
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")
|
||||||
|
events = TaskService.get_ready_signals_with_button_labels(process_instance.id)
|
||||||
|
assert len(events) == 1
|
||||||
|
signal_event = events[0]
|
||||||
|
assert signal_event["event"]["name"] == "eat_spam"
|
||||||
|
assert signal_event["event"]["typename"] == "SignalEventDefinition"
|
||||||
|
assert signal_event["label"] == "Eat Spam"
|
||||||
|
|
||||||
|
print(events)
|
||||||
|
1022
spiffworkflow-frontend/package-lock.json
generated
1022
spiffworkflow-frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -31,14 +31,14 @@
|
|||||||
"autoprefixer": "10.4.8",
|
"autoprefixer": "10.4.8",
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
"bootstrap": "^5.2.0",
|
"bootstrap": "^5.2.0",
|
||||||
"bpmn-js": "^9.3.2",
|
"bpmn-js": "^13.0.0",
|
||||||
"bpmn-js-properties-panel": "^1.10.0",
|
"bpmn-js-properties-panel": "^1.22.0",
|
||||||
"bpmn-js-spiffworkflow": "github:sartography/bpmn-js-spiffworkflow#main",
|
"bpmn-js-spiffworkflow": "github:sartography/bpmn-js-spiffworkflow#main",
|
||||||
"cookie": "^0.5.0",
|
"cookie": "^0.5.0",
|
||||||
"craco": "^0.0.3",
|
"craco": "^0.0.3",
|
||||||
"cypress-slow-down": "^1.2.1",
|
"cypress-slow-down": "^1.2.1",
|
||||||
"date-fns": "^2.28.0",
|
"date-fns": "^2.28.0",
|
||||||
"diagram-js": "^8.5.0",
|
"diagram-js": "^11.9.1",
|
||||||
"dmn-js": "^12.2.0",
|
"dmn-js": "^12.2.0",
|
||||||
"dmn-js-properties-panel": "^1.1",
|
"dmn-js-properties-panel": "^1.1",
|
||||||
"dmn-js-shared": "^12.1.1",
|
"dmn-js-shared": "^12.1.1",
|
||||||
|
@ -138,7 +138,9 @@ export default function ProcessInstanceListTable({
|
|||||||
);
|
);
|
||||||
const canSearchUsers: boolean = ability.can('GET', targetUris.userSearch);
|
const canSearchUsers: boolean = ability.can('GET', targetUris.userSearch);
|
||||||
|
|
||||||
const [processInstances, setProcessInstances] = useState([]);
|
const [processInstances, setProcessInstances] = useState<ProcessInstance[]>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
const [reportMetadata, setReportMetadata] = useState<ReportMetadata | null>();
|
const [reportMetadata, setReportMetadata] = useState<ReportMetadata | null>();
|
||||||
const [pagination, setPagination] = useState<PaginationObject | null>(null);
|
const [pagination, setPagination] = useState<PaginationObject | null>(null);
|
||||||
|
|
||||||
@ -215,7 +217,10 @@ export default function ProcessInstanceListTable({
|
|||||||
);
|
);
|
||||||
const [userGroups, setUserGroups] = useState<string[]>([]);
|
const [userGroups, setUserGroups] = useState<string[]>([]);
|
||||||
const systemReportOptions: string[] = useMemo(() => {
|
const systemReportOptions: string[] = useMemo(() => {
|
||||||
return ['with_tasks_i_can_complete', 'with_tasks_completed_by_me'];
|
return [
|
||||||
|
'instances_with_tasks_waiting_for_me',
|
||||||
|
'instances_with_tasks_completed_by_me',
|
||||||
|
];
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const [reportHash, setReportHash] = useState<string | null>(null);
|
const [reportHash, setReportHash] = useState<string | null>(null);
|
||||||
@ -846,6 +851,9 @@ export default function ProcessInstanceListTable({
|
|||||||
setEndToDate('');
|
setEndToDate('');
|
||||||
setEndToTime('');
|
setEndToTime('');
|
||||||
setProcessInitiatorSelection(null);
|
setProcessInitiatorSelection(null);
|
||||||
|
setWithOldestOpenTask(false);
|
||||||
|
setSystemReport(null);
|
||||||
|
setSelectedUserGroup(null);
|
||||||
setRequiresRefilter(true);
|
setRequiresRefilter(true);
|
||||||
if (reportMetadata) {
|
if (reportMetadata) {
|
||||||
reportMetadata.filter_by = [];
|
reportMetadata.filter_by = [];
|
||||||
@ -1213,9 +1221,9 @@ export default function ProcessInstanceListTable({
|
|||||||
<Column md={4} lg={8} sm={2}>
|
<Column md={4} lg={8} sm={2}>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
id="system-report-dropdown"
|
id="system-report-dropdown"
|
||||||
titleText="System Report"
|
titleText="System report"
|
||||||
items={['', ...systemReportOptions]}
|
items={['', ...systemReportOptions]}
|
||||||
itemToString={(item: any) => item}
|
itemToString={(item: any) => titleizeString(item)}
|
||||||
selectedItem={systemReport}
|
selectedItem={systemReport}
|
||||||
onChange={(value: any) => {
|
onChange={(value: any) => {
|
||||||
setSystemReport(value.selectedItem);
|
setSystemReport(value.selectedItem);
|
||||||
@ -1226,7 +1234,7 @@ export default function ProcessInstanceListTable({
|
|||||||
<Column md={4} lg={8} sm={2}>
|
<Column md={4} lg={8} sm={2}>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
id="user-group-dropdown"
|
id="user-group-dropdown"
|
||||||
titleText="User Group"
|
titleText="Assigned user group"
|
||||||
items={['', ...userGroups]}
|
items={['', ...userGroups]}
|
||||||
itemToString={(item: any) => item}
|
itemToString={(item: any) => item}
|
||||||
selectedItem={selectedUserGroup}
|
selectedItem={selectedUserGroup}
|
||||||
@ -1262,6 +1270,7 @@ export default function ProcessInstanceListTable({
|
|||||||
onRequestSubmit={handleAdvancedOptionsClose}
|
onRequestSubmit={handleAdvancedOptionsClose}
|
||||||
onRequestClose={handleAdvancedOptionsClose}
|
onRequestClose={handleAdvancedOptionsClose}
|
||||||
hasScrollingContent
|
hasScrollingContent
|
||||||
|
size="lg"
|
||||||
>
|
>
|
||||||
{formElements}
|
{formElements}
|
||||||
</Modal>
|
</Modal>
|
||||||
@ -1336,7 +1345,7 @@ export default function ProcessInstanceListTable({
|
|||||||
return null;
|
return null;
|
||||||
}}
|
}}
|
||||||
placeholder="Start typing username"
|
placeholder="Start typing username"
|
||||||
titleText="Started By"
|
titleText="Started by"
|
||||||
selectedItem={processInitiatorSelection}
|
selectedItem={processInitiatorSelection}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -1345,7 +1354,7 @@ export default function ProcessInstanceListTable({
|
|||||||
<TextInput
|
<TextInput
|
||||||
id="process-instance-initiator-search"
|
id="process-instance-initiator-search"
|
||||||
placeholder="Enter username"
|
placeholder="Enter username"
|
||||||
labelText="Started By"
|
labelText="Started by"
|
||||||
invalid={processInitiatorNotFoundErrorText !== ''}
|
invalid={processInitiatorNotFoundErrorText !== ''}
|
||||||
invalidText={processInitiatorNotFoundErrorText}
|
invalidText={processInitiatorNotFoundErrorText}
|
||||||
onChange={(event: any) => {
|
onChange={(event: any) => {
|
||||||
@ -1509,7 +1518,7 @@ export default function ProcessInstanceListTable({
|
|||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
|
|
||||||
const formattedColumn = (row: any, column: any) => {
|
const formattedColumn = (row: ProcessInstance, column: ReportColumn) => {
|
||||||
const reportColumnFormatters: Record<string, any> = {
|
const reportColumnFormatters: Record<string, any> = {
|
||||||
id: formatProcessInstanceId,
|
id: formatProcessInstanceId,
|
||||||
process_model_identifier: formatProcessModelIdentifier,
|
process_model_identifier: formatProcessModelIdentifier,
|
||||||
@ -1520,40 +1529,41 @@ export default function ProcessInstanceListTable({
|
|||||||
updated_at_in_seconds: formatSecondsForDisplay,
|
updated_at_in_seconds: formatSecondsForDisplay,
|
||||||
task_updated_at_in_seconds: formatSecondsForDisplay,
|
task_updated_at_in_seconds: formatSecondsForDisplay,
|
||||||
};
|
};
|
||||||
|
const columnAccessor = column.accessor as keyof ProcessInstance;
|
||||||
const formatter =
|
const formatter =
|
||||||
reportColumnFormatters[column.accessor] ?? defaultFormatter;
|
reportColumnFormatters[columnAccessor] ?? defaultFormatter;
|
||||||
const value = row[column.accessor];
|
const value = row[columnAccessor];
|
||||||
|
|
||||||
if (column.accessor === 'status') {
|
if (columnAccessor === 'status') {
|
||||||
return (
|
return (
|
||||||
<td data-qa={`process-instance-status-${value}`}>
|
<td data-qa={`process-instance-status-${value}`}>
|
||||||
{formatter(row, value)}
|
{formatter(row, value)}
|
||||||
</td>
|
</td>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (column.accessor === 'process_model_display_name') {
|
if (columnAccessor === 'process_model_display_name') {
|
||||||
return <td> {formatter(row, value)} </td>;
|
return <td> {formatter(row, value)} </td>;
|
||||||
}
|
}
|
||||||
if (column.accessor === 'waiting_for') {
|
if (columnAccessor === 'waiting_for') {
|
||||||
return <td> {getWaitingForTableCellComponent(row)} </td>;
|
return <td> {getWaitingForTableCellComponent(row)} </td>;
|
||||||
}
|
}
|
||||||
if (column.accessor === 'updated_at_in_seconds') {
|
if (columnAccessor === 'updated_at_in_seconds') {
|
||||||
return (
|
return (
|
||||||
<TableCellWithTimeAgoInWords
|
<TableCellWithTimeAgoInWords
|
||||||
timeInSeconds={row.updated_at_in_seconds}
|
timeInSeconds={row.updated_at_in_seconds}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (column.accessor === 'task_updated_at_in_seconds') {
|
if (columnAccessor === 'task_updated_at_in_seconds') {
|
||||||
return (
|
return (
|
||||||
<TableCellWithTimeAgoInWords
|
<TableCellWithTimeAgoInWords
|
||||||
timeInSeconds={row.task_updated_at_in_seconds}
|
timeInSeconds={row.task_updated_at_in_seconds || 0}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
|
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
|
||||||
<td data-qa={`process-instance-show-link-${column.accessor}`}>
|
<td data-qa={`process-instance-show-link-${columnAccessor}`}>
|
||||||
{formatter(row, value)}
|
{formatter(row, value)}
|
||||||
</td>
|
</td>
|
||||||
);
|
);
|
||||||
@ -1561,27 +1571,15 @@ export default function ProcessInstanceListTable({
|
|||||||
|
|
||||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
const buildTable = () => {
|
const buildTable = () => {
|
||||||
const headerLabels: Record<string, string> = {
|
const headers = reportColumns().map((column: ReportColumn) => {
|
||||||
id: 'Id',
|
return column.Header;
|
||||||
process_model_identifier: 'Process',
|
|
||||||
process_model_display_name: 'Process',
|
|
||||||
start_in_seconds: 'Start Time',
|
|
||||||
end_in_seconds: 'End Time',
|
|
||||||
status: 'Status',
|
|
||||||
process_initiator_username: 'Started By',
|
|
||||||
};
|
|
||||||
const getHeaderLabel = (header: string) => {
|
|
||||||
return headerLabels[header] ?? header;
|
|
||||||
};
|
|
||||||
const headers = reportColumns().map((column: any) => {
|
|
||||||
return getHeaderLabel((column as any).Header);
|
|
||||||
});
|
});
|
||||||
if (showActionsColumn) {
|
if (showActionsColumn) {
|
||||||
headers.push('Action');
|
headers.push('Action');
|
||||||
}
|
}
|
||||||
|
|
||||||
const rows = processInstances.map((row: any) => {
|
const rows = processInstances.map((row: ProcessInstance) => {
|
||||||
const currentRow = reportColumns().map((column: any) => {
|
const currentRow = reportColumns().map((column: ReportColumn) => {
|
||||||
return formattedColumn(row, column);
|
return formattedColumn(row, column);
|
||||||
});
|
});
|
||||||
if (showActionsColumn) {
|
if (showActionsColumn) {
|
||||||
|
@ -33,9 +33,14 @@ export default function ProcessModelSearch({
|
|||||||
const getProcessModelLabelForDisplay = (processModel: ProcessModel) => {
|
const getProcessModelLabelForDisplay = (processModel: ProcessModel) => {
|
||||||
let processModelId = processModel.id;
|
let processModelId = processModel.id;
|
||||||
if (truncateProcessModelDisplayName) {
|
if (truncateProcessModelDisplayName) {
|
||||||
processModelId = processModelId.split('/').slice(-2).join('/');
|
let processModelIdArray = processModelId.split('/');
|
||||||
|
if (processModelIdArray.length > 2) {
|
||||||
|
processModelIdArray = processModelIdArray.slice(-2);
|
||||||
|
processModelIdArray.unshift('...');
|
||||||
|
}
|
||||||
|
processModelId = processModelIdArray.join('/');
|
||||||
}
|
}
|
||||||
return `${processModel.display_name} (${processModelId})`;
|
return `${processModel.display_name} - ${processModelId}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getProcessModelLabelForSearch = (processModel: ProcessModel) => {
|
const getProcessModelLabelForSearch = (processModel: ProcessModel) => {
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
/* eslint-disable sonarjs/cognitive-complexity */
|
/* eslint-disable sonarjs/cognitive-complexity */
|
||||||
// @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'bpmn... Remove this comment to see the full error message
|
|
||||||
import BpmnModeler from 'bpmn-js/lib/Modeler';
|
import BpmnModeler from 'bpmn-js/lib/Modeler';
|
||||||
// @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'bpmn... Remove this comment to see the full error message
|
|
||||||
import BpmnViewer from 'bpmn-js/lib/Viewer';
|
import BpmnViewer from 'bpmn-js/lib/Viewer';
|
||||||
import {
|
import {
|
||||||
BpmnPropertiesPanelModule,
|
BpmnPropertiesPanelModule,
|
||||||
@ -144,11 +142,14 @@ export default function ReactDiagramEditor({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const temp = document.createElement('template');
|
const temp = document.createElement('template');
|
||||||
|
const panelId: string =
|
||||||
|
diagramType === 'readonly'
|
||||||
|
? 'hidden-properties-panel'
|
||||||
|
: 'js-properties-panel';
|
||||||
temp.innerHTML = `
|
temp.innerHTML = `
|
||||||
<div class="content with-diagram" id="js-drop-zone">
|
<div class="content with-diagram" id="js-drop-zone">
|
||||||
<div class="canvas ${canvasClass}" id="canvas"
|
<div class="canvas ${canvasClass}" id="canvas"></div>
|
||||||
></div>
|
<div class="properties-panel-parent" id="${panelId}"></div>
|
||||||
<div class="properties-panel-parent" id="js-properties-panel"></div>
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
const frag = temp.content;
|
const frag = temp.content;
|
||||||
|
@ -37,6 +37,11 @@ export interface EventDefinition {
|
|||||||
message_var?: string;
|
message_var?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SignalButton {
|
||||||
|
label: string;
|
||||||
|
event: EventDefinition;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: merge with ProcessInstanceTask
|
// TODO: merge with ProcessInstanceTask
|
||||||
export interface Task {
|
export interface Task {
|
||||||
id: number;
|
id: number;
|
||||||
@ -60,6 +65,7 @@ export interface Task {
|
|||||||
can_complete: boolean;
|
can_complete: boolean;
|
||||||
form_schema: any;
|
form_schema: any;
|
||||||
form_ui_schema: any;
|
form_ui_schema: any;
|
||||||
|
signal_buttons: SignalButton[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProcessInstanceTask {
|
export interface ProcessInstanceTask {
|
||||||
@ -138,6 +144,12 @@ export interface ProcessInstance {
|
|||||||
bpmn_version_control_type: string;
|
bpmn_version_control_type: string;
|
||||||
process_metadata?: ProcessInstanceMetadata[];
|
process_metadata?: ProcessInstanceMetadata[];
|
||||||
process_model_with_diagram_identifier?: string;
|
process_model_with_diagram_identifier?: string;
|
||||||
|
|
||||||
|
// from tasks
|
||||||
|
potential_owner_usernames?: string;
|
||||||
|
task_id?: string;
|
||||||
|
task_updated_at_in_seconds?: number;
|
||||||
|
waiting_for?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MessageCorrelationProperties {
|
export interface MessageCorrelationProperties {
|
||||||
|
@ -58,7 +58,6 @@ export default function ProcessInterstitial() {
|
|||||||
// Added this seperate use effect so that the timer interval will be cleared if
|
// Added this seperate use effect so that the timer interval will be cleared if
|
||||||
// we end up redirecting back to the TaskShow page.
|
// we end up redirecting back to the TaskShow page.
|
||||||
if (shouldRedirect(lastTask)) {
|
if (shouldRedirect(lastTask)) {
|
||||||
setState('REDIRECTING');
|
|
||||||
lastTask.properties.instructionsForEndUser = '';
|
lastTask.properties.instructionsForEndUser = '';
|
||||||
const timerId = setInterval(() => {
|
const timerId = setInterval(() => {
|
||||||
navigate(`/tasks/${lastTask.process_instance_id}/${lastTask.id}`);
|
navigate(`/tasks/${lastTask.process_instance_id}/${lastTask.id}`);
|
||||||
@ -103,9 +102,9 @@ export default function ProcessInterstitial() {
|
|||||||
const getReturnHomeButton = (index: number) => {
|
const getReturnHomeButton = (index: number) => {
|
||||||
if (
|
if (
|
||||||
index === 0 &&
|
index === 0 &&
|
||||||
state !== 'REDIRECTING' &&
|
!shouldRedirect(lastTask) &&
|
||||||
['WAITING', 'ERROR', 'LOCKED', 'COMPLETED', 'READY'].includes(getStatus())
|
['WAITING', 'ERROR', 'LOCKED', 'COMPLETED', 'READY'].includes(getStatus())
|
||||||
)
|
) {
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: '10px 0 0 0' }}>
|
<div style={{ padding: '10px 0 0 0' }}>
|
||||||
<Button kind="secondary" onClick={() => navigate(`/tasks`)}>
|
<Button kind="secondary" onClick={() => navigate(`/tasks`)}>
|
||||||
@ -113,6 +112,7 @@ export default function ProcessInterstitial() {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
return '';
|
return '';
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -165,7 +165,7 @@ export default function ProcessInterstitial() {
|
|||||||
|
|
||||||
/** In the event there is no task information and the connection closed,
|
/** In the event there is no task information and the connection closed,
|
||||||
* redirect to the home page. */
|
* redirect to the home page. */
|
||||||
if (state === 'closed' && lastTask === null) {
|
if (state === 'CLOSED' && lastTask === null) {
|
||||||
navigate(`/tasks`);
|
navigate(`/tasks`);
|
||||||
}
|
}
|
||||||
if (lastTask) {
|
if (lastTask) {
|
||||||
|
@ -18,7 +18,7 @@ import Form from '../themes/carbon';
|
|||||||
import HttpService from '../services/HttpService';
|
import HttpService from '../services/HttpService';
|
||||||
import useAPIError from '../hooks/UseApiError';
|
import useAPIError from '../hooks/UseApiError';
|
||||||
import { modifyProcessIdentifierForPathParam } from '../helpers';
|
import { modifyProcessIdentifierForPathParam } from '../helpers';
|
||||||
import { Task } from '../interfaces';
|
import { EventDefinition, Task } from '../interfaces';
|
||||||
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
||||||
import InstructionsForEndUser from '../components/InstructionsForEndUser';
|
import InstructionsForEndUser from '../components/InstructionsForEndUser';
|
||||||
|
|
||||||
@ -195,6 +195,23 @@ export default function TaskShow() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSignalSubmit = (event: EventDefinition) => {
|
||||||
|
if (disabled || !task) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
HttpService.makeCallToBackend({
|
||||||
|
path: `/send-event/${modifyProcessIdentifierForPathParam(
|
||||||
|
task.process_model_identifier
|
||||||
|
)}/${params.process_instance_id}`,
|
||||||
|
successCallback: processSubmitResult,
|
||||||
|
failureCallback: (error: any) => {
|
||||||
|
addError(error);
|
||||||
|
},
|
||||||
|
httpMethod: 'POST',
|
||||||
|
postBody: event,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const buildTaskNavigation = () => {
|
const buildTaskNavigation = () => {
|
||||||
let userTasksElement;
|
let userTasksElement;
|
||||||
let selectedTabIndex = 0;
|
let selectedTabIndex = 0;
|
||||||
@ -355,6 +372,17 @@ export default function TaskShow() {
|
|||||||
{submitButtonText}
|
{submitButtonText}
|
||||||
</Button>
|
</Button>
|
||||||
{saveAsDraftButton}
|
{saveAsDraftButton}
|
||||||
|
<>
|
||||||
|
{task.signal_buttons.map((signal) => (
|
||||||
|
<Button
|
||||||
|
name="signal.signal"
|
||||||
|
disabled={disabled}
|
||||||
|
onClick={() => handleSignalSubmit(signal.event)}
|
||||||
|
>
|
||||||
|
{signal.label}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
</ButtonSet>
|
</ButtonSet>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user