mirror of
https://github.com/sartography/spiff-arena.git
synced 2025-02-22 22:28:15 +00:00
Merge pull request #212 from sartography/feature/home_page_refactor
Feature/home page refactor
This commit is contained in:
commit
884dcb3653
3
.flake8
3
.flake8
@ -44,3 +44,6 @@ per-file-ignores =
|
|||||||
# S607 Starting a process with a partial executable path
|
# S607 Starting a process with a partial executable path
|
||||||
# S605 Starting a process with a shell: Seems safe, but may be changed in the future, consider rewriting without shell
|
# S605 Starting a process with a shell: Seems safe, but may be changed in the future, consider rewriting without shell
|
||||||
spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py:S607,S101,S605,D102,D103,D101
|
spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py:S607,S101,S605,D102,D103,D101
|
||||||
|
|
||||||
|
# TODO: refactor this service so complexity should be reduced throughout
|
||||||
|
spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py:C901,D100,D101,D102,D103,D107
|
||||||
|
@ -41,3 +41,6 @@ per-file-ignores =
|
|||||||
src/spiffworkflow_backend/services/logging_service.py:N802,B950
|
src/spiffworkflow_backend/services/logging_service.py:N802,B950
|
||||||
|
|
||||||
tests/spiffworkflow_backend/integration/test_process_api.py:S607,S101,S605,D102,D103,D101
|
tests/spiffworkflow_backend/integration/test_process_api.py:S607,S101,S605,D102,D103,D101
|
||||||
|
|
||||||
|
# TODO: refactor this service so complexity should be reduced throughout
|
||||||
|
src/spiffworkflow_backend/services/process_instance_report_service.py:C901,D100,D101,D102,D103,D107
|
||||||
|
@ -3,10 +3,14 @@ default_group: everybody
|
|||||||
groups:
|
groups:
|
||||||
admin:
|
admin:
|
||||||
users: [admin@spiffworkflow.org]
|
users: [admin@spiffworkflow.org]
|
||||||
|
group1:
|
||||||
|
users: [jason@sartography.com, kb@sartography.com]
|
||||||
|
group2:
|
||||||
|
users: [dan@sartography.com]
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
admin:
|
admin:
|
||||||
groups: [admin]
|
groups: [admin, group1, group2]
|
||||||
users: []
|
users: []
|
||||||
allowed_permissions: [create, read, update, delete]
|
allowed_permissions: [create, read, update, delete]
|
||||||
uri: /*
|
uri: /*
|
||||||
|
@ -161,9 +161,12 @@ class ProcessInstanceModel(SpiffworkflowBaseDBModel):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def terminal_statuses(cls) -> list[str]:
|
def terminal_statuses(cls) -> list[str]:
|
||||||
"""Terminal_statuses."""
|
|
||||||
return ["complete", "error", "terminated"]
|
return ["complete", "error", "terminated"]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def active_statuses(cls) -> list[str]:
|
||||||
|
return ["user_input_required", "waiting"]
|
||||||
|
|
||||||
|
|
||||||
class ProcessInstanceModelSchema(Schema):
|
class ProcessInstanceModelSchema(Schema):
|
||||||
"""ProcessInstanceModelSchema."""
|
"""ProcessInstanceModelSchema."""
|
||||||
|
@ -6,11 +6,13 @@ from typing import Optional
|
|||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
|
from flask import current_app
|
||||||
from sqlalchemy import and_
|
from sqlalchemy import and_
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
from sqlalchemy import or_
|
from sqlalchemy import or_
|
||||||
from sqlalchemy.orm import aliased
|
from sqlalchemy.orm import aliased
|
||||||
from sqlalchemy.orm import selectinload
|
from sqlalchemy.orm import selectinload
|
||||||
|
from sqlalchemy.orm.util import AliasedClass
|
||||||
|
|
||||||
from spiffworkflow_backend.exceptions.api_error import ApiError
|
from spiffworkflow_backend.exceptions.api_error import ApiError
|
||||||
from spiffworkflow_backend.models.db import SpiffworkflowBaseDBModel
|
from spiffworkflow_backend.models.db import SpiffworkflowBaseDBModel
|
||||||
@ -46,12 +48,15 @@ class ProcessInstanceReportFilter:
|
|||||||
process_status: Optional[list[str]] = None
|
process_status: Optional[list[str]] = None
|
||||||
initiated_by_me: Optional[bool] = None
|
initiated_by_me: Optional[bool] = None
|
||||||
has_terminal_status: Optional[bool] = None
|
has_terminal_status: Optional[bool] = None
|
||||||
|
has_active_status: Optional[bool] = None
|
||||||
with_tasks_completed_by_me: Optional[bool] = None
|
with_tasks_completed_by_me: Optional[bool] = None
|
||||||
|
with_tasks_i_can_complete: Optional[bool] = None
|
||||||
with_tasks_assigned_to_my_group: Optional[bool] = None
|
with_tasks_assigned_to_my_group: Optional[bool] = None
|
||||||
with_relation_to_me: Optional[bool] = None
|
with_relation_to_me: Optional[bool] = None
|
||||||
process_initiator_username: Optional[str] = None
|
process_initiator_username: Optional[str] = None
|
||||||
report_column_list: Optional[list] = None
|
report_column_list: Optional[list] = None
|
||||||
report_filter_by_list: Optional[list] = None
|
report_filter_by_list: Optional[list] = None
|
||||||
|
oldest_open_human_task_fields: Optional[list] = None
|
||||||
|
|
||||||
def to_dict(self) -> dict[str, str]:
|
def to_dict(self) -> dict[str, str]:
|
||||||
"""To_dict."""
|
"""To_dict."""
|
||||||
@ -75,8 +80,12 @@ class ProcessInstanceReportFilter:
|
|||||||
d["initiated_by_me"] = str(self.initiated_by_me).lower()
|
d["initiated_by_me"] = str(self.initiated_by_me).lower()
|
||||||
if self.has_terminal_status is not None:
|
if self.has_terminal_status is not None:
|
||||||
d["has_terminal_status"] = str(self.has_terminal_status).lower()
|
d["has_terminal_status"] = str(self.has_terminal_status).lower()
|
||||||
|
if self.has_active_status is not None:
|
||||||
|
d["has_active_status"] = str(self.has_active_status).lower()
|
||||||
if self.with_tasks_completed_by_me is not None:
|
if self.with_tasks_completed_by_me is not None:
|
||||||
d["with_tasks_completed_by_me"] = str(self.with_tasks_completed_by_me).lower()
|
d["with_tasks_completed_by_me"] = str(self.with_tasks_completed_by_me).lower()
|
||||||
|
if self.with_tasks_i_can_complete is not None:
|
||||||
|
d["with_tasks_i_can_complete"] = str(self.with_tasks_i_can_complete).lower()
|
||||||
if self.with_tasks_assigned_to_my_group is not None:
|
if self.with_tasks_assigned_to_my_group is not None:
|
||||||
d["with_tasks_assigned_to_my_group"] = str(self.with_tasks_assigned_to_my_group).lower()
|
d["with_tasks_assigned_to_my_group"] = str(self.with_tasks_assigned_to_my_group).lower()
|
||||||
if self.with_relation_to_me is not None:
|
if self.with_relation_to_me is not None:
|
||||||
@ -87,6 +96,8 @@ class ProcessInstanceReportFilter:
|
|||||||
d["report_column_list"] = str(self.report_column_list)
|
d["report_column_list"] = str(self.report_column_list)
|
||||||
if self.report_filter_by_list is not None:
|
if self.report_filter_by_list is not None:
|
||||||
d["report_filter_by_list"] = str(self.report_filter_by_list)
|
d["report_filter_by_list"] = str(self.report_filter_by_list)
|
||||||
|
if self.oldest_open_human_task_fields is not None:
|
||||||
|
d["oldest_open_human_task_fields"] = str(self.oldest_open_human_task_fields)
|
||||||
|
|
||||||
return d
|
return d
|
||||||
|
|
||||||
@ -140,6 +151,78 @@ class ProcessInstanceReportService:
|
|||||||
],
|
],
|
||||||
"order_by": ["-start_in_seconds", "-id"],
|
"order_by": ["-start_in_seconds", "-id"],
|
||||||
},
|
},
|
||||||
|
"system_report_in_progress_instances_initiated_by_me": {
|
||||||
|
"columns": [
|
||||||
|
{"Header": "id", "accessor": "id"},
|
||||||
|
{
|
||||||
|
"Header": "process_model_display_name",
|
||||||
|
"accessor": "process_model_display_name",
|
||||||
|
},
|
||||||
|
{"Header": "Task", "accessor": "task_title"},
|
||||||
|
{"Header": "Waiting For", "accessor": "waiting_for"},
|
||||||
|
{"Header": "Started", "accessor": "start_in_seconds"},
|
||||||
|
{"Header": "Last Updated", "accessor": "updated_at_in_seconds"},
|
||||||
|
{"Header": "status", "accessor": "status"},
|
||||||
|
],
|
||||||
|
"filter_by": [
|
||||||
|
{"field_name": "initiated_by_me", "field_value": "true"},
|
||||||
|
{"field_name": "has_terminal_status", "field_value": "false"},
|
||||||
|
{
|
||||||
|
"field_name": "oldest_open_human_task_fields",
|
||||||
|
"field_value": (
|
||||||
|
"task_id,task_title,task_name,potential_owner_usernames,assigned_user_group_identifier"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"order_by": ["-start_in_seconds", "-id"],
|
||||||
|
},
|
||||||
|
"system_report_in_progress_instances_with_tasks_for_me": {
|
||||||
|
"columns": [
|
||||||
|
{"Header": "id", "accessor": "id"},
|
||||||
|
{
|
||||||
|
"Header": "process_model_display_name",
|
||||||
|
"accessor": "process_model_display_name",
|
||||||
|
},
|
||||||
|
{"Header": "Task", "accessor": "task_title"},
|
||||||
|
{"Header": "Started By", "accessor": "process_initiator_username"},
|
||||||
|
{"Header": "Started", "accessor": "start_in_seconds"},
|
||||||
|
{"Header": "Last Updated", "accessor": "updated_at_in_seconds"},
|
||||||
|
],
|
||||||
|
"filter_by": [
|
||||||
|
{"field_name": "with_tasks_i_can_complete", "field_value": "true"},
|
||||||
|
{"field_name": "has_active_status", "field_value": "true"},
|
||||||
|
{
|
||||||
|
"field_name": "oldest_open_human_task_fields",
|
||||||
|
"field_value": "task_id,task_title,task_name",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"order_by": ["-start_in_seconds", "-id"],
|
||||||
|
},
|
||||||
|
"system_report_in_progress_instances_with_tasks_for_my_group": {
|
||||||
|
"columns": [
|
||||||
|
{"Header": "id", "accessor": "id"},
|
||||||
|
{
|
||||||
|
"Header": "process_model_display_name",
|
||||||
|
"accessor": "process_model_display_name",
|
||||||
|
},
|
||||||
|
{"Header": "Task", "accessor": "task_title"},
|
||||||
|
{"Header": "Started By", "accessor": "process_initiator_username"},
|
||||||
|
{"Header": "Started", "accessor": "start_in_seconds"},
|
||||||
|
{"Header": "Last Updated", "accessor": "updated_at_in_seconds"},
|
||||||
|
],
|
||||||
|
"filter_by": [
|
||||||
|
{
|
||||||
|
"field_name": "with_tasks_assigned_to_my_group",
|
||||||
|
"field_value": "true",
|
||||||
|
},
|
||||||
|
{"field_name": "has_active_status", "field_value": "true"},
|
||||||
|
{
|
||||||
|
"field_name": "oldest_open_human_task_fields",
|
||||||
|
"field_value": "task_id,task_title,task_name",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"order_by": ["-start_in_seconds", "-id"],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if metadata_key not in temp_system_metadata_map:
|
if metadata_key not in temp_system_metadata_map:
|
||||||
@ -199,14 +282,18 @@ class ProcessInstanceReportService:
|
|||||||
|
|
||||||
def bool_value(key: str) -> Optional[bool]:
|
def bool_value(key: str) -> Optional[bool]:
|
||||||
"""Bool_value."""
|
"""Bool_value."""
|
||||||
return bool(filters[key]) if key in filters else None
|
if key not in filters:
|
||||||
|
return None
|
||||||
|
# bool returns True if not an empty string so check explicitly for false
|
||||||
|
if filters[key] in ["false", "False"]:
|
||||||
|
return False
|
||||||
|
return bool(filters[key])
|
||||||
|
|
||||||
def int_value(key: str) -> Optional[int]:
|
def int_value(key: str) -> Optional[int]:
|
||||||
"""Int_value."""
|
"""Int_value."""
|
||||||
return int(filters[key]) if key in filters else None
|
return int(filters[key]) if key in filters else None
|
||||||
|
|
||||||
def list_value(key: str) -> Optional[list[str]]:
|
def list_value(key: str) -> Optional[list[str]]:
|
||||||
"""List_value."""
|
|
||||||
return filters[key].split(",") if key in filters else None
|
return filters[key].split(",") if key in filters else None
|
||||||
|
|
||||||
process_model_identifier = filters.get("process_model_identifier")
|
process_model_identifier = filters.get("process_model_identifier")
|
||||||
@ -218,12 +305,15 @@ class ProcessInstanceReportService:
|
|||||||
process_status = list_value("process_status")
|
process_status = list_value("process_status")
|
||||||
initiated_by_me = bool_value("initiated_by_me")
|
initiated_by_me = bool_value("initiated_by_me")
|
||||||
has_terminal_status = bool_value("has_terminal_status")
|
has_terminal_status = bool_value("has_terminal_status")
|
||||||
|
has_active_status = bool_value("has_active_status")
|
||||||
with_tasks_completed_by_me = bool_value("with_tasks_completed_by_me")
|
with_tasks_completed_by_me = bool_value("with_tasks_completed_by_me")
|
||||||
|
with_tasks_i_can_complete = bool_value("with_tasks_i_can_complete")
|
||||||
with_tasks_assigned_to_my_group = bool_value("with_tasks_assigned_to_my_group")
|
with_tasks_assigned_to_my_group = bool_value("with_tasks_assigned_to_my_group")
|
||||||
with_relation_to_me = bool_value("with_relation_to_me")
|
with_relation_to_me = bool_value("with_relation_to_me")
|
||||||
process_initiator_username = filters.get("process_initiator_username")
|
process_initiator_username = filters.get("process_initiator_username")
|
||||||
report_column_list = list_value("report_column_list")
|
report_column_list = list_value("report_column_list")
|
||||||
report_filter_by_list = list_value("report_filter_by_list")
|
report_filter_by_list = list_value("report_filter_by_list")
|
||||||
|
oldest_open_human_task_fields = list_value("oldest_open_human_task_fields")
|
||||||
|
|
||||||
report_filter = ProcessInstanceReportFilter(
|
report_filter = ProcessInstanceReportFilter(
|
||||||
process_model_identifier=process_model_identifier,
|
process_model_identifier=process_model_identifier,
|
||||||
@ -235,12 +325,15 @@ class ProcessInstanceReportService:
|
|||||||
process_status=process_status,
|
process_status=process_status,
|
||||||
initiated_by_me=initiated_by_me,
|
initiated_by_me=initiated_by_me,
|
||||||
has_terminal_status=has_terminal_status,
|
has_terminal_status=has_terminal_status,
|
||||||
|
has_active_status=has_active_status,
|
||||||
with_tasks_completed_by_me=with_tasks_completed_by_me,
|
with_tasks_completed_by_me=with_tasks_completed_by_me,
|
||||||
|
with_tasks_i_can_complete=with_tasks_i_can_complete,
|
||||||
with_tasks_assigned_to_my_group=with_tasks_assigned_to_my_group,
|
with_tasks_assigned_to_my_group=with_tasks_assigned_to_my_group,
|
||||||
with_relation_to_me=with_relation_to_me,
|
with_relation_to_me=with_relation_to_me,
|
||||||
process_initiator_username=process_initiator_username,
|
process_initiator_username=process_initiator_username,
|
||||||
report_column_list=report_column_list,
|
report_column_list=report_column_list,
|
||||||
report_filter_by_list=report_filter_by_list,
|
report_filter_by_list=report_filter_by_list,
|
||||||
|
oldest_open_human_task_fields=oldest_open_human_task_fields,
|
||||||
)
|
)
|
||||||
|
|
||||||
return report_filter
|
return report_filter
|
||||||
@ -258,12 +351,15 @@ class ProcessInstanceReportService:
|
|||||||
process_status: Optional[str] = None,
|
process_status: Optional[str] = None,
|
||||||
initiated_by_me: Optional[bool] = None,
|
initiated_by_me: Optional[bool] = None,
|
||||||
has_terminal_status: Optional[bool] = None,
|
has_terminal_status: Optional[bool] = None,
|
||||||
|
has_active_status: Optional[bool] = None,
|
||||||
with_tasks_completed_by_me: Optional[bool] = None,
|
with_tasks_completed_by_me: Optional[bool] = None,
|
||||||
|
with_tasks_i_can_complete: Optional[bool] = None,
|
||||||
with_tasks_assigned_to_my_group: Optional[bool] = None,
|
with_tasks_assigned_to_my_group: Optional[bool] = None,
|
||||||
with_relation_to_me: Optional[bool] = None,
|
with_relation_to_me: Optional[bool] = None,
|
||||||
process_initiator_username: Optional[str] = None,
|
process_initiator_username: Optional[str] = None,
|
||||||
report_column_list: Optional[list] = None,
|
report_column_list: Optional[list] = None,
|
||||||
report_filter_by_list: Optional[list] = None,
|
report_filter_by_list: Optional[list] = None,
|
||||||
|
oldest_open_human_task_fields: Optional[list] = None,
|
||||||
) -> ProcessInstanceReportFilter:
|
) -> ProcessInstanceReportFilter:
|
||||||
"""Filter_from_metadata_with_overrides."""
|
"""Filter_from_metadata_with_overrides."""
|
||||||
report_filter = cls.filter_from_metadata(process_instance_report)
|
report_filter = cls.filter_from_metadata(process_instance_report)
|
||||||
@ -286,14 +382,20 @@ class ProcessInstanceReportService:
|
|||||||
report_filter.initiated_by_me = initiated_by_me
|
report_filter.initiated_by_me = initiated_by_me
|
||||||
if has_terminal_status is not None:
|
if has_terminal_status is not None:
|
||||||
report_filter.has_terminal_status = has_terminal_status
|
report_filter.has_terminal_status = has_terminal_status
|
||||||
|
if has_active_status is not None:
|
||||||
|
report_filter.has_active_status = has_active_status
|
||||||
if with_tasks_completed_by_me is not None:
|
if with_tasks_completed_by_me is not None:
|
||||||
report_filter.with_tasks_completed_by_me = with_tasks_completed_by_me
|
report_filter.with_tasks_completed_by_me = with_tasks_completed_by_me
|
||||||
|
if with_tasks_i_can_complete is not None:
|
||||||
|
report_filter.with_tasks_i_can_complete = with_tasks_i_can_complete
|
||||||
if process_initiator_username is not None:
|
if process_initiator_username is not None:
|
||||||
report_filter.process_initiator_username = process_initiator_username
|
report_filter.process_initiator_username = process_initiator_username
|
||||||
if report_column_list is not None:
|
if report_column_list is not None:
|
||||||
report_filter.report_column_list = report_column_list
|
report_filter.report_column_list = report_column_list
|
||||||
if report_filter_by_list is not None:
|
if report_filter_by_list is not None:
|
||||||
report_filter.report_filter_by_list = report_filter_by_list
|
report_filter.report_filter_by_list = report_filter_by_list
|
||||||
|
if oldest_open_human_task_fields is not None:
|
||||||
|
report_filter.oldest_open_human_task_fields = oldest_open_human_task_fields
|
||||||
if with_tasks_assigned_to_my_group is not None:
|
if with_tasks_assigned_to_my_group is not None:
|
||||||
report_filter.with_tasks_assigned_to_my_group = with_tasks_assigned_to_my_group
|
report_filter.with_tasks_assigned_to_my_group = with_tasks_assigned_to_my_group
|
||||||
if with_relation_to_me is not None:
|
if with_relation_to_me is not None:
|
||||||
@ -321,6 +423,54 @@ class ProcessInstanceReportService:
|
|||||||
results.append(process_instance_dict)
|
results.append(process_instance_dict)
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def add_human_task_fields(
|
||||||
|
cls, process_instance_dicts: list[dict], oldest_open_human_task_fields: list
|
||||||
|
) -> list[dict]:
|
||||||
|
for process_instance_dict in process_instance_dicts:
|
||||||
|
assigned_user = aliased(UserModel)
|
||||||
|
human_task_query = (
|
||||||
|
HumanTaskModel.query.filter_by(process_instance_id=process_instance_dict["id"], completed=False)
|
||||||
|
.group_by(HumanTaskModel.id)
|
||||||
|
.outerjoin(
|
||||||
|
HumanTaskUserModel,
|
||||||
|
HumanTaskModel.id == HumanTaskUserModel.human_task_id,
|
||||||
|
)
|
||||||
|
.outerjoin(assigned_user, assigned_user.id == HumanTaskUserModel.user_id)
|
||||||
|
.outerjoin(GroupModel, GroupModel.id == HumanTaskModel.lane_assignment_id)
|
||||||
|
)
|
||||||
|
potential_owner_usernames_from_group_concat_or_similar = cls._get_potential_owner_usernames(assigned_user)
|
||||||
|
human_task = (
|
||||||
|
human_task_query.add_columns(
|
||||||
|
HumanTaskModel.task_id,
|
||||||
|
HumanTaskModel.task_name,
|
||||||
|
HumanTaskModel.task_title,
|
||||||
|
func.max(GroupModel.identifier).label("assigned_user_group_identifier"),
|
||||||
|
potential_owner_usernames_from_group_concat_or_similar,
|
||||||
|
)
|
||||||
|
.order_by(HumanTaskModel.id.asc()) # type: ignore
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
if human_task is not None:
|
||||||
|
for field in oldest_open_human_task_fields:
|
||||||
|
process_instance_dict[field] = getattr(human_task, field)
|
||||||
|
return process_instance_dicts
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _get_potential_owner_usernames(cls, assigned_user: AliasedClass) -> Any:
|
||||||
|
"""_get_potential_owner_usernames."""
|
||||||
|
potential_owner_usernames_from_group_concat_or_similar = func.group_concat(
|
||||||
|
assigned_user.username.distinct()
|
||||||
|
).label("potential_owner_usernames")
|
||||||
|
db_type = current_app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_TYPE")
|
||||||
|
|
||||||
|
if db_type == "postgres":
|
||||||
|
potential_owner_usernames_from_group_concat_or_similar = func.string_agg(
|
||||||
|
assigned_user.username.distinct(), ", "
|
||||||
|
).label("potential_owner_usernames")
|
||||||
|
|
||||||
|
return potential_owner_usernames_from_group_concat_or_similar
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_column_names_for_model(cls, model: Type[SpiffworkflowBaseDBModel]) -> list[str]:
|
def get_column_names_for_model(cls, model: Type[SpiffworkflowBaseDBModel]) -> list[str]:
|
||||||
"""Get_column_names_for_model."""
|
"""Get_column_names_for_model."""
|
||||||
@ -405,6 +555,14 @@ class ProcessInstanceReportService:
|
|||||||
process_instance_query = process_instance_query.filter(
|
process_instance_query = process_instance_query.filter(
|
||||||
ProcessInstanceModel.status.in_(ProcessInstanceModel.terminal_statuses()) # type: ignore
|
ProcessInstanceModel.status.in_(ProcessInstanceModel.terminal_statuses()) # type: ignore
|
||||||
)
|
)
|
||||||
|
elif report_filter.has_terminal_status is False:
|
||||||
|
process_instance_query = process_instance_query.filter(
|
||||||
|
ProcessInstanceModel.status.not_in(ProcessInstanceModel.terminal_statuses()) # type: ignore
|
||||||
|
)
|
||||||
|
if report_filter.has_active_status is True:
|
||||||
|
process_instance_query = process_instance_query.filter(
|
||||||
|
ProcessInstanceModel.status.in_(ProcessInstanceModel.active_statuses()) # type: ignore
|
||||||
|
)
|
||||||
|
|
||||||
if report_filter.process_initiator_username is not None:
|
if report_filter.process_initiator_username is not None:
|
||||||
initiator = UserModel.query.filter_by(username=report_filter.process_initiator_username).first()
|
initiator = UserModel.query.filter_by(username=report_filter.process_initiator_username).first()
|
||||||
@ -416,6 +574,7 @@ class ProcessInstanceReportService:
|
|||||||
if (
|
if (
|
||||||
not report_filter.with_tasks_completed_by_me
|
not report_filter.with_tasks_completed_by_me
|
||||||
and not report_filter.with_tasks_assigned_to_my_group
|
and not report_filter.with_tasks_assigned_to_my_group
|
||||||
|
and not report_filter.with_tasks_i_can_complete
|
||||||
and report_filter.with_relation_to_me is True
|
and report_filter.with_relation_to_me is True
|
||||||
):
|
):
|
||||||
process_instance_query = process_instance_query.outerjoin(HumanTaskModel).outerjoin(
|
process_instance_query = process_instance_query.outerjoin(HumanTaskModel).outerjoin(
|
||||||
@ -444,6 +603,21 @@ class ProcessInstanceReportService:
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if report_filter.with_tasks_i_can_complete is True:
|
||||||
|
process_instance_query = process_instance_query.filter(
|
||||||
|
ProcessInstanceModel.process_initiator_id != user.id
|
||||||
|
)
|
||||||
|
process_instance_query = process_instance_query.join(
|
||||||
|
HumanTaskModel,
|
||||||
|
and_(
|
||||||
|
HumanTaskModel.process_instance_id == ProcessInstanceModel.id,
|
||||||
|
HumanTaskModel.lane_assignment_id.is_(None), # type: ignore
|
||||||
|
),
|
||||||
|
).join(
|
||||||
|
HumanTaskUserModel,
|
||||||
|
and_(HumanTaskUserModel.human_task_id == HumanTaskModel.id, HumanTaskUserModel.user_id == user.id),
|
||||||
|
)
|
||||||
|
|
||||||
if report_filter.with_tasks_assigned_to_my_group is True:
|
if report_filter.with_tasks_assigned_to_my_group is True:
|
||||||
group_model_join_conditions = [GroupModel.id == HumanTaskModel.lane_assignment_id]
|
group_model_join_conditions = [GroupModel.id == HumanTaskModel.lane_assignment_id]
|
||||||
if report_filter.user_group_identifier:
|
if report_filter.user_group_identifier:
|
||||||
@ -457,7 +631,7 @@ class ProcessInstanceReportService:
|
|||||||
process_instance_query = process_instance_query.filter(UserGroupAssignmentModel.user_id == user.id)
|
process_instance_query = process_instance_query.filter(UserGroupAssignmentModel.user_id == user.id)
|
||||||
|
|
||||||
instance_metadata_aliases = {}
|
instance_metadata_aliases = {}
|
||||||
stock_columns = ProcessInstanceReportService.get_column_names_for_model(ProcessInstanceModel)
|
stock_columns = cls.get_column_names_for_model(ProcessInstanceModel)
|
||||||
if isinstance(report_filter.report_column_list, list):
|
if isinstance(report_filter.report_column_list, list):
|
||||||
process_instance_report.report_metadata["columns"] = report_filter.report_column_list
|
process_instance_report.report_metadata["columns"] = report_filter.report_column_list
|
||||||
if isinstance(report_filter.report_filter_by_list, list):
|
if isinstance(report_filter.report_filter_by_list, list):
|
||||||
@ -507,16 +681,19 @@ class ProcessInstanceReportService:
|
|||||||
order_by_query_array.append(func.max(instance_metadata_aliases[attribute].value).desc())
|
order_by_query_array.append(func.max(instance_metadata_aliases[attribute].value).desc())
|
||||||
else:
|
else:
|
||||||
order_by_query_array.append(func.max(instance_metadata_aliases[attribute].value).asc())
|
order_by_query_array.append(func.max(instance_metadata_aliases[attribute].value).asc())
|
||||||
# return process_instance_query
|
|
||||||
process_instances = (
|
process_instances = (
|
||||||
process_instance_query.group_by(ProcessInstanceModel.id)
|
process_instance_query.group_by(ProcessInstanceModel.id)
|
||||||
.add_columns(ProcessInstanceModel.id)
|
.add_columns(ProcessInstanceModel.id)
|
||||||
.order_by(*order_by_query_array)
|
.order_by(*order_by_query_array)
|
||||||
.paginate(page=page, per_page=per_page, error_out=False)
|
.paginate(page=page, per_page=per_page, error_out=False)
|
||||||
)
|
)
|
||||||
results = ProcessInstanceReportService.add_metadata_columns_to_process_instance(
|
results = cls.add_metadata_columns_to_process_instance(
|
||||||
process_instances.items, process_instance_report.report_metadata["columns"]
|
process_instances.items, process_instance_report.report_metadata["columns"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if report_filter.oldest_open_human_task_fields:
|
||||||
|
results = cls.add_human_task_fields(results, report_filter.oldest_open_human_task_fields)
|
||||||
response_json = {
|
response_json = {
|
||||||
"report": process_instance_report,
|
"report": process_instance_report,
|
||||||
"results": results,
|
"results": results,
|
||||||
|
@ -90,7 +90,9 @@ describe('tasks', () => {
|
|||||||
cy.get('.is-visible .cds--modal-close').click();
|
cy.get('.is-visible .cds--modal-close').click();
|
||||||
|
|
||||||
cy.navigateToHome();
|
cy.navigateToHome();
|
||||||
cy.contains('Tasks').should('exist');
|
|
||||||
|
// look for somethig to make sure the homepage has loaded
|
||||||
|
cy.contains('Waiting for me').should('exist');
|
||||||
|
|
||||||
// FIXME: this will probably need a better way to link to the proper form that we want
|
// FIXME: this will probably need a better way to link to the proper form that we want
|
||||||
cy.contains('Go').click();
|
cy.contains('Go').click();
|
||||||
|
@ -69,6 +69,8 @@ import { Notification } from './Notification';
|
|||||||
import useAPIError from '../hooks/UseApiError';
|
import useAPIError from '../hooks/UseApiError';
|
||||||
import { usePermissionFetcher } from '../hooks/PermissionService';
|
import { usePermissionFetcher } from '../hooks/PermissionService';
|
||||||
import { Can } from '../contexts/Can';
|
import { Can } from '../contexts/Can';
|
||||||
|
import TableCellWithTimeAgoInWords from './TableCellWithTimeAgoInWords';
|
||||||
|
import UserService from '../services/UserService';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
filtersEnabled?: boolean;
|
filtersEnabled?: boolean;
|
||||||
@ -82,6 +84,8 @@ type OwnProps = {
|
|||||||
autoReload?: boolean;
|
autoReload?: boolean;
|
||||||
additionalParams?: string;
|
additionalParams?: string;
|
||||||
variant?: string;
|
variant?: string;
|
||||||
|
canCompleteAllTasks?: boolean;
|
||||||
|
showActionsColumn?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface dateParameters {
|
interface dateParameters {
|
||||||
@ -100,6 +104,8 @@ export default function ProcessInstanceListTable({
|
|||||||
paginationClassName,
|
paginationClassName,
|
||||||
autoReload = false,
|
autoReload = false,
|
||||||
variant = 'for-me',
|
variant = 'for-me',
|
||||||
|
canCompleteAllTasks = false,
|
||||||
|
showActionsColumn = false,
|
||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
let apiPath = '/process-instances/for-me';
|
let apiPath = '/process-instances/for-me';
|
||||||
if (variant === 'all') {
|
if (variant === 'all') {
|
||||||
@ -141,6 +147,9 @@ export default function ProcessInstanceListTable({
|
|||||||
const [requiresRefilter, setRequiresRefilter] = useState<boolean>(false);
|
const [requiresRefilter, setRequiresRefilter] = useState<boolean>(false);
|
||||||
const [lastColumnFilter, setLastColumnFilter] = useState<string>('');
|
const [lastColumnFilter, setLastColumnFilter] = useState<string>('');
|
||||||
|
|
||||||
|
const preferredUsername = UserService.getPreferredUsername();
|
||||||
|
const userEmail = UserService.getUserEmail();
|
||||||
|
|
||||||
const processInstanceListPathPrefix =
|
const processInstanceListPathPrefix =
|
||||||
variant === 'all'
|
variant === 'all'
|
||||||
? '/admin/process-instances/all'
|
? '/admin/process-instances/all'
|
||||||
@ -245,6 +254,8 @@ export default function ProcessInstanceListTable({
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const clearRefreshRef = useRef<any>(null);
|
||||||
|
|
||||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function setProcessInstancesFromResult(result: any) {
|
function setProcessInstancesFromResult(result: any) {
|
||||||
@ -259,6 +270,11 @@ export default function ProcessInstanceListTable({
|
|||||||
setProcessInstanceReportSelection(result.report);
|
setProcessInstanceReportSelection(result.report);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const stopRefreshing = () => {
|
||||||
|
if (clearRefreshRef.current) {
|
||||||
|
clearRefreshRef.current();
|
||||||
|
}
|
||||||
|
};
|
||||||
function getProcessInstances() {
|
function getProcessInstances() {
|
||||||
// eslint-disable-next-line prefer-const
|
// eslint-disable-next-line prefer-const
|
||||||
let { page, perPage } = getPageInfoFromSearchParams(
|
let { page, perPage } = getPageInfoFromSearchParams(
|
||||||
@ -343,6 +359,7 @@ export default function ProcessInstanceListTable({
|
|||||||
HttpService.makeCallToBackend({
|
HttpService.makeCallToBackend({
|
||||||
path: `${apiPath}?${queryParamString}`,
|
path: `${apiPath}?${queryParamString}`,
|
||||||
successCallback: setProcessInstancesFromResult,
|
successCallback: setProcessInstancesFromResult,
|
||||||
|
onUnauthorized: stopRefreshing,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function processResultForProcessModels(result: any) {
|
function processResultForProcessModels(result: any) {
|
||||||
@ -387,11 +404,12 @@ export default function ProcessInstanceListTable({
|
|||||||
|
|
||||||
checkFiltersAndRun();
|
checkFiltersAndRun();
|
||||||
if (autoReload) {
|
if (autoReload) {
|
||||||
return refreshAtInterval(
|
clearRefreshRef.current = refreshAtInterval(
|
||||||
REFRESH_INTERVAL_SECONDS,
|
REFRESH_INTERVAL_SECONDS,
|
||||||
REFRESH_TIMEOUT_SECONDS,
|
REFRESH_TIMEOUT_SECONDS,
|
||||||
checkFiltersAndRun
|
checkFiltersAndRun
|
||||||
);
|
);
|
||||||
|
return clearRefreshRef.current;
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}, [
|
}, [
|
||||||
@ -1294,6 +1312,111 @@ export default function ProcessInstanceListTable({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getWaitingForTableCellComponent = (processInstanceTask: any) => {
|
||||||
|
let fullUsernameString = '';
|
||||||
|
let shortUsernameString = '';
|
||||||
|
if (processInstanceTask.potential_owner_usernames) {
|
||||||
|
fullUsernameString = processInstanceTask.potential_owner_usernames;
|
||||||
|
const usernames =
|
||||||
|
processInstanceTask.potential_owner_usernames.split(',');
|
||||||
|
const firstTwoUsernames = usernames.slice(0, 2);
|
||||||
|
if (usernames.length > 2) {
|
||||||
|
firstTwoUsernames.push('...');
|
||||||
|
}
|
||||||
|
shortUsernameString = firstTwoUsernames.join(',');
|
||||||
|
}
|
||||||
|
if (processInstanceTask.assigned_user_group_identifier) {
|
||||||
|
fullUsernameString = processInstanceTask.assigned_user_group_identifier;
|
||||||
|
shortUsernameString = processInstanceTask.assigned_user_group_identifier;
|
||||||
|
}
|
||||||
|
return <span title={fullUsernameString}>{shortUsernameString}</span>;
|
||||||
|
};
|
||||||
|
const formatProcessInstanceId = (row: ProcessInstance, id: number) => {
|
||||||
|
return <span data-qa="paginated-entity-id">{id}</span>;
|
||||||
|
};
|
||||||
|
const formatProcessModelIdentifier = (_row: any, identifier: any) => {
|
||||||
|
return <span>{identifier}</span>;
|
||||||
|
};
|
||||||
|
const formatProcessModelDisplayName = (_row: any, identifier: any) => {
|
||||||
|
return <span>{identifier}</span>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatSecondsForDisplay = (_row: any, seconds: any) => {
|
||||||
|
return convertSecondsToFormattedDateTime(seconds) || '-';
|
||||||
|
};
|
||||||
|
const defaultFormatter = (_row: any, value: any) => {
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formattedColumn = (row: any, column: any) => {
|
||||||
|
const reportColumnFormatters: Record<string, any> = {
|
||||||
|
id: formatProcessInstanceId,
|
||||||
|
process_model_identifier: formatProcessModelIdentifier,
|
||||||
|
process_model_display_name: formatProcessModelDisplayName,
|
||||||
|
start_in_seconds: formatSecondsForDisplay,
|
||||||
|
end_in_seconds: formatSecondsForDisplay,
|
||||||
|
updated_at_in_seconds: formatSecondsForDisplay,
|
||||||
|
};
|
||||||
|
const formatter =
|
||||||
|
reportColumnFormatters[column.accessor] ?? defaultFormatter;
|
||||||
|
const value = row[column.accessor];
|
||||||
|
const modifiedModelId = modifyProcessIdentifierForPathParam(
|
||||||
|
row.process_model_identifier
|
||||||
|
);
|
||||||
|
const navigateToProcessInstance = () => {
|
||||||
|
navigate(`${processInstanceShowPathPrefix}/${modifiedModelId}/${row.id}`);
|
||||||
|
};
|
||||||
|
const navigateToProcessModel = () => {
|
||||||
|
navigate(`/admin/process-models/${modifiedModelId}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (column.accessor === 'status') {
|
||||||
|
return (
|
||||||
|
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
|
||||||
|
<td
|
||||||
|
onClick={navigateToProcessInstance}
|
||||||
|
onKeyDown={navigateToProcessInstance}
|
||||||
|
data-qa={`process-instance-status-${value}`}
|
||||||
|
>
|
||||||
|
{formatter(row, value)}
|
||||||
|
</td>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (column.accessor === 'process_model_display_name') {
|
||||||
|
const pmStyle = { background: 'rgba(0, 0, 0, .02)' };
|
||||||
|
return (
|
||||||
|
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
|
||||||
|
<td
|
||||||
|
style={pmStyle}
|
||||||
|
onClick={navigateToProcessModel}
|
||||||
|
onKeyDown={navigateToProcessModel}
|
||||||
|
>
|
||||||
|
{formatter(row, value)}
|
||||||
|
</td>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (column.accessor === 'waiting_for') {
|
||||||
|
return <td>{getWaitingForTableCellComponent(row)}</td>;
|
||||||
|
}
|
||||||
|
if (column.accessor === 'updated_at_in_seconds') {
|
||||||
|
return (
|
||||||
|
<TableCellWithTimeAgoInWords
|
||||||
|
timeInSeconds={row.updated_at_in_seconds}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
|
||||||
|
<td
|
||||||
|
data-qa={`process-instance-show-link-${column.accessor}`}
|
||||||
|
onKeyDown={navigateToProcessModel}
|
||||||
|
onClick={navigateToProcessInstance}
|
||||||
|
>
|
||||||
|
{formatter(row, value)}
|
||||||
|
</td>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const buildTable = () => {
|
const buildTable = () => {
|
||||||
const headerLabels: Record<string, string> = {
|
const headerLabels: Record<string, string> = {
|
||||||
id: 'Id',
|
id: 'Id',
|
||||||
@ -1308,92 +1431,45 @@ export default function ProcessInstanceListTable({
|
|||||||
return headerLabels[header] ?? header;
|
return headerLabels[header] ?? header;
|
||||||
};
|
};
|
||||||
const headers = reportColumns().map((column: any) => {
|
const headers = reportColumns().map((column: any) => {
|
||||||
// return <th>{getHeaderLabel((column as any).Header)}</th>;
|
|
||||||
return getHeaderLabel((column as any).Header);
|
return getHeaderLabel((column as any).Header);
|
||||||
});
|
});
|
||||||
|
if (showActionsColumn) {
|
||||||
const formatProcessInstanceId = (row: ProcessInstance, id: number) => {
|
headers.push('Actions');
|
||||||
return <span data-qa="paginated-entity-id">{id}</span>;
|
}
|
||||||
};
|
|
||||||
const formatProcessModelIdentifier = (_row: any, identifier: any) => {
|
|
||||||
return <span>{identifier}</span>;
|
|
||||||
};
|
|
||||||
const formatProcessModelDisplayName = (_row: any, identifier: any) => {
|
|
||||||
return <span>{identifier}</span>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatSecondsForDisplay = (_row: any, seconds: any) => {
|
|
||||||
return convertSecondsToFormattedDateTime(seconds) || '-';
|
|
||||||
};
|
|
||||||
const defaultFormatter = (_row: any, value: any) => {
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
|
|
||||||
const reportColumnFormatters: Record<string, any> = {
|
|
||||||
id: formatProcessInstanceId,
|
|
||||||
process_model_identifier: formatProcessModelIdentifier,
|
|
||||||
process_model_display_name: formatProcessModelDisplayName,
|
|
||||||
start_in_seconds: formatSecondsForDisplay,
|
|
||||||
end_in_seconds: formatSecondsForDisplay,
|
|
||||||
};
|
|
||||||
const formattedColumn = (row: any, column: any) => {
|
|
||||||
const formatter =
|
|
||||||
reportColumnFormatters[column.accessor] ?? defaultFormatter;
|
|
||||||
const value = row[column.accessor];
|
|
||||||
const modifiedModelId = modifyProcessIdentifierForPathParam(
|
|
||||||
row.process_model_identifier
|
|
||||||
);
|
|
||||||
const navigateToProcessInstance = () => {
|
|
||||||
navigate(
|
|
||||||
`${processInstanceShowPathPrefix}/${modifiedModelId}/${row.id}`
|
|
||||||
);
|
|
||||||
};
|
|
||||||
const navigateToProcessModel = () => {
|
|
||||||
navigate(`/admin/process-models/${modifiedModelId}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (column.accessor === 'status') {
|
|
||||||
return (
|
|
||||||
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
|
|
||||||
<td
|
|
||||||
onClick={navigateToProcessInstance}
|
|
||||||
onKeyDown={navigateToProcessInstance}
|
|
||||||
data-qa={`process-instance-status-${value}`}
|
|
||||||
>
|
|
||||||
{formatter(row, value)}
|
|
||||||
</td>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (column.accessor === 'process_model_display_name') {
|
|
||||||
const pmStyle = { background: 'rgba(0, 0, 0, .02)' };
|
|
||||||
return (
|
|
||||||
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
|
|
||||||
<td
|
|
||||||
style={pmStyle}
|
|
||||||
onClick={navigateToProcessModel}
|
|
||||||
onKeyDown={navigateToProcessModel}
|
|
||||||
>
|
|
||||||
{formatter(row, value)}
|
|
||||||
</td>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
|
|
||||||
<td
|
|
||||||
data-qa={`process-instance-show-link-${column.accessor}`}
|
|
||||||
onKeyDown={navigateToProcessModel}
|
|
||||||
onClick={navigateToProcessInstance}
|
|
||||||
>
|
|
||||||
{formatter(row, value)}
|
|
||||||
</td>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const rows = processInstances.map((row: any) => {
|
const rows = processInstances.map((row: any) => {
|
||||||
const currentRow = reportColumns().map((column: any) => {
|
const currentRow = reportColumns().map((column: any) => {
|
||||||
return formattedColumn(row, column);
|
return formattedColumn(row, column);
|
||||||
});
|
});
|
||||||
|
if (showActionsColumn) {
|
||||||
|
let buttonElement = null;
|
||||||
|
if (row.task_id) {
|
||||||
|
const taskUrl = `/tasks/${row.id}/${row.task_id}`;
|
||||||
|
const regex = new RegExp(`\\b(${preferredUsername}|${userEmail})\\b`);
|
||||||
|
let hasAccessToCompleteTask = false;
|
||||||
|
if (
|
||||||
|
canCompleteAllTasks ||
|
||||||
|
(row.potential_owner_usernames || '').match(regex)
|
||||||
|
) {
|
||||||
|
hasAccessToCompleteTask = true;
|
||||||
|
}
|
||||||
|
buttonElement = (
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
href={taskUrl}
|
||||||
|
hidden={row.status === 'suspended'}
|
||||||
|
disabled={!hasAccessToCompleteTask}
|
||||||
|
>
|
||||||
|
Go
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentRow.push(<td>{buttonElement}</td>);
|
||||||
|
}
|
||||||
|
|
||||||
const rowStyle = { cursor: 'pointer' };
|
const rowStyle = { cursor: 'pointer' };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr style={rowStyle} key={row.id}>
|
<tr style={rowStyle} key={row.id}>
|
||||||
{currentRow}
|
{currentRow}
|
||||||
|
@ -467,7 +467,9 @@ export default function ReactDiagramEditor({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
diagramModelerToUse.importXML(diagramXMLToDisplay).then(() => {
|
diagramModelerToUse.importXML(diagramXMLToDisplay).then(() => {
|
||||||
diagramModelerToUse.get('canvas').zoom('fit-viewport');
|
if (diagramType === 'bpmn' || diagramType === 'readonly') {
|
||||||
|
diagramModelerToUse.get('canvas').zoom('fit-viewport');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
alreadyImportedXmlRef.current = true;
|
alreadyImportedXmlRef.current = true;
|
||||||
|
@ -336,10 +336,13 @@ td.actions-cell {
|
|||||||
width: 1em;
|
width: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.process-instance-table-header {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
.no-results-message {
|
.no-results-message {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
margin-left: 2em;
|
margin-left: 2em;
|
||||||
margin-top: 1em;
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,13 +18,12 @@ export default function CompletedInstances() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return userGroups.map((userGroup: string) => {
|
return userGroups.map((userGroup: string) => {
|
||||||
|
const titleText = `This is a list of instances with tasks that were completed by the ${userGroup} group.`;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h2>With tasks completed by group: {userGroup}</h2>
|
<h2 title={titleText} className="process-instance-table-header">
|
||||||
<p className="data-table-description">
|
With tasks completed by <strong>{userGroup}</strong>
|
||||||
This is a list of instances with tasks that were completed by the{' '}
|
</h2>
|
||||||
{userGroup} group.
|
|
||||||
</p>
|
|
||||||
<ProcessInstanceListTable
|
<ProcessInstanceListTable
|
||||||
filtersEnabled={false}
|
filtersEnabled={false}
|
||||||
paginationQueryParamPrefix="group_completed_instances"
|
paginationQueryParamPrefix="group_completed_instances"
|
||||||
@ -40,12 +39,19 @@ export default function CompletedInstances() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const startedByMeTitleText =
|
||||||
|
'This is a list of instances you started that are now complete.';
|
||||||
|
const withTasksCompletedByMeTitleText =
|
||||||
|
'This is a list of instances where you have completed tasks.';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h2>My completed instances</h2>
|
<h2
|
||||||
<p className="data-table-description">
|
title={startedByMeTitleText}
|
||||||
This is a list of instances you started that are now complete.
|
className="process-instance-table-header"
|
||||||
</p>
|
>
|
||||||
|
Started by me
|
||||||
|
</h2>
|
||||||
<ProcessInstanceListTable
|
<ProcessInstanceListTable
|
||||||
filtersEnabled={false}
|
filtersEnabled={false}
|
||||||
paginationQueryParamPrefix="my_completed_instances"
|
paginationQueryParamPrefix="my_completed_instances"
|
||||||
@ -56,10 +62,12 @@ export default function CompletedInstances() {
|
|||||||
paginationClassName="with-large-bottom-margin"
|
paginationClassName="with-large-bottom-margin"
|
||||||
autoReload
|
autoReload
|
||||||
/>
|
/>
|
||||||
<h2>With tasks completed by me</h2>
|
<h2
|
||||||
<p className="data-table-description">
|
title={withTasksCompletedByMeTitleText}
|
||||||
This is a list of instances where you have completed tasks.
|
className="process-instance-table-header"
|
||||||
</p>
|
>
|
||||||
|
With tasks completed by me
|
||||||
|
</h2>
|
||||||
<ProcessInstanceListTable
|
<ProcessInstanceListTable
|
||||||
filtersEnabled={false}
|
filtersEnabled={false}
|
||||||
paginationQueryParamPrefix="my_completed_tasks"
|
paginationQueryParamPrefix="my_completed_tasks"
|
||||||
|
@ -4,9 +4,9 @@ import { Route, Routes, useLocation, useNavigate } from 'react-router-dom';
|
|||||||
import { Tabs, TabList, Tab } from '@carbon/react';
|
import { Tabs, TabList, Tab } from '@carbon/react';
|
||||||
import TaskShow from './TaskShow';
|
import TaskShow from './TaskShow';
|
||||||
import MyTasks from './MyTasks';
|
import MyTasks from './MyTasks';
|
||||||
import GroupedTasks from './GroupedTasks';
|
|
||||||
import CompletedInstances from './CompletedInstances';
|
import CompletedInstances from './CompletedInstances';
|
||||||
import CreateNewInstance from './CreateNewInstance';
|
import CreateNewInstance from './CreateNewInstance';
|
||||||
|
import InProgressInstances from './InProgressInstances';
|
||||||
|
|
||||||
export default function HomePageRoutes() {
|
export default function HomePageRoutes() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@ -51,10 +51,10 @@ export default function HomePageRoutes() {
|
|||||||
<>
|
<>
|
||||||
{renderTabs()}
|
{renderTabs()}
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<GroupedTasks />} />
|
<Route path="/" element={<InProgressInstances />} />
|
||||||
<Route path="my-tasks" element={<MyTasks />} />
|
<Route path="my-tasks" element={<MyTasks />} />
|
||||||
<Route path=":process_instance_id/:task_id" element={<TaskShow />} />
|
<Route path=":process_instance_id/:task_id" element={<TaskShow />} />
|
||||||
<Route path="grouped" element={<GroupedTasks />} />
|
<Route path="grouped" element={<InProgressInstances />} />
|
||||||
<Route path="completed-instances" element={<CompletedInstances />} />
|
<Route path="completed-instances" element={<CompletedInstances />} />
|
||||||
<Route path="create-new-instance" element={<CreateNewInstance />} />
|
<Route path="create-new-instance" element={<CreateNewInstance />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
92
spiffworkflow-frontend/src/routes/InProgressInstances.tsx
Normal file
92
spiffworkflow-frontend/src/routes/InProgressInstances.tsx
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import ProcessInstanceListTable from '../components/ProcessInstanceListTable';
|
||||||
|
import { slugifyString } from '../helpers';
|
||||||
|
import HttpService from '../services/HttpService';
|
||||||
|
|
||||||
|
export default function InProgressInstances() {
|
||||||
|
const [userGroups, setUserGroups] = useState<string[] | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
HttpService.makeCallToBackend({
|
||||||
|
path: `/user-groups/for-current-user`,
|
||||||
|
successCallback: setUserGroups,
|
||||||
|
});
|
||||||
|
}, [setUserGroups]);
|
||||||
|
|
||||||
|
const groupTableComponents = () => {
|
||||||
|
if (!userGroups) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return userGroups.map((userGroup: string) => {
|
||||||
|
const titleText = `This is a list of instances with tasks that are waiting for the ${userGroup} group.`;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h2 title={titleText} className="process-instance-table-header">
|
||||||
|
Waiting for <strong>{userGroup}</strong>
|
||||||
|
</h2>
|
||||||
|
<ProcessInstanceListTable
|
||||||
|
filtersEnabled={false}
|
||||||
|
paginationQueryParamPrefix={`waiting_for_${slugifyString(
|
||||||
|
userGroup
|
||||||
|
).replace('-', '_')}`}
|
||||||
|
paginationClassName="with-large-bottom-margin"
|
||||||
|
perPageOptions={[2, 5, 25]}
|
||||||
|
reportIdentifier="system_report_in_progress_instances_with_tasks_for_my_group"
|
||||||
|
showReports={false}
|
||||||
|
textToShowIfEmpty="This group has no instances waiting on it at this time."
|
||||||
|
additionalParams={`user_group_identifier=${userGroup}`}
|
||||||
|
canCompleteAllTasks
|
||||||
|
showActionsColumn
|
||||||
|
autoReload
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const startedByMeTitleText =
|
||||||
|
'This is a list of open instances that you started.';
|
||||||
|
const waitingForMeTitleText =
|
||||||
|
'This is a list of instances that have tasks that you can complete.';
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h2
|
||||||
|
title={startedByMeTitleText}
|
||||||
|
className="process-instance-table-header"
|
||||||
|
>
|
||||||
|
Started by me
|
||||||
|
</h2>
|
||||||
|
<ProcessInstanceListTable
|
||||||
|
filtersEnabled={false}
|
||||||
|
paginationQueryParamPrefix="open_instances_started_by_me"
|
||||||
|
perPageOptions={[2, 5, 25]}
|
||||||
|
reportIdentifier="system_report_in_progress_instances_initiated_by_me"
|
||||||
|
showReports={false}
|
||||||
|
textToShowIfEmpty="There are no open instances you started at this time."
|
||||||
|
paginationClassName="with-large-bottom-margin"
|
||||||
|
showActionsColumn
|
||||||
|
autoReload
|
||||||
|
/>
|
||||||
|
<h2
|
||||||
|
title={waitingForMeTitleText}
|
||||||
|
className="process-instance-table-header"
|
||||||
|
>
|
||||||
|
Waiting for me
|
||||||
|
</h2>
|
||||||
|
<ProcessInstanceListTable
|
||||||
|
filtersEnabled={false}
|
||||||
|
paginationQueryParamPrefix="waiting_for_me"
|
||||||
|
perPageOptions={[2, 5, 25]}
|
||||||
|
reportIdentifier="system_report_in_progress_instances_with_tasks_for_me"
|
||||||
|
showReports={false}
|
||||||
|
textToShowIfEmpty="There are no instances waiting on you at this time."
|
||||||
|
paginationClassName="with-large-bottom-margin"
|
||||||
|
canCompleteAllTasks
|
||||||
|
showActionsColumn
|
||||||
|
autoReload
|
||||||
|
/>
|
||||||
|
{groupTableComponents()}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -19,10 +19,7 @@ import MDEditor from '@uiw/react-md-editor';
|
|||||||
import Form from '../themes/carbon';
|
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 {
|
import { modifyProcessIdentifierForPathParam } from '../helpers';
|
||||||
dateStringToYMDFormat,
|
|
||||||
modifyProcessIdentifierForPathParam,
|
|
||||||
} from '../helpers';
|
|
||||||
import { ProcessInstanceTask } from '../interfaces';
|
import { ProcessInstanceTask } from '../interfaces';
|
||||||
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user