From 3984aa16aa690b96f56b5068c45b46090047368b Mon Sep 17 00:00:00 2001
From: jasquat
Date: Tue, 25 Apr 2023 17:21:38 -0400
Subject: [PATCH 01/18] attempting to filter process instances with json
instead of report filters w/ burnettk
---
.../src/spiffworkflow_backend/api.yml | 4 +-
.../models/process_instance_report.py | 16 +-
.../routes/process_instances_controller.py | 156 ++++----
.../process_instance_report_service.py | 358 +++++++++---------
.../components/ProcessInstanceListTable.tsx | 14 +-
5 files changed, 289 insertions(+), 259 deletions(-)
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml
index c37bd380..e87cc395 100755
--- a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml
@@ -697,7 +697,7 @@ paths:
description: Base64 encoded json of report filter by.
schema:
type: string
- get:
+ post:
operationId: spiffworkflow_backend.routes.process_instances_controller.process_instance_list_for_me
summary: Returns a list of process instances that are associated with me.
tags:
@@ -828,7 +828,7 @@ paths:
description: Base64 encoded json of report filter by.
schema:
type: string
- get:
+ post:
operationId: spiffworkflow_backend.routes.process_instances_controller.process_instance_list
summary: Returns a list of process instances.
tags:
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance_report.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance_report.py
index ad2041cb..2c8a442d 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance_report.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance_report.py
@@ -22,7 +22,21 @@ from spiffworkflow_backend.services.process_instance_processor import (
)
-ReportMetadata = dict[str, Any]
+class FilterValue(TypedDict):
+ field_name: str
+ field_value: str
+ operator: str
+
+
+class ReportMetadataColumn(TypedDict):
+ Header: str
+ accessor: str
+
+
+class ReportMetadata(TypedDict):
+ columns: list[ReportMetadataColumn]
+ filter_by: list[FilterValue]
+ order_by: list[str]
class ProcessInstanceReportAlreadyExistsError(Exception):
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py
index 5b74ba03..dc0f2d76 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py
@@ -226,103 +226,107 @@ def process_instance_resume(
def process_instance_list_for_me(
+ body: Dict[str, Any],
process_model_identifier: Optional[str] = None,
page: int = 1,
per_page: int = 100,
- start_from: Optional[int] = None,
- start_to: Optional[int] = None,
- end_from: Optional[int] = None,
- end_to: Optional[int] = None,
- process_status: Optional[str] = None,
- user_filter: Optional[bool] = False,
- report_identifier: Optional[str] = None,
- report_id: Optional[int] = None,
- user_group_identifier: Optional[str] = None,
- process_initiator_username: Optional[str] = None,
- report_columns: Optional[str] = None,
- report_filter_by: Optional[str] = None,
+ # start_from: Optional[int] = None,
+ # start_to: Optional[int] = None,
+ # end_from: Optional[int] = None,
+ # end_to: Optional[int] = None,
+ # process_status: Optional[str] = None,
+ # user_filter: Optional[bool] = False,
+ # report_identifier: Optional[str] = None,
+ # report_id: Optional[int] = None,
+ # user_group_identifier: Optional[str] = None,
+ # process_initiator_username: Optional[str] = None,
+ # report_columns: Optional[str] = None,
+ # report_filter_by: Optional[str] = None,
) -> flask.wrappers.Response:
"""Process_instance_list_for_me."""
return process_instance_list(
process_model_identifier=process_model_identifier,
page=page,
per_page=per_page,
- start_from=start_from,
- start_to=start_to,
- end_from=end_from,
- end_to=end_to,
- process_status=process_status,
- user_filter=user_filter,
- report_identifier=report_identifier,
- report_id=report_id,
- user_group_identifier=user_group_identifier,
- with_relation_to_me=True,
- report_columns=report_columns,
- report_filter_by=report_filter_by,
- process_initiator_username=process_initiator_username,
+ # start_from=start_from,
+ # start_to=start_to,
+ # end_from=end_from,
+ # end_to=end_to,
+ # process_status=process_status,
+ # user_filter=user_filter,
+ # report_identifier=report_identifier,
+ # report_id=report_id,
+ # user_group_identifier=user_group_identifier,
+ # with_relation_to_me=True,
+ # report_columns=report_columns,
+ # report_filter_by=report_filter_by,
+ # process_initiator_username=process_initiator_username,
+ body=body
)
def process_instance_list(
+ body: Dict[str, Any],
process_model_identifier: Optional[str] = None,
page: int = 1,
per_page: int = 100,
- start_from: Optional[int] = None,
- start_to: Optional[int] = None,
- end_from: Optional[int] = None,
- end_to: Optional[int] = None,
- process_status: Optional[str] = None,
- with_relation_to_me: Optional[bool] = None,
- user_filter: Optional[bool] = False,
- report_identifier: Optional[str] = None,
- report_id: Optional[int] = None,
- user_group_identifier: Optional[str] = None,
- process_initiator_username: Optional[str] = None,
- report_columns: Optional[str] = None,
- report_filter_by: Optional[str] = None,
+ # start_from: Optional[int] = None,
+ # start_to: Optional[int] = None,
+ # end_from: Optional[int] = None,
+ # end_to: Optional[int] = None,
+ # process_status: Optional[str] = None,
+ # with_relation_to_me: Optional[bool] = None,
+ # user_filter: Optional[bool] = False,
+ # report_identifier: Optional[str] = None,
+ # report_id: Optional[int] = None,
+ # user_group_identifier: Optional[str] = None,
+ # process_initiator_username: Optional[str] = None,
+ # report_columns: Optional[str] = None,
+ # report_filter_by: Optional[str] = None,
) -> flask.wrappers.Response:
- """Process_instance_list."""
- process_instance_report = ProcessInstanceReportService.report_with_identifier(g.user, report_id, report_identifier)
+ # process_instance_report = ProcessInstanceReportService.report_with_identifier(g.user, body['report_id'], body['report_identifier'])
+ process_instance_report = ProcessInstanceReportService.report_with_identifier(g.user)
- report_column_list = None
- if report_columns:
- report_column_list = json.loads(base64.b64decode(report_columns))
- report_filter_by_list = None
- if report_filter_by:
- report_filter_by_list = json.loads(base64.b64decode(report_filter_by))
+ # report_column_list = None
+ # if report_columns:
+ # report_column_list = json.loads(base64.b64decode(report_columns))
+ # report_filter_by_list = None
+ # if report_filter_by:
+ # report_filter_by_list = json.loads(base64.b64decode(report_filter_by))
- if user_filter:
- report_filter = ProcessInstanceReportFilter(
- process_model_identifier=process_model_identifier,
- user_group_identifier=user_group_identifier,
- start_from=start_from,
- start_to=start_to,
- end_from=end_from,
- end_to=end_to,
- with_relation_to_me=with_relation_to_me,
- process_status=process_status.split(",") if process_status else None,
- process_initiator_username=process_initiator_username,
- report_column_list=report_column_list,
- report_filter_by_list=report_filter_by_list,
- )
- else:
- report_filter = ProcessInstanceReportService.filter_from_metadata_with_overrides(
- process_instance_report=process_instance_report,
- process_model_identifier=process_model_identifier,
- user_group_identifier=user_group_identifier,
- start_from=start_from,
- start_to=start_to,
- end_from=end_from,
- end_to=end_to,
- process_status=process_status,
- with_relation_to_me=with_relation_to_me,
- process_initiator_username=process_initiator_username,
- report_column_list=report_column_list,
- report_filter_by_list=report_filter_by_list,
- )
+ # if user_filter:
+ # report_filter = ProcessInstanceReportFilter(
+ # process_model_identifier=process_model_identifier,
+ # # user_group_identifier=user_group_identifier,
+ # # start_from=start_from,
+ # # start_to=start_to,
+ # # end_from=end_from,
+ # # end_to=end_to,
+ # # with_relation_to_me=with_relation_to_me,
+ # # process_status=process_status.split(",") if process_status else None,
+ # # process_initiator_username=process_initiator_username,
+ # # report_column_list=report_column_list,
+ # # report_filter_by_list=report_filter_by_list,
+ # )
+ # else:
+ # report_filter = ProcessInstanceReportService.filter_from_metadata_with_overrides(
+ # process_instance_report=process_instance_report,
+ # process_model_identifier=process_model_identifier,
+ # # user_group_identifier=user_group_identifier,
+ # # start_from=start_from,
+ # # start_to=start_to,
+ # # end_from=end_from,
+ # # end_to=end_to,
+ # # process_status=process_status,
+ # # with_relation_to_me=with_relation_to_me,
+ # # process_initiator_username=process_initiator_username,
+ # # report_column_list=report_column_list,
+ # # report_filter_by_list=report_filter_by_list,
+ # )
response_json = ProcessInstanceReportService.run_process_instance_report(
- report_filter=report_filter,
+ # report_filter=report_filter,
+ report_metadata=body['report_metadata'],
process_instance_report=process_instance_report,
page=page,
per_page=per_page,
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py
index 04510969..a4cadb96 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py
@@ -1,7 +1,7 @@
"""Process_instance_report_service."""
import re
from dataclasses import dataclass
-from typing import Any
+from typing import Any, Generator, Iterable
from typing import Optional
from typing import Type
@@ -24,7 +24,9 @@ from spiffworkflow_backend.models.process_instance_metadata import (
ProcessInstanceMetadataModel,
)
from spiffworkflow_backend.models.process_instance_report import (
+ FilterValue,
ProcessInstanceReportModel,
+ ReportMetadata,
)
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.models.user_group_assignment import UserGroupAssignmentModel
@@ -236,7 +238,6 @@ class ProcessInstanceReportService:
report_id: Optional[int] = None,
report_identifier: Optional[str] = None,
) -> ProcessInstanceReportModel:
- """Report_with_filter."""
if report_id is not None:
process_instance_report = ProcessInstanceReportModel.query.filter_by(
id=report_id, created_by_id=user.id
@@ -496,25 +497,31 @@ class ProcessInstanceReportService:
{"Header": "Status", "accessor": "status", "filterable": False},
]
+ @classmethod
+ def blah(cls, filters: list[FilterValue], filter_key: str) -> Generator:
+ for filter in filters:
+ if filter['field_name'] == filter_key and filter['field_value'] is not None:
+ yield filter['field_value']
+
@classmethod
def run_process_instance_report(
cls,
- report_filter: ProcessInstanceReportFilter,
+ # report_filter: ProcessInstanceReportFilter,
+ report_metadata: ReportMetadata,
process_instance_report: ProcessInstanceReportModel,
user: UserModel,
page: int = 1,
per_page: int = 100,
) -> dict:
- """Run_process_instance_report."""
process_instance_query = ProcessInstanceModel.query
# Always join that hot user table for good performance at serialization time.
process_instance_query = process_instance_query.options(selectinload(ProcessInstanceModel.process_initiator))
+ filters = report_metadata['filter_by']
- if report_filter.process_model_identifier is not None:
+ for value in cls.blah(filters, 'process_model_identifier'):
process_model = ProcessModelService.get_process_model(
- f"{report_filter.process_model_identifier}",
+ f"{value}",
)
-
process_instance_query = process_instance_query.filter_by(process_model_identifier=process_model.id)
# this can never happen. obviously the class has the columns it defines. this is just to appease mypy.
@@ -527,187 +534,188 @@ class ProcessInstanceReportService:
)
)
- if report_filter.start_from is not None:
- process_instance_query = process_instance_query.filter(
- ProcessInstanceModel.start_in_seconds >= report_filter.start_from
- )
- if report_filter.start_to is not None:
- process_instance_query = process_instance_query.filter(
- ProcessInstanceModel.start_in_seconds <= report_filter.start_to
- )
- if report_filter.end_from is not None:
- process_instance_query = process_instance_query.filter(
- ProcessInstanceModel.end_in_seconds >= report_filter.end_from
- )
- if report_filter.end_to is not None:
- process_instance_query = process_instance_query.filter(
- ProcessInstanceModel.end_in_seconds <= report_filter.end_to
- )
- if report_filter.process_status is not None:
- process_instance_query = process_instance_query.filter(
- ProcessInstanceModel.status.in_(report_filter.process_status) # type: ignore
- )
-
- if report_filter.initiated_by_me is True:
- process_instance_query = process_instance_query.filter_by(process_initiator=user)
-
- if report_filter.has_terminal_status is True:
- process_instance_query = process_instance_query.filter(
- 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:
- initiator = UserModel.query.filter_by(username=report_filter.process_initiator_username).first()
- process_initiator_id = -1
- if initiator:
- process_initiator_id = initiator.id
- process_instance_query = process_instance_query.filter_by(process_initiator_id=process_initiator_id)
-
- if (
- not report_filter.with_tasks_completed_by_me
- 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
- ):
- process_instance_query = process_instance_query.outerjoin(HumanTaskModel).outerjoin(
- HumanTaskUserModel,
- and_(
- HumanTaskModel.id == HumanTaskUserModel.human_task_id,
- HumanTaskUserModel.user_id == user.id,
- ),
- )
- process_instance_query = process_instance_query.filter(
- or_(
- HumanTaskUserModel.id.is_not(None),
- ProcessInstanceModel.process_initiator_id == user.id,
- )
- )
-
- if report_filter.with_tasks_completed_by_me 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.completed_by_user_id == user.id,
- ),
- )
-
- 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.has_active_status:
- process_instance_query = process_instance_query.filter(
- HumanTaskModel.completed.is_(False) # type: ignore
- )
-
- if report_filter.with_tasks_assigned_to_my_group is True:
- group_model_join_conditions = [GroupModel.id == HumanTaskModel.lane_assignment_id]
- if report_filter.user_group_identifier:
- group_model_join_conditions.append(GroupModel.identifier == report_filter.user_group_identifier)
-
- process_instance_query = process_instance_query.join(HumanTaskModel)
- if report_filter.has_active_status:
- process_instance_query = process_instance_query.filter(
- HumanTaskModel.completed.is_(False) # type: ignore
- )
-
- process_instance_query = process_instance_query.join(GroupModel, and_(*group_model_join_conditions))
- process_instance_query = process_instance_query.join(
- UserGroupAssignmentModel,
- UserGroupAssignmentModel.group_id == GroupModel.id,
- )
- process_instance_query = process_instance_query.filter(UserGroupAssignmentModel.user_id == user.id)
-
- instance_metadata_aliases = {}
- stock_columns = cls.get_column_names_for_model(ProcessInstanceModel)
- if isinstance(report_filter.report_column_list, list):
- process_instance_report.report_metadata["columns"] = report_filter.report_column_list
- if isinstance(report_filter.report_filter_by_list, list):
- process_instance_report.report_metadata["filter_by"] = report_filter.report_filter_by_list
-
- for column in process_instance_report.report_metadata["columns"]:
- if column["accessor"] in stock_columns:
- continue
- instance_metadata_alias = aliased(ProcessInstanceMetadataModel)
- instance_metadata_aliases[column["accessor"]] = instance_metadata_alias
-
- filter_for_column = None
- if "filter_by" in process_instance_report.report_metadata:
- filter_for_column = next(
- (
- f
- for f in process_instance_report.report_metadata["filter_by"]
- if f["field_name"] == column["accessor"]
- ),
- None,
- )
- isouter = True
- conditions = [
- ProcessInstanceModel.id == instance_metadata_alias.process_instance_id,
- instance_metadata_alias.key == column["accessor"],
- ]
- if filter_for_column:
- isouter = False
- conditions.append(instance_metadata_alias.value == filter_for_column["field_value"])
- process_instance_query = process_instance_query.join(
- instance_metadata_alias, and_(*conditions), isouter=isouter
- ).add_columns(func.max(instance_metadata_alias.value).label(column["accessor"]))
-
- order_by_query_array = []
- order_by_array = process_instance_report.report_metadata["order_by"]
- if len(order_by_array) < 1:
- order_by_array = ProcessInstanceReportModel.default_order_by()
- for order_by_option in order_by_array:
- attribute = re.sub("^-", "", order_by_option)
- if attribute in stock_columns:
- if order_by_option.startswith("-"):
- order_by_query_array.append(getattr(ProcessInstanceModel, attribute).desc())
- else:
- order_by_query_array.append(getattr(ProcessInstanceModel, attribute).asc())
- elif attribute in instance_metadata_aliases:
- if order_by_option.startswith("-"):
- order_by_query_array.append(func.max(instance_metadata_aliases[attribute].value).desc())
- else:
- order_by_query_array.append(func.max(instance_metadata_aliases[attribute].value).asc())
+ # if report_filter.start_from is not None:
+ # process_instance_query = process_instance_query.filter(
+ # ProcessInstanceModel.start_in_seconds >= report_filter.start_from
+ # )
+ # if report_filter.start_to is not None:
+ # process_instance_query = process_instance_query.filter(
+ # ProcessInstanceModel.start_in_seconds <= report_filter.start_to
+ # )
+ # if report_filter.end_from is not None:
+ # process_instance_query = process_instance_query.filter(
+ # ProcessInstanceModel.end_in_seconds >= report_filter.end_from
+ # )
+ # if report_filter.end_to is not None:
+ # process_instance_query = process_instance_query.filter(
+ # ProcessInstanceModel.end_in_seconds <= report_filter.end_to
+ # )
+ # if report_filter.process_status is not None:
+ # process_instance_query = process_instance_query.filter(
+ # ProcessInstanceModel.status.in_(report_filter.process_status) # type: ignore
+ # )
+ #
+ # if report_filter.initiated_by_me is True:
+ # process_instance_query = process_instance_query.filter_by(process_initiator=user)
+ #
+ # if report_filter.has_terminal_status is True:
+ # process_instance_query = process_instance_query.filter(
+ # 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:
+ # initiator = UserModel.query.filter_by(username=report_filter.process_initiator_username).first()
+ # process_initiator_id = -1
+ # if initiator:
+ # process_initiator_id = initiator.id
+ # process_instance_query = process_instance_query.filter_by(process_initiator_id=process_initiator_id)
+ #
+ # if (
+ # not report_filter.with_tasks_completed_by_me
+ # 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
+ # ):
+ # process_instance_query = process_instance_query.outerjoin(HumanTaskModel).outerjoin(
+ # HumanTaskUserModel,
+ # and_(
+ # HumanTaskModel.id == HumanTaskUserModel.human_task_id,
+ # HumanTaskUserModel.user_id == user.id,
+ # ),
+ # )
+ # process_instance_query = process_instance_query.filter(
+ # or_(
+ # HumanTaskUserModel.id.is_not(None),
+ # ProcessInstanceModel.process_initiator_id == user.id,
+ # )
+ # )
+ #
+ # if report_filter.with_tasks_completed_by_me 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.completed_by_user_id == user.id,
+ # ),
+ # )
+ #
+ # 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.has_active_status:
+ # process_instance_query = process_instance_query.filter(
+ # HumanTaskModel.completed.is_(False) # type: ignore
+ # )
+ #
+ # if report_filter.with_tasks_assigned_to_my_group is True:
+ # group_model_join_conditions = [GroupModel.id == HumanTaskModel.lane_assignment_id]
+ # if report_filter.user_group_identifier:
+ # group_model_join_conditions.append(GroupModel.identifier == report_filter.user_group_identifier)
+ #
+ # process_instance_query = process_instance_query.join(HumanTaskModel)
+ # if report_filter.has_active_status:
+ # process_instance_query = process_instance_query.filter(
+ # HumanTaskModel.completed.is_(False) # type: ignore
+ # )
+ #
+ # process_instance_query = process_instance_query.join(GroupModel, and_(*group_model_join_conditions))
+ # process_instance_query = process_instance_query.join(
+ # UserGroupAssignmentModel,
+ # UserGroupAssignmentModel.group_id == GroupModel.id,
+ # )
+ # process_instance_query = process_instance_query.filter(UserGroupAssignmentModel.user_id == user.id)
+ #
+ # instance_metadata_aliases = {}
+ # stock_columns = cls.get_column_names_for_model(ProcessInstanceModel)
+ # if isinstance(report_filter.report_column_list, list):
+ # process_instance_report.report_metadata["columns"] = report_filter.report_column_list
+ # if isinstance(report_filter.report_filter_by_list, list):
+ # process_instance_report.report_metadata["filter_by"] = report_filter.report_filter_by_list
+ #
+ # for column in process_instance_report.report_metadata["columns"]:
+ # if column["accessor"] in stock_columns:
+ # continue
+ # instance_metadata_alias = aliased(ProcessInstanceMetadataModel)
+ # instance_metadata_aliases[column["accessor"]] = instance_metadata_alias
+ #
+ # filter_for_column = None
+ # if "filter_by" in process_instance_report.report_metadata:
+ # filter_for_column = next(
+ # (
+ # f
+ # for f in process_instance_report.report_metadata["filter_by"]
+ # if f["field_name"] == column["accessor"]
+ # ),
+ # None,
+ # )
+ # isouter = True
+ # conditions = [
+ # ProcessInstanceModel.id == instance_metadata_alias.process_instance_id,
+ # instance_metadata_alias.key == column["accessor"],
+ # ]
+ # if filter_for_column:
+ # isouter = False
+ # conditions.append(instance_metadata_alias.value == filter_for_column["field_value"])
+ # process_instance_query = process_instance_query.join(
+ # instance_metadata_alias, and_(*conditions), isouter=isouter
+ # ).add_columns(func.max(instance_metadata_alias.value).label(column["accessor"]))
+ #
+ # order_by_query_array = []
+ # order_by_array = process_instance_report.report_metadata["order_by"]
+ # if len(order_by_array) < 1:
+ # order_by_array = ProcessInstanceReportModel.default_order_by()
+ # for order_by_option in order_by_array:
+ # attribute = re.sub("^-", "", order_by_option)
+ # if attribute in stock_columns:
+ # if order_by_option.startswith("-"):
+ # order_by_query_array.append(getattr(ProcessInstanceModel, attribute).desc())
+ # else:
+ # order_by_query_array.append(getattr(ProcessInstanceModel, attribute).asc())
+ # elif attribute in instance_metadata_aliases:
+ # if order_by_option.startswith("-"):
+ # order_by_query_array.append(func.max(instance_metadata_aliases[attribute].value).desc())
+ # else:
+ # order_by_query_array.append(func.max(instance_metadata_aliases[attribute].value).asc())
process_instances = (
process_instance_query.group_by(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)
)
results = cls.add_metadata_columns_to_process_instance(
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)
+ # if report_filter.oldest_open_human_task_fields:
+ # results = cls.add_human_task_fields(results, report_filter.oldest_open_human_task_fields)
response_json = {
"report": process_instance_report,
"results": results,
- "filters": report_filter.to_dict(),
+ "filters": filters,
+ "hash": "HEY",
"pagination": {
"count": len(results),
"total": process_instances.total,
diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
index ccd62075..112957f6 100644
--- a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
+++ b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
@@ -114,12 +114,12 @@ export default function ProcessInstanceListTable({
canCompleteAllTasks = false,
showActionsColumn = false,
}: OwnProps) {
- let apiPath = '/process-instances/for-me';
+ let processInstanceApiSearchPath = '/process-instances/for-me';
if (variant === 'all') {
- apiPath = '/process-instances';
+ processInstanceApiSearchPath = '/process-instances';
}
const params = useParams();
- const [searchParams] = useSearchParams();
+ const [searchParams, setSearchParams] = useSearchParams();
const navigate = useNavigate();
const { addError, removeError } = useAPIError();
@@ -284,6 +284,8 @@ export default function ProcessInstanceListTable({
if (result.report.id) {
setProcessInstanceReportSelection(result.report);
}
+ // searchParams.set('key', result.hash);
+ // setSearchParams(searchParams);
}
const stopRefreshing = () => {
if (clearRefreshRef.current) {
@@ -372,9 +374,11 @@ export default function ProcessInstanceListTable({
}
HttpService.makeCallToBackend({
- path: `${apiPath}?${queryParamString}`,
+ path: `${processInstanceApiSearchPath}?${queryParamString}`,
successCallback: setProcessInstancesFromResult,
+ httpMethod: 'POST',
onUnauthorized: stopRefreshing,
+ postBody: {'report_metadata': {'filter_by': [{'field_name': 'process_model_identifier', 'field_value': 'example/with-milestones' }]}},
});
}
function processResultForProcessModels(result: any) {
@@ -441,7 +445,7 @@ export default function ProcessInstanceListTable({
perPageOptions,
reportIdentifier,
additionalParams,
- apiPath,
+ processInstanceApiSearchPath,
]);
// This sets the filter data using the saved reports returned from the initial instance_list query.
From 8ac5f0228f37e8180bab4af69257b46c3c06184e Mon Sep 17 00:00:00 2001
From: jasquat
Date: Wed, 26 Apr 2023 11:06:01 -0400
Subject: [PATCH 02/18] can somewhat search now w/ burnettk
---
.../routes/process_instances_controller.py | 6 +-
.../process_instance_report_service.py | 757 +++++++++---------
.../components/ProcessInstanceListTable.tsx | 120 ++-
spiffworkflow-frontend/src/interfaces.ts | 2 +-
4 files changed, 494 insertions(+), 391 deletions(-)
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py
index dc0f2d76..65821bfb 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py
@@ -68,9 +68,9 @@ from spiffworkflow_backend.services.process_instance_queue_service import (
from spiffworkflow_backend.services.process_instance_queue_service import (
ProcessInstanceQueueService,
)
-from spiffworkflow_backend.services.process_instance_report_service import (
- ProcessInstanceReportFilter,
-)
+# from spiffworkflow_backend.services.process_instance_report_service import (
+# ProcessInstanceReportFilter,
+# )
from spiffworkflow_backend.services.process_instance_report_service import (
ProcessInstanceReportService,
)
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py
index a4cadb96..92040b2b 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py
@@ -37,71 +37,71 @@ class ProcessInstanceReportNotFoundError(Exception):
"""ProcessInstanceReportNotFoundError."""
-@dataclass
-class ProcessInstanceReportFilter:
- """ProcessInstanceReportFilter."""
-
- process_model_identifier: Optional[str] = None
- user_group_identifier: Optional[str] = None
- start_from: Optional[int] = None
- start_to: Optional[int] = None
- end_from: Optional[int] = None
- end_to: Optional[int] = None
- process_status: Optional[list[str]] = None
- initiated_by_me: 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_i_can_complete: Optional[bool] = None
- with_tasks_assigned_to_my_group: Optional[bool] = None
- with_relation_to_me: Optional[bool] = None
- process_initiator_username: Optional[str] = None
- report_column_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]:
- """To_dict."""
- d = {}
-
- if self.process_model_identifier is not None:
- d["process_model_identifier"] = self.process_model_identifier
- if self.user_group_identifier is not None:
- d["user_group_identifier"] = self.user_group_identifier
- if self.start_from is not None:
- d["start_from"] = str(self.start_from)
- if self.start_to is not None:
- d["start_to"] = str(self.start_to)
- if self.end_from is not None:
- d["end_from"] = str(self.end_from)
- if self.end_to is not None:
- d["end_to"] = str(self.end_to)
- if self.process_status is not None:
- d["process_status"] = ",".join(self.process_status)
- if self.initiated_by_me is not None:
- d["initiated_by_me"] = str(self.initiated_by_me).lower()
- if self.has_terminal_status is not None:
- 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:
- 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:
- 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:
- d["with_relation_to_me"] = str(self.with_relation_to_me).lower()
- if self.process_initiator_username is not None:
- d["process_initiator_username"] = str(self.process_initiator_username)
- if self.report_column_list is not None:
- d["report_column_list"] = str(self.report_column_list)
- if self.report_filter_by_list is not None:
- 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
+# @dataclass
+# class ProcessInstanceReportFilter:
+# """ProcessInstanceReportFilter."""
+#
+# process_model_identifier: Optional[str] = None
+# user_group_identifier: Optional[str] = None
+# start_from: Optional[int] = None
+# start_to: Optional[int] = None
+# end_from: Optional[int] = None
+# end_to: Optional[int] = None
+# process_status: Optional[list[str]] = None
+# initiated_by_me: 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_i_can_complete: Optional[bool] = None
+# with_tasks_assigned_to_my_group: Optional[bool] = None
+# with_relation_to_me: Optional[bool] = None
+# process_initiator_username: Optional[str] = None
+# report_column_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]:
+# """To_dict."""
+# d = {}
+#
+# if self.process_model_identifier is not None:
+# d["process_model_identifier"] = self.process_model_identifier
+# if self.user_group_identifier is not None:
+# d["user_group_identifier"] = self.user_group_identifier
+# if self.start_from is not None:
+# d["start_from"] = str(self.start_from)
+# if self.start_to is not None:
+# d["start_to"] = str(self.start_to)
+# if self.end_from is not None:
+# d["end_from"] = str(self.end_from)
+# if self.end_to is not None:
+# d["end_to"] = str(self.end_to)
+# if self.process_status is not None:
+# d["process_status"] = ",".join(self.process_status)
+# if self.initiated_by_me is not None:
+# d["initiated_by_me"] = str(self.initiated_by_me).lower()
+# if self.has_terminal_status is not None:
+# 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:
+# 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:
+# 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:
+# d["with_relation_to_me"] = str(self.with_relation_to_me).lower()
+# if self.process_initiator_username is not None:
+# d["process_initiator_username"] = str(self.process_initiator_username)
+# if self.report_column_list is not None:
+# d["report_column_list"] = str(self.report_column_list)
+# if self.report_filter_by_list is not None:
+# 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
class ProcessInstanceReportService:
@@ -268,141 +268,141 @@ class ProcessInstanceReportService:
return process_instance_report # type: ignore
- @classmethod
- def filter_by_to_dict(cls, process_instance_report: ProcessInstanceReportModel) -> dict[str, str]:
- """Filter_by_to_dict."""
- metadata = process_instance_report.report_metadata
- filter_by = metadata.get("filter_by", [])
- filters = {d["field_name"]: d["field_value"] for d in filter_by if "field_name" in d and "field_value" in d}
- return filters
-
- @classmethod
- def filter_from_metadata(cls, process_instance_report: ProcessInstanceReportModel) -> ProcessInstanceReportFilter:
- """Filter_from_metadata."""
- filters = cls.filter_by_to_dict(process_instance_report)
-
- def bool_value(key: str) -> Optional[bool]:
- """Bool_value."""
- 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]:
- """Int_value."""
- return int(filters[key]) if key in filters else None
-
- def list_value(key: str) -> Optional[list[str]]:
- return filters[key].split(",") if key in filters else None
-
- process_model_identifier = filters.get("process_model_identifier")
- user_group_identifier = filters.get("user_group_identifier")
- start_from = int_value("start_from")
- start_to = int_value("start_to")
- end_from = int_value("end_from")
- end_to = int_value("end_to")
- process_status = list_value("process_status")
- initiated_by_me = bool_value("initiated_by_me")
- 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_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_relation_to_me = bool_value("with_relation_to_me")
- process_initiator_username = filters.get("process_initiator_username")
- report_column_list = list_value("report_column_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(
- process_model_identifier=process_model_identifier,
- user_group_identifier=user_group_identifier,
- start_from=start_from,
- start_to=start_to,
- end_from=end_from,
- end_to=end_to,
- process_status=process_status,
- initiated_by_me=initiated_by_me,
- has_terminal_status=has_terminal_status,
- has_active_status=has_active_status,
- 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_relation_to_me=with_relation_to_me,
- process_initiator_username=process_initiator_username,
- report_column_list=report_column_list,
- report_filter_by_list=report_filter_by_list,
- oldest_open_human_task_fields=oldest_open_human_task_fields,
- )
-
- return report_filter
-
- @classmethod
- def filter_from_metadata_with_overrides(
- cls,
- process_instance_report: ProcessInstanceReportModel,
- process_model_identifier: Optional[str] = None,
- user_group_identifier: Optional[str] = None,
- start_from: Optional[int] = None,
- start_to: Optional[int] = None,
- end_from: Optional[int] = None,
- end_to: Optional[int] = None,
- process_status: Optional[str] = None,
- initiated_by_me: 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_i_can_complete: Optional[bool] = None,
- with_tasks_assigned_to_my_group: Optional[bool] = None,
- with_relation_to_me: Optional[bool] = None,
- process_initiator_username: Optional[str] = None,
- report_column_list: Optional[list] = None,
- report_filter_by_list: Optional[list] = None,
- oldest_open_human_task_fields: Optional[list] = None,
- ) -> ProcessInstanceReportFilter:
- """Filter_from_metadata_with_overrides."""
- report_filter = cls.filter_from_metadata(process_instance_report)
-
- if process_model_identifier is not None:
- report_filter.process_model_identifier = process_model_identifier
- if user_group_identifier is not None:
- report_filter.user_group_identifier = user_group_identifier
- if start_from is not None:
- report_filter.start_from = start_from
- if start_to is not None:
- report_filter.start_to = start_to
- if end_from is not None:
- report_filter.end_from = end_from
- if end_to is not None:
- report_filter.end_to = end_to
- if process_status is not None:
- report_filter.process_status = process_status.split(",")
- if initiated_by_me is not None:
- report_filter.initiated_by_me = initiated_by_me
- if has_terminal_status is not None:
- 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:
- 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:
- report_filter.process_initiator_username = process_initiator_username
- if report_column_list is not None:
- report_filter.report_column_list = report_column_list
- if report_filter_by_list is not None:
- 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:
- report_filter.with_tasks_assigned_to_my_group = with_tasks_assigned_to_my_group
- if with_relation_to_me is not None:
- report_filter.with_relation_to_me = with_relation_to_me
-
- return report_filter
+ # @classmethod
+ # def filter_by_to_dict(cls, process_instance_report: ProcessInstanceReportModel) -> dict[str, str]:
+ # """Filter_by_to_dict."""
+ # metadata = process_instance_report.report_metadata
+ # filter_by = metadata.get("filter_by", [])
+ # filters = {d["field_name"]: d["field_value"] for d in filter_by if "field_name" in d and "field_value" in d}
+ # return filters
+ #
+ # @classmethod
+ # def filter_from_metadata(cls, process_instance_report: ProcessInstanceReportModel) -> ProcessInstanceReportFilter:
+ # """Filter_from_metadata."""
+ # filters = cls.filter_by_to_dict(process_instance_report)
+ #
+ # def bool_value(key: str) -> Optional[bool]:
+ # """Bool_value."""
+ # 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]:
+ # """Int_value."""
+ # return int(filters[key]) if key in filters else None
+ #
+ # def list_value(key: str) -> Optional[list[str]]:
+ # return filters[key].split(",") if key in filters else None
+ #
+ # process_model_identifier = filters.get("process_model_identifier")
+ # user_group_identifier = filters.get("user_group_identifier")
+ # start_from = int_value("start_from")
+ # start_to = int_value("start_to")
+ # end_from = int_value("end_from")
+ # end_to = int_value("end_to")
+ # process_status = list_value("process_status")
+ # initiated_by_me = bool_value("initiated_by_me")
+ # 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_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_relation_to_me = bool_value("with_relation_to_me")
+ # process_initiator_username = filters.get("process_initiator_username")
+ # report_column_list = list_value("report_column_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(
+ # process_model_identifier=process_model_identifier,
+ # user_group_identifier=user_group_identifier,
+ # start_from=start_from,
+ # start_to=start_to,
+ # end_from=end_from,
+ # end_to=end_to,
+ # process_status=process_status,
+ # initiated_by_me=initiated_by_me,
+ # has_terminal_status=has_terminal_status,
+ # has_active_status=has_active_status,
+ # 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_relation_to_me=with_relation_to_me,
+ # process_initiator_username=process_initiator_username,
+ # report_column_list=report_column_list,
+ # report_filter_by_list=report_filter_by_list,
+ # oldest_open_human_task_fields=oldest_open_human_task_fields,
+ # )
+ #
+ # return report_filter
+ #
+ # @classmethod
+ # def filter_from_metadata_with_overrides(
+ # cls,
+ # process_instance_report: ProcessInstanceReportModel,
+ # process_model_identifier: Optional[str] = None,
+ # user_group_identifier: Optional[str] = None,
+ # start_from: Optional[int] = None,
+ # start_to: Optional[int] = None,
+ # end_from: Optional[int] = None,
+ # end_to: Optional[int] = None,
+ # process_status: Optional[str] = None,
+ # initiated_by_me: 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_i_can_complete: Optional[bool] = None,
+ # with_tasks_assigned_to_my_group: Optional[bool] = None,
+ # with_relation_to_me: Optional[bool] = None,
+ # process_initiator_username: Optional[str] = None,
+ # report_column_list: Optional[list] = None,
+ # report_filter_by_list: Optional[list] = None,
+ # oldest_open_human_task_fields: Optional[list] = None,
+ # ) -> ProcessInstanceReportFilter:
+ # """Filter_from_metadata_with_overrides."""
+ # report_filter = cls.filter_from_metadata(process_instance_report)
+ #
+ # if process_model_identifier is not None:
+ # report_filter.process_model_identifier = process_model_identifier
+ # if user_group_identifier is not None:
+ # report_filter.user_group_identifier = user_group_identifier
+ # if start_from is not None:
+ # report_filter.start_from = start_from
+ # if start_to is not None:
+ # report_filter.start_to = start_to
+ # if end_from is not None:
+ # report_filter.end_from = end_from
+ # if end_to is not None:
+ # report_filter.end_to = end_to
+ # if process_status is not None:
+ # report_filter.process_status = process_status.split(",")
+ # if initiated_by_me is not None:
+ # report_filter.initiated_by_me = initiated_by_me
+ # if has_terminal_status is not None:
+ # 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:
+ # 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:
+ # report_filter.process_initiator_username = process_initiator_username
+ # if report_column_list is not None:
+ # report_filter.report_column_list = report_column_list
+ # if report_filter_by_list is not None:
+ # 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:
+ # report_filter.with_tasks_assigned_to_my_group = with_tasks_assigned_to_my_group
+ # if with_relation_to_me is not None:
+ # report_filter.with_relation_to_me = with_relation_to_me
+ #
+ # return report_filter
@classmethod
def add_metadata_columns_to_process_instance(
@@ -498,10 +498,16 @@ class ProcessInstanceReportService:
]
@classmethod
- def blah(cls, filters: list[FilterValue], filter_key: str) -> Generator:
+ def get_filter_value(cls, filters: list[FilterValue], filter_key: str) -> Any:
for filter in filters:
if filter['field_name'] == filter_key and filter['field_value'] is not None:
- yield filter['field_value']
+ return filter['field_value']
+
+ @classmethod
+ def check_filter_value(cls, filters: list[FilterValue], filter_key: str) -> Generator:
+ value = cls.get_filter_value(filters, filter_key)
+ if value is not None:
+ yield value
@classmethod
def run_process_instance_report(
@@ -518,7 +524,7 @@ class ProcessInstanceReportService:
process_instance_query = process_instance_query.options(selectinload(ProcessInstanceModel.process_initiator))
filters = report_metadata['filter_by']
- for value in cls.blah(filters, 'process_model_identifier'):
+ for value in cls.check_filter_value(filters, 'process_model_identifier'):
process_model = ProcessModelService.get_process_model(
f"{value}",
)
@@ -534,183 +540,194 @@ class ProcessInstanceReportService:
)
)
- # if report_filter.start_from is not None:
- # process_instance_query = process_instance_query.filter(
- # ProcessInstanceModel.start_in_seconds >= report_filter.start_from
- # )
- # if report_filter.start_to is not None:
- # process_instance_query = process_instance_query.filter(
- # ProcessInstanceModel.start_in_seconds <= report_filter.start_to
- # )
- # if report_filter.end_from is not None:
- # process_instance_query = process_instance_query.filter(
- # ProcessInstanceModel.end_in_seconds >= report_filter.end_from
- # )
- # if report_filter.end_to is not None:
- # process_instance_query = process_instance_query.filter(
- # ProcessInstanceModel.end_in_seconds <= report_filter.end_to
- # )
- # if report_filter.process_status is not None:
- # process_instance_query = process_instance_query.filter(
- # ProcessInstanceModel.status.in_(report_filter.process_status) # type: ignore
- # )
- #
- # if report_filter.initiated_by_me is True:
- # process_instance_query = process_instance_query.filter_by(process_initiator=user)
- #
- # if report_filter.has_terminal_status is True:
- # process_instance_query = process_instance_query.filter(
- # 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:
- # initiator = UserModel.query.filter_by(username=report_filter.process_initiator_username).first()
- # process_initiator_id = -1
- # if initiator:
- # process_initiator_id = initiator.id
- # process_instance_query = process_instance_query.filter_by(process_initiator_id=process_initiator_id)
- #
- # if (
- # not report_filter.with_tasks_completed_by_me
- # 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
- # ):
- # process_instance_query = process_instance_query.outerjoin(HumanTaskModel).outerjoin(
- # HumanTaskUserModel,
- # and_(
- # HumanTaskModel.id == HumanTaskUserModel.human_task_id,
- # HumanTaskUserModel.user_id == user.id,
- # ),
- # )
- # process_instance_query = process_instance_query.filter(
- # or_(
- # HumanTaskUserModel.id.is_not(None),
- # ProcessInstanceModel.process_initiator_id == user.id,
- # )
- # )
- #
- # if report_filter.with_tasks_completed_by_me 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.completed_by_user_id == user.id,
- # ),
- # )
- #
- # 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.has_active_status:
- # process_instance_query = process_instance_query.filter(
- # HumanTaskModel.completed.is_(False) # type: ignore
- # )
- #
- # if report_filter.with_tasks_assigned_to_my_group is True:
- # group_model_join_conditions = [GroupModel.id == HumanTaskModel.lane_assignment_id]
- # if report_filter.user_group_identifier:
- # group_model_join_conditions.append(GroupModel.identifier == report_filter.user_group_identifier)
- #
- # process_instance_query = process_instance_query.join(HumanTaskModel)
- # if report_filter.has_active_status:
- # process_instance_query = process_instance_query.filter(
- # HumanTaskModel.completed.is_(False) # type: ignore
- # )
- #
- # process_instance_query = process_instance_query.join(GroupModel, and_(*group_model_join_conditions))
- # process_instance_query = process_instance_query.join(
- # UserGroupAssignmentModel,
- # UserGroupAssignmentModel.group_id == GroupModel.id,
- # )
- # process_instance_query = process_instance_query.filter(UserGroupAssignmentModel.user_id == user.id)
- #
- # instance_metadata_aliases = {}
- # stock_columns = cls.get_column_names_for_model(ProcessInstanceModel)
- # if isinstance(report_filter.report_column_list, list):
- # process_instance_report.report_metadata["columns"] = report_filter.report_column_list
- # if isinstance(report_filter.report_filter_by_list, list):
- # process_instance_report.report_metadata["filter_by"] = report_filter.report_filter_by_list
- #
- # for column in process_instance_report.report_metadata["columns"]:
- # if column["accessor"] in stock_columns:
- # continue
- # instance_metadata_alias = aliased(ProcessInstanceMetadataModel)
- # instance_metadata_aliases[column["accessor"]] = instance_metadata_alias
- #
- # filter_for_column = None
- # if "filter_by" in process_instance_report.report_metadata:
- # filter_for_column = next(
- # (
- # f
- # for f in process_instance_report.report_metadata["filter_by"]
- # if f["field_name"] == column["accessor"]
- # ),
- # None,
- # )
- # isouter = True
- # conditions = [
- # ProcessInstanceModel.id == instance_metadata_alias.process_instance_id,
- # instance_metadata_alias.key == column["accessor"],
- # ]
- # if filter_for_column:
- # isouter = False
- # conditions.append(instance_metadata_alias.value == filter_for_column["field_value"])
- # process_instance_query = process_instance_query.join(
- # instance_metadata_alias, and_(*conditions), isouter=isouter
- # ).add_columns(func.max(instance_metadata_alias.value).label(column["accessor"]))
- #
- # order_by_query_array = []
- # order_by_array = process_instance_report.report_metadata["order_by"]
- # if len(order_by_array) < 1:
- # order_by_array = ProcessInstanceReportModel.default_order_by()
- # for order_by_option in order_by_array:
- # attribute = re.sub("^-", "", order_by_option)
- # if attribute in stock_columns:
- # if order_by_option.startswith("-"):
- # order_by_query_array.append(getattr(ProcessInstanceModel, attribute).desc())
- # else:
- # order_by_query_array.append(getattr(ProcessInstanceModel, attribute).asc())
- # elif attribute in instance_metadata_aliases:
- # if order_by_option.startswith("-"):
- # order_by_query_array.append(func.max(instance_metadata_aliases[attribute].value).desc())
- # else:
- # order_by_query_array.append(func.max(instance_metadata_aliases[attribute].value).asc())
+ for value in cls.check_filter_value(filters, 'start_from'):
+ process_instance_query = process_instance_query.filter(
+ ProcessInstanceModel.start_in_seconds >= value
+ )
+ for value in cls.check_filter_value(filters, 'start_to'):
+ process_instance_query = process_instance_query.filter(
+ ProcessInstanceModel.start_in_seconds <= value
+ )
+ for value in cls.check_filter_value(filters, 'end_from'):
+ process_instance_query = process_instance_query.filter(
+ ProcessInstanceModel.end_in_seconds >= value
+ )
+ for value in cls.check_filter_value(filters, 'end_to'):
+ process_instance_query = process_instance_query.filter(
+ ProcessInstanceModel.end_in_seconds <= value
+ )
+ for value in cls.check_filter_value(filters, 'process_status'):
+ process_instance_query = process_instance_query.filter(
+ ProcessInstanceModel.status.in_(value.split(',')) # type: ignore
+ )
+
+ for value in cls.check_filter_value(filters, 'initiated_by_me'):
+ if value is True:
+ process_instance_query = process_instance_query.filter_by(process_initiator=user)
+
+ for value in cls.check_filter_value(filters, 'has_terminal_status'):
+ if value is True:
+ process_instance_query = process_instance_query.filter(
+ ProcessInstanceModel.status.in_(ProcessInstanceModel.terminal_statuses()) # type: ignore
+ )
+ elif value is False:
+ process_instance_query = process_instance_query.filter(
+ ProcessInstanceModel.status.not_in(ProcessInstanceModel.terminal_statuses()) # type: ignore
+ )
+ for value in cls.check_filter_value(filters, 'has_active_status'):
+ if value is True:
+ process_instance_query = process_instance_query.filter(
+ ProcessInstanceModel.status.in_(ProcessInstanceModel.active_statuses()) # type: ignore
+ )
+
+ for value in cls.check_filter_value(filters, 'process_initiator_username'):
+ initiator = UserModel.query.filter_by(username=value).first()
+ process_initiator_id = -1
+ if initiator:
+ process_initiator_id = 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')
+ with_tasks_assigned_to_my_group = cls.get_filter_value(filters, 'with_tasks_assigned_to_my_group')
+ with_tasks_i_can_complete = cls.get_filter_value(filters, 'with_tasks_i_can_complete')
+ with_relation_to_me = cls.get_filter_value(filters, 'with_relation_to_me')
+ has_active_status = cls.get_filter_value(filters, 'has_active_status')
+ user_group_identifier = cls.get_filter_value(filters, 'user_group_identifier')
+ if (
+ not with_tasks_completed_by_me
+ and not with_tasks_assigned_to_my_group
+ and not with_tasks_i_can_complete
+ and with_relation_to_me is True
+ ):
+ process_instance_query = process_instance_query.outerjoin(HumanTaskModel).outerjoin(
+ HumanTaskUserModel,
+ and_(
+ HumanTaskModel.id == HumanTaskUserModel.human_task_id,
+ HumanTaskUserModel.user_id == user.id,
+ ),
+ )
+ process_instance_query = process_instance_query.filter(
+ or_(
+ HumanTaskUserModel.id.is_not(None),
+ ProcessInstanceModel.process_initiator_id == user.id,
+ )
+ )
+
+ if with_tasks_completed_by_me 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.completed_by_user_id == user.id,
+ ),
+ )
+
+ if 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 has_active_status:
+ process_instance_query = process_instance_query.filter(
+ HumanTaskModel.completed.is_(False) # type: ignore
+ )
+
+ if with_tasks_assigned_to_my_group is True:
+ group_model_join_conditions = [GroupModel.id == HumanTaskModel.lane_assignment_id]
+ if user_group_identifier:
+ group_model_join_conditions.append(GroupModel.identifier == user_group_identifier)
+
+ process_instance_query = process_instance_query.join(HumanTaskModel)
+ if has_active_status:
+ process_instance_query = process_instance_query.filter(
+ HumanTaskModel.completed.is_(False) # type: ignore
+ )
+
+ process_instance_query = process_instance_query.join(GroupModel, and_(*group_model_join_conditions))
+ process_instance_query = process_instance_query.join(
+ UserGroupAssignmentModel,
+ UserGroupAssignmentModel.group_id == GroupModel.id,
+ )
+ process_instance_query = process_instance_query.filter(UserGroupAssignmentModel.user_id == user.id)
+
+ instance_metadata_aliases = {}
+ report_column_list = cls.get_filter_value(filters, 'report_column_list')
+ report_filter_by_list = cls.get_filter_value(filters, 'report_filter_by_list')
+ stock_columns = cls.get_column_names_for_model(ProcessInstanceModel)
+ if isinstance(report_column_list, list):
+ process_instance_report.report_metadata["columns"] = report_column_list
+ if isinstance(report_filter_by_list, list):
+ process_instance_report.report_metadata["filter_by"] = report_filter_by_list
+
+ for column in process_instance_report.report_metadata["columns"]:
+ if column["accessor"] in stock_columns:
+ continue
+ instance_metadata_alias = aliased(ProcessInstanceMetadataModel)
+ instance_metadata_aliases[column["accessor"]] = instance_metadata_alias
+
+ filter_for_column = None
+ if "filter_by" in process_instance_report.report_metadata:
+ filter_for_column = next(
+ (
+ f
+ for f in process_instance_report.report_metadata["filter_by"]
+ if f["field_name"] == column["accessor"]
+ ),
+ None,
+ )
+ isouter = True
+ conditions = [
+ ProcessInstanceModel.id == instance_metadata_alias.process_instance_id,
+ instance_metadata_alias.key == column["accessor"],
+ ]
+ if filter_for_column:
+ isouter = False
+ conditions.append(instance_metadata_alias.value == filter_for_column["field_value"])
+ process_instance_query = process_instance_query.join(
+ instance_metadata_alias, and_(*conditions), isouter=isouter
+ ).add_columns(func.max(instance_metadata_alias.value).label(column["accessor"]))
+
+ order_by_query_array = []
+ order_by_array = process_instance_report.report_metadata["order_by"]
+ if len(order_by_array) < 1:
+ order_by_array = ProcessInstanceReportModel.default_order_by()
+ for order_by_option in order_by_array:
+ attribute = re.sub("^-", "", order_by_option)
+ if attribute in stock_columns:
+ if order_by_option.startswith("-"):
+ order_by_query_array.append(getattr(ProcessInstanceModel, attribute).desc())
+ else:
+ order_by_query_array.append(getattr(ProcessInstanceModel, attribute).asc())
+ elif attribute in instance_metadata_aliases:
+ if order_by_option.startswith("-"):
+ order_by_query_array.append(func.max(instance_metadata_aliases[attribute].value).desc())
+ else:
+ order_by_query_array.append(func.max(instance_metadata_aliases[attribute].value).asc())
process_instances = (
process_instance_query.group_by(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)
)
results = cls.add_metadata_columns_to_process_instance(
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)
+ for value in cls.check_filter_value(filters, 'oldest_open_human_task_fields'):
+ results = cls.add_human_task_fields(results, value)
response_json = {
"report": process_instance_report,
"results": results,
diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
index 112957f6..083ce875 100644
--- a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
+++ b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
@@ -37,6 +37,7 @@ import {
convertSecondsToFormattedDateString,
convertSecondsToFormattedDateTime,
convertSecondsToFormattedTimeHoursMinutes,
+ decodeBase64,
encodeBase64,
getPageInfoFromSearchParams,
getProcessModelFullIdentifierFromSearchParams,
@@ -287,10 +288,16 @@ export default function ProcessInstanceListTable({
// searchParams.set('key', result.hash);
// setSearchParams(searchParams);
}
- const stopRefreshing = () => {
+
+ // Useful to stop refreshing if an api call gets an error
+ // since those errors can make the page unusable in any way
+ const stopRefreshing = (error: any) => {
if (clearRefreshRef.current) {
clearRefreshRef.current();
}
+ if (error) {
+ console.error(error);
+ }
};
function getProcessInstances() {
// eslint-disable-next-line prefer-const
@@ -306,10 +313,31 @@ export default function ProcessInstanceListTable({
}
let queryParamString = `per_page=${perPage}&page=${page}`;
- const userAppliedFilter = searchParams.get('user_filter');
- if (userAppliedFilter) {
- queryParamString += `&user_filter=${userAppliedFilter}`;
- }
+ // "columns": [
+ // {"Header": "id", "accessor": "id"},
+ // {
+ // "Header": "process_model_display_name",
+ // "accessor": "process_model_display_name",
+ // },
+ // {"Header": "start_in_seconds", "accessor": "start_in_seconds"},
+ // {"Header": "end_in_seconds", "accessor": "end_in_seconds"},
+ // {"Header": "status", "accessor": "status"},
+ // ],
+ // "filter_by": [
+ // {"field_name": "initiated_by_me", "field_value": "true"},
+ // {"field_name": "has_terminal_status", "field_value": "true"},
+ // ],
+ // "order_by": ["-start_in_seconds", "-id"],
+ const postBody: ReportMetadata = {
+ columns: [],
+ filter_by: [],
+ order_by: [],
+ };
+
+ // const userAppliedFilter = searchParams.get('user_filter');
+ // if (userAppliedFilter) {
+ // queryParamString += `&user_filter=${userAppliedFilter}`;
+ // }
if (searchParams.get('report_id')) {
queryParamString += `&report_id=${searchParams.get('report_id')}`;
@@ -318,14 +346,29 @@ export default function ProcessInstanceListTable({
}
if (searchParams.get('report_columns')) {
- queryParamString += `&report_columns=${searchParams.get(
- 'report_columns'
- )}`;
+ // queryParamString += `&report_columns=${searchParams.get(
+ // 'report_columns'
+ // )}`;
+ // const reportColumnsBase64 = encodeBase64(JSON.stringify(reportColumns()));
+ const reportColumnsBase64 = searchParams.get('report_columns');
+ if (reportColumnsBase64) {
+ const reportColumnsList = JSON.parse(
+ decodeBase64(reportColumnsBase64)
+ );
+ postBody.columns = reportColumnsList;
+ }
}
if (searchParams.get('report_filter_by')) {
- queryParamString += `&report_filter_by=${searchParams.get(
- 'report_filter_by'
- )}`;
+ // queryParamString += `&report_filter_by=${searchParams.get(
+ // 'report_filter_by'
+ // )}`;
+ const reportFilterByBase64 = searchParams.get('report_filter_by');
+ if (reportFilterByBase64) {
+ const reportFilterByList = JSON.parse(
+ decodeBase64(reportFilterByBase64)
+ );
+ postBody.filter_by = reportFilterByList;
+ }
}
Object.keys(dateParametersToAlwaysFilterBy).forEach(
@@ -336,7 +379,11 @@ export default function ProcessInstanceListTable({
dateParametersToAlwaysFilterBy[paramName][1];
const searchParamValue = searchParams.get(paramName);
if (searchParamValue) {
- queryParamString += `&${paramName}=${searchParamValue}`;
+ // queryParamString += `&${paramName}=${searchParamValue}`;
+ postBody.filter_by.push({
+ field_name: paramName,
+ field_value: searchParamValue,
+ });
const dateString = convertSecondsToFormattedDateString(
searchParamValue as any
);
@@ -356,11 +403,19 @@ export default function ProcessInstanceListTable({
paramName === 'process_model_identifier' &&
processModelFullIdentifier
) {
- queryParamString += `&process_model_identifier=${processModelFullIdentifier}`;
+ // queryParamString += `&process_model_identifier=${processModelFullIdentifier}`;
+ postBody.filter_by.push({
+ field_name: 'process_model_identifier',
+ field_value: processModelFullIdentifier,
+ });
} else if (searchParams.get(paramName)) {
// @ts-expect-error TS(7053) FIXME:
const functionToCall = parametersToGetFromSearchParams[paramName];
- queryParamString += `&${paramName}=${searchParams.get(paramName)}`;
+ // queryParamString += `&${paramName}=${searchParams.get(paramName)}`;
+ postBody.filter_by.push({
+ field_name: paramName,
+ field_value: searchParams.get(paramName),
+ });
if (functionToCall !== null) {
functionToCall(searchParams.get(paramName) || '');
}
@@ -377,8 +432,19 @@ export default function ProcessInstanceListTable({
path: `${processInstanceApiSearchPath}?${queryParamString}`,
successCallback: setProcessInstancesFromResult,
httpMethod: 'POST',
+ failureCallback: stopRefreshing,
onUnauthorized: stopRefreshing,
- postBody: {'report_metadata': {'filter_by': [{'field_name': 'process_model_identifier', 'field_value': 'example/with-milestones' }]}},
+ postBody: {
+ report_metadata: postBody,
+ // report_metadata: {
+ // filter_by: [
+ // {
+ // field_name: 'process_model_identifier',
+ // field_value: 'example/with-milestones',
+ // },
+ // ],
+ // },
+ },
});
}
function processResultForProcessModels(result: any) {
@@ -618,7 +684,8 @@ export default function ProcessInstanceListTable({
undefined,
paginationQueryParamPrefix
);
- let queryParamString = `per_page=${perPage}&page=${page}&user_filter=true`;
+ // let queryParamString = `per_page=${perPage}&page=${page}&user_filter=true`;
+ let queryParamString = `per_page=${perPage}&page=${page}`;
const {
valid,
startFromSeconds,
@@ -631,6 +698,24 @@ export default function ProcessInstanceListTable({
return;
}
+ // "columns": [
+ // {"Header": "id", "accessor": "id"},
+ // {
+ // "Header": "process_model_display_name",
+ // "accessor": "process_model_display_name",
+ // },
+ // {"Header": "start_in_seconds", "accessor": "start_in_seconds"},
+ // {"Header": "end_in_seconds", "accessor": "end_in_seconds"},
+ // {"Header": "status", "accessor": "status"},
+ // ],
+ // "filter_by": [
+ // {"field_name": "initiated_by_me", "field_value": "true"},
+ // {"field_name": "has_terminal_status", "field_value": "true"},
+ // ],
+ // "order_by": ["-start_in_seconds", "-id"],
+
+ // const postBody = {};
+
if (startFromSeconds) {
queryParamString += `&start_from=${startFromSeconds}`;
}
@@ -947,7 +1032,8 @@ export default function ProcessInstanceListTable({
reportColumnForEditing.accessor
);
if (reportFilter) {
- reportColumnForEditing.filter_field_value = reportFilter.field_value;
+ reportColumnForEditing.filter_field_value =
+ reportFilter.field_value || '';
reportColumnForEditing.filter_operator =
reportFilter.operator || 'equals';
}
diff --git a/spiffworkflow-frontend/src/interfaces.ts b/spiffworkflow-frontend/src/interfaces.ts
index aa0579e9..b3e6bcb2 100644
--- a/spiffworkflow-frontend/src/interfaces.ts
+++ b/spiffworkflow-frontend/src/interfaces.ts
@@ -163,7 +163,7 @@ export interface MessageInstance {
export interface ReportFilter {
field_name: string;
- field_value: string;
+ field_value: string | null;
operator?: string;
}
From 871d7a73deed658311b49926d5b4fe52e3d541a5 Mon Sep 17 00:00:00 2001
From: jasquat
Date: Wed, 26 Apr 2023 13:24:08 -0400
Subject: [PATCH 03/18] some more updates for searching w/ burnettk
---
.../components/ProcessInstanceListTable.tsx | 235 ++++++++++++------
1 file changed, 155 insertions(+), 80 deletions(-)
diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
index 083ce875..1652ca36 100644
--- a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
+++ b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
@@ -269,8 +269,30 @@ export default function ProcessInstanceListTable({
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
+ function setProcessInstancesFromResult(result: any) {
+ setRequiresRefilter(false);
+ const processInstancesFromApi = result.results;
+ setProcessInstances(processInstancesFromApi);
+ setPagination(result.pagination);
+ setProcessInstanceFilters(result.filters);
+
+ setReportMetadata(result.report.report_metadata);
+ if (result.report.id) {
+ setProcessInstanceReportSelection(result.report);
+ }
+ // searchParams.set('key', result.hash);
+ // setSearchParams(searchParams);
+ }
const clearRefreshRef = useRef(null);
+ const stopRefreshing = (error: any) => {
+ if (clearRefreshRef.current) {
+ clearRefreshRef.current();
+ }
+ if (error) {
+ console.error(error);
+ }
+ };
// eslint-disable-next-line sonarjs/cognitive-complexity
useEffect(() => {
@@ -428,24 +450,28 @@ export default function ProcessInstanceListTable({
queryParamString += `&${additionalParams}`;
}
- HttpService.makeCallToBackend({
- path: `${processInstanceApiSearchPath}?${queryParamString}`,
- successCallback: setProcessInstancesFromResult,
- httpMethod: 'POST',
- failureCallback: stopRefreshing,
- onUnauthorized: stopRefreshing,
- postBody: {
- report_metadata: postBody,
- // report_metadata: {
- // filter_by: [
- // {
- // field_name: 'process_model_identifier',
- // field_value: 'example/with-milestones',
- // },
- // ],
- // },
- },
- });
+ const reportMetadataBase64 = searchParams.get('report_metadata_base64');
+ if (reportMetadataBase64) {
+ const reportMetadata = JSON.parse(decodeBase64(reportMetadataBase64))
+ HttpService.makeCallToBackend({
+ path: `${processInstanceApiSearchPath}?${queryParamString}`,
+ successCallback: setProcessInstancesFromResult,
+ httpMethod: 'POST',
+ failureCallback: stopRefreshing,
+ onUnauthorized: stopRefreshing,
+ postBody: {
+ report_metadata: reportMetadata,
+ // report_metadata: {
+ // filter_by: [
+ // {
+ // field_name: 'process_model_identifier',
+ // field_value: 'example/with-milestones',
+ // },
+ // ],
+ // },
+ },
+ });
+ }
}
function processResultForProcessModels(result: any) {
const processModelFullIdentifierFromSearchParams =
@@ -660,7 +686,10 @@ export default function ProcessInstanceListTable({
};
const reportColumns = () => {
- return (reportMetadata as any).columns;
+ if (reportMetadata) {
+ return (reportMetadata as any).columns;
+ }
+ return null;
};
const reportFilterBy = () => {
@@ -674,6 +703,19 @@ export default function ProcessInstanceListTable({
navigate(`${processInstanceListPathPrefix}?${queryParamString}`);
};
+ const addFieldValueToReportMetadata = (
+ postBody: ReportMetadata,
+ fieldName: string,
+ fieldValue: string
+ ) => {
+ if (fieldValue) {
+ postBody.filter_by.push({
+ field_name: fieldName,
+ field_value: fieldValue,
+ });
+ }
+ };
+
const applyFilter = (event: any) => {
event.preventDefault();
setProcessInitiatorNotFoundErrorText('');
@@ -685,7 +727,7 @@ export default function ProcessInstanceListTable({
paginationQueryParamPrefix
);
// let queryParamString = `per_page=${perPage}&page=${page}&user_filter=true`;
- let queryParamString = `per_page=${perPage}&page=${page}`;
+ const queryParamString = `per_page=${perPage}&page=${page}`;
const {
valid,
startFromSeconds,
@@ -713,60 +755,94 @@ export default function ProcessInstanceListTable({
// {"field_name": "has_terminal_status", "field_value": "true"},
// ],
// "order_by": ["-start_in_seconds", "-id"],
+ const postBody: ReportMetadata = {
+ columns: [],
+ filter_by: [],
+ order_by: [],
+ };
- // const postBody = {};
-
- if (startFromSeconds) {
- queryParamString += `&start_from=${startFromSeconds}`;
- }
- if (startToSeconds) {
- queryParamString += `&start_to=${startToSeconds}`;
- }
- if (endFromSeconds) {
- queryParamString += `&end_from=${endFromSeconds}`;
- }
- if (endToSeconds) {
- queryParamString += `&end_to=${endToSeconds}`;
- }
+ addFieldValueToReportMetadata(postBody, 'start_from', startFromSeconds);
+ addFieldValueToReportMetadata(postBody, 'start_to', startToSeconds);
+ addFieldValueToReportMetadata(postBody, 'end_from', endFromSeconds);
+ addFieldValueToReportMetadata(postBody, 'end_to', endToSeconds);
if (processStatusSelection.length > 0) {
- queryParamString += `&process_status=${processStatusSelection}`;
+ addFieldValueToReportMetadata(
+ postBody,
+ 'process_status',
+ processStatusSelection.join(',')
+ );
}
if (processModelSelection) {
- queryParamString += `&process_model_identifier=${processModelSelection.id}`;
+ addFieldValueToReportMetadata(
+ postBody,
+ 'process_model_identifier',
+ processModelSelection.id
+ );
}
if (processInstanceReportSelection) {
- queryParamString += `&report_id=${processInstanceReportSelection.id}`;
+ addFieldValueToReportMetadata(
+ postBody,
+ 'report_id',
+ processInstanceReportSelection.id.toString()
+ );
}
- const reportColumnsBase64 = encodeBase64(JSON.stringify(reportColumns()));
- queryParamString += `&report_columns=${reportColumnsBase64}`;
- const reportFilterByBase64 = encodeBase64(JSON.stringify(reportFilterBy()));
- queryParamString += `&report_filter_by=${reportFilterByBase64}`;
+ // console.log('reportColumns()', reportColumns())
+ // const reportColumnsBase64 = encodeBase64(JSON.stringify(reportColumns()));
+ // queryParamString += `&report_columns=${reportColumnsBase64}`;
+ postBody.columns = reportColumns();
+ // console.log('reportFilterBy()', reportFilterBy())
+ // const reportFilterByBase64 = encodeBase64(JSON.stringify(reportFilterBy()));
+ // queryParamString += `&report_filter_by=${reportFilterByBase64}`;
if (processInitiatorSelection) {
- queryParamString += `&process_initiator_username=${processInitiatorSelection.username}`;
- navigateToNewReport(queryParamString);
- } else if (processInitiatorText) {
- HttpService.makeCallToBackend({
- path: targetUris.userExists,
- httpMethod: 'POST',
- postBody: { username: processInitiatorText },
- successCallback: (result: any) => {
- if (result.user_found) {
- queryParamString += `&process_initiator_username=${processInitiatorText}`;
- navigateToNewReport(queryParamString);
- } else {
- setProcessInitiatorNotFoundErrorText(
- `The provided username is invalid. Please type the exact username.`
- );
- }
- },
- });
- } else {
- navigateToNewReport(queryParamString);
+ // queryParamString += `&process_initiator_username=${processInitiatorSelection.username}`;
+ addFieldValueToReportMetadata(
+ postBody,
+ 'process_initiator_username',
+ processInitiatorSelection.username
+ );
+ // navigateToNewReport(queryParamString);
}
+ // } else if (processInitiatorText) {
+ // HttpService.makeCallToBackend({
+ // path: targetUris.userExists,
+ // httpMethod: 'POST',
+ // postBody: { username: processInitiatorText },
+ // successCallback: (result: any) => {
+ // if (result.user_found) {
+ // queryParamString += `&process_initiator_username=${processInitiatorText}`;
+ // // navigateToNewReport(queryParamString);
+ // } else {
+ // setProcessInitiatorNotFoundErrorText(
+ // `The provided username is invalid. Please type the exact username.`
+ // );
+ // }
+ // },
+ // });
+ // } else {
+ // navigateToNewReport(queryParamString);
+ // }
+
+ // http://localhost:7001/admin/process-instances/for-me?per_page=50&page=1&report_metadata_base64=eyJjb2x1bW5zIjpudWxsLCJmaWx0ZXJfYnkiOlt7ImZpZWxkX25hbWUiOiJwcm9jZXNzX3N0YXR1cyIsImZpZWxkX3ZhbHVlIjoiY29tcGxldGUifV0sIm9yZGVyX2J5IjpbXX0%3D
+ // const queryParamString = `per_page=${perPage}&page=${page}`;
+ searchParams.set('per_page', perPage.toString());
+ searchParams.set('page', page.toString());
+ const reportMetadataBase64 = encodeBase64(JSON.stringify(postBody));
+ searchParams.set('report_metadata_base64', reportMetadataBase64);
+ setSearchParams(searchParams);
+ HttpService.makeCallToBackend({
+ path: `${processInstanceApiSearchPath}?${queryParamString}`,
+ httpMethod: 'POST',
+ postBody: { report_metadata: postBody },
+ failureCallback: stopRefreshing,
+ onUnauthorized: stopRefreshing,
+ successCallback: (result: any) => {
+ setProcessInstancesFromResult(result);
+ },
+ });
};
const dateComponent = (
@@ -1609,6 +1685,7 @@ export default function ProcessInstanceListTable({
return null;
};
+ let resultsTable = null;
if (pagination && (!textToShowIfEmpty || pagination.total > 0)) {
// eslint-disable-next-line prefer-const
let { page, perPage } = getPageInfoFromSearchParams(
@@ -1630,7 +1707,7 @@ export default function ProcessInstanceListTable({
);
}
- const resultsTable = (
+ resultsTable = (
<>
{refilterTextComponent}
>
);
- return (
- <>
- {reportColumnForm()}
- {processInstanceReportSaveTag()}
-
- {resultsTable}
- >
- );
- }
- if (textToShowIfEmpty) {
- return (
+ } else if (textToShowIfEmpty) {
+ resultsTable = (
{textToShowIfEmpty}
);
}
- return null;
+ return (
+ <>
+ {reportColumnForm()}
+ {processInstanceReportSaveTag()}
+
+ {resultsTable}
+ >
+ );
}
From 2376080267ce1138ebd0992bbd9ce594745750ed Mon Sep 17 00:00:00 2001
From: jasquat
Date: Thu, 27 Apr 2023 07:28:06 -0400
Subject: [PATCH 04/18] more filter work
---
.../src/spiffworkflow_backend/api.yml | 23 +
.../routes/process_instances_controller.py | 20 +
.../unit/test_process_instance_processor.py | 1 +
.../components/ProcessInstanceListTable.tsx | 429 +++++++++---------
4 files changed, 247 insertions(+), 226 deletions(-)
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml
index e87cc395..653738f4 100755
--- a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml
@@ -1298,6 +1298,29 @@ paths:
items:
$ref: "#/components/schemas/Workflow"
+ /process-instances/report-metadata/{report_hash}:
+ parameters:
+ - name: report_hash
+ in: path
+ required: true
+ description: The unique id of an existing report
+ schema:
+ type: string
+ get:
+ operationId: spiffworkflow_backend.routes.process_instances_controller.process_instance_report_metadata_show
+ summary: Returns the metadata associated with a given report hash.
+ tags:
+ - Process Instances
+ responses:
+ "200":
+ description: Workflow.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: "#/components/schemas/Workflow"
+
/process-instances/reports/{report_id}:
parameters:
- name: report_id
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py
index 65821bfb..3009749f 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py
@@ -1,5 +1,7 @@
"""APIs for dealing with process groups, process models, and process instances."""
import base64
+from hashlib import sha256
+from spiffworkflow_backend.models.json_data import JsonDataModel # noqa: F401
import json
from typing import Any
from typing import Dict
@@ -333,9 +335,27 @@ def process_instance_list(
user=g.user,
)
+ json_data_hash = sha256(json.dumps(body['report_metadata'], sort_keys=True).encode("utf8")).hexdigest()
+ TaskService.insert_or_update_json_data_dict({'hash': json_data_hash, 'data': body['report_metadata']})
+ db.session.commit()
+ # json_data = JsonDataModel.query.filter_by(json_data_hash)
+ response_json['report_hash'] = json_data_hash
+
return make_response(jsonify(response_json), 200)
+def process_instance_report_metadata_show(
+ report_hash: str,
+) -> flask.wrappers.Response:
+ json_data = JsonDataModel.query.filter_by(hash=report_hash).first()
+ if json_data is None:
+ raise ApiError(
+ error_code="report_metadata_not_found",
+ message=f"Could not find report metadata for {report_hash}.",
+ )
+ return make_response(jsonify(json_data.data), 200)
+
+
def process_instance_report_column_list(
process_model_identifier: Optional[str] = None,
) -> flask.wrappers.Response:
diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_processor.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_processor.py
index a6ff70e3..0f410ad6 100644
--- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_processor.py
+++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_processor.py
@@ -357,6 +357,7 @@ class TestProcessInstanceProcessor(BaseTest):
human_task_one = process_instance.active_human_tasks[0]
spiff_manual_task = processor.bpmn_process_instance.get_task_from_id(UUID(human_task_one.task_id))
ProcessInstanceService.complete_form_task(processor, spiff_manual_task, {}, initiator_user, human_task_one)
+ import pdb; pdb.set_trace()
assert (
len(process_instance.active_human_tasks) == 0
), "expected 0 active human tasks after 2nd one is completed"
diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
index 1652ca36..5faaabe9 100644
--- a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
+++ b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
@@ -155,6 +155,9 @@ export default function ProcessInstanceListTable({
const [requiresRefilter, setRequiresRefilter] = useState(false);
const [lastColumnFilter, setLastColumnFilter] = useState('');
+ const [listHasBeenFiltered, setListHasBeenFiltered] =
+ useState(false);
+
const preferredUsername = UserService.getPreferredUsername();
const userEmail = UserService.getUserEmail();
@@ -280,8 +283,10 @@ export default function ProcessInstanceListTable({
if (result.report.id) {
setProcessInstanceReportSelection(result.report);
}
- // searchParams.set('key', result.hash);
- // setSearchParams(searchParams);
+ if (result.report_hash) {
+ searchParams.set('report_hash', result.report_hash);
+ setSearchParams(searchParams);
+ }
}
const clearRefreshRef = useRef(null);
@@ -296,6 +301,10 @@ export default function ProcessInstanceListTable({
// eslint-disable-next-line sonarjs/cognitive-complexity
useEffect(() => {
+ // we apparently cannot use a state set in a useEffect from within that same useEffect
+ // so use a variable instead
+ let processModelSelectionItemsForUseEffect: ProcessModel[] = [];
+
function setProcessInstancesFromResult(result: any) {
setRequiresRefilter(false);
const processInstancesFromApi = result.results;
@@ -307,8 +316,6 @@ export default function ProcessInstanceListTable({
if (result.report.id) {
setProcessInstanceReportSelection(result.report);
}
- // searchParams.set('key', result.hash);
- // setSearchParams(searchParams);
}
// Useful to stop refreshing if an api call gets an error
@@ -321,7 +328,115 @@ export default function ProcessInstanceListTable({
console.error(error);
}
};
- function getProcessInstances() {
+ function getProcessInstances(
+ reportMetadataBody: ReportMetadata | null = null
+ ) {
+ if (listHasBeenFiltered) {
+ return;
+ }
+ let reportMetadataBodyToUse = reportMetadataBody;
+ if (!reportMetadataBodyToUse) {
+ reportMetadataBodyToUse = {
+ columns: [],
+ filter_by: [],
+ order_by: [],
+ };
+ }
+
+ let selectedProcessModelIdentifier = processModelFullIdentifier;
+ reportMetadataBodyToUse.filter_by.forEach(
+ (reportFilter: ReportFilter) => {
+ if (reportFilter.field_name === 'process_status') {
+ setProcessStatusSelection(
+ (reportFilter.field_value || '').split(',')
+ );
+ } else if (reportFilter.field_name === 'process_model_identifier') {
+ selectedProcessModelIdentifier =
+ reportFilter.field_value || undefined;
+ } else if (dateParametersToAlwaysFilterBy[reportFilter.field_name]) {
+ const dateFunctionToCall =
+ dateParametersToAlwaysFilterBy[reportFilter.field_name][0];
+ const timeFunctionToCall =
+ dateParametersToAlwaysFilterBy[reportFilter.field_name][1];
+ if (reportFilter.field_value) {
+ const dateString = convertSecondsToFormattedDateString(
+ reportFilter.field_value as any
+ );
+ dateFunctionToCall(dateString);
+ const timeString = convertSecondsToFormattedTimeHoursMinutes(
+ reportFilter.field_value as any
+ );
+ timeFunctionToCall(timeString);
+ setShowFilterOptions(true);
+ }
+ }
+ }
+ );
+
+ processModelSelectionItemsForUseEffect.forEach(
+ (processModel: ProcessModel) => {
+ if (processModel.id === selectedProcessModelIdentifier) {
+ setProcessModelSelection(processModel);
+ }
+ }
+ );
+
+ // const postBody: ReportMetadata = {
+ // columns: [],
+ // filter_by: [],
+ // order_by: [],
+ // };
+ //
+ // if (searchParams.get('report_id')) {
+ // queryParamString += `&report_id=${searchParams.get('report_id')}`;
+ // } else if (reportIdentifier) {
+ // queryParamString += `&report_identifier=${reportIdentifier}`;
+ // }
+
+ // if (searchParams.get('report_columns')) {
+ // const reportColumnsBase64 = searchParams.get('report_columns');
+ // if (reportColumnsBase64) {
+ // const reportColumnsList = JSON.parse(
+ // decodeBase64(reportColumnsBase64)
+ // );
+ // postBody.columns = reportColumnsList;
+ // }
+ // }
+ // if (searchParams.get('report_filter_by')) {
+ // const reportFilterByBase64 = searchParams.get('report_filter_by');
+ // if (reportFilterByBase64) {
+ // const reportFilterByList = JSON.parse(
+ // decodeBase64(reportFilterByBase64)
+ // );
+ // postBody.filter_by = reportFilterByList;
+ // }
+ // }
+ //
+ // Object.keys(parametersToGetFromSearchParams).forEach(
+ // (paramName: string) => {
+ // if (
+ // paramName === 'process_model_identifier' &&
+ // processModelFullIdentifier
+ // ) {
+ // postBody.filter_by.push({
+ // field_name: 'process_model_identifier',
+ // field_value: processModelFullIdentifier,
+ // });
+ // } else if (searchParams.get(paramName)) {
+ // // @ts-expect-error TS(7053) FIXME:
+ // const functionToCall = parametersToGetFromSearchParams[paramName];
+ // postBody.filter_by.push({
+ // field_name: paramName,
+ // field_value: searchParams.get(paramName),
+ // });
+ // if (functionToCall !== null) {
+ // functionToCall(searchParams.get(paramName) || '');
+ // }
+ // setShowFilterOptions(true);
+ // }
+ // }
+ // );
+
// eslint-disable-next-line prefer-const
let { page, perPage } = getPageInfoFromSearchParams(
searchParams,
@@ -334,172 +449,52 @@ export default function ProcessInstanceListTable({
perPage = perPageOptions[1];
}
let queryParamString = `per_page=${perPage}&page=${page}`;
-
- // "columns": [
- // {"Header": "id", "accessor": "id"},
- // {
- // "Header": "process_model_display_name",
- // "accessor": "process_model_display_name",
- // },
- // {"Header": "start_in_seconds", "accessor": "start_in_seconds"},
- // {"Header": "end_in_seconds", "accessor": "end_in_seconds"},
- // {"Header": "status", "accessor": "status"},
- // ],
- // "filter_by": [
- // {"field_name": "initiated_by_me", "field_value": "true"},
- // {"field_name": "has_terminal_status", "field_value": "true"},
- // ],
- // "order_by": ["-start_in_seconds", "-id"],
- const postBody: ReportMetadata = {
- columns: [],
- filter_by: [],
- order_by: [],
- };
-
- // const userAppliedFilter = searchParams.get('user_filter');
- // if (userAppliedFilter) {
- // queryParamString += `&user_filter=${userAppliedFilter}`;
- // }
-
- if (searchParams.get('report_id')) {
- queryParamString += `&report_id=${searchParams.get('report_id')}`;
- } else if (reportIdentifier) {
- queryParamString += `&report_identifier=${reportIdentifier}`;
- }
-
- if (searchParams.get('report_columns')) {
- // queryParamString += `&report_columns=${searchParams.get(
- // 'report_columns'
- // )}`;
- // const reportColumnsBase64 = encodeBase64(JSON.stringify(reportColumns()));
- const reportColumnsBase64 = searchParams.get('report_columns');
- if (reportColumnsBase64) {
- const reportColumnsList = JSON.parse(
- decodeBase64(reportColumnsBase64)
- );
- postBody.columns = reportColumnsList;
- }
- }
- if (searchParams.get('report_filter_by')) {
- // queryParamString += `&report_filter_by=${searchParams.get(
- // 'report_filter_by'
- // )}`;
- const reportFilterByBase64 = searchParams.get('report_filter_by');
- if (reportFilterByBase64) {
- const reportFilterByList = JSON.parse(
- decodeBase64(reportFilterByBase64)
- );
- postBody.filter_by = reportFilterByList;
- }
- }
-
- Object.keys(dateParametersToAlwaysFilterBy).forEach(
- (paramName: string) => {
- const dateFunctionToCall =
- dateParametersToAlwaysFilterBy[paramName][0];
- const timeFunctionToCall =
- dateParametersToAlwaysFilterBy[paramName][1];
- const searchParamValue = searchParams.get(paramName);
- if (searchParamValue) {
- // queryParamString += `&${paramName}=${searchParamValue}`;
- postBody.filter_by.push({
- field_name: paramName,
- field_value: searchParamValue,
- });
- const dateString = convertSecondsToFormattedDateString(
- searchParamValue as any
- );
- dateFunctionToCall(dateString);
- const timeString = convertSecondsToFormattedTimeHoursMinutes(
- searchParamValue as any
- );
- timeFunctionToCall(timeString);
- setShowFilterOptions(true);
- }
- }
- );
-
- Object.keys(parametersToGetFromSearchParams).forEach(
- (paramName: string) => {
- if (
- paramName === 'process_model_identifier' &&
- processModelFullIdentifier
- ) {
- // queryParamString += `&process_model_identifier=${processModelFullIdentifier}`;
- postBody.filter_by.push({
- field_name: 'process_model_identifier',
- field_value: processModelFullIdentifier,
- });
- } else if (searchParams.get(paramName)) {
- // @ts-expect-error TS(7053) FIXME:
- const functionToCall = parametersToGetFromSearchParams[paramName];
- // queryParamString += `&${paramName}=${searchParams.get(paramName)}`;
- postBody.filter_by.push({
- field_name: paramName,
- field_value: searchParams.get(paramName),
- });
- if (functionToCall !== null) {
- functionToCall(searchParams.get(paramName) || '');
- }
- setShowFilterOptions(true);
- }
- }
- );
-
if (additionalParams) {
queryParamString += `&${additionalParams}`;
}
- const reportMetadataBase64 = searchParams.get('report_metadata_base64');
- if (reportMetadataBase64) {
- const reportMetadata = JSON.parse(decodeBase64(reportMetadataBase64))
+ HttpService.makeCallToBackend({
+ path: `${processInstanceApiSearchPath}?${queryParamString}`,
+ successCallback: setProcessInstancesFromResult,
+ httpMethod: 'POST',
+ failureCallback: stopRefreshing,
+ onUnauthorized: stopRefreshing,
+ postBody: {
+ report_metadata: reportMetadataBody,
+ },
+ });
+ }
+ function getReportMetadataWithReportHash() {
+ if (listHasBeenFiltered) {
+ return;
+ }
+ const reportHash = searchParams.get('report_hash');
+ if (reportHash) {
HttpService.makeCallToBackend({
- path: `${processInstanceApiSearchPath}?${queryParamString}`,
- successCallback: setProcessInstancesFromResult,
- httpMethod: 'POST',
- failureCallback: stopRefreshing,
- onUnauthorized: stopRefreshing,
- postBody: {
- report_metadata: reportMetadata,
- // report_metadata: {
- // filter_by: [
- // {
- // field_name: 'process_model_identifier',
- // field_value: 'example/with-milestones',
- // },
- // ],
- // },
- },
+ path: `/process-instances/report-metadata/${reportHash}`,
+ successCallback: getProcessInstances,
});
+ } else {
+ getProcessInstances();
}
}
function processResultForProcessModels(result: any) {
- const processModelFullIdentifierFromSearchParams =
- getProcessModelFullIdentifierFromSearchParams(searchParams);
const selectionArray = result.results.map((item: any) => {
const label = `${item.id}`;
Object.assign(item, { label });
- if (label === processModelFullIdentifierFromSearchParams) {
- setProcessModelSelection(item);
- }
return item;
});
+ processModelSelectionItemsForUseEffect = selectionArray;
setProcessModelAvailableItems(selectionArray);
- const processStatusSelectedArray: string[] = [];
const processStatusAllOptionsArray = PROCESS_STATUSES.map(
(processStatusOption: any) => {
- const regex = new RegExp(`\\b${processStatusOption}\\b`);
- if ((searchParams.get('process_status') || '').match(regex)) {
- processStatusSelectedArray.push(processStatusOption);
- }
return processStatusOption;
}
);
- setProcessStatusSelection(processStatusSelectedArray);
setProcessStatusAllOptions(processStatusAllOptionsArray);
- getProcessInstances();
+ getReportMetadataWithReportHash();
}
const checkFiltersAndRun = () => {
if (filtersEnabled) {
@@ -509,7 +504,7 @@ export default function ProcessInstanceListTable({
successCallback: processResultForProcessModels,
});
} else {
- getProcessInstances();
+ getReportMetadataWithReportHash();
}
};
@@ -540,59 +535,59 @@ export default function ProcessInstanceListTable({
processInstanceApiSearchPath,
]);
- // This sets the filter data using the saved reports returned from the initial instance_list query.
- // This could probably be merged into the main useEffect but it works here now.
- useEffect(() => {
- const filters = processInstanceFilters as any;
- Object.keys(dateParametersToAlwaysFilterBy).forEach((paramName: string) => {
- const dateFunctionToCall = dateParametersToAlwaysFilterBy[paramName][0];
- const timeFunctionToCall = dateParametersToAlwaysFilterBy[paramName][1];
- const paramValue = filters[paramName];
- dateFunctionToCall('');
- timeFunctionToCall('');
- if (paramValue) {
- const dateString = convertSecondsToFormattedDateString(
- paramValue as any
- );
- dateFunctionToCall(dateString);
- const timeString = convertSecondsToFormattedTimeHoursMinutes(
- paramValue as any
- );
- timeFunctionToCall(timeString);
- setShowFilterOptions(true);
- }
- });
-
- setProcessModelSelection(null);
- processModelAvailableItems.forEach((item: any) => {
- if (item.id === filters.process_model_identifier) {
- setProcessModelSelection(item);
- }
- });
-
- if (filters.process_initiator_username) {
- const functionToCall =
- parametersToGetFromSearchParams.process_initiator_username;
- functionToCall(filters.process_initiator_username);
- }
-
- const processStatusSelectedArray: string[] = [];
- if (filters.process_status) {
- PROCESS_STATUSES.forEach((processStatusOption: any) => {
- const regex = new RegExp(`\\b${processStatusOption}\\b`);
- if (filters.process_status.match(regex)) {
- processStatusSelectedArray.push(processStatusOption);
- }
- });
- setShowFilterOptions(true);
- }
- setProcessStatusSelection(processStatusSelectedArray);
- }, [
- processInstanceFilters,
- dateParametersToAlwaysFilterBy,
- parametersToGetFromSearchParams,
- processModelAvailableItems,
- ]);
+ // // This sets the filter data using the saved reports returned from the initial instance_list query.
+ // // This could probably be merged into the main useEffect but it works here now.
+ // useEffect(() => {
+ // const filters = processInstanceFilters as any;
+ // // Object.keys(dateParametersToAlwaysFilterBy).forEach((paramName: string) => {
+ // // const dateFunctionToCall = dateParametersToAlwaysFilterBy[paramName][0];
+ // // const timeFunctionToCall = dateParametersToAlwaysFilterBy[paramName][1];
+ // // const paramValue = filters[paramName];
+ // // dateFunctionToCall('');
+ // // timeFunctionToCall('');
+ // // if (paramValue) {
+ // // const dateString = convertSecondsToFormattedDateString(
+ // // paramValue as any
+ // // );
+ // // dateFunctionToCall(dateString);
+ // // const timeString = convertSecondsToFormattedTimeHoursMinutes(
+ // // paramValue as any
+ // // );
+ // // timeFunctionToCall(timeString);
+ // // setShowFilterOptions(true);
+ // // }
+ // // });
+ //
+ // // setProcessModelSelection(null);
+ // // processModelAvailableItems.forEach((item: any) => {
+ // // if (item.id === filters.process_model_identifier) {
+ // // setProcessModelSelection(item);
+ // // }
+ // // });
+ //
+ // if (filters.process_initiator_username) {
+ // const functionToCall =
+ // parametersToGetFromSearchParams.process_initiator_username;
+ // functionToCall(filters.process_initiator_username);
+ // }
+ //
+ // const processStatusSelectedArray: string[] = [];
+ // if (filters.process_status) {
+ // PROCESS_STATUSES.forEach((processStatusOption: any) => {
+ // const regex = new RegExp(`\\b${processStatusOption}\\b`);
+ // if (filters.process_status.match(regex)) {
+ // processStatusSelectedArray.push(processStatusOption);
+ // }
+ // });
+ // setShowFilterOptions(true);
+ // }
+ // // setProcessStatusSelection(processStatusSelectedArray);
+ // }, [
+ // processInstanceFilters,
+ // dateParametersToAlwaysFilterBy,
+ // parametersToGetFromSearchParams,
+ // processModelAvailableItems,
+ // ]);
const processInstanceReportSaveTag = () => {
if (processInstanceReportJustSaved) {
@@ -740,21 +735,6 @@ export default function ProcessInstanceListTable({
return;
}
- // "columns": [
- // {"Header": "id", "accessor": "id"},
- // {
- // "Header": "process_model_display_name",
- // "accessor": "process_model_display_name",
- // },
- // {"Header": "start_in_seconds", "accessor": "start_in_seconds"},
- // {"Header": "end_in_seconds", "accessor": "end_in_seconds"},
- // {"Header": "status", "accessor": "status"},
- // ],
- // "filter_by": [
- // {"field_name": "initiated_by_me", "field_value": "true"},
- // {"field_name": "has_terminal_status", "field_value": "true"},
- // ],
- // "order_by": ["-start_in_seconds", "-id"],
const postBody: ReportMetadata = {
columns: [],
filter_by: [],
@@ -789,16 +769,9 @@ export default function ProcessInstanceListTable({
);
}
- // console.log('reportColumns()', reportColumns())
- // const reportColumnsBase64 = encodeBase64(JSON.stringify(reportColumns()));
- // queryParamString += `&report_columns=${reportColumnsBase64}`;
postBody.columns = reportColumns();
- // console.log('reportFilterBy()', reportFilterBy())
- // const reportFilterByBase64 = encodeBase64(JSON.stringify(reportFilterBy()));
- // queryParamString += `&report_filter_by=${reportFilterByBase64}`;
if (processInitiatorSelection) {
- // queryParamString += `&process_initiator_username=${processInitiatorSelection.username}`;
addFieldValueToReportMetadata(
postBody,
'process_initiator_username',
@@ -828,11 +801,15 @@ export default function ProcessInstanceListTable({
// http://localhost:7001/admin/process-instances/for-me?per_page=50&page=1&report_metadata_base64=eyJjb2x1bW5zIjpudWxsLCJmaWx0ZXJfYnkiOlt7ImZpZWxkX25hbWUiOiJwcm9jZXNzX3N0YXR1cyIsImZpZWxkX3ZhbHVlIjoiY29tcGxldGUifV0sIm9yZGVyX2J5IjpbXX0%3D
// const queryParamString = `per_page=${perPage}&page=${page}`;
+
+ setListHasBeenFiltered(true);
searchParams.set('per_page', perPage.toString());
searchParams.set('page', page.toString());
- const reportMetadataBase64 = encodeBase64(JSON.stringify(postBody));
- searchParams.set('report_metadata_base64', reportMetadataBase64);
+
+ // const reportMetadataBase64 = encodeBase64(JSON.stringify(postBody));
+ // searchParams.set('report_metadata_base64', reportMetadataBase64);
setSearchParams(searchParams);
+
HttpService.makeCallToBackend({
path: `${processInstanceApiSearchPath}?${queryParamString}`,
httpMethod: 'POST',
From e49734b9ef9bdcedefa4a3d8a47a619e6f9b342f Mon Sep 17 00:00:00 2001
From: jasquat
Date: Thu, 27 Apr 2023 11:47:48 -0400
Subject: [PATCH 05/18] most of the process instance filters are working. still
need to add back in support for reports w/ burnettk
---
.../process_instance_report_service.py | 63 ++++---
.../components/ProcessInstanceListTable.tsx | 157 +++++++-----------
2 files changed, 102 insertions(+), 118 deletions(-)
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py
index 92040b2b..ab180fee 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py
@@ -412,11 +412,16 @@ class ProcessInstanceReportService:
) -> list[dict]:
"""Add_metadata_columns_to_process_instance."""
results = []
+ non_metadata_columns = cls.non_metadata_columns()
for process_instance_row in process_instance_sqlalchemy_rows:
process_instance_mapping = process_instance_row._mapping
process_instance_dict = process_instance_row[0].serialized
+ # import pdb; pdb.set_trace()
for metadata_column in metadata_columns:
+ # if metadata_column["accessor"] not in non_metadata_columns:
+ # if metadata_column["accessor"] not in cls.process_instance_stock_columns():
if metadata_column["accessor"] not in process_instance_dict:
+ # import pdb; pdb.set_trace()
process_instance_dict[metadata_column["accessor"]] = process_instance_mapping[
metadata_column["accessor"]
]
@@ -477,6 +482,14 @@ class ProcessInstanceReportService:
"""Get_column_names_for_model."""
return [i.name for i in model.__table__.columns]
+ @classmethod
+ def process_instance_stock_columns(cls) -> list[str]:
+ return cls.get_column_names_for_model(ProcessInstanceModel)
+
+ @classmethod
+ def non_metadata_columns(cls) -> list[str]:
+ return cls.process_instance_stock_columns() + ['process_initiator_username']
+
@classmethod
def builtin_column_options(cls) -> list[dict]:
"""Builtin_column_options."""
@@ -663,16 +676,15 @@ class ProcessInstanceReportService:
process_instance_query = process_instance_query.filter(UserGroupAssignmentModel.user_id == user.id)
instance_metadata_aliases = {}
- report_column_list = cls.get_filter_value(filters, 'report_column_list')
- report_filter_by_list = cls.get_filter_value(filters, 'report_filter_by_list')
- stock_columns = cls.get_column_names_for_model(ProcessInstanceModel)
- if isinstance(report_column_list, list):
- process_instance_report.report_metadata["columns"] = report_column_list
- if isinstance(report_filter_by_list, list):
- process_instance_report.report_metadata["filter_by"] = report_filter_by_list
+ # print(f"report_metadata['columns']: {report_metadata['columns']}")
+ # import pdb; pdb.set_trace()
+ if report_metadata['columns'] and len(report_metadata['columns']) > 0:
+ process_instance_report.report_metadata["columns"] = report_metadata['columns']
+ if report_metadata['filter_by'] and len(report_metadata['filter_by']) > 0:
+ process_instance_report.report_metadata["filter_by"] = report_metadata['filter_by']
for column in process_instance_report.report_metadata["columns"]:
- if column["accessor"] in stock_columns:
+ if column["accessor"] in cls.non_metadata_columns():
continue
instance_metadata_alias = aliased(ProcessInstanceMetadataModel)
instance_metadata_aliases[column["accessor"]] = instance_metadata_alias
@@ -699,32 +711,33 @@ class ProcessInstanceReportService:
instance_metadata_alias, and_(*conditions), isouter=isouter
).add_columns(func.max(instance_metadata_alias.value).label(column["accessor"]))
- order_by_query_array = []
- order_by_array = process_instance_report.report_metadata["order_by"]
- if len(order_by_array) < 1:
- order_by_array = ProcessInstanceReportModel.default_order_by()
- for order_by_option in order_by_array:
- attribute = re.sub("^-", "", order_by_option)
- if attribute in stock_columns:
- if order_by_option.startswith("-"):
- order_by_query_array.append(getattr(ProcessInstanceModel, attribute).desc())
- else:
- order_by_query_array.append(getattr(ProcessInstanceModel, attribute).asc())
- elif attribute in instance_metadata_aliases:
- if order_by_option.startswith("-"):
- order_by_query_array.append(func.max(instance_metadata_aliases[attribute].value).desc())
- else:
- order_by_query_array.append(func.max(instance_metadata_aliases[attribute].value).asc())
+ # order_by_query_array = []
+ # order_by_array = process_instance_report.report_metadata["order_by"]
+ # if len(order_by_array) < 1:
+ # order_by_array = ProcessInstanceReportModel.default_order_by()
+ # for order_by_option in order_by_array:
+ # attribute = re.sub("^-", "", order_by_option)
+ # if attribute in cls.process_instance_stock_columns():
+ # if order_by_option.startswith("-"):
+ # order_by_query_array.append(getattr(ProcessInstanceModel, attribute).desc())
+ # else:
+ # order_by_query_array.append(getattr(ProcessInstanceModel, attribute).asc())
+ # elif attribute in instance_metadata_aliases:
+ # if order_by_option.startswith("-"):
+ # order_by_query_array.append(func.max(instance_metadata_aliases[attribute].value).desc())
+ # else:
+ # order_by_query_array.append(func.max(instance_metadata_aliases[attribute].value).asc())
process_instances = (
process_instance_query.group_by(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)
)
results = cls.add_metadata_columns_to_process_instance(
process_instances.items, process_instance_report.report_metadata["columns"]
)
+ # import pdb; pdb.set_trace()
for value in cls.check_filter_value(filters, 'oldest_open_human_task_fields'):
results = cls.add_human_task_fields(results, value)
diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
index 9b521e35..85de9ba9 100644
--- a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
+++ b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
@@ -128,7 +128,9 @@ export default function ProcessInstanceListTable({
const permissionRequestData: PermissionsToCheck = {
[targetUris.userSearch]: ['GET'],
};
- const { ability } = usePermissionFetcher(permissionRequestData);
+ const { ability, permissionsLoaded } = usePermissionFetcher(
+ permissionRequestData
+ );
const canSearchUsers: boolean = ability.can('GET', targetUris.userSearch);
const [processInstances, setProcessInstances] = useState([]);
@@ -301,6 +303,10 @@ export default function ProcessInstanceListTable({
// eslint-disable-next-line sonarjs/cognitive-complexity
useEffect(() => {
+ if (!permissionsLoaded) {
+ return;
+ }
+
// we apparently cannot use a state set in a useEffect from within that same useEffect
// so use a variable instead
let processModelSelectionItemsForUseEffect: ProcessModel[] = [];
@@ -311,7 +317,6 @@ export default function ProcessInstanceListTable({
setProcessInstances(processInstancesFromApi);
setPagination(result.pagination);
setProcessInstanceFilters(result.filters);
-
setReportMetadata(result.report.report_metadata);
if (result.report.id) {
setProcessInstanceReportSelection(result.report);
@@ -350,6 +355,10 @@ export default function ProcessInstanceListTable({
setProcessStatusSelection(
(reportFilter.field_value || '').split(',')
);
+ setShowFilterOptions(true);
+ } else if (reportFilter.field_name === 'process_initiator_username') {
+ searchForProcessInitiator(reportFilter.field_value || '');
+ setShowFilterOptions(true);
} else if (reportFilter.field_name === 'process_model_identifier') {
selectedProcessModelIdentifier =
reportFilter.field_value || undefined;
@@ -377,16 +386,11 @@ export default function ProcessInstanceListTable({
(processModel: ProcessModel) => {
if (processModel.id === selectedProcessModelIdentifier) {
setProcessModelSelection(processModel);
+ setShowFilterOptions(true);
}
}
);
- // const postBody: ReportMetadata = {
- // columns: [],
- // filter_by: [],
- // order_by: [],
- // };
- //
// if (searchParams.get('report_id')) {
// queryParamString += `&report_id=${searchParams.get('report_id')}`;
// } else if (reportIdentifier) {
@@ -402,40 +406,6 @@ export default function ProcessInstanceListTable({
// postBody.columns = reportColumnsList;
// }
// }
- // if (searchParams.get('report_filter_by')) {
- // const reportFilterByBase64 = searchParams.get('report_filter_by');
- // if (reportFilterByBase64) {
- // const reportFilterByList = JSON.parse(
- // decodeBase64(reportFilterByBase64)
- // );
- // postBody.filter_by = reportFilterByList;
- // }
- // }
- //
- // Object.keys(parametersToGetFromSearchParams).forEach(
- // (paramName: string) => {
- // if (
- // paramName === 'process_model_identifier' &&
- // processModelFullIdentifier
- // ) {
- // postBody.filter_by.push({
- // field_name: 'process_model_identifier',
- // field_value: processModelFullIdentifier,
- // });
- // } else if (searchParams.get(paramName)) {
- // // @ts-expect-error TS(7053) FIXME:
- // const functionToCall = parametersToGetFromSearchParams[paramName];
- // postBody.filter_by.push({
- // field_name: paramName,
- // field_value: searchParams.get(paramName),
- // });
- // if (functionToCall !== null) {
- // functionToCall(searchParams.get(paramName) || '');
- // }
- // setShowFilterOptions(true);
- // }
- // }
- // );
// eslint-disable-next-line prefer-const
let { page, perPage } = getPageInfoFromSearchParams(
@@ -460,7 +430,7 @@ export default function ProcessInstanceListTable({
failureCallback: stopRefreshing,
onUnauthorized: stopRefreshing,
postBody: {
- report_metadata: reportMetadataBody,
+ report_metadata: reportMetadataBodyToUse,
},
});
}
@@ -533,6 +503,7 @@ export default function ProcessInstanceListTable({
reportIdentifier,
additionalParams,
processInstanceApiSearchPath,
+ permissionsLoaded,
]);
// // This sets the filter data using the saved reports returned from the initial instance_list query.
@@ -682,14 +653,17 @@ export default function ProcessInstanceListTable({
const reportColumns = () => {
if (reportMetadata) {
- return (reportMetadata as any).columns;
+ return reportMetadata.columns;
}
- return null;
+ return [];
};
- const reportFilterBy = () => {
- return (reportMetadata as any).filter_by;
- };
+ // const reportFilterBy = () => {
+ // if (reportMetadata) {
+ // return reportMetadata.filter_by;
+ // }
+ // return null
+ // };
const navigateToNewReport = (queryParamString: string) => {
removeError();
@@ -698,6 +672,16 @@ export default function ProcessInstanceListTable({
navigate(`${processInstanceListPathPrefix}?${queryParamString}`);
};
+ const removeFieldFromReportMetadata = (
+ postBody: ReportMetadata,
+ fieldName: string
+ ) => {
+ const filtersToKeep = postBody.filter_by.filter(
+ (rf: ReportFilter) => rf.field_name !== fieldName
+ );
+ postBody.filter_by = filtersToKeep;
+ };
+
const addFieldValueToReportMetadata = (
postBody: ReportMetadata,
fieldName: string,
@@ -708,6 +692,8 @@ export default function ProcessInstanceListTable({
field_name: fieldName,
field_value: fieldValue,
});
+ } else {
+ removeFieldFromReportMetadata(postBody, fieldName);
}
};
@@ -721,8 +707,6 @@ export default function ProcessInstanceListTable({
undefined,
paginationQueryParamPrefix
);
- // let queryParamString = `per_page=${perPage}&page=${page}&user_filter=true`;
- const queryParamString = `per_page=${perPage}&page=${page}`;
const {
valid,
startFromSeconds,
@@ -735,11 +719,17 @@ export default function ProcessInstanceListTable({
return;
}
- const postBody: ReportMetadata = {
- columns: [],
- filter_by: [],
- order_by: [],
- };
+ let postBody = null;
+ if (reportMetadata) {
+ postBody = { ...reportMetadata };
+ }
+ if (!postBody) {
+ postBody = {
+ columns: [],
+ filter_by: [],
+ order_by: [],
+ };
+ }
addFieldValueToReportMetadata(postBody, 'start_from', startFromSeconds);
addFieldValueToReportMetadata(postBody, 'start_to', startToSeconds);
@@ -751,6 +741,8 @@ export default function ProcessInstanceListTable({
'process_status',
processStatusSelection.join(',')
);
+ } else {
+ removeFieldFromReportMetadata(postBody, 'process_status');
}
if (processModelSelection) {
@@ -759,17 +751,17 @@ export default function ProcessInstanceListTable({
'process_model_identifier',
processModelSelection.id
);
+ } else {
+ removeFieldFromReportMetadata(postBody, 'process_model_identifier');
}
- if (processInstanceReportSelection) {
- addFieldValueToReportMetadata(
- postBody,
- 'report_id',
- processInstanceReportSelection.id.toString()
- );
- }
-
- postBody.columns = reportColumns();
+ // if (processInstanceReportSelection) {
+ // addFieldValueToReportMetadata(
+ // postBody,
+ // 'report_id',
+ // processInstanceReportSelection.id.toString()
+ // );
+ // }
if (processInitiatorSelection) {
addFieldValueToReportMetadata(
@@ -777,39 +769,17 @@ export default function ProcessInstanceListTable({
'process_initiator_username',
processInitiatorSelection.username
);
- // navigateToNewReport(queryParamString);
+ } else {
+ removeFieldFromReportMetadata(postBody, 'process_initiator_username');
}
- // } else if (processInitiatorText) {
- // HttpService.makeCallToBackend({
- // path: targetUris.userExists,
- // httpMethod: 'POST',
- // postBody: { username: processInitiatorText },
- // successCallback: (result: any) => {
- // if (result.user_found) {
- // queryParamString += `&process_initiator_username=${processInitiatorText}`;
- // // navigateToNewReport(queryParamString);
- // } else {
- // setProcessInitiatorNotFoundErrorText(
- // `The provided username is invalid. Please type the exact username.`
- // );
- // }
- // },
- // });
- // } else {
- // navigateToNewReport(queryParamString);
- // }
-
- // http://localhost:7001/admin/process-instances/for-me?per_page=50&page=1&report_metadata_base64=eyJjb2x1bW5zIjpudWxsLCJmaWx0ZXJfYnkiOlt7ImZpZWxkX25hbWUiOiJwcm9jZXNzX3N0YXR1cyIsImZpZWxkX3ZhbHVlIjoiY29tcGxldGUifV0sIm9yZGVyX2J5IjpbXX0%3D
- // const queryParamString = `per_page=${perPage}&page=${page}`;
setListHasBeenFiltered(true);
+ setReportMetadata(postBody);
searchParams.set('per_page', perPage.toString());
searchParams.set('page', page.toString());
-
- // const reportMetadataBase64 = encodeBase64(JSON.stringify(postBody));
- // searchParams.set('report_metadata_base64', reportMetadataBase64);
setSearchParams(searchParams);
+ const queryParamString = `per_page=${perPage}&page=${page}`;
HttpService.makeCallToBackend({
path: `${processInstanceApiSearchPath}?${queryParamString}`,
httpMethod: 'POST',
@@ -1081,6 +1051,7 @@ export default function ProcessInstanceListTable({
reportColumn,
{ filter_field_value: '', filter_operator: '' }
);
+ if (reportColumn.filterable) {
const reportFilter = getFilterByFromReportMetadata(
reportColumnForEditing.accessor
);
@@ -1090,6 +1061,7 @@ export default function ProcessInstanceListTable({
reportColumnForEditing.filter_operator =
reportFilter.operator || 'equals';
}
+ }
return reportColumnForEditing;
};
@@ -1212,10 +1184,9 @@ export default function ProcessInstanceListTable({
};
const columnSelections = () => {
- if (reportColumns()) {
+ if (reportColumns().length > 0) {
const tags: any = [];
-
- (reportColumns() as any).forEach((reportColumn: ReportColumn) => {
+ reportColumns().forEach((reportColumn: ReportColumn) => {
const reportColumnForEditing =
reportColumnToReportColumnForEditing(reportColumn);
From b86ddf8a96454b25955b44b7a5d9be076e255691 Mon Sep 17 00:00:00 2001
From: jasquat
Date: Thu, 27 Apr 2023 15:19:02 -0400
Subject: [PATCH 06/18] reports seem to be working again w/ burnettk
---
.../migrations/versions/68adb1d504e1_.py | 34 +++
.../src/spiffworkflow_backend/api.yml | 52 ++--
.../spiffworkflow_backend/models/json_data.py | 37 +++
.../models/process_instance_report.py | 9 +
.../routes/process_instances_controller.py | 105 ++++---
.../routes/tasks_controller.py | 5 +-
.../services/process_instance_processor.py | 2 +-
.../services/task_service.py | 30 +-
.../ProcessInstanceListSaveAsReport.tsx | 140 +++++----
.../components/ProcessInstanceListTable.tsx | 265 +++++++++---------
.../ProcessInstanceReportSearch.tsx | 19 +-
.../src/routes/AdminRoutes.tsx | 5 -
.../src/routes/ProcessInstanceReportShow.tsx | 97 -------
13 files changed, 401 insertions(+), 399 deletions(-)
create mode 100644 spiffworkflow-backend/migrations/versions/68adb1d504e1_.py
delete mode 100644 spiffworkflow-frontend/src/routes/ProcessInstanceReportShow.tsx
diff --git a/spiffworkflow-backend/migrations/versions/68adb1d504e1_.py b/spiffworkflow-backend/migrations/versions/68adb1d504e1_.py
new file mode 100644
index 00000000..dbeca2ba
--- /dev/null
+++ b/spiffworkflow-backend/migrations/versions/68adb1d504e1_.py
@@ -0,0 +1,34 @@
+"""empty message
+
+Revision ID: 68adb1d504e1
+Revises: 0c7428378d6e
+Create Date: 2023-04-27 12:24:01.771698
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = '68adb1d504e1'
+down_revision = '0c7428378d6e'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ with op.batch_alter_table('process_instance_report', schema=None) as batch_op:
+ batch_op.add_column(sa.Column('json_data_hash', sa.String(length=255), nullable=False))
+ batch_op.create_index(batch_op.f('ix_process_instance_report_json_data_hash'), ['json_data_hash'], unique=False)
+
+ # ### end Alembic commands ###
+
+
+def downgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ with op.batch_alter_table('process_instance_report', schema=None) as batch_op:
+ batch_op.drop_index(batch_op.f('ix_process_instance_report_json_data_hash'))
+ batch_op.drop_column('json_data_hash')
+
+ # ### end Alembic commands ###
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml
index 2ae0c4a7..27172a0f 100755
--- a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml
@@ -1298,17 +1298,29 @@ paths:
items:
$ref: "#/components/schemas/Workflow"
- /process-instances/report-metadata/{report_hash}:
+ /process-instances/report-metadata:
parameters:
- name: report_hash
- in: path
- required: true
- description: The unique id of an existing report
+ in: query
+ required: false
+ description: The hash of a query that has been searched before.
+ schema:
+ type: string
+ - name: report_id
+ in: query
+ required: false
+ description: The unique id of an existing report.
+ schema:
+ type: integer
+ - name: report_identifier
+ in: query
+ required: false
+ description: Specifies the identifier of a report to use, if any
schema:
type: string
get:
- operationId: spiffworkflow_backend.routes.process_instances_controller.process_instance_report_metadata_show
- summary: Returns the metadata associated with a given report hash.
+ operationId: spiffworkflow_backend.routes.process_instances_controller.process_instance_report_show
+ summary: Returns the metadata associated with a given report key. This favors report_hash over report_id and report_identifier.
tags:
- Process Instances
responses:
@@ -1341,20 +1353,20 @@ paths:
description: The page number to return. Defaults to page 1.
schema:
type: integer
- get:
- operationId: spiffworkflow_backend.routes.process_instances_controller.process_instance_report_show
- summary: Returns a report of process instances for a given process model
- tags:
- - Process Instances
- responses:
- "200":
- description: Workflow.
- content:
- application/json:
- schema:
- type: array
- items:
- $ref: "#/components/schemas/Workflow"
+ # get:
+ # operationId: spiffworkflow_backend.routes.process_instances_controller.process_instance_report_show
+ # summary: Returns a report of process instances for a given process model
+ # tags:
+ # - Process Instances
+ # responses:
+ # "200":
+ # description: Workflow.
+ # content:
+ # application/json:
+ # schema:
+ # type: array
+ # items:
+ # $ref: "#/components/schemas/Workflow"
put:
operationId: spiffworkflow_backend.routes.process_instances_controller.process_instance_report_update
summary: Updates a process instance report
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/json_data.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/json_data.py
index 3c0aed85..5c094b86 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/models/json_data.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/json_data.py
@@ -1,4 +1,10 @@
from __future__ import annotations
+import json
+from hashlib import sha256
+from flask import current_app
+from typing import TypedDict
+from sqlalchemy.dialects.mysql import insert as mysql_insert
+from sqlalchemy.dialects.postgresql import insert as postgres_insert
from spiffworkflow_backend.models.db import db
from spiffworkflow_backend.models.db import SpiffworkflowBaseDBModel
@@ -8,6 +14,11 @@ class JsonDataModelNotFoundError(Exception):
pass
+class JsonDataDict(TypedDict):
+ hash: str
+ data: dict
+
+
# delta algorithm <- just to save it for when we want to try to implement it:
# a = {"hey": { "hey2": 2, "hey3": 3, "hey6": 7 }, "hey30": 3, "hey40": 4}
# b = {"hey": { "hey2": 4, "hey5": 3 }, "hey20": 2, "hey30": 3}
@@ -42,3 +53,29 @@ class JsonDataModel(SpiffworkflowBaseDBModel):
@classmethod
def find_data_dict_by_hash(cls, hash: str) -> dict:
return cls.find_object_by_hash(hash).data
+
+ @classmethod
+ def insert_or_update_json_data_records(
+ cls, json_data_hash_to_json_data_dict_mapping: dict[str, JsonDataDict]
+ ) -> None:
+ list_of_dicts = [*json_data_hash_to_json_data_dict_mapping.values()]
+ if len(list_of_dicts) > 0:
+ on_duplicate_key_stmt = None
+ if current_app.config["SPIFFWORKFLOW_BACKEND_DATABASE_TYPE"] == "mysql":
+ insert_stmt = mysql_insert(JsonDataModel).values(list_of_dicts)
+ on_duplicate_key_stmt = insert_stmt.on_duplicate_key_update(data=insert_stmt.inserted.data)
+ else:
+ insert_stmt = postgres_insert(JsonDataModel).values(list_of_dicts)
+ on_duplicate_key_stmt = insert_stmt.on_conflict_do_nothing(index_elements=["hash"])
+ db.session.execute(on_duplicate_key_stmt)
+
+ @classmethod
+ def insert_or_update_json_data_dict(cls, json_data_dict: JsonDataDict) -> None:
+ cls.insert_or_update_json_data_records({json_data_dict["hash"]: json_data_dict})
+
+ @classmethod
+ def create_and_insert_json_data_from_dict(cls, data: dict) -> str:
+ json_data_hash = sha256(json.dumps(data, sort_keys=True).encode("utf8")).hexdigest()
+ cls.insert_or_update_json_data_dict({'hash': json_data_hash, 'data': data})
+ db.session.commit()
+ return json_data_hash
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance_report.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance_report.py
index 2c8a442d..25497b1c 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance_report.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance_report.py
@@ -1,5 +1,6 @@
"""Process_instance."""
from __future__ import annotations
+from spiffworkflow_backend.models.json_data import JsonDataModel # noqa: F401
from dataclasses import dataclass
from typing import Any
@@ -88,6 +89,11 @@ class ProcessInstanceReportModel(SpiffworkflowBaseDBModel):
created_at_in_seconds = db.Column(db.Integer)
updated_at_in_seconds = db.Column(db.Integer)
+ json_data_hash: str = db.Column(db.String(255), nullable=False, index=True)
+
+ def get_report_metadata(self) -> dict:
+ return JsonDataModel.find_data_dict_by_hash(self.json_data_hash)
+
@classmethod
def default_order_by(cls) -> list[str]:
"""Default_order_by."""
@@ -154,10 +160,13 @@ class ProcessInstanceReportModel(SpiffworkflowBaseDBModel):
f"Process instance report with identifier already exists: {identifier}"
)
+ json_data_hash = JsonDataModel.create_and_insert_json_data_from_dict(report_metadata)
+
process_instance_report = cls(
identifier=identifier,
created_by_id=user.id,
report_metadata=report_metadata,
+ json_data_hash=json_data_hash,
)
db.session.add(process_instance_report)
db.session.commit()
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py
index 3009749f..a9471c9a 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py
@@ -335,25 +335,42 @@ def process_instance_list(
user=g.user,
)
- json_data_hash = sha256(json.dumps(body['report_metadata'], sort_keys=True).encode("utf8")).hexdigest()
- TaskService.insert_or_update_json_data_dict({'hash': json_data_hash, 'data': body['report_metadata']})
- db.session.commit()
- # json_data = JsonDataModel.query.filter_by(json_data_hash)
+ json_data_hash = JsonDataModel.create_and_insert_json_data_from_dict(body['report_metadata'])
response_json['report_hash'] = json_data_hash
+ db.session.commit()
return make_response(jsonify(response_json), 200)
-def process_instance_report_metadata_show(
- report_hash: str,
+def process_instance_report_show(
+ report_hash: Optional[str] = None,
+ report_id: Optional[int] = None,
+ report_identifier: Optional[str] = None,
) -> flask.wrappers.Response:
- json_data = JsonDataModel.query.filter_by(hash=report_hash).first()
- if json_data is None:
+
+ if report_hash is None and report_id is None and report_identifier is None:
raise ApiError(
- error_code="report_metadata_not_found",
- message=f"Could not find report metadata for {report_hash}.",
+ error_code="report_key_missing",
+ message="A report key is needed to lookup a report. Either choose a report_hash, report_id, or report_identifier.",
)
- return make_response(jsonify(json_data.data), 200)
+ response_result = {}
+ if report_hash is not None:
+ json_data = JsonDataModel.query.filter_by(hash=report_hash).first()
+ if json_data is None:
+ raise ApiError(
+ error_code="report_metadata_not_found",
+ message=f"Could not find report metadata for {report_hash}.",
+ )
+ response_result = {
+ "id": 0,
+ "identifier": "custom",
+ "name": "custom",
+ "report_metadata": json_data.data,
+ }
+ else:
+ response_result = ProcessInstanceReportService.report_with_identifier(g.user, report_id, report_identifier)
+
+ return make_response(jsonify(response_result), 200)
def process_instance_report_column_list(
@@ -490,39 +507,39 @@ def process_instance_report_delete(
return Response(json.dumps({"ok": True}), status=200, mimetype="application/json")
-def process_instance_report_show(
- report_id: int,
- page: int = 1,
- per_page: int = 100,
-) -> flask.wrappers.Response:
- """Process_instance_report_show."""
- process_instances = ProcessInstanceModel.query.order_by(
- ProcessInstanceModel.start_in_seconds.desc(), ProcessInstanceModel.id.desc() # type: ignore
- ).paginate(page=page, per_page=per_page, error_out=False)
-
- process_instance_report = ProcessInstanceReportModel.query.filter_by(
- id=report_id,
- created_by_id=g.user.id,
- ).first()
- if process_instance_report is None:
- raise ApiError(
- error_code="unknown_process_instance_report",
- message="Unknown process instance report",
- status_code=404,
- )
-
- substitution_variables = request.args.to_dict()
- result_dict = process_instance_report.generate_report(process_instances.items, substitution_variables)
-
- # update this if we go back to a database query instead of filtering in memory
- result_dict["pagination"] = {
- "count": len(result_dict["results"]),
- "total": len(result_dict["results"]),
- "pages": 1,
- }
-
- return Response(json.dumps(result_dict), status=200, mimetype="application/json")
-
+# def process_instance_report_show(
+# report_id: int,
+# page: int = 1,
+# per_page: int = 100,
+# ) -> flask.wrappers.Response:
+# """Process_instance_report_show."""
+# process_instances = ProcessInstanceModel.query.order_by(
+# ProcessInstanceModel.start_in_seconds.desc(), ProcessInstanceModel.id.desc() # type: ignore
+# ).paginate(page=page, per_page=per_page, error_out=False)
+#
+# process_instance_report = ProcessInstanceReportModel.query.filter_by(
+# id=report_id,
+# created_by_id=g.user.id,
+# ).first()
+# if process_instance_report is None:
+# raise ApiError(
+# error_code="unknown_process_instance_report",
+# message="Unknown process instance report",
+# status_code=404,
+# )
+#
+# substitution_variables = request.args.to_dict()
+# result_dict = process_instance_report.generate_report(process_instances.items, substitution_variables)
+#
+# # update this if we go back to a database query instead of filtering in memory
+# result_dict["pagination"] = {
+# "count": len(result_dict["results"]),
+# "total": len(result_dict["results"]),
+# "pages": 1,
+# }
+#
+# return Response(json.dumps(result_dict), status=200, mimetype="application/json")
+#
def process_instance_task_list_without_task_data_for_me(
modified_process_model_identifier: str,
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py
index 64c22c5b..b62cd1c1 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py
@@ -1,5 +1,6 @@
"""APIs for dealing with process groups, process models, and process instances."""
import json
+from spiffworkflow_backend.models.json_data import JsonDataModel # noqa: F401
import os
import uuid
from sys import exc_info
@@ -220,7 +221,7 @@ def task_data_update(
task_model, new_task_data_dict, "json_data_hash"
)
if json_data_dict is not None:
- TaskService.insert_or_update_json_data_records({json_data_dict["hash"]: json_data_dict})
+ JsonDataModel.insert_or_update_json_data_records({json_data_dict["hash"]: json_data_dict})
ProcessInstanceTmpService.add_event_to_process_instance(
process_instance, ProcessInstanceEventType.task_data_edited.value, task_guid=task_guid
)
@@ -537,7 +538,7 @@ def _task_submit_shared(
task_model, spiff_task.data, "json_data_hash"
)
if json_data_dict is not None:
- TaskService.insert_or_update_json_data_dict(json_data_dict)
+ JsonDataModel.insert_or_update_json_data_dict(json_data_dict)
db.session.add(task_model)
db.session.commit()
else:
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py
index 45d27509..39d37c2c 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py
@@ -1737,7 +1737,7 @@ class ProcessInstanceProcessor:
bpmn_definition_to_task_definitions_mappings=self.bpmn_definition_to_task_definitions_mappings,
)
task_service.update_task_model(task_model, spiff_task)
- TaskService.insert_or_update_json_data_records(task_service.json_data_dicts)
+ JsonDataModel.insert_or_update_json_data_records(task_service.json_data_dicts)
ProcessInstanceTmpService.add_event_to_process_instance(
self.process_instance_model,
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/task_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/task_service.py
index 14441475..ec088dd3 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/services/task_service.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/task_service.py
@@ -15,14 +15,12 @@ from SpiffWorkflow.exceptions import WorkflowException # type: ignore
from SpiffWorkflow.task import Task as SpiffTask # type: ignore
from SpiffWorkflow.task import TaskState
from SpiffWorkflow.task import TaskStateNames
-from sqlalchemy.dialects.mysql import insert as mysql_insert
-from sqlalchemy.dialects.postgresql import insert as postgres_insert
from spiffworkflow_backend.models.bpmn_process import BpmnProcessModel
from spiffworkflow_backend.models.bpmn_process import BpmnProcessNotFoundError
from spiffworkflow_backend.models.bpmn_process_definition import BpmnProcessDefinitionModel
from spiffworkflow_backend.models.db import db
-from spiffworkflow_backend.models.json_data import JsonDataModel # noqa: F401
+from spiffworkflow_backend.models.json_data import JsonDataDict, JsonDataModel # noqa: F401
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
from spiffworkflow_backend.models.process_instance_event import ProcessInstanceEventModel
from spiffworkflow_backend.models.process_instance_event import ProcessInstanceEventType
@@ -38,11 +36,6 @@ class StartAndEndTimes(TypedDict):
end_in_seconds: Optional[float]
-class JsonDataDict(TypedDict):
- hash: str
- data: dict
-
-
class TaskModelError(Exception):
"""Copied from SpiffWorkflow.exceptions.WorkflowTaskException.
@@ -130,7 +123,7 @@ class TaskService:
db.session.bulk_save_objects(self.bpmn_processes.values())
db.session.bulk_save_objects(self.task_models.values())
db.session.bulk_save_objects(self.process_instance_events.values())
- self.__class__.insert_or_update_json_data_records(self.json_data_dicts)
+ JsonDataModel.insert_or_update_json_data_records(self.json_data_dicts)
def process_parents_and_children_and_save_to_database(
self,
@@ -483,10 +476,6 @@ class TaskService:
bpmn_process.json_data_hash = bpmn_process_data_hash
return json_data_dict
- @classmethod
- def insert_or_update_json_data_dict(cls, json_data_dict: JsonDataDict) -> None:
- TaskService.insert_or_update_json_data_records({json_data_dict["hash"]: json_data_dict})
-
@classmethod
def update_task_data_on_task_model_and_return_dict_if_updated(
cls, task_model: TaskModel, task_data_dict: dict, task_model_data_column: str
@@ -610,21 +599,6 @@ class TaskService:
new_properties_json["state"] = getattr(TaskState, state)
task_model.properties_json = new_properties_json
- @classmethod
- def insert_or_update_json_data_records(
- cls, json_data_hash_to_json_data_dict_mapping: dict[str, JsonDataDict]
- ) -> None:
- list_of_dicts = [*json_data_hash_to_json_data_dict_mapping.values()]
- if len(list_of_dicts) > 0:
- on_duplicate_key_stmt = None
- if current_app.config["SPIFFWORKFLOW_BACKEND_DATABASE_TYPE"] == "mysql":
- insert_stmt = mysql_insert(JsonDataModel).values(list_of_dicts)
- on_duplicate_key_stmt = insert_stmt.on_duplicate_key_update(data=insert_stmt.inserted.data)
- else:
- insert_stmt = postgres_insert(JsonDataModel).values(list_of_dicts)
- on_duplicate_key_stmt = insert_stmt.on_conflict_do_nothing(index_elements=["hash"])
- db.session.execute(on_duplicate_key_stmt)
-
@classmethod
def get_extensions_from_task_model(cls, task_model: TaskModel) -> dict:
task_definition = task_model.task_definition
diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListSaveAsReport.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListSaveAsReport.tsx
index 6953a20c..78186d97 100644
--- a/spiffworkflow-frontend/src/components/ProcessInstanceListSaveAsReport.tsx
+++ b/spiffworkflow-frontend/src/components/ProcessInstanceListSaveAsReport.tsx
@@ -75,73 +75,73 @@ export default function ProcessInstanceListSaveAsReport({
const addProcessInstanceReport = (event: any) => {
event.preventDefault();
- // TODO: make a field to set this
- let orderByArray = ['-start_in_seconds', '-id'];
- if (orderBy) {
- orderByArray = orderBy.split(',').filter((n) => n);
- }
- const filterByArray: any = [];
-
- if (processModelSelection) {
- filterByArray.push({
- field_name: 'process_model_identifier',
- field_value: processModelSelection.id,
- });
- }
-
- if (processInitiatorSelection) {
- filterByArray.push({
- field_name: 'process_initiator_username',
- field_value: processInitiatorSelection.username,
- });
- }
-
- if (processStatusSelection.length > 0) {
- filterByArray.push({
- field_name: 'process_status',
- field_value: processStatusSelection.join(','),
- operator: 'in',
- });
- }
-
- if (startFromSeconds) {
- filterByArray.push({
- field_name: 'start_from',
- field_value: startFromSeconds,
- });
- }
-
- if (startToSeconds) {
- filterByArray.push({
- field_name: 'start_to',
- field_value: startToSeconds,
- });
- }
-
- if (endFromSeconds) {
- filterByArray.push({
- field_name: 'end_from',
- field_value: endFromSeconds,
- });
- }
-
- if (endToSeconds) {
- filterByArray.push({
- field_name: 'end_to',
- field_value: endToSeconds,
- });
- }
-
- reportMetadata.filter_by.forEach((reportFilter: ReportFilter) => {
- columnArray.forEach((reportColumn: ReportColumn) => {
- if (
- reportColumn.accessor === reportFilter.field_name &&
- reportColumn.filterable
- ) {
- filterByArray.push(reportFilter);
- }
- });
- });
+ // // TODO: make a field to set this
+ // let orderByArray = ['-start_in_seconds', '-id'];
+ // if (orderBy) {
+ // orderByArray = orderBy.split(',').filter((n) => n);
+ // }
+ // const filterByArray: any = [];
+ //
+ // if (processModelSelection) {
+ // filterByArray.push({
+ // field_name: 'process_model_identifier',
+ // field_value: processModelSelection.id,
+ // });
+ // }
+ //
+ // if (processInitiatorSelection) {
+ // filterByArray.push({
+ // field_name: 'process_initiator_username',
+ // field_value: processInitiatorSelection.username,
+ // });
+ // }
+ //
+ // if (processStatusSelection.length > 0) {
+ // filterByArray.push({
+ // field_name: 'process_status',
+ // field_value: processStatusSelection.join(','),
+ // operator: 'in',
+ // });
+ // }
+ //
+ // if (startFromSeconds) {
+ // filterByArray.push({
+ // field_name: 'start_from',
+ // field_value: startFromSeconds,
+ // });
+ // }
+ //
+ // if (startToSeconds) {
+ // filterByArray.push({
+ // field_name: 'start_to',
+ // field_value: startToSeconds,
+ // });
+ // }
+ //
+ // if (endFromSeconds) {
+ // filterByArray.push({
+ // field_name: 'end_from',
+ // field_value: endFromSeconds,
+ // });
+ // }
+ //
+ // if (endToSeconds) {
+ // filterByArray.push({
+ // field_name: 'end_to',
+ // field_value: endToSeconds,
+ // });
+ // }
+ //
+ // reportMetadata.filter_by.forEach((reportFilter: ReportFilter) => {
+ // columnArray.forEach((reportColumn: ReportColumn) => {
+ // if (
+ // reportColumn.accessor === reportFilter.field_name &&
+ // reportColumn.filterable
+ // ) {
+ // filterByArray.push(reportFilter);
+ // }
+ // });
+ // });
let path = `/process-instances/reports`;
let httpMethod = 'POST';
@@ -156,11 +156,7 @@ export default function ProcessInstanceListSaveAsReport({
httpMethod,
postBody: {
identifier,
- report_metadata: {
- columns: columnArray,
- order_by: orderByArray,
- filter_by: filterByArray,
- },
+ report_metadata: reportMetadata,
},
});
handleSaveFormClose();
diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
index 85de9ba9..39f7871b 100644
--- a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
+++ b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useMemo, useRef, useState } from 'react';
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
// @ts-ignore
@@ -37,10 +37,7 @@ import {
convertSecondsToFormattedDateString,
convertSecondsToFormattedDateTime,
convertSecondsToFormattedTimeHoursMinutes,
- decodeBase64,
- encodeBase64,
getPageInfoFromSearchParams,
- getProcessModelFullIdentifierFromSearchParams,
modifyProcessIdentifierForPathParam,
refreshAtInterval,
REFRESH_INTERVAL_SECONDS,
@@ -119,6 +116,7 @@ export default function ProcessInstanceListTable({
if (variant === 'all') {
processInstanceApiSearchPath = '/process-instances';
}
+
const params = useParams();
const [searchParams, setSearchParams] = useSearchParams();
const navigate = useNavigate();
@@ -282,9 +280,11 @@ export default function ProcessInstanceListTable({
setProcessInstanceFilters(result.filters);
setReportMetadata(result.report.report_metadata);
- if (result.report.id) {
- setProcessInstanceReportSelection(result.report);
- }
+ // if (processInstanceReport) {
+ // if (processInstanceReport.id > 0) {
+ // setProcessInstanceReportSelection(processInstanceReport);
+ // }
+ // }
if (result.report_hash) {
searchParams.set('report_hash', result.report_hash);
setSearchParams(searchParams);
@@ -301,6 +301,11 @@ export default function ProcessInstanceListTable({
}
};
+ const getData = useCallback(() => {
+ console.log('WE DO STUFF');
+ stopRefreshing(null);
+ }, []);
+
// eslint-disable-next-line sonarjs/cognitive-complexity
useEffect(() => {
if (!permissionsLoaded) {
@@ -318,9 +323,6 @@ export default function ProcessInstanceListTable({
setPagination(result.pagination);
setProcessInstanceFilters(result.filters);
setReportMetadata(result.report.report_metadata);
- if (result.report.id) {
- setProcessInstanceReportSelection(result.report);
- }
}
// Useful to stop refreshing if an api call gets an error
@@ -334,18 +336,21 @@ export default function ProcessInstanceListTable({
}
};
function getProcessInstances(
- reportMetadataBody: ReportMetadata | null = null
+ processInstanceReport: ProcessInstanceReport | null = null
) {
if (listHasBeenFiltered) {
return;
}
- let reportMetadataBodyToUse = reportMetadataBody;
- if (!reportMetadataBodyToUse) {
- reportMetadataBodyToUse = {
- columns: [],
- filter_by: [],
- order_by: [],
- };
+ let reportMetadataBodyToUse: ReportMetadata = {
+ columns: [],
+ filter_by: [],
+ order_by: [],
+ };
+ if (processInstanceReport) {
+ reportMetadataBodyToUse = processInstanceReport.report_metadata;
+ if (processInstanceReport.id > 0) {
+ setProcessInstanceReportSelection(processInstanceReport);
+ }
}
let selectedProcessModelIdentifier = processModelFullIdentifier;
@@ -391,22 +396,6 @@ export default function ProcessInstanceListTable({
}
);
- // if (searchParams.get('report_id')) {
- // queryParamString += `&report_id=${searchParams.get('report_id')}`;
- // } else if (reportIdentifier) {
- // queryParamString += `&report_identifier=${reportIdentifier}`;
- // }
-
- // if (searchParams.get('report_columns')) {
- // const reportColumnsBase64 = searchParams.get('report_columns');
- // if (reportColumnsBase64) {
- // const reportColumnsList = JSON.parse(
- // decodeBase64(reportColumnsBase64)
- // );
- // postBody.columns = reportColumnsList;
- // }
- // }
-
// eslint-disable-next-line prefer-const
let { page, perPage } = getPageInfoFromSearchParams(
searchParams,
@@ -427,8 +416,8 @@ export default function ProcessInstanceListTable({
path: `${processInstanceApiSearchPath}?${queryParamString}`,
successCallback: setProcessInstancesFromResult,
httpMethod: 'POST',
- failureCallback: stopRefreshing,
- onUnauthorized: stopRefreshing,
+ failureCallback: getData,
+ onUnauthorized: getData,
postBody: {
report_metadata: reportMetadataBodyToUse,
},
@@ -438,10 +427,19 @@ export default function ProcessInstanceListTable({
if (listHasBeenFiltered) {
return;
}
- const reportHash = searchParams.get('report_hash');
- if (reportHash) {
+ const queryParams: string[] = [];
+ ['report_hash', 'report_id', 'report_identifier'].forEach(
+ (paramName: string) => {
+ if (searchParams.get(paramName)) {
+ queryParams.push(`${paramName}=${searchParams.get(paramName)}`);
+ }
+ }
+ );
+
+ if (queryParams.length > 0) {
+ const queryParamString = `?${queryParams.join('&')}`;
HttpService.makeCallToBackend({
- path: `/process-instances/report-metadata/${reportHash}`,
+ path: `/process-instances/report-metadata${queryParamString}`,
successCallback: getProcessInstances,
});
} else {
@@ -697,6 +695,85 @@ export default function ProcessInstanceListTable({
}
};
+ const getNewReportMetadataBasedOnPageWidgets = () => {
+ const {
+ valid,
+ startFromSeconds,
+ startToSeconds,
+ endFromSeconds,
+ endToSeconds,
+ } = calculateStartAndEndSeconds();
+
+ if (!valid) {
+ return null;
+ }
+
+ let newReportMetadata = null;
+ if (reportMetadata) {
+ newReportMetadata = { ...reportMetadata };
+ }
+ if (!newReportMetadata) {
+ newReportMetadata = {
+ columns: [],
+ filter_by: [],
+ order_by: [],
+ };
+ }
+
+ addFieldValueToReportMetadata(
+ newReportMetadata,
+ 'start_from',
+ startFromSeconds
+ );
+ addFieldValueToReportMetadata(
+ newReportMetadata,
+ 'start_to',
+ startToSeconds
+ );
+ addFieldValueToReportMetadata(
+ newReportMetadata,
+ 'end_from',
+ endFromSeconds
+ );
+ addFieldValueToReportMetadata(newReportMetadata, 'end_to', endToSeconds);
+ if (processStatusSelection.length > 0) {
+ addFieldValueToReportMetadata(
+ newReportMetadata,
+ 'process_status',
+ processStatusSelection.join(',')
+ );
+ } else {
+ removeFieldFromReportMetadata(newReportMetadata, 'process_status');
+ }
+
+ if (processModelSelection) {
+ addFieldValueToReportMetadata(
+ newReportMetadata,
+ 'process_model_identifier',
+ processModelSelection.id
+ );
+ } else {
+ removeFieldFromReportMetadata(
+ newReportMetadata,
+ 'process_model_identifier'
+ );
+ }
+
+ if (processInitiatorSelection) {
+ addFieldValueToReportMetadata(
+ newReportMetadata,
+ 'process_initiator_username',
+ processInitiatorSelection.username
+ );
+ } else {
+ removeFieldFromReportMetadata(
+ newReportMetadata,
+ 'process_initiator_username'
+ );
+ }
+ return newReportMetadata;
+ };
+
const applyFilter = (event: any) => {
event.preventDefault();
setProcessInitiatorNotFoundErrorText('');
@@ -707,74 +784,10 @@ export default function ProcessInstanceListTable({
undefined,
paginationQueryParamPrefix
);
- const {
- valid,
- startFromSeconds,
- startToSeconds,
- endFromSeconds,
- endToSeconds,
- } = calculateStartAndEndSeconds();
-
- if (!valid) {
- return;
- }
-
- let postBody = null;
- if (reportMetadata) {
- postBody = { ...reportMetadata };
- }
- if (!postBody) {
- postBody = {
- columns: [],
- filter_by: [],
- order_by: [],
- };
- }
-
- addFieldValueToReportMetadata(postBody, 'start_from', startFromSeconds);
- addFieldValueToReportMetadata(postBody, 'start_to', startToSeconds);
- addFieldValueToReportMetadata(postBody, 'end_from', endFromSeconds);
- addFieldValueToReportMetadata(postBody, 'end_to', endToSeconds);
- if (processStatusSelection.length > 0) {
- addFieldValueToReportMetadata(
- postBody,
- 'process_status',
- processStatusSelection.join(',')
- );
- } else {
- removeFieldFromReportMetadata(postBody, 'process_status');
- }
-
- if (processModelSelection) {
- addFieldValueToReportMetadata(
- postBody,
- 'process_model_identifier',
- processModelSelection.id
- );
- } else {
- removeFieldFromReportMetadata(postBody, 'process_model_identifier');
- }
-
- // if (processInstanceReportSelection) {
- // addFieldValueToReportMetadata(
- // postBody,
- // 'report_id',
- // processInstanceReportSelection.id.toString()
- // );
- // }
-
- if (processInitiatorSelection) {
- addFieldValueToReportMetadata(
- postBody,
- 'process_initiator_username',
- processInitiatorSelection.username
- );
- } else {
- removeFieldFromReportMetadata(postBody, 'process_initiator_username');
- }
+ const newReportMetadata = getNewReportMetadataBasedOnPageWidgets();
setListHasBeenFiltered(true);
- setReportMetadata(postBody);
+ setReportMetadata(newReportMetadata);
searchParams.set('per_page', perPage.toString());
searchParams.set('page', page.toString());
setSearchParams(searchParams);
@@ -783,7 +796,7 @@ export default function ProcessInstanceListTable({
HttpService.makeCallToBackend({
path: `${processInstanceApiSearchPath}?${queryParamString}`,
httpMethod: 'POST',
- postBody: { report_metadata: postBody },
+ postBody: { report_metadata: newReportMetadata },
failureCallback: stopRefreshing,
onUnauthorized: stopRefreshing,
successCallback: (result: any) => {
@@ -907,14 +920,12 @@ export default function ProcessInstanceListTable({
});
};
- // TODO onSuccess reload/select the new report in the report search
- const onSaveReportSuccess = (result: any, mode: string) => {
- processInstanceReportDidChange(
- {
- selectedItem: result,
- },
- mode
- );
+ const onSaveReportSuccess = (
+ processInstanceReport: ProcessInstanceReport
+ ) => {
+ setProcessInstanceReportSelection(processInstanceReport);
+ searchParams.set('report_id', processInstanceReport.id.toString());
+ setSearchParams(searchParams);
};
const saveAsReportComponent = () => {
@@ -926,7 +937,9 @@ export default function ProcessInstanceListTable({
endToSeconds,
} = calculateStartAndEndSeconds(false);
- if (!valid || !reportMetadata) {
+ const newReportMetadata = getNewReportMetadataBasedOnPageWidgets();
+
+ if (!valid || !reportMetadata || !newReportMetadata) {
return null;
}
return (
@@ -940,7 +953,7 @@ export default function ProcessInstanceListTable({
processInitiatorSelection={processInitiatorSelection}
processStatusSelection={processStatusSelection}
processInstanceReportSelection={processInstanceReportSelection}
- reportMetadata={reportMetadata}
+ reportMetadata={newReportMetadata}
startFromSeconds={startFromSeconds}
startToSeconds={startToSeconds}
endFromSeconds={endFromSeconds}
@@ -1052,15 +1065,15 @@ export default function ProcessInstanceListTable({
{ filter_field_value: '', filter_operator: '' }
);
if (reportColumn.filterable) {
- const reportFilter = getFilterByFromReportMetadata(
- reportColumnForEditing.accessor
- );
- if (reportFilter) {
- reportColumnForEditing.filter_field_value =
- reportFilter.field_value || '';
- reportColumnForEditing.filter_operator =
- reportFilter.operator || 'equals';
- }
+ const reportFilter = getFilterByFromReportMetadata(
+ reportColumnForEditing.accessor
+ );
+ if (reportFilter) {
+ reportColumnForEditing.filter_field_value =
+ reportFilter.field_value || '';
+ reportColumnForEditing.filter_operator =
+ reportFilter.operator || 'equals';
+ }
}
return reportColumnForEditing;
};
@@ -1622,6 +1635,8 @@ export default function ProcessInstanceListTable({
,
];
diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceReportSearch.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceReportSearch.tsx
index 644244d7..1c5ba33d 100644
--- a/spiffworkflow-frontend/src/components/ProcessInstanceReportSearch.tsx
+++ b/spiffworkflow-frontend/src/components/ProcessInstanceReportSearch.tsx
@@ -5,7 +5,6 @@ import {
FormLabel,
// @ts-ignore
} from '@carbon/react';
-import { useSearchParams } from 'react-router-dom';
import { truncateString } from '../helpers';
import { ProcessInstanceReport } from '../interfaces';
import HttpService from '../services/HttpService';
@@ -14,32 +13,42 @@ type OwnProps = {
onChange: (..._args: any[]) => any;
selectedItem?: ProcessInstanceReport | null;
titleText?: string;
+ selectedReportId?: string | null;
+ handleSetSelectedReportCallback?: Function;
};
export default function ProcessInstanceReportSearch({
selectedItem,
onChange,
+ selectedReportId,
+ handleSetSelectedReportCallback,
titleText = 'Process instance perspectives',
}: OwnProps) {
const [processInstanceReports, setProcessInstanceReports] = useState<
ProcessInstanceReport[] | null
>(null);
- const [searchParams] = useSearchParams();
- const reportId = searchParams.get('report_id');
-
useEffect(() => {
+ const selectedReportIdAsNumber = Number(selectedReportId);
+
function setProcessInstanceReportsFromResult(
result: ProcessInstanceReport[]
) {
setProcessInstanceReports(result);
+ if (selectedReportId && handleSetSelectedReportCallback) {
+ result.forEach((processInstanceReport: ProcessInstanceReport) => {
+ if (processInstanceReport.id === selectedReportIdAsNumber) {
+ handleSetSelectedReportCallback(processInstanceReport);
+ }
+ });
+ }
}
HttpService.makeCallToBackend({
path: `/process-instances/reports`,
successCallback: setProcessInstanceReportsFromResult,
});
- }, [reportId]);
+ }, [handleSetSelectedReportCallback, selectedReportId]);
const reportSelectionString = (
processInstanceReport: ProcessInstanceReport
diff --git a/spiffworkflow-frontend/src/routes/AdminRoutes.tsx b/spiffworkflow-frontend/src/routes/AdminRoutes.tsx
index d04d50b1..0e9df5d9 100644
--- a/spiffworkflow-frontend/src/routes/AdminRoutes.tsx
+++ b/spiffworkflow-frontend/src/routes/AdminRoutes.tsx
@@ -8,7 +8,6 @@ import ProcessGroupEdit from './ProcessGroupEdit';
import ProcessModelShow from './ProcessModelShow';
import ProcessModelEditDiagram from './ProcessModelEditDiagram';
import ProcessInstanceList from './ProcessInstanceList';
-import ProcessInstanceReportShow from './ProcessInstanceReportShow';
import ProcessModelNew from './ProcessModelNew';
import ProcessModelEdit from './ProcessModelEdit';
import ProcessInstanceShow from './ProcessInstanceShow';
@@ -88,10 +87,6 @@ export default function AdminRoutes() {
path="process-instances/reports"
element={}
/>
- }
- />
}
diff --git a/spiffworkflow-frontend/src/routes/ProcessInstanceReportShow.tsx b/spiffworkflow-frontend/src/routes/ProcessInstanceReportShow.tsx
deleted file mode 100644
index 38622691..00000000
--- a/spiffworkflow-frontend/src/routes/ProcessInstanceReportShow.tsx
+++ /dev/null
@@ -1,97 +0,0 @@
-import { useEffect, useState } from 'react';
-import { useParams, useSearchParams } from 'react-router-dom';
-
-// @ts-ignore
-import { Button, Table } from '@carbon/react';
-import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
-
-import PaginationForTable from '../components/PaginationForTable';
-import HttpService from '../services/HttpService';
-import { getPageInfoFromSearchParams } from '../helpers';
-
-const PER_PAGE_FOR_PROCESS_INSTANCE_REPORT = 500;
-
-export default function ProcessInstanceReport() {
- const params = useParams();
- const [searchParams] = useSearchParams();
-
- const [processInstances, setProcessInstances] = useState([]);
- const [reportMetadata, setReportMetadata] = useState({});
- const [pagination, setPagination] = useState(null);
-
- useEffect(() => {
- const processResult = (result: any) => {
- const processInstancesFromApi = result.results;
- setProcessInstances(processInstancesFromApi);
- setReportMetadata(result.report_metadata);
- setPagination(result.pagination);
- };
-
- function getProcessInstances() {
- const { page, perPage } = getPageInfoFromSearchParams(
- searchParams,
- PER_PAGE_FOR_PROCESS_INSTANCE_REPORT
- );
- let query = `?page=${page}&per_page=${perPage}`;
- searchParams.forEach((value, key) => {
- if (key !== 'page' && key !== 'per_page') {
- query += `&${key}=${value}`;
- }
- });
- HttpService.makeCallToBackend({
- path: `/process-instances/reports/${params.report_identifier}${query}`,
- successCallback: processResult,
- });
- }
-
- getProcessInstances();
- }, [searchParams, params]);
-
- const buildTable = () => {
- const headers = (reportMetadata as any).columns.map((column: any) => {
- return {(column as any).Header} | ;
- });
-
- const rows = processInstances.map((row) => {
- const currentRow = (reportMetadata as any).columns.map((column: any) => {
- return {(row as any)[column.accessor]} | ;
- });
- return {currentRow}
;
- });
- return (
-
-
- {headers}
-
- {rows}
-
- );
- };
-
- if (pagination) {
- const { page, perPage } = getPageInfoFromSearchParams(
- searchParams,
- PER_PAGE_FOR_PROCESS_INSTANCE_REPORT
- );
- return (
-
-
- Process Instance Perspective: {params.report_identifier}
-
-
-
- );
- }
- return null;
-}
From a4fd81cb9af27c5dcc6cefbefa0053040cbee933 Mon Sep 17 00:00:00 2001
From: jasquat
Date: Thu, 27 Apr 2023 17:33:25 -0400
Subject: [PATCH 07/18] remove a lot of unused code and fixed linting issues w/
burnettk
---
.../bin/import_tickets_for_script_task.py | 108 ---
.../spiffworkflow_backend/models/json_data.py | 6 +-
.../models/process_instance_report.py | 120 +--
.../routes/process_instances_controller.py | 108 +--
.../routes/tasks_controller.py | 2 +-
.../process_instance_report_service.py | 596 +++++---------
.../services/task_service.py | 4 +-
.../integration/test_process_api.py | 45 +-
.../unit/test_process_instance_processor.py | 4 +-
.../test_process_instance_report_service.py | 758 +-----------------
spiffworkflow-frontend/.eslintrc.js | 1 +
.../ProcessInstanceListSaveAsReport.tsx | 95 +--
.../components/ProcessInstanceListTable.tsx | 189 +----
13 files changed, 301 insertions(+), 1735 deletions(-)
delete mode 100644 spiffworkflow-backend/bin/import_tickets_for_script_task.py
diff --git a/spiffworkflow-backend/bin/import_tickets_for_script_task.py b/spiffworkflow-backend/bin/import_tickets_for_script_task.py
deleted file mode 100644
index 9550699c..00000000
--- a/spiffworkflow-backend/bin/import_tickets_for_script_task.py
+++ /dev/null
@@ -1,108 +0,0 @@
-"""Import tickets, for use in script task."""
-
-
-def main():
- """Use main to avoid global namespace."""
- import csv
-
- from spiffworkflow_backend.models.db import db
-
- from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
- from spiffworkflow_backend.models.user import UserModel
- from spiffworkflow_backend.services.process_instance_processor import (
- ProcessInstanceProcessor,
- )
- from spiffworkflow_backend.services.process_instance_service import (
- ProcessInstanceService,
- )
- from spiffworkflow_backend.models.process_instance_report import (
- ProcessInstanceReportModel,
- )
-
- process_model_identifier_ticket = "ticket"
- db.session.query(ProcessInstanceModel).filter(
- ProcessInstanceModel.process_model_identifier == process_model_identifier_ticket
- ).delete()
- db.session.commit()
-
- """Print process instance count."""
- process_instances = ProcessInstanceModel.query.filter_by(
- process_model_identifier=process_model_identifier_ticket
- ).all()
- process_instance_count = len(process_instances)
- print(f"process_instance_count: {process_instance_count}")
-
- columns_to_data_key_mappings = {
- "Month": "month",
- "MS": "milestone",
- "Done?": "done",
- "#": "notion_id",
- "ID": "req_id",
- "Dev Days": "dev_days",
- "Feature": "feature",
- "Feature description": "feature_description",
- "Priority": "priority",
- }
- columns_to_header_index_mappings = {}
-
- user = UserModel.query.first()
-
- with open("tests/files/tickets.csv") as infile:
- reader = csv.reader(infile, delimiter=",")
-
- # first row is garbage
- next(reader)
-
- header = next(reader)
- for column_name in columns_to_data_key_mappings:
- columns_to_header_index_mappings[column_name] = header.index(column_name)
- id_index = header.index("ID")
- priority_index = header.index("Priority")
- month_index = header.index("Month")
- print(f"header: {header}")
- for row in reader:
- ticket_identifier = row[id_index]
- priority = row[priority_index]
- month = row[month_index]
- print(f"ticket_identifier: {ticket_identifier}")
- print(f"priority: {priority}")
- # if there is no month, who cares about it.
- if month:
- process_instance = ProcessInstanceService.create_process_instance_from_process_model_identifier(
- process_model_identifier=process_model_identifier_ticket,
- user=user,
- process_group_identifier="sartography-admin",
- )
- processor = ProcessInstanceProcessor(process_instance)
-
- processor.do_engine_steps()
- # processor.save()
-
- for (
- column_name,
- desired_data_key,
- ) in columns_to_data_key_mappings.items():
- appropriate_index = columns_to_header_index_mappings[column_name]
- print(f"appropriate_index: {appropriate_index}")
- processor.bpmn_process_instance.data[desired_data_key] = row[appropriate_index]
-
- # you at least need a month, or else this row in the csv is considered garbage
- month_value = processor.bpmn_process_instance.data["month"]
- if month_value == "" or month_value is None:
- db.session.delete(process_instance)
- db.session.commit()
- continue
-
- processor.save()
-
- process_instance_data = processor.get_data()
- print(f"process_instance_data: {process_instance_data}")
-
- ProcessInstanceReportModel.add_fixtures()
- print("added report fixtures")
-
-
-main()
-
-# to avoid serialization issues
-del main
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/json_data.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/json_data.py
index 5c094b86..be4eb8d7 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/models/json_data.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/json_data.py
@@ -1,8 +1,10 @@
from __future__ import annotations
+
import json
from hashlib import sha256
-from flask import current_app
from typing import TypedDict
+
+from flask import current_app
from sqlalchemy.dialects.mysql import insert as mysql_insert
from sqlalchemy.dialects.postgresql import insert as postgres_insert
@@ -76,6 +78,6 @@ class JsonDataModel(SpiffworkflowBaseDBModel):
@classmethod
def create_and_insert_json_data_from_dict(cls, data: dict) -> str:
json_data_hash = sha256(json.dumps(data, sort_keys=True).encode("utf8")).hexdigest()
- cls.insert_or_update_json_data_dict({'hash': json_data_hash, 'data': data})
+ cls.insert_or_update_json_data_dict({"hash": json_data_hash, "data": data})
db.session.commit()
return json_data_hash
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance_report.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance_report.py
index 25497b1c..74ee1a48 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance_report.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance_report.py
@@ -1,21 +1,20 @@
"""Process_instance."""
from __future__ import annotations
-from spiffworkflow_backend.models.json_data import JsonDataModel # noqa: F401
+import typing
from dataclasses import dataclass
from typing import Any
from typing import cast
+from typing import NotRequired
from typing import Optional
from typing import TypedDict
from sqlalchemy import ForeignKey
from sqlalchemy.orm import relationship
-from spiffworkflow_backend.exceptions.process_entity_not_found_error import (
- ProcessEntityNotFoundError,
-)
from spiffworkflow_backend.models.db import db
from spiffworkflow_backend.models.db import SpiffworkflowBaseDBModel
+from spiffworkflow_backend.models.json_data import JsonDataModel # noqa: F401
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.services.process_instance_processor import (
@@ -25,13 +24,14 @@ from spiffworkflow_backend.services.process_instance_processor import (
class FilterValue(TypedDict):
field_name: str
- field_value: str
- operator: str
+ field_value: str | int | bool
+ operator: NotRequired[str]
class ReportMetadataColumn(TypedDict):
Header: str
accessor: str
+ filterable: NotRequired[bool]
class ReportMetadata(TypedDict):
@@ -83,7 +83,7 @@ class ProcessInstanceReportModel(SpiffworkflowBaseDBModel):
id: int = db.Column(db.Integer, primary_key=True)
identifier: str = db.Column(db.String(50), nullable=False, index=True)
- report_metadata: dict = db.Column(db.JSON)
+ report_metadata: ReportMetadata = db.Column(db.JSON)
created_by_id = db.Column(ForeignKey(UserModel.id), nullable=False, index=True) # type: ignore
created_by = relationship("UserModel")
created_at_in_seconds = db.Column(db.Integer)
@@ -99,49 +99,6 @@ class ProcessInstanceReportModel(SpiffworkflowBaseDBModel):
"""Default_order_by."""
return ["-start_in_seconds", "-id"]
- @classmethod
- def add_fixtures(cls) -> None:
- """Add_fixtures."""
- try:
- # process_model = ProcessModelService.get_process_model(
- # process_model_id="sartography-admin/ticket"
- # )
- user = UserModel.query.first()
- columns = [
- {"Header": "id", "accessor": "id"},
- {"Header": "month", "accessor": "month"},
- {"Header": "milestone", "accessor": "milestone"},
- {"Header": "req_id", "accessor": "req_id"},
- {"Header": "feature", "accessor": "feature"},
- {"Header": "dev_days", "accessor": "dev_days"},
- {"Header": "priority", "accessor": "priority"},
- ]
- json = {"order": "month asc", "columns": columns}
-
- cls.create_report(
- identifier="standard",
- user=user,
- report_metadata=json,
- )
- cls.create_report(
- identifier="for-month",
- user=user,
- report_metadata=cls.ticket_for_month_report(),
- )
- cls.create_report(
- identifier="for-month-3",
- user=user,
- report_metadata=cls.ticket_for_month_3_report(),
- )
- cls.create_report(
- identifier="hot-report",
- user=user,
- report_metadata=cls.process_model_with_form_report_fixture(),
- )
-
- except ProcessEntityNotFoundError:
- print("Did not find process models so not adding report fixtures for them")
-
@classmethod
def create_report(
cls,
@@ -160,7 +117,8 @@ class ProcessInstanceReportModel(SpiffworkflowBaseDBModel):
f"Process instance report with identifier already exists: {identifier}"
)
- json_data_hash = JsonDataModel.create_and_insert_json_data_from_dict(report_metadata)
+ report_metadata_dict = typing.cast(typing.Dict[str, Any], report_metadata)
+ json_data_hash = JsonDataModel.create_and_insert_json_data_from_dict(report_metadata_dict)
process_instance_report = cls(
identifier=identifier,
@@ -173,69 +131,11 @@ class ProcessInstanceReportModel(SpiffworkflowBaseDBModel):
return process_instance_report # type: ignore
- @classmethod
- def ticket_for_month_report(cls) -> dict:
- """Ticket_for_month_report."""
- return {
- "columns": [
- {"Header": "id", "accessor": "id"},
- {"Header": "month", "accessor": "month"},
- {"Header": "milestone", "accessor": "milestone"},
- {"Header": "req_id", "accessor": "req_id"},
- {"Header": "feature", "accessor": "feature"},
- {"Header": "priority", "accessor": "priority"},
- ],
- "order": "month asc",
- "filter_by": [
- {
- "field_name": "month",
- "operator": "equals",
- "field_value": "{{month}}",
- }
- ],
- }
-
- @classmethod
- def ticket_for_month_3_report(cls) -> dict:
- """Ticket_for_month_report."""
- return {
- "columns": [
- {"Header": "id", "accessor": "id"},
- {"Header": "month", "accessor": "month"},
- {"Header": "milestone", "accessor": "milestone"},
- {"Header": "req_id", "accessor": "req_id"},
- {"Header": "feature", "accessor": "feature"},
- {"Header": "dev_days", "accessor": "dev_days"},
- {"Header": "priority", "accessor": "priority"},
- ],
- "order": "month asc",
- "filter_by": [{"field_name": "month", "operator": "equals", "field_value": "3"}],
- }
-
- @classmethod
- def process_model_with_form_report_fixture(cls) -> dict:
- """Process_model_with_form_report_fixture."""
- return {
- "columns": [
- {"Header": "id", "accessor": "id"},
- {
- "Header": "system_generated_number",
- "accessor": "system_generated_number",
- },
- {
- "Header": "user_generated_number",
- "accessor": "user_generated_number",
- },
- {"Header": "product", "accessor": "product"},
- ],
- "order": "-id",
- }
-
@classmethod
def create_with_attributes(
cls,
identifier: str,
- report_metadata: dict,
+ report_metadata: ReportMetadata,
user: UserModel,
) -> ProcessInstanceReportModel:
"""Create_with_attributes."""
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py
index a9471c9a..dd249372 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py
@@ -1,7 +1,4 @@
"""APIs for dealing with process groups, process models, and process instances."""
-import base64
-from hashlib import sha256
-from spiffworkflow_backend.models.json_data import JsonDataModel # noqa: F401
import json
from typing import Any
from typing import Dict
@@ -12,7 +9,6 @@ from flask import current_app
from flask import g
from flask import jsonify
from flask import make_response
-from flask import request
from flask.wrappers import Response
from sqlalchemy import and_
from sqlalchemy import or_
@@ -26,6 +22,7 @@ from spiffworkflow_backend.models.bpmn_process_definition import (
from spiffworkflow_backend.models.db import db
from spiffworkflow_backend.models.human_task import HumanTaskModel
from spiffworkflow_backend.models.human_task_user import HumanTaskUserModel
+from spiffworkflow_backend.models.json_data import JsonDataModel # noqa: F401
from spiffworkflow_backend.models.process_instance import ProcessInstanceApiSchema
from spiffworkflow_backend.models.process_instance import (
ProcessInstanceCannotBeDeletedError,
@@ -70,9 +67,6 @@ from spiffworkflow_backend.services.process_instance_queue_service import (
from spiffworkflow_backend.services.process_instance_queue_service import (
ProcessInstanceQueueService,
)
-# from spiffworkflow_backend.services.process_instance_report_service import (
-# ProcessInstanceReportFilter,
-# )
from spiffworkflow_backend.services.process_instance_report_service import (
ProcessInstanceReportService,
)
@@ -83,6 +77,10 @@ from spiffworkflow_backend.services.process_model_service import ProcessModelSer
from spiffworkflow_backend.services.spec_file_service import SpecFileService
from spiffworkflow_backend.services.task_service import TaskService
+# from spiffworkflow_backend.services.process_instance_report_service import (
+# ProcessInstanceReportFilter,
+# )
+
def process_instance_create(
modified_process_model_identifier: str,
@@ -232,38 +230,13 @@ def process_instance_list_for_me(
process_model_identifier: Optional[str] = None,
page: int = 1,
per_page: int = 100,
- # start_from: Optional[int] = None,
- # start_to: Optional[int] = None,
- # end_from: Optional[int] = None,
- # end_to: Optional[int] = None,
- # process_status: Optional[str] = None,
- # user_filter: Optional[bool] = False,
- # report_identifier: Optional[str] = None,
- # report_id: Optional[int] = None,
- # user_group_identifier: Optional[str] = None,
- # process_initiator_username: Optional[str] = None,
- # report_columns: Optional[str] = None,
- # report_filter_by: Optional[str] = None,
) -> flask.wrappers.Response:
"""Process_instance_list_for_me."""
return process_instance_list(
process_model_identifier=process_model_identifier,
page=page,
per_page=per_page,
- # start_from=start_from,
- # start_to=start_to,
- # end_from=end_from,
- # end_to=end_to,
- # process_status=process_status,
- # user_filter=user_filter,
- # report_identifier=report_identifier,
- # report_id=report_id,
- # user_group_identifier=user_group_identifier,
- # with_relation_to_me=True,
- # report_columns=report_columns,
- # report_filter_by=report_filter_by,
- # process_initiator_username=process_initiator_username,
- body=body
+ body=body,
)
@@ -272,71 +245,16 @@ def process_instance_list(
process_model_identifier: Optional[str] = None,
page: int = 1,
per_page: int = 100,
- # start_from: Optional[int] = None,
- # start_to: Optional[int] = None,
- # end_from: Optional[int] = None,
- # end_to: Optional[int] = None,
- # process_status: Optional[str] = None,
- # with_relation_to_me: Optional[bool] = None,
- # user_filter: Optional[bool] = False,
- # report_identifier: Optional[str] = None,
- # report_id: Optional[int] = None,
- # user_group_identifier: Optional[str] = None,
- # process_initiator_username: Optional[str] = None,
- # report_columns: Optional[str] = None,
- # report_filter_by: Optional[str] = None,
) -> flask.wrappers.Response:
- # process_instance_report = ProcessInstanceReportService.report_with_identifier(g.user, body['report_id'], body['report_identifier'])
- process_instance_report = ProcessInstanceReportService.report_with_identifier(g.user)
-
- # report_column_list = None
- # if report_columns:
- # report_column_list = json.loads(base64.b64decode(report_columns))
- # report_filter_by_list = None
- # if report_filter_by:
- # report_filter_by_list = json.loads(base64.b64decode(report_filter_by))
-
- # if user_filter:
- # report_filter = ProcessInstanceReportFilter(
- # process_model_identifier=process_model_identifier,
- # # user_group_identifier=user_group_identifier,
- # # start_from=start_from,
- # # start_to=start_to,
- # # end_from=end_from,
- # # end_to=end_to,
- # # with_relation_to_me=with_relation_to_me,
- # # process_status=process_status.split(",") if process_status else None,
- # # process_initiator_username=process_initiator_username,
- # # report_column_list=report_column_list,
- # # report_filter_by_list=report_filter_by_list,
- # )
- # else:
- # report_filter = ProcessInstanceReportService.filter_from_metadata_with_overrides(
- # process_instance_report=process_instance_report,
- # process_model_identifier=process_model_identifier,
- # # user_group_identifier=user_group_identifier,
- # # start_from=start_from,
- # # start_to=start_to,
- # # end_from=end_from,
- # # end_to=end_to,
- # # process_status=process_status,
- # # with_relation_to_me=with_relation_to_me,
- # # process_initiator_username=process_initiator_username,
- # # report_column_list=report_column_list,
- # # report_filter_by_list=report_filter_by_list,
- # )
-
response_json = ProcessInstanceReportService.run_process_instance_report(
- # report_filter=report_filter,
- report_metadata=body['report_metadata'],
- process_instance_report=process_instance_report,
+ report_metadata=body["report_metadata"],
page=page,
per_page=per_page,
user=g.user,
)
- json_data_hash = JsonDataModel.create_and_insert_json_data_from_dict(body['report_metadata'])
- response_json['report_hash'] = json_data_hash
+ json_data_hash = JsonDataModel.create_and_insert_json_data_from_dict(body["report_metadata"])
+ response_json["report_hash"] = json_data_hash
db.session.commit()
return make_response(jsonify(response_json), 200)
@@ -347,11 +265,13 @@ def process_instance_report_show(
report_id: Optional[int] = None,
report_identifier: Optional[str] = None,
) -> flask.wrappers.Response:
-
if report_hash is None and report_id is None and report_identifier is None:
raise ApiError(
error_code="report_key_missing",
- message="A report key is needed to lookup a report. Either choose a report_hash, report_id, or report_identifier.",
+ message=(
+ "A report key is needed to lookup a report. Either choose a report_hash, report_id, or"
+ " report_identifier."
+ ),
)
response_result = {}
if report_hash is not None:
@@ -454,7 +374,6 @@ def process_instance_report_list(page: int = 1, per_page: int = 100) -> flask.wr
def process_instance_report_create(body: Dict[str, Any]) -> flask.wrappers.Response:
- """Process_instance_report_create."""
process_instance_report = ProcessInstanceReportModel.create_report(
identifier=body["identifier"],
user=g.user,
@@ -541,6 +460,7 @@ def process_instance_report_delete(
# return Response(json.dumps(result_dict), status=200, mimetype="application/json")
#
+
def process_instance_task_list_without_task_data_for_me(
modified_process_model_identifier: str,
process_instance_id: int,
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py
index b62cd1c1..bf2d8bc9 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/tasks_controller.py
@@ -1,6 +1,5 @@
"""APIs for dealing with process groups, process models, and process instances."""
import json
-from spiffworkflow_backend.models.json_data import JsonDataModel # noqa: F401
import os
import uuid
from sys import exc_info
@@ -37,6 +36,7 @@ from spiffworkflow_backend.models.db import db
from spiffworkflow_backend.models.group import GroupModel
from spiffworkflow_backend.models.human_task import HumanTaskModel
from spiffworkflow_backend.models.human_task_user import HumanTaskUserModel
+from spiffworkflow_backend.models.json_data import JsonDataModel # noqa: F401
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
from spiffworkflow_backend.models.process_instance import ProcessInstanceModelSchema
from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py
index ab180fee..a95cec34 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py
@@ -1,7 +1,7 @@
"""Process_instance_report_service."""
import re
-from dataclasses import dataclass
-from typing import Any, Generator, Iterable
+from typing import Any
+from typing import Generator
from typing import Optional
from typing import Type
@@ -23,11 +23,10 @@ from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
from spiffworkflow_backend.models.process_instance_metadata import (
ProcessInstanceMetadataModel,
)
-from spiffworkflow_backend.models.process_instance_report import (
- FilterValue,
- ProcessInstanceReportModel,
- ReportMetadata,
-)
+from spiffworkflow_backend.models.process_instance_report import FilterValue
+from spiffworkflow_backend.models.process_instance_report import ProcessInstanceReportModel
+from spiffworkflow_backend.models.process_instance_report import ReportMetadata
+from spiffworkflow_backend.models.process_instance_report import ReportMetadataColumn
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.models.user_group_assignment import UserGroupAssignmentModel
from spiffworkflow_backend.services.process_model_service import ProcessModelService
@@ -37,196 +36,144 @@ class ProcessInstanceReportNotFoundError(Exception):
"""ProcessInstanceReportNotFoundError."""
-# @dataclass
-# class ProcessInstanceReportFilter:
-# """ProcessInstanceReportFilter."""
-#
-# process_model_identifier: Optional[str] = None
-# user_group_identifier: Optional[str] = None
-# start_from: Optional[int] = None
-# start_to: Optional[int] = None
-# end_from: Optional[int] = None
-# end_to: Optional[int] = None
-# process_status: Optional[list[str]] = None
-# initiated_by_me: 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_i_can_complete: Optional[bool] = None
-# with_tasks_assigned_to_my_group: Optional[bool] = None
-# with_relation_to_me: Optional[bool] = None
-# process_initiator_username: Optional[str] = None
-# report_column_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]:
-# """To_dict."""
-# d = {}
-#
-# if self.process_model_identifier is not None:
-# d["process_model_identifier"] = self.process_model_identifier
-# if self.user_group_identifier is not None:
-# d["user_group_identifier"] = self.user_group_identifier
-# if self.start_from is not None:
-# d["start_from"] = str(self.start_from)
-# if self.start_to is not None:
-# d["start_to"] = str(self.start_to)
-# if self.end_from is not None:
-# d["end_from"] = str(self.end_from)
-# if self.end_to is not None:
-# d["end_to"] = str(self.end_to)
-# if self.process_status is not None:
-# d["process_status"] = ",".join(self.process_status)
-# if self.initiated_by_me is not None:
-# d["initiated_by_me"] = str(self.initiated_by_me).lower()
-# if self.has_terminal_status is not None:
-# 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:
-# 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:
-# 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:
-# d["with_relation_to_me"] = str(self.with_relation_to_me).lower()
-# if self.process_initiator_username is not None:
-# d["process_initiator_username"] = str(self.process_initiator_username)
-# if self.report_column_list is not None:
-# d["report_column_list"] = str(self.report_column_list)
-# if self.report_filter_by_list is not None:
-# 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
-
-
class ProcessInstanceReportService:
"""ProcessInstanceReportService."""
@classmethod
- def system_metadata_map(cls, metadata_key: str) -> Optional[dict[str, Any]]:
+ def system_metadata_map(cls, metadata_key: str) -> Optional[ReportMetadata]:
"""System_metadata_map."""
# TODO replace with system reports that are loaded on launch (or similar)
- temp_system_metadata_map = {
- "default": {
- "columns": cls.builtin_column_options(),
- "filter_by": [],
- "order_by": ["-start_in_seconds", "-id"],
- },
- "system_report_completed_instances_initiated_by_me": {
- "columns": [
- {"Header": "id", "accessor": "id"},
- {
- "Header": "process_model_display_name",
- "accessor": "process_model_display_name",
- },
- {"Header": "start_in_seconds", "accessor": "start_in_seconds"},
- {"Header": "end_in_seconds", "accessor": "end_in_seconds"},
- {"Header": "status", "accessor": "status"},
- ],
- "filter_by": [
- {"field_name": "initiated_by_me", "field_value": "true"},
- {"field_name": "has_terminal_status", "field_value": "true"},
- ],
- "order_by": ["-start_in_seconds", "-id"],
- },
- "system_report_completed_instances_with_tasks_completed_by_me": {
- "columns": cls.builtin_column_options(),
- "filter_by": [
- {"field_name": "with_tasks_completed_by_me", "field_value": "true"},
- {"field_name": "has_terminal_status", "field_value": "true"},
- ],
- "order_by": ["-start_in_seconds", "-id"],
- },
- "system_report_completed_instances_with_tasks_completed_by_my_groups": {
- "columns": cls.builtin_column_options(),
- "filter_by": [
- {
- "field_name": "with_tasks_assigned_to_my_group",
- "field_value": "true",
- },
- {"field_name": "has_terminal_status", "field_value": "true"},
- ],
- "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"],
- },
+ default: ReportMetadata = {
+ "columns": cls.builtin_column_options(),
+ "filter_by": [],
+ "order_by": ["-start_in_seconds", "-id"],
+ }
+ system_report_completed_instances_initiated_by_me: ReportMetadata = {
+ "columns": [
+ {"Header": "id", "accessor": "id"},
+ {
+ "Header": "process_model_display_name",
+ "accessor": "process_model_display_name",
+ },
+ {"Header": "start_in_seconds", "accessor": "start_in_seconds"},
+ {"Header": "end_in_seconds", "accessor": "end_in_seconds"},
+ {"Header": "status", "accessor": "status"},
+ ],
+ "filter_by": [
+ {"field_name": "initiated_by_me", "field_value": True},
+ {"field_name": "has_terminal_status", "field_value": True},
+ ],
+ "order_by": ["-start_in_seconds", "-id"],
+ }
+ system_report_completed_instances_with_tasks_completed_by_me: ReportMetadata = {
+ "columns": cls.builtin_column_options(),
+ "filter_by": [
+ {"field_name": "with_tasks_completed_by_me", "field_value": True},
+ {"field_name": "has_terminal_status", "field_value": True},
+ ],
+ "order_by": ["-start_in_seconds", "-id"],
+ }
+ system_report_completed_instances_with_tasks_completed_by_my_groups: ReportMetadata = {
+ "columns": cls.builtin_column_options(),
+ "filter_by": [
+ {
+ "field_name": "with_tasks_assigned_to_my_group",
+ "field_value": True,
+ },
+ {"field_name": "has_terminal_status", "field_value": True},
+ ],
+ "order_by": ["-start_in_seconds", "-id"],
+ }
+ system_report_in_progress_instances_initiated_by_me: ReportMetadata = {
+ "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: ReportMetadata = {
+ "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: ReportMetadata = {
+ "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"],
}
+ temp_system_metadata_map = {
+ "default": default,
+ "system_report_completed_instances_initiated_by_me": system_report_completed_instances_initiated_by_me,
+ "system_report_completed_instances_with_tasks_completed_by_me": (
+ system_report_completed_instances_with_tasks_completed_by_me
+ ),
+ "system_report_completed_instances_with_tasks_completed_by_my_groups": (
+ system_report_completed_instances_with_tasks_completed_by_my_groups
+ ),
+ "system_report_in_progress_instances_initiated_by_me": system_report_in_progress_instances_initiated_by_me,
+ "system_report_in_progress_instances_with_tasks_for_me": (
+ system_report_in_progress_instances_with_tasks_for_me
+ ),
+ "system_report_in_progress_instances_with_tasks_for_my_group": (
+ system_report_in_progress_instances_with_tasks_for_my_group
+ ),
+ }
if metadata_key not in temp_system_metadata_map:
return None
return temp_system_metadata_map[metadata_key]
@@ -268,160 +215,20 @@ class ProcessInstanceReportService:
return process_instance_report # type: ignore
- # @classmethod
- # def filter_by_to_dict(cls, process_instance_report: ProcessInstanceReportModel) -> dict[str, str]:
- # """Filter_by_to_dict."""
- # metadata = process_instance_report.report_metadata
- # filter_by = metadata.get("filter_by", [])
- # filters = {d["field_name"]: d["field_value"] for d in filter_by if "field_name" in d and "field_value" in d}
- # return filters
- #
- # @classmethod
- # def filter_from_metadata(cls, process_instance_report: ProcessInstanceReportModel) -> ProcessInstanceReportFilter:
- # """Filter_from_metadata."""
- # filters = cls.filter_by_to_dict(process_instance_report)
- #
- # def bool_value(key: str) -> Optional[bool]:
- # """Bool_value."""
- # 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]:
- # """Int_value."""
- # return int(filters[key]) if key in filters else None
- #
- # def list_value(key: str) -> Optional[list[str]]:
- # return filters[key].split(",") if key in filters else None
- #
- # process_model_identifier = filters.get("process_model_identifier")
- # user_group_identifier = filters.get("user_group_identifier")
- # start_from = int_value("start_from")
- # start_to = int_value("start_to")
- # end_from = int_value("end_from")
- # end_to = int_value("end_to")
- # process_status = list_value("process_status")
- # initiated_by_me = bool_value("initiated_by_me")
- # 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_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_relation_to_me = bool_value("with_relation_to_me")
- # process_initiator_username = filters.get("process_initiator_username")
- # report_column_list = list_value("report_column_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(
- # process_model_identifier=process_model_identifier,
- # user_group_identifier=user_group_identifier,
- # start_from=start_from,
- # start_to=start_to,
- # end_from=end_from,
- # end_to=end_to,
- # process_status=process_status,
- # initiated_by_me=initiated_by_me,
- # has_terminal_status=has_terminal_status,
- # has_active_status=has_active_status,
- # 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_relation_to_me=with_relation_to_me,
- # process_initiator_username=process_initiator_username,
- # report_column_list=report_column_list,
- # report_filter_by_list=report_filter_by_list,
- # oldest_open_human_task_fields=oldest_open_human_task_fields,
- # )
- #
- # return report_filter
- #
- # @classmethod
- # def filter_from_metadata_with_overrides(
- # cls,
- # process_instance_report: ProcessInstanceReportModel,
- # process_model_identifier: Optional[str] = None,
- # user_group_identifier: Optional[str] = None,
- # start_from: Optional[int] = None,
- # start_to: Optional[int] = None,
- # end_from: Optional[int] = None,
- # end_to: Optional[int] = None,
- # process_status: Optional[str] = None,
- # initiated_by_me: 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_i_can_complete: Optional[bool] = None,
- # with_tasks_assigned_to_my_group: Optional[bool] = None,
- # with_relation_to_me: Optional[bool] = None,
- # process_initiator_username: Optional[str] = None,
- # report_column_list: Optional[list] = None,
- # report_filter_by_list: Optional[list] = None,
- # oldest_open_human_task_fields: Optional[list] = None,
- # ) -> ProcessInstanceReportFilter:
- # """Filter_from_metadata_with_overrides."""
- # report_filter = cls.filter_from_metadata(process_instance_report)
- #
- # if process_model_identifier is not None:
- # report_filter.process_model_identifier = process_model_identifier
- # if user_group_identifier is not None:
- # report_filter.user_group_identifier = user_group_identifier
- # if start_from is not None:
- # report_filter.start_from = start_from
- # if start_to is not None:
- # report_filter.start_to = start_to
- # if end_from is not None:
- # report_filter.end_from = end_from
- # if end_to is not None:
- # report_filter.end_to = end_to
- # if process_status is not None:
- # report_filter.process_status = process_status.split(",")
- # if initiated_by_me is not None:
- # report_filter.initiated_by_me = initiated_by_me
- # if has_terminal_status is not None:
- # 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:
- # 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:
- # report_filter.process_initiator_username = process_initiator_username
- # if report_column_list is not None:
- # report_filter.report_column_list = report_column_list
- # if report_filter_by_list is not None:
- # 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:
- # report_filter.with_tasks_assigned_to_my_group = with_tasks_assigned_to_my_group
- # if with_relation_to_me is not None:
- # report_filter.with_relation_to_me = with_relation_to_me
- #
- # return report_filter
-
@classmethod
def add_metadata_columns_to_process_instance(
cls,
process_instance_sqlalchemy_rows: list[sqlalchemy.engine.row.Row], # type: ignore
- metadata_columns: list[dict],
+ metadata_columns: list[ReportMetadataColumn],
) -> list[dict]:
"""Add_metadata_columns_to_process_instance."""
results = []
- non_metadata_columns = cls.non_metadata_columns()
+ cls.non_metadata_columns()
for process_instance_row in process_instance_sqlalchemy_rows:
process_instance_mapping = process_instance_row._mapping
process_instance_dict = process_instance_row[0].serialized
- # import pdb; pdb.set_trace()
for metadata_column in metadata_columns:
- # if metadata_column["accessor"] not in non_metadata_columns:
- # if metadata_column["accessor"] not in cls.process_instance_stock_columns():
if metadata_column["accessor"] not in process_instance_dict:
- # import pdb; pdb.set_trace()
process_instance_dict[metadata_column["accessor"]] = process_instance_mapping[
metadata_column["accessor"]
]
@@ -488,12 +295,12 @@ class ProcessInstanceReportService:
@classmethod
def non_metadata_columns(cls) -> list[str]:
- return cls.process_instance_stock_columns() + ['process_initiator_username']
+ return cls.process_instance_stock_columns() + ["process_initiator_username"]
@classmethod
- def builtin_column_options(cls) -> list[dict]:
+ def builtin_column_options(cls) -> list[ReportMetadataColumn]:
"""Builtin_column_options."""
- return [
+ return_value: list[ReportMetadataColumn] = [
{"Header": "Id", "accessor": "id", "filterable": False},
{
"Header": "Process",
@@ -509,12 +316,13 @@ class ProcessInstanceReportService:
},
{"Header": "Status", "accessor": "status", "filterable": False},
]
+ return return_value
@classmethod
def get_filter_value(cls, filters: list[FilterValue], filter_key: str) -> Any:
for filter in filters:
- if filter['field_name'] == filter_key and filter['field_value'] is not None:
- return filter['field_value']
+ if filter["field_name"] == filter_key and filter["field_value"] is not None:
+ return filter["field_value"]
@classmethod
def check_filter_value(cls, filters: list[FilterValue], filter_key: str) -> Generator:
@@ -525,9 +333,7 @@ class ProcessInstanceReportService:
@classmethod
def run_process_instance_report(
cls,
- # report_filter: ProcessInstanceReportFilter,
report_metadata: ReportMetadata,
- process_instance_report: ProcessInstanceReportModel,
user: UserModel,
page: int = 1,
per_page: int = 100,
@@ -535,9 +341,9 @@ class ProcessInstanceReportService:
process_instance_query = ProcessInstanceModel.query
# Always join that hot user table for good performance at serialization time.
process_instance_query = process_instance_query.options(selectinload(ProcessInstanceModel.process_initiator))
- filters = report_metadata['filter_by']
+ filters = report_metadata["filter_by"]
- for value in cls.check_filter_value(filters, 'process_model_identifier'):
+ for value in cls.check_filter_value(filters, "process_model_identifier"):
process_model = ProcessModelService.get_process_model(
f"{value}",
)
@@ -553,32 +359,24 @@ class ProcessInstanceReportService:
)
)
- for value in cls.check_filter_value(filters, 'start_from'):
+ for value in cls.check_filter_value(filters, "start_from"):
+ process_instance_query = process_instance_query.filter(ProcessInstanceModel.start_in_seconds >= value)
+ for value in cls.check_filter_value(filters, "start_to"):
+ process_instance_query = process_instance_query.filter(ProcessInstanceModel.start_in_seconds <= value)
+ for value in cls.check_filter_value(filters, "end_from"):
+ process_instance_query = process_instance_query.filter(ProcessInstanceModel.end_in_seconds >= value)
+ for value in cls.check_filter_value(filters, "end_to"):
+ process_instance_query = process_instance_query.filter(ProcessInstanceModel.end_in_seconds <= value)
+ for value in cls.check_filter_value(filters, "process_status"):
process_instance_query = process_instance_query.filter(
- ProcessInstanceModel.start_in_seconds >= value
- )
- for value in cls.check_filter_value(filters, 'start_to'):
- process_instance_query = process_instance_query.filter(
- ProcessInstanceModel.start_in_seconds <= value
- )
- for value in cls.check_filter_value(filters, 'end_from'):
- process_instance_query = process_instance_query.filter(
- ProcessInstanceModel.end_in_seconds >= value
- )
- for value in cls.check_filter_value(filters, 'end_to'):
- process_instance_query = process_instance_query.filter(
- ProcessInstanceModel.end_in_seconds <= value
- )
- for value in cls.check_filter_value(filters, 'process_status'):
- process_instance_query = process_instance_query.filter(
- ProcessInstanceModel.status.in_(value.split(',')) # type: ignore
+ ProcessInstanceModel.status.in_(value.split(",")) # type: ignore
)
- for value in cls.check_filter_value(filters, 'initiated_by_me'):
+ for value in cls.check_filter_value(filters, "initiated_by_me"):
if value is True:
process_instance_query = process_instance_query.filter_by(process_initiator=user)
- for value in cls.check_filter_value(filters, 'has_terminal_status'):
+ for value in cls.check_filter_value(filters, "has_terminal_status"):
if value is True:
process_instance_query = process_instance_query.filter(
ProcessInstanceModel.status.in_(ProcessInstanceModel.terminal_statuses()) # type: ignore
@@ -587,25 +385,25 @@ class ProcessInstanceReportService:
process_instance_query = process_instance_query.filter(
ProcessInstanceModel.status.not_in(ProcessInstanceModel.terminal_statuses()) # type: ignore
)
- for value in cls.check_filter_value(filters, 'has_active_status'):
+ for value in cls.check_filter_value(filters, "has_active_status"):
if value is True:
process_instance_query = process_instance_query.filter(
ProcessInstanceModel.status.in_(ProcessInstanceModel.active_statuses()) # type: ignore
)
- for value in cls.check_filter_value(filters, 'process_initiator_username'):
+ for value in cls.check_filter_value(filters, "process_initiator_username"):
initiator = UserModel.query.filter_by(username=value).first()
process_initiator_id = -1
if initiator:
process_initiator_id = 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')
- with_tasks_assigned_to_my_group = cls.get_filter_value(filters, 'with_tasks_assigned_to_my_group')
- with_tasks_i_can_complete = cls.get_filter_value(filters, 'with_tasks_i_can_complete')
- with_relation_to_me = cls.get_filter_value(filters, 'with_relation_to_me')
- has_active_status = cls.get_filter_value(filters, 'has_active_status')
- user_group_identifier = cls.get_filter_value(filters, 'user_group_identifier')
+ with_tasks_completed_by_me = cls.get_filter_value(filters, "with_tasks_completed_by_me")
+ with_tasks_assigned_to_my_group = cls.get_filter_value(filters, "with_tasks_assigned_to_my_group")
+ with_tasks_i_can_complete = cls.get_filter_value(filters, "with_tasks_i_can_complete")
+ with_relation_to_me = cls.get_filter_value(filters, "with_relation_to_me")
+ has_active_status = cls.get_filter_value(filters, "has_active_status")
+ user_group_identifier = cls.get_filter_value(filters, "user_group_identifier")
if (
not with_tasks_completed_by_me
and not with_tasks_assigned_to_my_group
@@ -676,27 +474,19 @@ class ProcessInstanceReportService:
process_instance_query = process_instance_query.filter(UserGroupAssignmentModel.user_id == user.id)
instance_metadata_aliases = {}
- # print(f"report_metadata['columns']: {report_metadata['columns']}")
- # import pdb; pdb.set_trace()
- if report_metadata['columns'] and len(report_metadata['columns']) > 0:
- process_instance_report.report_metadata["columns"] = report_metadata['columns']
- if report_metadata['filter_by'] and len(report_metadata['filter_by']) > 0:
- process_instance_report.report_metadata["filter_by"] = report_metadata['filter_by']
+ if report_metadata["columns"] is None or len(report_metadata["columns"]) < 1:
+ report_metadata["columns"] = cls.builtin_column_options()
- for column in process_instance_report.report_metadata["columns"]:
+ for column in report_metadata["columns"]:
if column["accessor"] in cls.non_metadata_columns():
continue
instance_metadata_alias = aliased(ProcessInstanceMetadataModel)
instance_metadata_aliases[column["accessor"]] = instance_metadata_alias
filter_for_column = None
- if "filter_by" in process_instance_report.report_metadata:
+ if "filter_by" in report_metadata:
filter_for_column = next(
- (
- f
- for f in process_instance_report.report_metadata["filter_by"]
- if f["field_name"] == column["accessor"]
- ),
+ (f for f in report_metadata["filter_by"] if f["field_name"] == column["accessor"]),
None,
)
isouter = True
@@ -711,41 +501,37 @@ class ProcessInstanceReportService:
instance_metadata_alias, and_(*conditions), isouter=isouter
).add_columns(func.max(instance_metadata_alias.value).label(column["accessor"]))
- # order_by_query_array = []
- # order_by_array = process_instance_report.report_metadata["order_by"]
- # if len(order_by_array) < 1:
- # order_by_array = ProcessInstanceReportModel.default_order_by()
- # for order_by_option in order_by_array:
- # attribute = re.sub("^-", "", order_by_option)
- # if attribute in cls.process_instance_stock_columns():
- # if order_by_option.startswith("-"):
- # order_by_query_array.append(getattr(ProcessInstanceModel, attribute).desc())
- # else:
- # order_by_query_array.append(getattr(ProcessInstanceModel, attribute).asc())
- # elif attribute in instance_metadata_aliases:
- # if order_by_option.startswith("-"):
- # order_by_query_array.append(func.max(instance_metadata_aliases[attribute].value).desc())
- # else:
- # order_by_query_array.append(func.max(instance_metadata_aliases[attribute].value).asc())
+ order_by_query_array = []
+ order_by_array = report_metadata["order_by"]
+ if len(order_by_array) < 1:
+ order_by_array = ProcessInstanceReportModel.default_order_by()
+ for order_by_option in order_by_array:
+ attribute = re.sub("^-", "", order_by_option)
+ if attribute in cls.process_instance_stock_columns():
+ if order_by_option.startswith("-"):
+ order_by_query_array.append(getattr(ProcessInstanceModel, attribute).desc())
+ else:
+ order_by_query_array.append(getattr(ProcessInstanceModel, attribute).asc())
+ elif attribute in instance_metadata_aliases:
+ if order_by_option.startswith("-"):
+ order_by_query_array.append(func.max(instance_metadata_aliases[attribute].value).desc())
+ else:
+ order_by_query_array.append(func.max(instance_metadata_aliases[attribute].value).asc())
process_instances = (
process_instance_query.group_by(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)
)
- results = cls.add_metadata_columns_to_process_instance(
- process_instances.items, process_instance_report.report_metadata["columns"]
- )
- # import pdb; pdb.set_trace()
+ results = cls.add_metadata_columns_to_process_instance(process_instances.items, report_metadata["columns"])
- for value in cls.check_filter_value(filters, 'oldest_open_human_task_fields'):
+ for value in cls.check_filter_value(filters, "oldest_open_human_task_fields"):
results = cls.add_human_task_fields(results, value)
response_json = {
- "report": process_instance_report,
+ "report_metadata": report_metadata,
"results": results,
"filters": filters,
- "hash": "HEY",
"pagination": {
"count": len(results),
"total": process_instances.total,
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/task_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/task_service.py
index ec088dd3..4b22605c 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/services/task_service.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/task_service.py
@@ -8,7 +8,6 @@ from typing import TypedDict
from typing import Union
from uuid import UUID
-from flask import current_app
from SpiffWorkflow.bpmn.serializer.workflow import BpmnWorkflow # type: ignore
from SpiffWorkflow.bpmn.serializer.workflow import BpmnWorkflowSerializer
from SpiffWorkflow.exceptions import WorkflowException # type: ignore
@@ -20,7 +19,8 @@ from spiffworkflow_backend.models.bpmn_process import BpmnProcessModel
from spiffworkflow_backend.models.bpmn_process import BpmnProcessNotFoundError
from spiffworkflow_backend.models.bpmn_process_definition import BpmnProcessDefinitionModel
from spiffworkflow_backend.models.db import db
-from spiffworkflow_backend.models.json_data import JsonDataDict, JsonDataModel # noqa: F401
+from spiffworkflow_backend.models.json_data import JsonDataDict
+from spiffworkflow_backend.models.json_data import JsonDataModel
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
from spiffworkflow_backend.models.process_instance_event import ProcessInstanceEventModel
from spiffworkflow_backend.models.process_instance_event import ProcessInstanceEventType
diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py
index fa49cac0..8fec7d26 100644
--- a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py
+++ b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py
@@ -25,9 +25,8 @@ from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus
from spiffworkflow_backend.models.process_instance_metadata import (
ProcessInstanceMetadataModel,
)
-from spiffworkflow_backend.models.process_instance_report import (
- ProcessInstanceReportModel,
-)
+from spiffworkflow_backend.models.process_instance_report import ProcessInstanceReportModel
+from spiffworkflow_backend.models.process_instance_report import ReportMetadata
from spiffworkflow_backend.models.process_model import NotificationType
from spiffworkflow_backend.models.process_model import ProcessModelInfoSchema
from spiffworkflow_backend.models.spec_reference import SpecReferenceCache
@@ -1975,7 +1974,7 @@ class TestProcessApi(BaseTest):
self.logged_in_headers(with_super_admin_user)
report_identifier = "testreport"
- report_metadata = {"order_by": ["month"]}
+ report_metadata: ReportMetadata = {"order_by": ["month"], "filter_by": [], "columns": []}
ProcessInstanceReportModel.create_with_attributes(
identifier=report_identifier,
report_metadata=report_metadata,
@@ -3078,12 +3077,12 @@ class TestProcessApi(BaseTest):
).all()
assert len(process_instance_metadata) == 3
- report_metadata = {
+ report_metadata: ReportMetadata = {
"columns": [
- {"Header": "ID", "accessor": "id"},
- {"Header": "Status", "accessor": "status"},
- {"Header": "Key One", "accessor": "key1"},
- {"Header": "Key Two", "accessor": "key2"},
+ {"Header": "ID", "accessor": "id", "filterable": False},
+ {"Header": "Status", "accessor": "status", "filterable": False},
+ {"Header": "Key One", "accessor": "key1", "filterable": False},
+ {"Header": "Key Two", "accessor": "key2", "filterable": False},
],
"order_by": ["status"],
"filter_by": [],
@@ -3130,11 +3129,11 @@ class TestProcessApi(BaseTest):
self.create_process_instance_from_process_model(process_model=process_model, user=user_one)
self.create_process_instance_from_process_model(process_model=process_model, user=with_super_admin_user)
- dne_report_metadata = {
+ dne_report_metadata: ReportMetadata = {
"columns": [
- {"Header": "ID", "accessor": "id"},
- {"Header": "Status", "accessor": "status"},
- {"Header": "Process Initiator", "accessor": "username"},
+ {"Header": "ID", "accessor": "id", "filterable": False},
+ {"Header": "Status", "accessor": "status", "filterable": False},
+ {"Header": "Process Initiator", "accessor": "username", "filterable": False},
],
"order_by": ["status"],
"filter_by": [
@@ -3146,11 +3145,11 @@ class TestProcessApi(BaseTest):
],
}
- user_one_report_metadata = {
+ user_one_report_metadata: ReportMetadata = {
"columns": [
- {"Header": "ID", "accessor": "id"},
- {"Header": "Status", "accessor": "status"},
- {"Header": "Process Initiator", "accessor": "username"},
+ {"Header": "ID", "accessor": "id", "filterable": False},
+ {"Header": "Status", "accessor": "status", "filterable": False},
+ {"Header": "Process Initiator", "accessor": "username", "filterable": False},
],
"order_by": ["status"],
"filter_by": [
@@ -3308,12 +3307,13 @@ class TestProcessApi(BaseTest):
processor.do_engine_steps(save=True)
assert process_instance_two.status == "complete"
- report_metadata = {
+ report_metadata: ReportMetadata = {
"columns": [
- {"Header": "id", "accessor": "id"},
- {"Header": "Time", "accessor": "time_ns"},
+ {"Header": "id", "accessor": "id", "filterable": True},
+ {"Header": "Time", "accessor": "time_ns", "filterable": True},
],
"order_by": ["time_ns"],
+ "filter_by": [],
}
report_one = ProcessInstanceReportModel.create_with_attributes(
identifier="report_one",
@@ -3333,10 +3333,11 @@ class TestProcessApi(BaseTest):
report_metadata = {
"columns": [
- {"Header": "id", "accessor": "id"},
- {"Header": "Time", "accessor": "time_ns"},
+ {"Header": "id", "accessor": "id", "filterable": True},
+ {"Header": "Time", "accessor": "time_ns", "filterable": True},
],
"order_by": ["-time_ns"],
+ "filter_by": [],
}
report_two = ProcessInstanceReportModel.create_with_attributes(
identifier="report_two",
diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_processor.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_processor.py
index 57e1cbfc..e89a959a 100644
--- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_processor.py
+++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_processor.py
@@ -362,7 +362,9 @@ class TestProcessInstanceProcessor(BaseTest):
human_task_one = process_instance.active_human_tasks[0]
spiff_manual_task = processor.bpmn_process_instance.get_task_from_id(UUID(human_task_one.task_id))
ProcessInstanceService.complete_form_task(processor, spiff_manual_task, {}, initiator_user, human_task_one)
- import pdb; pdb.set_trace()
+ import pdb
+
+ pdb.set_trace()
assert (
len(process_instance.active_human_tasks) == 1
), "expected 1 active human tasks after 2nd one is completed, as we have looped back around."
diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_report_service.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_report_service.py
index 3542e7dd..e7e2ad61 100644
--- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_report_service.py
+++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_report_service.py
@@ -1,6 +1,4 @@
"""Test_process_instance_report_service."""
-from typing import Optional
-
from flask import Flask
from flask.testing import FlaskClient
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
@@ -9,738 +7,13 @@ from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
from spiffworkflow_backend.models.db import db
from spiffworkflow_backend.models.group import GroupModel
from spiffworkflow_backend.models.human_task import HumanTaskModel
-from spiffworkflow_backend.models.process_instance_report import (
- ProcessInstanceReportModel,
-)
-from spiffworkflow_backend.models.user import UserModel
-from spiffworkflow_backend.services.process_instance_report_service import (
- ProcessInstanceReportFilter,
-)
from spiffworkflow_backend.services.process_instance_report_service import (
ProcessInstanceReportService,
)
from spiffworkflow_backend.services.user_service import UserService
-class TestProcessInstanceReportFilter(BaseTest):
- """TestProcessInstanceReportFilter."""
-
- def test_empty_filter_to_dict(
- self,
- app: Flask,
- client: FlaskClient,
- with_db_and_bpmn_file_cleanup: None,
- with_super_admin_user: UserModel,
- ) -> None:
- """Docstring."""
- d = ProcessInstanceReportFilter().to_dict()
-
- assert d == {}
-
- def test_string_value_filter_to_dict(
- self,
- app: Flask,
- client: FlaskClient,
- with_db_and_bpmn_file_cleanup: None,
- with_super_admin_user: UserModel,
- ) -> None:
- """Docstring."""
- d = ProcessInstanceReportFilter(process_model_identifier="bob").to_dict()
-
- assert d == {"process_model_identifier": "bob"}
-
- def test_int_value_filter_to_dict(
- self,
- app: Flask,
- client: FlaskClient,
- with_db_and_bpmn_file_cleanup: None,
- with_super_admin_user: UserModel,
- ) -> None:
- """Docstring."""
- d = ProcessInstanceReportFilter(
- start_from=1,
- start_to=2,
- end_from=3,
- end_to=4,
- ).to_dict()
-
- assert d == {
- "start_from": "1",
- "start_to": "2",
- "end_from": "3",
- "end_to": "4",
- }
-
- def test_list_single_value_filter_to_dict(
- self,
- app: Flask,
- client: FlaskClient,
- with_db_and_bpmn_file_cleanup: None,
- with_super_admin_user: UserModel,
- ) -> None:
- """Docstring."""
- d = ProcessInstanceReportFilter(process_status=["bob"]).to_dict()
-
- assert d == {"process_status": "bob"}
-
- def test_list_multiple_value_filter_to_dict(
- self,
- app: Flask,
- client: FlaskClient,
- with_db_and_bpmn_file_cleanup: None,
- with_super_admin_user: UserModel,
- ) -> None:
- """Docstring."""
- d = ProcessInstanceReportFilter(process_status=["joe", "bob", "sue"]).to_dict()
-
- assert d == {"process_status": "joe,bob,sue"}
-
-
class TestProcessInstanceReportService(BaseTest):
- """TestProcessInstanceReportService."""
-
- def _filter_from_metadata(self, report_metadata: dict) -> ProcessInstanceReportFilter:
- """Docstring."""
- report = ProcessInstanceReportModel(
- identifier="test",
- created_by_id=1,
- report_metadata=report_metadata,
- )
- return ProcessInstanceReportService.filter_from_metadata(report)
-
- def _filter_from_metadata_with_overrides(
- self,
- report_metadata: dict,
- process_model_identifier: Optional[str] = None,
- start_from: Optional[int] = None,
- start_to: Optional[int] = None,
- end_from: Optional[int] = None,
- end_to: Optional[int] = None,
- process_status: Optional[str] = None,
- ) -> ProcessInstanceReportFilter:
- """Docstring."""
- report = ProcessInstanceReportModel(
- identifier="test",
- created_by_id=1,
- report_metadata=report_metadata,
- )
- return ProcessInstanceReportService.filter_from_metadata_with_overrides(
- process_instance_report=report,
- process_model_identifier=process_model_identifier,
- start_from=start_from,
- start_to=start_to,
- end_from=end_from,
- end_to=end_to,
- process_status=process_status,
- )
-
- def _filter_by_dict_from_metadata(self, report_metadata: dict) -> dict[str, str]:
- """Docstring."""
- report = ProcessInstanceReportModel(
- identifier="test",
- created_by_id=1,
- report_metadata=report_metadata,
- )
- return ProcessInstanceReportService.filter_by_to_dict(report)
-
- def test_filter_by_to_dict_no_filter_by(
- self,
- app: Flask,
- client: FlaskClient,
- with_db_and_bpmn_file_cleanup: None,
- with_super_admin_user: UserModel,
- ) -> None:
- """Docstring."""
- filters = self._filter_by_dict_from_metadata(
- {
- "columns": [],
- }
- )
-
- assert filters == {}
-
- def test_filter_by_to_dict_empty_filter_by(
- self,
- app: Flask,
- client: FlaskClient,
- with_db_and_bpmn_file_cleanup: None,
- with_super_admin_user: UserModel,
- ) -> None:
- """Docstring."""
- filters = self._filter_by_dict_from_metadata(
- {
- "columns": [],
- "filter_by": [],
- }
- )
-
- assert filters == {}
-
- def test_filter_by_to_dict_single_filter_by(
- self,
- app: Flask,
- client: FlaskClient,
- with_db_and_bpmn_file_cleanup: None,
- with_super_admin_user: UserModel,
- ) -> None:
- """Docstring."""
- filters = self._filter_by_dict_from_metadata(
- {
- "columns": [],
- "filter_by": [{"field_name": "end_to", "field_value": "1234"}],
- }
- )
-
- assert filters == {"end_to": "1234"}
-
- def test_filter_by_to_dict_mulitple_filter_by(
- self,
- app: Flask,
- client: FlaskClient,
- with_db_and_bpmn_file_cleanup: None,
- with_super_admin_user: UserModel,
- ) -> None:
- """Docstring."""
- filters = self._filter_by_dict_from_metadata(
- {
- "columns": [],
- "filter_by": [
- {"field_name": "end_to", "field_value": "1234"},
- {"field_name": "end_from", "field_value": "4321"},
- ],
- }
- )
-
- assert filters == {"end_to": "1234", "end_from": "4321"}
-
- def test_report_with_no_filter(
- self,
- app: Flask,
- client: FlaskClient,
- with_db_and_bpmn_file_cleanup: None,
- with_super_admin_user: UserModel,
- ) -> None:
- """Docstring."""
- report_filter = self._filter_from_metadata(
- {
- "columns": [],
- }
- )
-
- assert report_filter.process_model_identifier is None
- assert report_filter.start_from is None
- assert report_filter.start_to is None
- assert report_filter.end_from is None
- assert report_filter.end_to is None
- assert report_filter.process_status is None
-
- def test_report_with_empty_filter(
- self,
- app: Flask,
- client: FlaskClient,
- with_db_and_bpmn_file_cleanup: None,
- with_super_admin_user: UserModel,
- ) -> None:
- """Docstring."""
- report_filter = self._filter_from_metadata(
- {
- "columns": [],
- "filter_by": [],
- }
- )
-
- assert report_filter.process_model_identifier is None
- assert report_filter.start_from is None
- assert report_filter.start_to is None
- assert report_filter.end_from is None
- assert report_filter.end_to is None
- assert report_filter.process_status is None
-
- def test_report_with_unknown_filter_field_name(
- self,
- app: Flask,
- client: FlaskClient,
- with_db_and_bpmn_file_cleanup: None,
- with_super_admin_user: UserModel,
- ) -> None:
- """Docstring."""
- report_filter = self._filter_from_metadata(
- {
- "columns": [],
- "filter_by": [{"field_name": "bob", "field_value": "joe"}],
- }
- )
-
- assert report_filter.process_model_identifier is None
- assert report_filter.start_from is None
- assert report_filter.start_to is None
- assert report_filter.end_from is None
- assert report_filter.end_to is None
- assert report_filter.process_status is None
-
- def test_report_with_unknown_filter_keys(
- self,
- app: Flask,
- client: FlaskClient,
- with_db_and_bpmn_file_cleanup: None,
- with_super_admin_user: UserModel,
- ) -> None:
- """Docstring."""
- report_filter = self._filter_from_metadata(
- {
- "columns": [],
- "filter_by": [{"_name": "bob", "_value": "joe"}],
- }
- )
-
- assert report_filter.process_model_identifier is None
- assert report_filter.start_from is None
- assert report_filter.start_to is None
- assert report_filter.end_from is None
- assert report_filter.end_to is None
- assert report_filter.process_status is None
-
- def test_report_with_process_model_identifier_filter(
- self,
- app: Flask,
- client: FlaskClient,
- with_db_and_bpmn_file_cleanup: None,
- with_super_admin_user: UserModel,
- ) -> None:
- """Docstring."""
- report_filter = self._filter_from_metadata(
- {
- "columns": [],
- "filter_by": [{"field_name": "process_model_identifier", "field_value": "bob"}],
- }
- )
-
- assert report_filter.process_model_identifier == "bob"
- assert report_filter.start_from is None
- assert report_filter.start_to is None
- assert report_filter.end_from is None
- assert report_filter.end_to is None
- assert report_filter.process_status is None
-
- def test_report_with_start_from_filter(
- self,
- app: Flask,
- client: FlaskClient,
- with_db_and_bpmn_file_cleanup: None,
- with_super_admin_user: UserModel,
- ) -> None:
- """Docstring."""
- report_filter = self._filter_from_metadata(
- {
- "columns": [],
- "filter_by": [{"field_name": "start_from", "field_value": "1234"}],
- }
- )
-
- assert report_filter.process_model_identifier is None
- assert report_filter.start_from == 1234
- assert report_filter.start_to is None
- assert report_filter.end_from is None
- assert report_filter.end_to is None
- assert report_filter.process_status is None
-
- def test_report_with_start_to_filter(
- self,
- app: Flask,
- client: FlaskClient,
- with_db_and_bpmn_file_cleanup: None,
- with_super_admin_user: UserModel,
- ) -> None:
- """Docstring."""
- report_filter = self._filter_from_metadata(
- {
- "columns": [],
- "filter_by": [{"field_name": "start_to", "field_value": "1234"}],
- }
- )
-
- assert report_filter.process_model_identifier is None
- assert report_filter.start_from is None
- assert report_filter.start_to == 1234
- assert report_filter.end_from is None
- assert report_filter.end_to is None
- assert report_filter.process_status is None
-
- def test_report_with_end_from_filter(
- self,
- app: Flask,
- client: FlaskClient,
- with_db_and_bpmn_file_cleanup: None,
- with_super_admin_user: UserModel,
- ) -> None:
- """Docstring."""
- report_filter = self._filter_from_metadata(
- {
- "columns": [],
- "filter_by": [{"field_name": "end_from", "field_value": "1234"}],
- }
- )
-
- assert report_filter.process_model_identifier is None
- assert report_filter.start_from is None
- assert report_filter.start_to is None
- assert report_filter.end_from == 1234
- assert report_filter.end_to is None
- assert report_filter.process_status is None
-
- def test_report_with_end_to_filter(
- self,
- app: Flask,
- client: FlaskClient,
- with_db_and_bpmn_file_cleanup: None,
- with_super_admin_user: UserModel,
- ) -> None:
- """Docstring."""
- report_filter = self._filter_from_metadata(
- {
- "columns": [],
- "filter_by": [{"field_name": "end_to", "field_value": "1234"}],
- }
- )
-
- assert report_filter.process_model_identifier is None
- assert report_filter.start_from is None
- assert report_filter.start_to is None
- assert report_filter.end_from is None
- assert report_filter.end_to == 1234
- assert report_filter.process_status is None
-
- def test_report_with_single_startus_filter(
- self,
- app: Flask,
- client: FlaskClient,
- with_db_and_bpmn_file_cleanup: None,
- with_super_admin_user: UserModel,
- ) -> None:
- """Docstring."""
- report_filter = self._filter_from_metadata(
- {
- "columns": [],
- "filter_by": [{"field_name": "process_status", "field_value": "ready"}],
- }
- )
-
- assert report_filter.process_model_identifier is None
- assert report_filter.start_from is None
- assert report_filter.start_to is None
- assert report_filter.end_from is None
- assert report_filter.end_to is None
- assert report_filter.process_status == ["ready"]
-
- def test_report_with_multiple_startus_filters(
- self,
- app: Flask,
- client: FlaskClient,
- with_db_and_bpmn_file_cleanup: None,
- with_super_admin_user: UserModel,
- ) -> None:
- """Docstring."""
- report_filter = self._filter_from_metadata(
- {
- "columns": [],
- "filter_by": [
- {
- "field_name": "process_status",
- "field_value": "ready,completed,other",
- }
- ],
- }
- )
-
- assert report_filter.process_model_identifier is None
- assert report_filter.start_from is None
- assert report_filter.start_to is None
- assert report_filter.end_from is None
- assert report_filter.end_to is None
- assert report_filter.process_status == ["ready", "completed", "other"]
-
- def test_report_with_multiple_filters(
- self,
- app: Flask,
- client: FlaskClient,
- with_db_and_bpmn_file_cleanup: None,
- with_super_admin_user: UserModel,
- ) -> None:
- """Docstring."""
- report_filter = self._filter_from_metadata(
- {
- "columns": [],
- "filter_by": [
- {"field_name": "start_from", "field_value": "44"},
- {"field_name": "end_from", "field_value": "55"},
- {"field_name": "process_status", "field_value": "ready"},
- ],
- }
- )
-
- assert report_filter.process_model_identifier is None
- assert report_filter.start_from == 44
- assert report_filter.start_to is None
- assert report_filter.end_from == 55
- assert report_filter.end_to is None
- assert report_filter.process_status == ["ready"]
-
- def test_report_no_override_with_no_filter(
- self,
- app: Flask,
- client: FlaskClient,
- with_db_and_bpmn_file_cleanup: None,
- with_super_admin_user: UserModel,
- ) -> None:
- """Docstring."""
- report_filter = self._filter_from_metadata_with_overrides(
- {
- "columns": [],
- },
- )
-
- assert report_filter.process_model_identifier is None
- assert report_filter.start_from is None
- assert report_filter.start_to is None
- assert report_filter.end_from is None
- assert report_filter.end_to is None
- assert report_filter.process_status is None
-
- def test_report_override_with_no_filter(
- self,
- app: Flask,
- client: FlaskClient,
- with_db_and_bpmn_file_cleanup: None,
- with_super_admin_user: UserModel,
- ) -> None:
- """Docstring."""
- report_filter = self._filter_from_metadata_with_overrides(
- {
- "columns": [],
- },
- end_to=54321,
- )
-
- assert report_filter.process_model_identifier is None
- assert report_filter.start_from is None
- assert report_filter.start_to is None
- assert report_filter.end_from is None
- assert report_filter.end_to == 54321
- assert report_filter.process_status is None
-
- def test_report_override_process_model_identifier_filter(
- self,
- app: Flask,
- client: FlaskClient,
- with_db_and_bpmn_file_cleanup: None,
- with_super_admin_user: UserModel,
- ) -> None:
- """Docstring."""
- report_filter = self._filter_from_metadata_with_overrides(
- {
- "columns": [],
- "filter_by": [{"field_name": "process_model_identifier", "field_value": "bob"}],
- },
- process_model_identifier="joe",
- )
-
- assert report_filter.process_model_identifier == "joe"
- assert report_filter.start_from is None
- assert report_filter.start_to is None
- assert report_filter.end_from is None
- assert report_filter.end_to is None
- assert report_filter.process_status is None
-
- def test_report_override_start_from_filter(
- self,
- app: Flask,
- client: FlaskClient,
- with_db_and_bpmn_file_cleanup: None,
- with_super_admin_user: UserModel,
- ) -> None:
- """Docstring."""
- report_filter = self._filter_from_metadata_with_overrides(
- {
- "columns": [],
- "filter_by": [{"field_name": "start_from", "field_value": "123"}],
- },
- start_from=321,
- )
-
- assert report_filter.process_model_identifier is None
- assert report_filter.start_from == 321
- assert report_filter.start_to is None
- assert report_filter.end_from is None
- assert report_filter.end_to is None
- assert report_filter.process_status is None
-
- def test_report_override_start_to_filter(
- self,
- app: Flask,
- client: FlaskClient,
- with_db_and_bpmn_file_cleanup: None,
- with_super_admin_user: UserModel,
- ) -> None:
- """Docstring."""
- report_filter = self._filter_from_metadata_with_overrides(
- {
- "columns": [],
- "filter_by": [{"field_name": "start_to", "field_value": "123"}],
- },
- start_to=321,
- )
-
- assert report_filter.process_model_identifier is None
- assert report_filter.start_from is None
- assert report_filter.start_to == 321
- assert report_filter.end_from is None
- assert report_filter.end_to is None
- assert report_filter.process_status is None
-
- def test_report_override_end_from_filter(
- self,
- app: Flask,
- client: FlaskClient,
- with_db_and_bpmn_file_cleanup: None,
- with_super_admin_user: UserModel,
- ) -> None:
- """Docstring."""
- report_filter = self._filter_from_metadata_with_overrides(
- {
- "columns": [],
- "filter_by": [{"field_name": "end_from", "field_value": "123"}],
- },
- end_from=321,
- )
-
- assert report_filter.process_model_identifier is None
- assert report_filter.start_from is None
- assert report_filter.start_to is None
- assert report_filter.end_from == 321
- assert report_filter.end_to is None
- assert report_filter.process_status is None
-
- def test_report_override_end_to_filter(
- self,
- app: Flask,
- client: FlaskClient,
- with_db_and_bpmn_file_cleanup: None,
- with_super_admin_user: UserModel,
- ) -> None:
- """Docstring."""
- report_filter = self._filter_from_metadata_with_overrides(
- {
- "columns": [],
- "filter_by": [{"field_name": "end_to", "field_value": "123"}],
- },
- end_to=321,
- )
-
- assert report_filter.process_model_identifier is None
- assert report_filter.start_from is None
- assert report_filter.start_to is None
- assert report_filter.end_from is None
- assert report_filter.end_to == 321
- assert report_filter.process_status is None
-
- def test_report_override_process_status_filter(
- self,
- app: Flask,
- client: FlaskClient,
- with_db_and_bpmn_file_cleanup: None,
- with_super_admin_user: UserModel,
- ) -> None:
- """Docstring."""
- report_filter = self._filter_from_metadata_with_overrides(
- {
- "columns": [],
- "filter_by": [{"field_name": "process_status", "field_value": "joe,bob"}],
- },
- process_status="sue",
- )
-
- assert report_filter.process_model_identifier is None
- assert report_filter.start_from is None
- assert report_filter.start_to is None
- assert report_filter.end_from is None
- assert report_filter.end_to is None
- assert report_filter.process_status == ["sue"]
-
- def test_report_override_mulitple_process_status_filter(
- self,
- app: Flask,
- client: FlaskClient,
- with_db_and_bpmn_file_cleanup: None,
- with_super_admin_user: UserModel,
- ) -> None:
- """Docstring."""
- report_filter = self._filter_from_metadata_with_overrides(
- {
- "columns": [],
- "filter_by": [{"field_name": "process_status", "field_value": "sue"}],
- },
- process_status="joe,bob",
- )
-
- assert report_filter.process_model_identifier is None
- assert report_filter.start_from is None
- assert report_filter.start_to is None
- assert report_filter.end_from is None
- assert report_filter.end_to is None
- assert report_filter.process_status == ["joe", "bob"]
-
- def test_report_override_does_not_override_other_filters(
- self,
- app: Flask,
- client: FlaskClient,
- with_db_and_bpmn_file_cleanup: None,
- with_super_admin_user: UserModel,
- ) -> None:
- """Docstring."""
- report_filter = self._filter_from_metadata_with_overrides(
- {
- "columns": [],
- "filter_by": [
- {"field_name": "process_model_identifier", "field_value": "sue"},
- {"field_name": "process_status", "field_value": "sue"},
- ],
- },
- process_status="joe,bob",
- )
-
- assert report_filter.process_model_identifier == "sue"
- assert report_filter.start_from is None
- assert report_filter.start_to is None
- assert report_filter.end_from is None
- assert report_filter.end_to is None
- assert report_filter.process_status == ["joe", "bob"]
-
- def test_report_override_of_none_does_not_override_filter(
- self,
- app: Flask,
- client: FlaskClient,
- with_db_and_bpmn_file_cleanup: None,
- with_super_admin_user: UserModel,
- ) -> None:
- """Docstring."""
- report_filter = self._filter_from_metadata_with_overrides(
- {
- "columns": [],
- "filter_by": [
- {"field_name": "process_model_identifier", "field_value": "sue"},
- {"field_name": "process_status", "field_value": "sue"},
- ],
- },
- process_status=None,
- )
-
- assert report_filter.process_model_identifier == "sue"
- assert report_filter.start_from is None
- assert report_filter.start_to is None
- assert report_filter.end_from is None
- assert report_filter.end_to is None
- assert report_filter.process_status == ["sue"]
-
def test_can_filter_by_completed_instances_initiated_by_me(
self,
app: Flask,
@@ -768,13 +41,8 @@ class TestProcessInstanceReportService(BaseTest):
user=user_one,
report_identifier="system_report_completed_instances_initiated_by_me",
)
- report_filter = ProcessInstanceReportService.filter_from_metadata_with_overrides(
- process_instance_report=process_instance_report,
- process_model_identifier=process_model.id,
- )
response_json = ProcessInstanceReportService.run_process_instance_report(
- report_filter=report_filter,
- process_instance_report=process_instance_report,
+ report_metadata=process_instance_report.report_metadata,
user=user_one,
)
@@ -850,13 +118,8 @@ class TestProcessInstanceReportService(BaseTest):
user=user_one,
report_identifier="system_report_completed_instances_with_tasks_completed_by_me",
)
- report_filter = ProcessInstanceReportService.filter_from_metadata_with_overrides(
- process_instance_report=process_instance_report,
- process_model_identifier=process_model.id,
- )
response_json = ProcessInstanceReportService.run_process_instance_report(
- report_filter=report_filter,
- process_instance_report=process_instance_report,
+ report_metadata=process_instance_report.report_metadata,
user=user_one,
)
@@ -936,13 +199,8 @@ class TestProcessInstanceReportService(BaseTest):
user=user_one,
report_identifier="system_report_completed_instances_with_tasks_completed_by_my_groups",
)
- report_filter = ProcessInstanceReportService.filter_from_metadata_with_overrides(
- process_instance_report=process_instance_report,
- process_model_identifier=process_model.id,
- )
response_json = ProcessInstanceReportService.run_process_instance_report(
- report_filter=report_filter,
- process_instance_report=process_instance_report,
+ report_metadata=process_instance_report.report_metadata,
user=user_one,
)
@@ -1026,14 +284,12 @@ class TestProcessInstanceReportService(BaseTest):
UserService.add_user_to_human_tasks_if_appropriate(user_one)
process_instance_report = ProcessInstanceReportService.report_with_identifier(user=user_one)
- report_filter = ProcessInstanceReportService.filter_from_metadata_with_overrides(
- process_instance_report=process_instance_report,
- process_model_identifier=process_model.id,
- with_relation_to_me=True,
+ report_metadata = process_instance_report.report_metadata
+ report_metadata["filter_by"].append(
+ {"field_name": "with_relation_to_me", "field_value": True, "operator": "equals"}
)
response_json = ProcessInstanceReportService.run_process_instance_report(
- report_filter=report_filter,
- process_instance_report=process_instance_report,
+ report_metadata=report_metadata,
user=user_one,
)
diff --git a/spiffworkflow-frontend/.eslintrc.js b/spiffworkflow-frontend/.eslintrc.js
index 5ed20900..75ad6cbc 100644
--- a/spiffworkflow-frontend/.eslintrc.js
+++ b/spiffworkflow-frontend/.eslintrc.js
@@ -27,6 +27,7 @@ module.exports = {
rules: {
// according to https://github.com/typescript-eslint/typescript-eslint/issues/2621, You should turn off the eslint core rule and turn on the typescript-eslint rule
// but not sure which of the above "extends" statements is maybe bringing in eslint core
+ 'max-len': ['error', { code: 200, ignoreUrls: true }],
'no-shadow': 'off',
'@typescript-eslint/no-shadow': ['error'],
'jest/expect-expect': 'off',
diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListSaveAsReport.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListSaveAsReport.tsx
index 78186d97..b196c2ee 100644
--- a/spiffworkflow-frontend/src/components/ProcessInstanceListSaveAsReport.tsx
+++ b/spiffworkflow-frontend/src/components/ProcessInstanceListSaveAsReport.tsx
@@ -6,27 +6,11 @@ import {
Modal,
// @ts-ignore
} from '@carbon/react';
-import {
- ReportFilter,
- ProcessInstanceReport,
- ProcessModel,
- ReportColumn,
- ReportMetadata,
- User,
-} from '../interfaces';
+import { ProcessInstanceReport, ReportMetadata } from '../interfaces';
import HttpService from '../services/HttpService';
type OwnProps = {
onSuccess: (..._args: any[]) => any;
- columnArray: ReportColumn[];
- orderBy: string;
- processModelSelection: ProcessModel | null;
- processInitiatorSelection: User | null;
- processStatusSelection: string[];
- startFromSeconds: string | null;
- startToSeconds: string | null;
- endFromSeconds: string | null;
- endToSeconds: string | null;
buttonText?: string;
buttonClassName?: string;
processInstanceReportSelection?: ProcessInstanceReport | null;
@@ -35,16 +19,7 @@ type OwnProps = {
export default function ProcessInstanceListSaveAsReport({
onSuccess,
- columnArray,
- orderBy,
- processModelSelection,
- processInitiatorSelection,
processInstanceReportSelection,
- processStatusSelection,
- startFromSeconds,
- startToSeconds,
- endFromSeconds,
- endToSeconds,
buttonClassName,
buttonText = 'Save as Perspective',
reportMetadata,
@@ -75,74 +50,6 @@ export default function ProcessInstanceListSaveAsReport({
const addProcessInstanceReport = (event: any) => {
event.preventDefault();
- // // TODO: make a field to set this
- // let orderByArray = ['-start_in_seconds', '-id'];
- // if (orderBy) {
- // orderByArray = orderBy.split(',').filter((n) => n);
- // }
- // const filterByArray: any = [];
- //
- // if (processModelSelection) {
- // filterByArray.push({
- // field_name: 'process_model_identifier',
- // field_value: processModelSelection.id,
- // });
- // }
- //
- // if (processInitiatorSelection) {
- // filterByArray.push({
- // field_name: 'process_initiator_username',
- // field_value: processInitiatorSelection.username,
- // });
- // }
- //
- // if (processStatusSelection.length > 0) {
- // filterByArray.push({
- // field_name: 'process_status',
- // field_value: processStatusSelection.join(','),
- // operator: 'in',
- // });
- // }
- //
- // if (startFromSeconds) {
- // filterByArray.push({
- // field_name: 'start_from',
- // field_value: startFromSeconds,
- // });
- // }
- //
- // if (startToSeconds) {
- // filterByArray.push({
- // field_name: 'start_to',
- // field_value: startToSeconds,
- // });
- // }
- //
- // if (endFromSeconds) {
- // filterByArray.push({
- // field_name: 'end_from',
- // field_value: endFromSeconds,
- // });
- // }
- //
- // if (endToSeconds) {
- // filterByArray.push({
- // field_name: 'end_to',
- // field_value: endToSeconds,
- // });
- // }
- //
- // reportMetadata.filter_by.forEach((reportFilter: ReportFilter) => {
- // columnArray.forEach((reportColumn: ReportColumn) => {
- // if (
- // reportColumn.accessor === reportFilter.field_name &&
- // reportColumn.filterable
- // ) {
- // filterByArray.push(reportFilter);
- // }
- // });
- // });
-
let path = `/process-instances/reports`;
let httpMethod = 'POST';
if (isEditMode() && processInstanceReportSelection) {
diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
index 39f7871b..7284eaf6 100644
--- a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
+++ b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
@@ -134,7 +134,6 @@ export default function ProcessInstanceListTable({
const [processInstances, setProcessInstances] = useState([]);
const [reportMetadata, setReportMetadata] = useState();
const [pagination, setPagination] = useState(null);
- const [processInstanceFilters, setProcessInstanceFilters] = useState({});
const oneHourInSeconds = 3600;
const oneMonthInSeconds = oneHourInSeconds * 24 * 30;
@@ -197,11 +196,10 @@ export default function ProcessInstanceListTable({
const [processInstanceInitiatorOptions, setProcessInstanceInitiatorOptions] =
useState([]);
- const [processInitiatorSelection, setProcessInitiatorSelection] =
- useState(null);
- const [processInitiatorText, setProcessInitiatorText] = useState<
+ const [processInitiatorSelection, setProcessInitiatorSelection] = useState<
string | null
>(null);
+
const [
processInitiatorNotFoundErrorText,
setProcessInitiatorNotFoundErrorText,
@@ -232,10 +230,12 @@ export default function ProcessInstanceListTable({
inputText: string
) => {
if (lastRequestedInitatorSearchTerm.current === result.username_prefix) {
- setProcessInstanceInitiatorOptions(result.users);
+ setProcessInstanceInitiatorOptions(
+ result.users.map((user: User) => user.username)
+ );
result.users.forEach((user: User) => {
if (user.username === inputText) {
- setProcessInitiatorSelection(user);
+ setProcessInitiatorSelection(user.username);
}
});
}
@@ -272,69 +272,46 @@ export default function ProcessInstanceListTable({
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
- function setProcessInstancesFromResult(result: any) {
+
+ const setProcessInstancesFromResult = useCallback((result: any) => {
setRequiresRefilter(false);
const processInstancesFromApi = result.results;
setProcessInstances(processInstancesFromApi);
setPagination(result.pagination);
- setProcessInstanceFilters(result.filters);
- setReportMetadata(result.report.report_metadata);
- // if (processInstanceReport) {
- // if (processInstanceReport.id > 0) {
- // setProcessInstanceReportSelection(processInstanceReport);
- // }
- // }
+ setReportMetadata(result.report_metadata);
+ }, []);
+
+ const setProcessInstancesFromApplyFilter = (result: any) => {
+ setProcessInstancesFromResult(result);
if (result.report_hash) {
searchParams.set('report_hash', result.report_hash);
setSearchParams(searchParams);
}
- }
+ };
+ // Useful to stop refreshing if an api call gets an error
+ // since those errors can make the page unusable in any way
const clearRefreshRef = useRef(null);
- const stopRefreshing = (error: any) => {
+ const stopRefreshing = useCallback((error: any) => {
if (clearRefreshRef.current) {
clearRefreshRef.current();
}
if (error) {
console.error(error);
}
- };
-
- const getData = useCallback(() => {
- console.log('WE DO STUFF');
- stopRefreshing(null);
}, []);
// eslint-disable-next-line sonarjs/cognitive-complexity
useEffect(() => {
if (!permissionsLoaded) {
- return;
+ return undefined;
}
// we apparently cannot use a state set in a useEffect from within that same useEffect
// so use a variable instead
let processModelSelectionItemsForUseEffect: ProcessModel[] = [];
- function setProcessInstancesFromResult(result: any) {
- setRequiresRefilter(false);
- const processInstancesFromApi = result.results;
- setProcessInstances(processInstancesFromApi);
- setPagination(result.pagination);
- setProcessInstanceFilters(result.filters);
- setReportMetadata(result.report.report_metadata);
- }
-
- // Useful to stop refreshing if an api call gets an error
- // since those errors can make the page unusable in any way
- const stopRefreshing = (error: any) => {
- if (clearRefreshRef.current) {
- clearRefreshRef.current();
- }
- if (error) {
- console.error(error);
- }
- };
function getProcessInstances(
processInstanceReport: ProcessInstanceReport | null = null
) {
@@ -362,7 +339,7 @@ export default function ProcessInstanceListTable({
);
setShowFilterOptions(true);
} else if (reportFilter.field_name === 'process_initiator_username') {
- searchForProcessInitiator(reportFilter.field_value || '');
+ setProcessInitiatorSelection(reportFilter.field_value || '');
setShowFilterOptions(true);
} else if (reportFilter.field_name === 'process_model_identifier') {
selectedProcessModelIdentifier =
@@ -416,8 +393,8 @@ export default function ProcessInstanceListTable({
path: `${processInstanceApiSearchPath}?${queryParamString}`,
successCallback: setProcessInstancesFromResult,
httpMethod: 'POST',
- failureCallback: getData,
- onUnauthorized: getData,
+ failureCallback: stopRefreshing,
+ onUnauthorized: stopRefreshing,
postBody: {
report_metadata: reportMetadataBodyToUse,
},
@@ -502,62 +479,11 @@ export default function ProcessInstanceListTable({
additionalParams,
processInstanceApiSearchPath,
permissionsLoaded,
+ listHasBeenFiltered,
+ setProcessInstancesFromResult,
+ stopRefreshing,
]);
- // // This sets the filter data using the saved reports returned from the initial instance_list query.
- // // This could probably be merged into the main useEffect but it works here now.
- // useEffect(() => {
- // const filters = processInstanceFilters as any;
- // // Object.keys(dateParametersToAlwaysFilterBy).forEach((paramName: string) => {
- // // const dateFunctionToCall = dateParametersToAlwaysFilterBy[paramName][0];
- // // const timeFunctionToCall = dateParametersToAlwaysFilterBy[paramName][1];
- // // const paramValue = filters[paramName];
- // // dateFunctionToCall('');
- // // timeFunctionToCall('');
- // // if (paramValue) {
- // // const dateString = convertSecondsToFormattedDateString(
- // // paramValue as any
- // // );
- // // dateFunctionToCall(dateString);
- // // const timeString = convertSecondsToFormattedTimeHoursMinutes(
- // // paramValue as any
- // // );
- // // timeFunctionToCall(timeString);
- // // setShowFilterOptions(true);
- // // }
- // // });
- //
- // // setProcessModelSelection(null);
- // // processModelAvailableItems.forEach((item: any) => {
- // // if (item.id === filters.process_model_identifier) {
- // // setProcessModelSelection(item);
- // // }
- // // });
- //
- // if (filters.process_initiator_username) {
- // const functionToCall =
- // parametersToGetFromSearchParams.process_initiator_username;
- // functionToCall(filters.process_initiator_username);
- // }
- //
- // const processStatusSelectedArray: string[] = [];
- // if (filters.process_status) {
- // PROCESS_STATUSES.forEach((processStatusOption: any) => {
- // const regex = new RegExp(`\\b${processStatusOption}\\b`);
- // if (filters.process_status.match(regex)) {
- // processStatusSelectedArray.push(processStatusOption);
- // }
- // });
- // setShowFilterOptions(true);
- // }
- // // setProcessStatusSelection(processStatusSelectedArray);
- // }, [
- // processInstanceFilters,
- // dateParametersToAlwaysFilterBy,
- // parametersToGetFromSearchParams,
- // processModelAvailableItems,
- // ]);
-
const processInstanceReportSaveTag = () => {
if (processInstanceReportJustSaved) {
let titleOperation = 'Updated';
@@ -656,20 +582,6 @@ export default function ProcessInstanceListTable({
return [];
};
- // const reportFilterBy = () => {
- // if (reportMetadata) {
- // return reportMetadata.filter_by;
- // }
- // return null
- // };
-
- const navigateToNewReport = (queryParamString: string) => {
- removeError();
- setProcessInstanceReportJustSaved(null);
- setProcessInstanceFilters({});
- navigate(`${processInstanceListPathPrefix}?${queryParamString}`);
- };
-
const removeFieldFromReportMetadata = (
postBody: ReportMetadata,
fieldName: string
@@ -677,21 +589,21 @@ export default function ProcessInstanceListTable({
const filtersToKeep = postBody.filter_by.filter(
(rf: ReportFilter) => rf.field_name !== fieldName
);
+ // eslint-disable-next-line no-param-reassign
postBody.filter_by = filtersToKeep;
};
- const addFieldValueToReportMetadata = (
+ const insertOrUpdateFieldInReportMetadata = (
postBody: ReportMetadata,
fieldName: string,
fieldValue: string
) => {
+ removeFieldFromReportMetadata(postBody, fieldName);
if (fieldValue) {
postBody.filter_by.push({
field_name: fieldName,
field_value: fieldValue,
});
- } else {
- removeFieldFromReportMetadata(postBody, fieldName);
}
};
@@ -720,24 +632,29 @@ export default function ProcessInstanceListTable({
};
}
- addFieldValueToReportMetadata(
+ insertOrUpdateFieldInReportMetadata(
newReportMetadata,
'start_from',
startFromSeconds
);
- addFieldValueToReportMetadata(
+ insertOrUpdateFieldInReportMetadata(
newReportMetadata,
'start_to',
startToSeconds
);
- addFieldValueToReportMetadata(
+ insertOrUpdateFieldInReportMetadata(
newReportMetadata,
'end_from',
endFromSeconds
);
- addFieldValueToReportMetadata(newReportMetadata, 'end_to', endToSeconds);
+ insertOrUpdateFieldInReportMetadata(
+ newReportMetadata,
+ 'end_to',
+ endToSeconds
+ );
+
if (processStatusSelection.length > 0) {
- addFieldValueToReportMetadata(
+ insertOrUpdateFieldInReportMetadata(
newReportMetadata,
'process_status',
processStatusSelection.join(',')
@@ -747,7 +664,7 @@ export default function ProcessInstanceListTable({
}
if (processModelSelection) {
- addFieldValueToReportMetadata(
+ insertOrUpdateFieldInReportMetadata(
newReportMetadata,
'process_model_identifier',
processModelSelection.id
@@ -760,10 +677,10 @@ export default function ProcessInstanceListTable({
}
if (processInitiatorSelection) {
- addFieldValueToReportMetadata(
+ insertOrUpdateFieldInReportMetadata(
newReportMetadata,
'process_initiator_username',
- processInitiatorSelection.username
+ processInitiatorSelection
);
} else {
removeFieldFromReportMetadata(
@@ -800,7 +717,7 @@ export default function ProcessInstanceListTable({
failureCallback: stopRefreshing,
onUnauthorized: stopRefreshing,
successCallback: (result: any) => {
- setProcessInstancesFromResult(result);
+ setProcessInstancesFromApplyFilter(result);
},
});
};
@@ -892,7 +809,6 @@ export default function ProcessInstanceListTable({
setEndToDate('');
setEndToTime('');
setProcessInitiatorSelection(null);
- setProcessInitiatorText('');
setRequiresRefilter(true);
if (reportMetadata) {
reportMetadata.filter_by = [];
@@ -929,35 +845,18 @@ export default function ProcessInstanceListTable({
};
const saveAsReportComponent = () => {
- const {
- valid,
- startFromSeconds,
- startToSeconds,
- endFromSeconds,
- endToSeconds,
- } = calculateStartAndEndSeconds(false);
-
const newReportMetadata = getNewReportMetadataBasedOnPageWidgets();
- if (!valid || !reportMetadata || !newReportMetadata) {
+ if (!newReportMetadata) {
return null;
}
return (
);
};
@@ -1325,7 +1224,7 @@ export default function ProcessInstanceListTable({
items={processInstanceInitiatorOptions}
itemToString={(processInstanceInitatorOption: User) => {
if (processInstanceInitatorOption) {
- return processInstanceInitatorOption.username;
+ return processInstanceInitatorOption;
}
return null;
}}
@@ -1343,7 +1242,7 @@ export default function ProcessInstanceListTable({
invalid={processInitiatorNotFoundErrorText !== ''}
invalidText={processInitiatorNotFoundErrorText}
onChange={(event: any) => {
- setProcessInitiatorText(event.target.value);
+ setProcessInitiatorSelection(event.target.value);
setRequiresRefilter(true);
}}
/>
From 3754d72f4f1a1e69dc10f9fc5daf54dcb39481f7 Mon Sep 17 00:00:00 2001
From: jasquat
Date: Fri, 28 Apr 2023 10:07:21 -0400
Subject: [PATCH 08/18] tests are now passing and pyl w/ burnettk
---
.../src/spiffworkflow_backend/api.yml | 261 ++++-----------
.../models/process_instance_report.py | 32 +-
.../routes/process_instances_controller.py | 10 +-
.../helpers/base_test.py | 24 ++
.../integration/test_debug_controller.py | 1 -
.../integration/test_process_api.py | 309 ++++++------------
.../unit/test_process_instance_processor.py | 3 -
.../unit/test_process_instance_report.py | 2 +-
.../components/ProcessInstanceListTable.tsx | 12 +-
9 files changed, 214 insertions(+), 440 deletions(-)
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml
index f6483c39..cab7621d 100755
--- a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml
@@ -616,107 +616,18 @@ paths:
description: The page number to return. Defaults to page 1.
schema:
type: integer
- - name: start_from
- in: query
- required: false
- description: For filtering - beginning of start window - in seconds since epoch
- schema:
- type: integer
- - name: start_to
- in: query
- required: false
- description: For filtering - end of start window - in seconds since epoch
- schema:
- type: integer
- - name: end_from
- in: query
- required: false
- description: For filtering - beginning of end window - in seconds since epoch
- schema:
- type: integer
- - name: end_to
- in: query
- required: false
- description: For filtering - end of end window - in seconds since epoch
- schema:
- type: integer
- - name: process_status
- in: query
- required: false
- description: For filtering - not_started, user_input_required, waiting, complete, error, or suspended
- schema:
- type: string
- - name: initiated_by_me
- in: query
- required: false
- description: For filtering - show instances initiated by me
- schema:
- type: boolean
- - name: with_tasks_completed_by_me
- in: query
- required: false
- description: For filtering - show instances with tasks completed by me
- schema:
- type: boolean
- - name: with_tasks_completed_by_my_group
- in: query
- required: false
- description: For filtering - show instances with tasks completed by my group
- schema:
- type: boolean
- - name: with_relation_to_me
- in: query
- required: false
- description: For filtering - show instances that have something to do with me
- schema:
- type: boolean
- - name: user_filter
- in: query
- required: false
- description: For filtering - indicates the user has manually entered a query
- schema:
- type: boolean
- - name: report_identifier
- in: query
- required: false
- description: Specifies the identifier of a report to use, if any
- schema:
- type: string
- - name: report_id
- in: query
- required: false
- description: Specifies the identifier of a report to use, if any
- schema:
- type: integer
- - name: user_group_identifier
- in: query
- required: false
- description: The identifier of the group to get the process instances for
- schema:
- type: string
- - name: process_initiator_username
- in: query
- required: false
- description: The username of the process initiator
- schema:
- type: string
- - name: report_columns
- in: query
- required: false
- description: Base64 encoded json of report columns.
- schema:
- type: string
- - name: report_filter_by
- in: query
- required: false
- description: Base64 encoded json of report filter by.
- schema:
- type: string
post:
operationId: spiffworkflow_backend.routes.process_instances_controller.process_instance_list_for_me
summary: Returns a list of process instances that are associated with me.
tags:
- Process Instances
+ requestBody:
+ description: Report dictionary to use.
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ProcessInstanceReport"
responses:
"200":
description: Workflow.
@@ -747,107 +658,18 @@ paths:
description: The page number to return. Defaults to page 1.
schema:
type: integer
- - name: start_from
- in: query
- required: false
- description: For filtering - beginning of start window - in seconds since epoch
- schema:
- type: integer
- - name: start_to
- in: query
- required: false
- description: For filtering - end of start window - in seconds since epoch
- schema:
- type: integer
- - name: end_from
- in: query
- required: false
- description: For filtering - beginning of end window - in seconds since epoch
- schema:
- type: integer
- - name: end_to
- in: query
- required: false
- description: For filtering - end of end window - in seconds since epoch
- schema:
- type: integer
- - name: process_status
- in: query
- required: false
- description: For filtering - not_started, user_input_required, waiting, complete, error, or suspended
- schema:
- type: string
- - name: initiated_by_me
- in: query
- required: false
- description: For filtering - show instances initiated by me
- schema:
- type: boolean
- - name: with_tasks_completed_by_me
- in: query
- required: false
- description: For filtering - show instances with tasks completed by me
- schema:
- type: boolean
- - name: with_tasks_completed_by_my_group
- in: query
- required: false
- description: For filtering - show instances with tasks completed by my group
- schema:
- type: boolean
- - name: with_relation_to_me
- in: query
- required: false
- description: For filtering - show instances that have something to do with me
- schema:
- type: boolean
- - name: user_filter
- in: query
- required: false
- description: For filtering - indicates the user has manually entered a query
- schema:
- type: boolean
- - name: report_identifier
- in: query
- required: false
- description: Specifies the identifier of a report to use, if any
- schema:
- type: string
- - name: report_id
- in: query
- required: false
- description: Specifies the identifier of a report to use, if any
- schema:
- type: integer
- - name: user_group_identifier
- in: query
- required: false
- description: The identifier of the group to get the process instances for
- schema:
- type: string
- - name: process_initiator_username
- in: query
- required: false
- description: The username of the process initiator
- schema:
- type: string
- - name: report_columns
- in: query
- required: false
- description: Base64 encoded json of report columns.
- schema:
- type: string
- - name: report_filter_by
- in: query
- required: false
- description: Base64 encoded json of report filter by.
- schema:
- type: string
post:
operationId: spiffworkflow_backend.routes.process_instances_controller.process_instance_list
summary: Returns a list of process instances.
tags:
- Process Instances
+ requestBody:
+ description: Report dictionary to use.
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ProcessInstanceReport"
responses:
"200":
description: Workflow.
@@ -1268,15 +1090,16 @@ paths:
summary: Returns all process instance reports for process model
tags:
- Process Instances
+ - Process Instances Reports
responses:
"200":
- description: Workflow.
+ description: Process Instance Report
content:
application/json:
schema:
type: array
items:
- $ref: "#/components/schemas/Workflow"
+ $ref: "#/components/schemas/ProcessInstanceReport"
post:
operationId: spiffworkflow_backend.routes.process_instances_controller.process_instance_report_create
summary: Returns all process instance reports for process model
@@ -3098,3 +2921,53 @@ components:
description: The timestamp returned in the log
type: number
example: 123456789.12345
+
+ ProcessInstanceReport:
+ properties:
+ id:
+ type: number
+ nullable: true
+ identifier:
+ type: string
+ nullable: true
+ name:
+ type: string
+ nullable: true
+ report_metadata:
+ nullable: false
+ $ref: "#/components/schemas/ReportMetadata"
+ ReportMetadata:
+ properties:
+ columns:
+ type: array
+ nullable: false
+ $ref: "#/components/schemas/ReportMetadataColumn"
+ filter_by:
+ type: array
+ nullable: false
+ $ref: "#/components/schemas/FilterValue"
+ order_by:
+ type: array
+ nullable: false
+ ReportMetadataColumn:
+ properties:
+ Header:
+ type: string
+ nullable: false
+ accessor:
+ type: string
+ nullable: false
+ fiilterable:
+ type: boolean
+ nullable: true
+ FilterValue:
+ properties:
+ field_name:
+ type: string
+ nullable: false
+ field_value:
+ type: string
+ nullable: false
+ fiilterable:
+ type: string
+ nullable: false
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance_report.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance_report.py
index 74ee1a48..e5b8c5c6 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance_report.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance_report.py
@@ -40,6 +40,13 @@ class ReportMetadata(TypedDict):
order_by: list[str]
+class Report(TypedDict):
+ id: int
+ identifier: str
+ name: str
+ report_metadata: ReportMetadata
+
+
class ProcessInstanceReportAlreadyExistsError(Exception):
"""ProcessInstanceReportAlreadyExistsError."""
@@ -91,8 +98,10 @@ class ProcessInstanceReportModel(SpiffworkflowBaseDBModel):
json_data_hash: str = db.Column(db.String(255), nullable=False, index=True)
- def get_report_metadata(self) -> dict:
- return JsonDataModel.find_data_dict_by_hash(self.json_data_hash)
+ def get_report_metadata(self) -> ReportMetadata:
+ rdata_dict = JsonDataModel.find_data_dict_by_hash(self.json_data_hash)
+ rdata = typing.cast(ReportMetadata, rdata_dict)
+ return rdata
@classmethod
def default_order_by(cls) -> list[str]:
@@ -106,7 +115,6 @@ class ProcessInstanceReportModel(SpiffworkflowBaseDBModel):
user: UserModel,
report_metadata: ReportMetadata,
) -> ProcessInstanceReportModel:
- """Make_fixture_report."""
process_instance_report = ProcessInstanceReportModel.query.filter_by(
identifier=identifier,
created_by_id=user.id,
@@ -131,25 +139,7 @@ class ProcessInstanceReportModel(SpiffworkflowBaseDBModel):
return process_instance_report # type: ignore
- @classmethod
- def create_with_attributes(
- cls,
- identifier: str,
- report_metadata: ReportMetadata,
- user: UserModel,
- ) -> ProcessInstanceReportModel:
- """Create_with_attributes."""
- process_instance_report = cls(
- identifier=identifier,
- created_by_id=user.id,
- report_metadata=report_metadata,
- )
- db.session.add(process_instance_report)
- db.session.commit()
- return process_instance_report
-
def with_substitutions(self, field_value: Any, substitution_variables: dict) -> Any:
- """With_substitutions."""
if substitution_variables is not None:
for key, value in substitution_variables.items():
if isinstance(value, str) or isinstance(value, int):
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py
index dd249372..ed02134c 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py
@@ -3,6 +3,7 @@ import json
from typing import Any
from typing import Dict
from typing import Optional
+from typing import Union
import flask.wrappers
from flask import current_app
@@ -35,9 +36,8 @@ from spiffworkflow_backend.models.process_instance_metadata import (
from spiffworkflow_backend.models.process_instance_queue import (
ProcessInstanceQueueModel,
)
-from spiffworkflow_backend.models.process_instance_report import (
- ProcessInstanceReportModel,
-)
+from spiffworkflow_backend.models.process_instance_report import ProcessInstanceReportModel
+from spiffworkflow_backend.models.process_instance_report import Report
from spiffworkflow_backend.models.process_model import ProcessModelInfo
from spiffworkflow_backend.models.spec_reference import SpecReferenceCache
from spiffworkflow_backend.models.spec_reference import SpecReferenceNotFoundError
@@ -231,7 +231,6 @@ def process_instance_list_for_me(
page: int = 1,
per_page: int = 100,
) -> flask.wrappers.Response:
- """Process_instance_list_for_me."""
return process_instance_list(
process_model_identifier=process_model_identifier,
page=page,
@@ -273,7 +272,7 @@ def process_instance_report_show(
" report_identifier."
),
)
- response_result = {}
+ response_result: Optional[Union[Report, ProcessInstanceReportModel]] = None
if report_hash is not None:
json_data = JsonDataModel.query.filter_by(hash=report_hash).first()
if json_data is None:
@@ -365,7 +364,6 @@ def process_instance_delete(
def process_instance_report_list(page: int = 1, per_page: int = 100) -> flask.wrappers.Response:
- """Process_instance_report_list."""
process_instance_reports = ProcessInstanceReportModel.query.filter_by(
created_by_id=g.user.id,
).all()
diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/base_test.py b/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/base_test.py
index 03620228..d89ed358 100644
--- a/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/base_test.py
+++ b/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/base_test.py
@@ -19,6 +19,7 @@ from spiffworkflow_backend.models.permission_target import PermissionTargetModel
from spiffworkflow_backend.models.process_group import ProcessGroup
from spiffworkflow_backend.models.process_group import ProcessGroupSchema
from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
+from spiffworkflow_backend.models.process_instance_report import ReportMetadata
from spiffworkflow_backend.models.process_model import NotificationType
from spiffworkflow_backend.models.process_model import ProcessModelInfo
from spiffworkflow_backend.models.process_model import ProcessModelInfoSchema
@@ -379,3 +380,26 @@ class BaseTest:
},
)
return process_model
+
+ def post_to_process_instance_list(
+ self,
+ client: FlaskClient,
+ user: UserModel,
+ report_metadata: Optional[ReportMetadata] = None,
+ param_string: Optional[str] = "",
+ ) -> TestResponse:
+ report_metadata_to_use = report_metadata
+ if report_metadata_to_use is None:
+ report_metadata_to_use = self.empty_report_metadata_body()
+ response = client.post(
+ f"/v1.0/process-instances{param_string}",
+ headers=self.logged_in_headers(user),
+ content_type="application/json",
+ data=json.dumps({"report_metadata": report_metadata_to_use}),
+ )
+ assert response.status_code == 200
+ assert response.json is not None
+ return response
+
+ def empty_report_metadata_body(self) -> ReportMetadata:
+ return {"filter_by": [], "columns": [], "order_by": []}
diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_debug_controller.py b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_debug_controller.py
index 84fcafb3..d9136020 100644
--- a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_debug_controller.py
+++ b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_debug_controller.py
@@ -4,7 +4,6 @@ from tests.spiffworkflow_backend.helpers.base_test import BaseTest
class TestDebugController(BaseTest):
-
def test_test_raise_error(
self,
app: Flask,
diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py
index 8fec7d26..20ef475a 100644
--- a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py
+++ b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py
@@ -1761,12 +1761,7 @@ class TestProcessApi(BaseTest):
headers = self.logged_in_headers(with_super_admin_user)
self.create_process_instance_from_process_model_id_with_api(client, process_model_identifier, headers)
- response = client.get(
- "/v1.0/process-instances",
- headers=self.logged_in_headers(with_super_admin_user),
- )
- assert response.status_code == 200
- assert response.json is not None
+ response = self.post_to_process_instance_list(client, with_super_admin_user)
assert len(response.json["results"]) == 1
assert response.json["pagination"]["count"] == 1
assert response.json["pagination"]["pages"] == 1
@@ -1807,23 +1802,13 @@ class TestProcessApi(BaseTest):
self.create_process_instance_from_process_model_id_with_api(client, process_model_identifier, headers)
self.create_process_instance_from_process_model_id_with_api(client, process_model_identifier, headers)
- response = client.get(
- "/v1.0/process-instances?per_page=2&page=3",
- headers=self.logged_in_headers(with_super_admin_user),
- )
- assert response.status_code == 200
- assert response.json is not None
+ response = self.post_to_process_instance_list(client, with_super_admin_user, param_string="?per_page=2&page=3")
assert len(response.json["results"]) == 1
assert response.json["pagination"]["count"] == 1
assert response.json["pagination"]["pages"] == 3
assert response.json["pagination"]["total"] == 5
- response = client.get(
- "/v1.0/process-instances?per_page=2&page=1",
- headers=self.logged_in_headers(with_super_admin_user),
- )
- assert response.status_code == 200
- assert response.json is not None
+ response = self.post_to_process_instance_list(client, with_super_admin_user, param_string="?per_page=2&page=1")
assert len(response.json["results"]) == 2
assert response.json["pagination"]["count"] == 2
assert response.json["pagination"]["pages"] == 3
@@ -1867,31 +1852,46 @@ class TestProcessApi(BaseTest):
db.session.commit()
# Without filtering we should get all 5 instances
- response = client.get(
- f"/v1.0/process-instances?process_model_identifier={process_model_identifier}",
- headers=self.logged_in_headers(with_super_admin_user),
+ report_metadata_body: ReportMetadata = {
+ "filter_by": [{"field_name": "process_model_identifier", "field_value": process_model_identifier}],
+ "columns": [],
+ "order_by": [],
+ }
+ response = self.post_to_process_instance_list(
+ client, with_super_admin_user, report_metadata=report_metadata_body
)
- assert response.json is not None
results = response.json["results"]
assert len(results) == 5
# filter for each of the status
# we should get 1 instance each time
for i in range(5):
- response = client.get(
- f"/v1.0/process-instances?process_status={ProcessInstanceStatus[statuses[i]].value}&process_model_identifier={process_model_identifier}",
- headers=self.logged_in_headers(with_super_admin_user),
+ report_metadata_body = {
+ "filter_by": [
+ {"field_name": "process_model_identifier", "field_value": process_model_identifier},
+ {"field_name": "process_status", "field_value": ProcessInstanceStatus[statuses[i]].value},
+ ],
+ "columns": [],
+ "order_by": [],
+ }
+ response = self.post_to_process_instance_list(
+ client, with_super_admin_user, report_metadata=report_metadata_body
)
- assert response.json is not None
results = response.json["results"]
assert len(results) == 1
assert results[0]["status"] == ProcessInstanceStatus[statuses[i]].value
- response = client.get(
- f"/v1.0/process-instances?process_status=not_started,complete&process_model_identifier={process_model_identifier}",
- headers=self.logged_in_headers(with_super_admin_user),
+ report_metadata_body = {
+ "filter_by": [
+ {"field_name": "process_model_identifier", "field_value": process_model_identifier},
+ {"field_name": "process_status", "field_value": "not_started,complete"},
+ ],
+ "columns": [],
+ "order_by": [],
+ }
+ response = self.post_to_process_instance_list(
+ client, with_super_admin_user, report_metadata=report_metadata_body
)
- assert response.json is not None
results = response.json["results"]
assert len(results) == 2
assert results[0]["status"] in ["complete", "not_started"]
@@ -1899,11 +1899,14 @@ class TestProcessApi(BaseTest):
# filter by start/end seconds
# start > 1000 - this should eliminate the first
- response = client.get(
- "/v1.0/process-instances?start_from=1001",
- headers=self.logged_in_headers(with_super_admin_user),
+ report_metadata_body = {
+ "filter_by": [{"field_name": "start_from", "field_value": 1001}],
+ "columns": [],
+ "order_by": [],
+ }
+ response = self.post_to_process_instance_list(
+ client, with_super_admin_user, report_metadata=report_metadata_body
)
- assert response.json is not None
results = response.json["results"]
assert len(results) == 4
for i in range(4):
@@ -1915,33 +1918,51 @@ class TestProcessApi(BaseTest):
)
# start > 2000, end < 5000 - this should eliminate the first 2 and the last
- response = client.get(
- "/v1.0/process-instances?start_from=2001&end_to=5999",
- headers=self.logged_in_headers(with_super_admin_user),
+ report_metadata_body = {
+ "filter_by": [
+ {"field_name": "start_from", "field_value": 2001},
+ {"field_name": "end_to", "field_value": 5999},
+ ],
+ "columns": [],
+ "order_by": [],
+ }
+ response = self.post_to_process_instance_list(
+ client, with_super_admin_user, report_metadata=report_metadata_body
)
- assert response.json is not None
results = response.json["results"]
assert len(results) == 2
assert json.loads(results[0]["bpmn_version_control_identifier"]) in (2, 3)
assert json.loads(results[1]["bpmn_version_control_identifier"]) in (2, 3)
# start > 1000, start < 4000 - this should eliminate the first and the last 2
- response = client.get(
- "/v1.0/process-instances?start_from=1001&start_to=3999",
- headers=self.logged_in_headers(with_super_admin_user),
+ report_metadata_body = {
+ "filter_by": [
+ {"field_name": "start_from", "field_value": 1001},
+ {"field_name": "start_to", "field_value": 3999},
+ ],
+ "columns": [],
+ "order_by": [],
+ }
+ response = self.post_to_process_instance_list(
+ client, with_super_admin_user, report_metadata=report_metadata_body
)
- assert response.json is not None
results = response.json["results"]
assert len(results) == 2
assert json.loads(results[0]["bpmn_version_control_identifier"]) in (1, 2)
assert json.loads(results[1]["bpmn_version_control_identifier"]) in (1, 2)
# end > 2000, end < 6000 - this should eliminate the first and the last
- response = client.get(
- "/v1.0/process-instances?end_from=2001&end_to=5999",
- headers=self.logged_in_headers(with_super_admin_user),
+ report_metadata_body = {
+ "filter_by": [
+ {"field_name": "end_from", "field_value": 2001},
+ {"field_name": "end_to", "field_value": 5999},
+ ],
+ "columns": [],
+ "order_by": [],
+ }
+ response = self.post_to_process_instance_list(
+ client, with_super_admin_user, report_metadata=report_metadata_body
)
- assert response.json is not None
results = response.json["results"]
assert len(results) == 3
for i in range(3):
@@ -1975,7 +1996,7 @@ class TestProcessApi(BaseTest):
report_identifier = "testreport"
report_metadata: ReportMetadata = {"order_by": ["month"], "filter_by": [], "columns": []}
- ProcessInstanceReportModel.create_with_attributes(
+ ProcessInstanceReportModel.create_report(
identifier=report_identifier,
report_metadata=report_metadata,
user=with_super_admin_user,
@@ -1990,129 +2011,6 @@ class TestProcessApi(BaseTest):
assert response.json[0]["identifier"] == report_identifier
assert response.json[0]["report_metadata"]["order_by"] == ["month"]
- # def test_process_instance_report_show_with_default_list(
- # self,
- # app: Flask,
- # client: FlaskClient,
- # with_db_and_bpmn_file_cleanup: None,
- # with_super_admin_user: UserModel,
- # setup_process_instances_for_reports: list[ProcessInstanceModel],
- # ) -> None:
- # """Test_process_instance_report_show_with_default_list."""
- # process_group_id = "runs_without_input"
- # process_model_id = "sample"
- # process_model_identifier = f"{process_group_id}/{process_model_id}"
- #
- # report_metadata = {
- # "columns": [
- # {"Header": "id", "accessor": "id"},
- # {
- # "Header": "process_model_identifier",
- # "accessor": "process_model_identifier",
- # },
- # {"Header": "process_group_id", "accessor": "process_group_identifier"},
- # {"Header": "start_in_seconds", "accessor": "start_in_seconds"},
- # {"Header": "status", "accessor": "status"},
- # {"Header": "Name", "accessor": "name"},
- # {"Header": "Status", "accessor": "status"},
- # ],
- # "order_by": ["test_score"],
- # "filter_by": [
- # {"field_name": "grade_level", "operator": "equals", "field_value": 2}
- # ],
- # }
- #
- # report = ProcessInstanceReportModel.create_with_attributes(
- # identifier="sure",
- # report_metadata=report_metadata,
- # user=with_super_admin_user,
- # )
- #
- # response = client.get(
- # f"/v1.0/process-instances/reports/{report.id}",
- # headers=self.logged_in_headers(with_super_admin_user),
- # )
- # assert response.status_code == 200
- # assert response.json is not None
- # assert len(response.json["results"]) == 2
- # assert response.json["pagination"]["count"] == 2
- # assert response.json["pagination"]["pages"] == 1
- # assert response.json["pagination"]["total"] == 2
- #
- # process_instance_dict = response.json["results"][0]
- # assert type(process_instance_dict["id"]) is int
- # assert (
- # process_instance_dict["process_model_identifier"]
- # == process_model_identifier
- # )
- # assert type(process_instance_dict["start_in_seconds"]) is int
- # assert process_instance_dict["start_in_seconds"] > 0
- # assert process_instance_dict["status"] == "complete"
- #
- # def test_process_instance_report_show_with_dynamic_filter_and_query_param(
- # self,
- # app: Flask,
- # client: FlaskClient,
- # with_db_and_bpmn_file_cleanup: None,
- # with_super_admin_user: UserModel,
- # setup_process_instances_for_reports: list[ProcessInstanceModel],
- # ) -> None:
- # """Test_process_instance_report_show_with_default_list."""
- # report_metadata = {
- # "filter_by": [
- # {
- # "field_name": "grade_level",
- # "operator": "equals",
- # "field_value": "{{grade_level}}",
- # }
- # ],
- # }
- #
- # report = ProcessInstanceReportModel.create_with_attributes(
- # identifier="sure",
- # report_metadata=report_metadata,
- # user=with_super_admin_user,
- # )
- #
- # response = client.get(
- # f"/v1.0/process-instances/reports/{report.id}?grade_level=1",
- # headers=self.logged_in_headers(with_super_admin_user),
- # )
- # assert response.status_code == 200
- # assert response.json is not None
- # assert len(response.json["results"]) == 1
-
- def test_process_instance_report_show_with_bad_identifier(
- self,
- app: Flask,
- client: FlaskClient,
- with_db_and_bpmn_file_cleanup: None,
- with_super_admin_user: UserModel,
- setup_process_instances_for_reports: list[ProcessInstanceModel],
- ) -> None:
- """Test_process_instance_report_show_with_bad_identifier."""
- response = client.get(
- "/v1.0/process-instances/reports/13000000?grade_level=1",
- headers=self.logged_in_headers(with_super_admin_user),
- )
- assert response.status_code == 404
- data = json.loads(response.get_data(as_text=True))
- assert data["error_code"] == "unknown_process_instance_report"
-
- def setup_testing_instance(
- self,
- client: FlaskClient,
- process_model_id: str,
- with_super_admin_user: UserModel,
- ) -> Any:
- """Setup_testing_instance."""
- headers = self.logged_in_headers(with_super_admin_user)
- response = self.create_process_instance_from_process_model_id_with_api(client, process_model_id, headers)
- process_instance = response.json
- assert isinstance(process_instance, dict)
- process_instance_id = process_instance["id"]
- return process_instance_id
-
def test_error_handler(
self,
app: Flask,
@@ -2134,7 +2032,7 @@ class TestProcessApi(BaseTest):
bpmn_file_location=bpmn_file_location,
)
- process_instance_id = self.setup_testing_instance(client, process_model_identifier, with_super_admin_user)
+ process_instance_id = self._setup_testing_instance(client, process_model_identifier, with_super_admin_user)
process = db.session.query(ProcessInstanceModel).filter(ProcessInstanceModel.id == process_instance_id).first()
assert process is not None
@@ -2175,7 +2073,7 @@ class TestProcessApi(BaseTest):
bpmn_file_location=bpmn_file_location,
)
- process_instance_id = self.setup_testing_instance(client, process_model_identifier, with_super_admin_user)
+ process_instance_id = self._setup_testing_instance(client, process_model_identifier, with_super_admin_user)
process_model = ProcessModelService.get_process_model(process_model_identifier)
ProcessModelService.update_process_model(
process_model,
@@ -2683,8 +2581,9 @@ class TestProcessApi(BaseTest):
content_type="application/json",
data=json.dumps(data),
)
-
- print("test_script_unit_test_run")
+ # TODO: fix this test. I'm not sure it ever worked since it used to NOT check the status code
+ # and only printed out the test name.
+ assert response.status_code == 404
def test_send_event(
self,
@@ -3060,7 +2959,6 @@ class TestProcessApi(BaseTest):
with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None:
- """Test_can_get_process_instance_list_with_report_metadata."""
process_model = load_test_spec(
process_model_id="save_process_instance_metadata/save_process_instance_metadata",
bpmn_file_name="save_process_instance_metadata.bpmn",
@@ -3087,20 +2985,16 @@ class TestProcessApi(BaseTest):
"order_by": ["status"],
"filter_by": [],
}
- process_instance_report = ProcessInstanceReportModel.create_with_attributes(
+ process_instance_report = ProcessInstanceReportModel.create_report(
identifier="sure",
report_metadata=report_metadata,
user=with_super_admin_user,
)
- response = client.get(
- f"/v1.0/process-instances?report_identifier={process_instance_report.identifier}",
- headers=self.logged_in_headers(with_super_admin_user),
+ response = self.post_to_process_instance_list(
+ client, with_super_admin_user, report_metadata=process_instance_report.get_report_metadata()
)
- assert response.json is not None
- assert response.status_code == 200
-
assert len(response.json["results"]) == 1
assert response.json["results"][0]["status"] == "complete"
assert response.json["results"][0]["id"] == process_instance.id
@@ -3160,33 +3054,27 @@ class TestProcessApi(BaseTest):
}
],
}
- process_instance_report_dne = ProcessInstanceReportModel.create_with_attributes(
+ process_instance_report_dne = ProcessInstanceReportModel.create_report(
identifier="dne_report",
report_metadata=dne_report_metadata,
user=user_one,
)
- process_instance_report_user_one = ProcessInstanceReportModel.create_with_attributes(
+ process_instance_report_user_one = ProcessInstanceReportModel.create_report(
identifier="user_one_report",
report_metadata=user_one_report_metadata,
user=user_one,
)
- response = client.get(
- f"/v1.0/process-instances?report_identifier={process_instance_report_user_one.identifier}",
- headers=self.logged_in_headers(user_one),
+ response = self.post_to_process_instance_list(
+ client, user_one, report_metadata=process_instance_report_user_one.get_report_metadata()
)
- assert response.json is not None
- assert response.status_code == 200
assert len(response.json["results"]) == 2
assert response.json["results"][0]["process_initiator_username"] == user_one.username
assert response.json["results"][1]["process_initiator_username"] == user_one.username
- response = client.get(
- f"/v1.0/process-instances?report_identifier={process_instance_report_dne.identifier}",
- headers=self.logged_in_headers(user_one),
+ response = self.post_to_process_instance_list(
+ client, user_one, report_metadata=process_instance_report_dne.get_report_metadata()
)
- assert response.json is not None
- assert response.status_code == 200
assert len(response.json["results"]) == 0
def test_can_get_process_instance_report_column_list(
@@ -3196,7 +3084,6 @@ class TestProcessApi(BaseTest):
with_db_and_bpmn_file_cleanup: None,
with_super_admin_user: UserModel,
) -> None:
- """Test_can_get_process_instance_list_with_report_metadata."""
process_model = self.create_process_model_with_metadata()
process_instance = self.create_process_instance_from_process_model(
process_model=process_model, user=with_super_admin_user
@@ -3315,18 +3202,15 @@ class TestProcessApi(BaseTest):
"order_by": ["time_ns"],
"filter_by": [],
}
- report_one = ProcessInstanceReportModel.create_with_attributes(
+ report_one = ProcessInstanceReportModel.create_report(
identifier="report_one",
report_metadata=report_metadata,
user=with_super_admin_user,
)
- response = client.get(
- f"/v1.0/process-instances?report_id={report_one.id}",
- headers=self.logged_in_headers(with_super_admin_user),
+ response = self.post_to_process_instance_list(
+ client, with_super_admin_user, report_metadata=report_one.get_report_metadata()
)
- assert response.status_code == 200
- assert response.json is not None
assert len(response.json["results"]) == 2
assert response.json["results"][0]["id"] == process_instance_one.id
assert response.json["results"][1]["id"] == process_instance_two.id
@@ -3339,19 +3223,15 @@ class TestProcessApi(BaseTest):
"order_by": ["-time_ns"],
"filter_by": [],
}
- report_two = ProcessInstanceReportModel.create_with_attributes(
+ report_two = ProcessInstanceReportModel.create_report(
identifier="report_two",
report_metadata=report_metadata,
user=with_super_admin_user,
)
- response = client.get(
- f"/v1.0/process-instances?report_id={report_two.id}",
- headers=self.logged_in_headers(with_super_admin_user),
+ response = self.post_to_process_instance_list(
+ client, with_super_admin_user, report_metadata=report_two.get_report_metadata()
)
-
- assert response.status_code == 200
- assert response.json is not None
assert len(response.json["results"]) == 2
assert response.json["results"][1]["id"] == process_instance_one.id
assert response.json["results"][0]["id"] == process_instance_two.id
@@ -3381,3 +3261,16 @@ class TestProcessApi(BaseTest):
assert response.status_code == 200
assert response.json is not None
assert response.json["process_data_value"] == "hey"
+
+ def _setup_testing_instance(
+ self,
+ client: FlaskClient,
+ process_model_id: str,
+ with_super_admin_user: UserModel,
+ ) -> Any:
+ headers = self.logged_in_headers(with_super_admin_user)
+ response = self.create_process_instance_from_process_model_id_with_api(client, process_model_id, headers)
+ process_instance = response.json
+ assert isinstance(process_instance, dict)
+ process_instance_id = process_instance["id"]
+ return process_instance_id
diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_processor.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_processor.py
index e89a959a..bfda1eb3 100644
--- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_processor.py
+++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_processor.py
@@ -362,9 +362,6 @@ class TestProcessInstanceProcessor(BaseTest):
human_task_one = process_instance.active_human_tasks[0]
spiff_manual_task = processor.bpmn_process_instance.get_task_from_id(UUID(human_task_one.task_id))
ProcessInstanceService.complete_form_task(processor, spiff_manual_task, {}, initiator_user, human_task_one)
- import pdb
-
- pdb.set_trace()
assert (
len(process_instance.active_human_tasks) == 1
), "expected 1 active human tasks after 2nd one is completed, as we have looped back around."
diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_report.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_report.py
index 51b3d222..64c7f3f7 100644
--- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_report.py
+++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_report.py
@@ -125,7 +125,7 @@
# substitution_variables: Optional[dict] = None,
# ) -> list[dict]:
# """Do_report_with_metadata_and_instances."""
-# process_instance_report = ProcessInstanceReportModel.create_with_attributes(
+# process_instance_report = ProcessInstanceReportModel.create_report(
# identifier="sure",
# report_metadata=report_metadata,
# user=BaseTest.find_or_create_user(),
diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
index 7284eaf6..b3b496d5 100644
--- a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
+++ b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
@@ -583,24 +583,24 @@ export default function ProcessInstanceListTable({
};
const removeFieldFromReportMetadata = (
- postBody: ReportMetadata,
+ reportMetadataToUse: ReportMetadata,
fieldName: string
) => {
- const filtersToKeep = postBody.filter_by.filter(
+ const filtersToKeep = reportMetadataToUse.filter_by.filter(
(rf: ReportFilter) => rf.field_name !== fieldName
);
// eslint-disable-next-line no-param-reassign
- postBody.filter_by = filtersToKeep;
+ reportMetadataToUse.filter_by = filtersToKeep;
};
const insertOrUpdateFieldInReportMetadata = (
- postBody: ReportMetadata,
+ reportMetadataToUse: ReportMetadata,
fieldName: string,
fieldValue: string
) => {
- removeFieldFromReportMetadata(postBody, fieldName);
+ removeFieldFromReportMetadata(reportMetadataToUse, fieldName);
if (fieldValue) {
- postBody.filter_by.push({
+ reportMetadataToUse.filter_by.push({
field_name: fieldName,
field_value: fieldValue,
});
From 80ec1194db2ead4a7225c6dd5da2cef3338ea1f3 Mon Sep 17 00:00:00 2001
From: jasquat
Date: Fri, 28 Apr 2023 11:56:52 -0400
Subject: [PATCH 09/18] fixed cypress process instance tests w/ burnettk
---
.../cypress/e2e/process_instances.cy.js | 10 ++++++++--
.../ProcessInstanceListSaveAsReport.tsx | 9 +++++++--
.../components/ProcessInstanceListTable.tsx | 19 +++++++------------
spiffworkflow-frontend/src/helpers.tsx | 4 ++++
4 files changed, 26 insertions(+), 16 deletions(-)
diff --git a/spiffworkflow-frontend/cypress/e2e/process_instances.cy.js b/spiffworkflow-frontend/cypress/e2e/process_instances.cy.js
index aa0c6626..db6f53d3 100644
--- a/spiffworkflow-frontend/cypress/e2e/process_instances.cy.js
+++ b/spiffworkflow-frontend/cypress/e2e/process_instances.cy.js
@@ -1,5 +1,6 @@
import { format } from 'date-fns';
import { DATE_FORMAT, PROCESS_STATUSES } from '../../src/config';
+import { titleizeString } from '../../src/helpers';
const filterByDate = (fromDate) => {
cy.get('#date-picker-start-from').clear().type(format(fromDate, DATE_FORMAT));
@@ -172,14 +173,19 @@ describe('process-instances', () => {
cy.contains('All Process Instances');
cy.assertAtLeastOneItemInPaginatedResults();
+ cy.getBySel('filter-section-expand-toggle').click();
+
const statusSelect = '#process-instance-status-select';
PROCESS_STATUSES.forEach((processStatus) => {
if (!['all', 'waiting'].includes(processStatus)) {
cy.get(statusSelect).click();
- cy.get(statusSelect).contains(processStatus).click();
+ cy.get(statusSelect).contains(titleizeString(processStatus)).click();
cy.get(statusSelect).click();
cy.getBySel('filter-button').click();
- cy.url().should('include', `status=${processStatus}`);
+
+ // make sure that there is 1 status item selected in the multiselect
+ cy.get(`${statusSelect} .cds--tag`).contains('1');
+
cy.assertAtLeastOneItemInPaginatedResults();
cy.getBySel(`process-instance-status-${processStatus}`);
diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListSaveAsReport.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListSaveAsReport.tsx
index b196c2ee..9f912628 100644
--- a/spiffworkflow-frontend/src/components/ProcessInstanceListSaveAsReport.tsx
+++ b/spiffworkflow-frontend/src/components/ProcessInstanceListSaveAsReport.tsx
@@ -14,7 +14,7 @@ type OwnProps = {
buttonText?: string;
buttonClassName?: string;
processInstanceReportSelection?: ProcessInstanceReport | null;
- reportMetadata: ReportMetadata;
+ getReportMetadataCallback: Function;
};
export default function ProcessInstanceListSaveAsReport({
@@ -22,7 +22,7 @@ export default function ProcessInstanceListSaveAsReport({
processInstanceReportSelection,
buttonClassName,
buttonText = 'Save as Perspective',
- reportMetadata,
+ getReportMetadataCallback,
}: OwnProps) {
const [identifier, setIdentifier] = useState(
processInstanceReportSelection?.identifier || ''
@@ -50,6 +50,11 @@ export default function ProcessInstanceListSaveAsReport({
const addProcessInstanceReport = (event: any) => {
event.preventDefault();
+ const reportMetadata = getReportMetadataCallback();
+ if (!reportMetadata) {
+ return;
+ }
+
let path = `/process-instances/reports`;
let httpMethod = 'POST';
if (isEditMode() && processInstanceReportSelection) {
diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
index b3b496d5..1bb76de9 100644
--- a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
+++ b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
@@ -31,7 +31,6 @@ import {
DATE_FORMAT_FOR_DISPLAY,
} from '../config';
import {
- capitalizeFirstLetter,
convertDateAndTimeStringsToSeconds,
convertDateObjectToFormattedHoursMinutes,
convertSecondsToFormattedDateString,
@@ -42,6 +41,7 @@ import {
refreshAtInterval,
REFRESH_INTERVAL_SECONDS,
REFRESH_TIMEOUT_SECONDS,
+ titleizeString,
} from '../helpers';
import { useUriListForPermissions } from '../hooks/UriListForPermissions';
@@ -337,10 +337,8 @@ export default function ProcessInstanceListTable({
setProcessStatusSelection(
(reportFilter.field_value || '').split(',')
);
- setShowFilterOptions(true);
} else if (reportFilter.field_name === 'process_initiator_username') {
setProcessInitiatorSelection(reportFilter.field_value || '');
- setShowFilterOptions(true);
} else if (reportFilter.field_name === 'process_model_identifier') {
selectedProcessModelIdentifier =
reportFilter.field_value || undefined;
@@ -358,7 +356,6 @@ export default function ProcessInstanceListTable({
reportFilter.field_value as any
);
timeFunctionToCall(timeString);
- setShowFilterOptions(true);
}
}
}
@@ -368,11 +365,14 @@ export default function ProcessInstanceListTable({
(processModel: ProcessModel) => {
if (processModel.id === selectedProcessModelIdentifier) {
setProcessModelSelection(processModel);
- setShowFilterOptions(true);
}
}
);
+ if (reportMetadataBodyToUse.filter_by.length > 1) {
+ setShowFilterOptions(true);
+ }
+
// eslint-disable-next-line prefer-const
let { page, perPage } = getPageInfoFromSearchParams(
searchParams,
@@ -774,7 +774,7 @@ export default function ProcessInstanceListTable({
};
const formatProcessInstanceStatus = (_row: any, value: any) => {
- return capitalizeFirstLetter((value || '').replaceAll('_', ' '));
+ return titleizeString((value || '').replaceAll('_', ' '));
};
const processStatusSearch = () => {
return (
@@ -845,18 +845,13 @@ export default function ProcessInstanceListTable({
};
const saveAsReportComponent = () => {
- const newReportMetadata = getNewReportMetadataBasedOnPageWidgets();
-
- if (!newReportMetadata) {
- return null;
- }
return (
);
};
diff --git a/spiffworkflow-frontend/src/helpers.tsx b/spiffworkflow-frontend/src/helpers.tsx
index 441d3c0f..08aff8cf 100644
--- a/spiffworkflow-frontend/src/helpers.tsx
+++ b/spiffworkflow-frontend/src/helpers.tsx
@@ -41,6 +41,10 @@ export const capitalizeFirstLetter = (string: any) => {
return string.charAt(0).toUpperCase() + string.slice(1);
};
+export const titleizeString = (string: any) => {
+ return capitalizeFirstLetter((string || '').replaceAll('_', ' '));
+};
+
export const convertDateToSeconds = (
date: any,
onChangeFunction: any = null
From b179b66e2619ff2d255a847d89eb548d853f072a Mon Sep 17 00:00:00 2001
From: jasquat
Date: Fri, 28 Apr 2023 12:36:16 -0400
Subject: [PATCH 10/18] home page tables are working again w/ burnettk
---
.../process_instance_report_service.py | 2 +-
.../ProcessInstanceListSaveAsReport.tsx | 2 +-
.../components/ProcessInstanceListTable.tsx | 33 +++++++++++--------
.../src/routes/CompletedInstances.tsx | 4 ++-
.../src/routes/InProgressInstances.tsx | 4 ++-
5 files changed, 28 insertions(+), 17 deletions(-)
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py
index a95cec34..dee7ea09 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py
@@ -527,7 +527,7 @@ class ProcessInstanceReportService:
results = cls.add_metadata_columns_to_process_instance(process_instances.items, report_metadata["columns"])
for value in cls.check_filter_value(filters, "oldest_open_human_task_fields"):
- results = cls.add_human_task_fields(results, value)
+ results = cls.add_human_task_fields(results, value.split(","))
response_json = {
"report_metadata": report_metadata,
"results": results,
diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListSaveAsReport.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListSaveAsReport.tsx
index 9f912628..6e43b212 100644
--- a/spiffworkflow-frontend/src/components/ProcessInstanceListSaveAsReport.tsx
+++ b/spiffworkflow-frontend/src/components/ProcessInstanceListSaveAsReport.tsx
@@ -6,7 +6,7 @@ import {
Modal,
// @ts-ignore
} from '@carbon/react';
-import { ProcessInstanceReport, ReportMetadata } from '../interfaces';
+import { ProcessInstanceReport } from '../interfaces';
import HttpService from '../services/HttpService';
type OwnProps = {
diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
index 1bb76de9..4430a500 100644
--- a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
+++ b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
@@ -87,7 +87,7 @@ type OwnProps = {
textToShowIfEmpty?: string;
paginationClassName?: string;
autoReload?: boolean;
- additionalParams?: string;
+ additionalReportFilters?: ReportFilter[];
variant?: string;
canCompleteAllTasks?: boolean;
showActionsColumn?: boolean;
@@ -102,7 +102,7 @@ export default function ProcessInstanceListTable({
processModelFullIdentifier,
paginationQueryParamPrefix,
perPageOptions,
- additionalParams,
+ additionalReportFilters,
showReports = true,
reportIdentifier,
textToShowIfEmpty,
@@ -330,6 +330,9 @@ export default function ProcessInstanceListTable({
}
}
+ // this is the code to re-populate the widgets on the page
+ // with values from the report metadata, which is derived
+ // from the searchParams (often report_hash)
let selectedProcessModelIdentifier = processModelFullIdentifier;
reportMetadataBodyToUse.filter_by.forEach(
(reportFilter: ReportFilter) => {
@@ -360,7 +363,6 @@ export default function ProcessInstanceListTable({
}
}
);
-
processModelSelectionItemsForUseEffect.forEach(
(processModel: ProcessModel) => {
if (processModel.id === selectedProcessModelIdentifier) {
@@ -384,9 +386,13 @@ export default function ProcessInstanceListTable({
// eslint-disable-next-line prefer-destructuring
perPage = perPageOptions[1];
}
- let queryParamString = `per_page=${perPage}&page=${page}`;
- if (additionalParams) {
- queryParamString += `&${additionalParams}`;
+ const queryParamString = `per_page=${perPage}&page=${page}`;
+ if (additionalReportFilters) {
+ additionalReportFilters.forEach((arf: ReportFilter) => {
+ if (!reportMetadataBodyToUse.filter_by.includes(arf)) {
+ reportMetadataBodyToUse.filter_by.push(arf);
+ }
+ });
}
HttpService.makeCallToBackend({
@@ -405,13 +411,14 @@ export default function ProcessInstanceListTable({
return;
}
const queryParams: string[] = [];
- ['report_hash', 'report_id', 'report_identifier'].forEach(
- (paramName: string) => {
- if (searchParams.get(paramName)) {
- queryParams.push(`${paramName}=${searchParams.get(paramName)}`);
- }
+ ['report_hash', 'report_id'].forEach((paramName: string) => {
+ if (searchParams.get(paramName)) {
+ queryParams.push(`${paramName}=${searchParams.get(paramName)}`);
}
- );
+ });
+ if (reportIdentifier) {
+ queryParams.push(`report_identifier=${reportIdentifier}`);
+ }
if (queryParams.length > 0) {
const queryParamString = `?${queryParams.join('&')}`;
@@ -476,7 +483,7 @@ export default function ProcessInstanceListTable({
processModelFullIdentifier,
perPageOptions,
reportIdentifier,
- additionalParams,
+ additionalReportFilters,
processInstanceApiSearchPath,
permissionsLoaded,
listHasBeenFiltered,
diff --git a/spiffworkflow-frontend/src/routes/CompletedInstances.tsx b/spiffworkflow-frontend/src/routes/CompletedInstances.tsx
index 6d43020d..581dbe04 100644
--- a/spiffworkflow-frontend/src/routes/CompletedInstances.tsx
+++ b/spiffworkflow-frontend/src/routes/CompletedInstances.tsx
@@ -32,7 +32,9 @@ export default function CompletedInstances() {
reportIdentifier="system_report_completed_instances_with_tasks_completed_by_my_groups"
showReports={false}
textToShowIfEmpty="This group has no completed instances at this time."
- additionalParams={`user_group_identifier=${userGroup}`}
+ additionalReportFilters={[
+ { field_name: 'user_group_identifier', field_value: userGroup },
+ ]}
showActionsColumn
/>
>
diff --git a/spiffworkflow-frontend/src/routes/InProgressInstances.tsx b/spiffworkflow-frontend/src/routes/InProgressInstances.tsx
index 9ba7cd26..1943e67d 100644
--- a/spiffworkflow-frontend/src/routes/InProgressInstances.tsx
+++ b/spiffworkflow-frontend/src/routes/InProgressInstances.tsx
@@ -35,7 +35,9 @@ export default function InProgressInstances() {
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}`}
+ additionalReportFilters={[
+ { field_name: 'user_group_identifier', field_value: userGroup },
+ ]}
canCompleteAllTasks
showActionsColumn
autoReload
From b01b3982876973830952afe70d6a2b59b95f091a Mon Sep 17 00:00:00 2001
From: jasquat
Date: Fri, 28 Apr 2023 14:29:56 -0400
Subject: [PATCH 11/18] added tmp link to homepage table w/ burnettk
---
.../routes/process_instances_controller.py | 1 +
.../process_instance_report_service.py | 37 ++++++++++++-------
.../components/ProcessInstanceListTable.tsx | 24 +++++++++++-
.../src/routes/InProgressInstances.tsx | 7 +++-
4 files changed, 53 insertions(+), 16 deletions(-)
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py
index ed02134c..9f849bd8 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py
@@ -231,6 +231,7 @@ def process_instance_list_for_me(
page: int = 1,
per_page: int = 100,
) -> flask.wrappers.Response:
+ ProcessInstanceReportService.add_or_update_filter(body['report_metadata']['filter_by'], {"field_name": 'with_relation_to_me', "field_value": True})
return process_instance_list(
process_model_identifier=process_model_identifier,
page=page,
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py
index dee7ea09..fa342cb5 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py
@@ -330,6 +330,17 @@ class ProcessInstanceReportService:
if value is not None:
yield value
+ @classmethod
+ def add_or_update_filter(cls, filters: list[FilterValue], new_filter: FilterValue) -> None:
+ filter_found = False
+ for filter in filters:
+ if filter["field_name"] == new_filter['field_name']:
+ filter['field_value'] = new_filter['field_value']
+ filter_found = True
+ if filter_found is False:
+ filters.append(new_filter)
+
+
@classmethod
def run_process_instance_report(
cls,
@@ -374,7 +385,8 @@ class ProcessInstanceReportService:
for value in cls.check_filter_value(filters, "initiated_by_me"):
if value is True:
- process_instance_query = process_instance_query.filter_by(process_initiator=user)
+ # process_instance_query = process_instance_query.filter_by(process_initiator=user)
+ cls.add_or_update_filter(filters, {'field_name': 'process_initiator_username', 'field_value': user.username})
for value in cls.check_filter_value(filters, "has_terminal_status"):
if value is True:
@@ -385,11 +397,12 @@ class ProcessInstanceReportService:
process_instance_query = process_instance_query.filter(
ProcessInstanceModel.status.not_in(ProcessInstanceModel.terminal_statuses()) # type: ignore
)
- for value in cls.check_filter_value(filters, "has_active_status"):
- if value is True:
- process_instance_query = process_instance_query.filter(
- ProcessInstanceModel.status.in_(ProcessInstanceModel.active_statuses()) # type: ignore
- )
+
+ has_active_status = cls.get_filter_value(filters, "has_active_status")
+ if has_active_status:
+ process_instance_query = process_instance_query.filter(
+ ProcessInstanceModel.status.in_(ProcessInstanceModel.active_statuses()) # type: ignore
+ )
for value in cls.check_filter_value(filters, "process_initiator_username"):
initiator = UserModel.query.filter_by(username=value).first()
@@ -402,8 +415,6 @@ class ProcessInstanceReportService:
with_tasks_assigned_to_my_group = cls.get_filter_value(filters, "with_tasks_assigned_to_my_group")
with_tasks_i_can_complete = cls.get_filter_value(filters, "with_tasks_i_can_complete")
with_relation_to_me = cls.get_filter_value(filters, "with_relation_to_me")
- has_active_status = cls.get_filter_value(filters, "has_active_status")
- user_group_identifier = cls.get_filter_value(filters, "user_group_identifier")
if (
not with_tasks_completed_by_me
and not with_tasks_assigned_to_my_group
@@ -436,6 +447,8 @@ class ProcessInstanceReportService:
),
)
+ # 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 with_tasks_i_can_complete is True:
process_instance_query = process_instance_query.filter(
ProcessInstanceModel.process_initiator_id != user.id
@@ -445,17 +458,15 @@ class ProcessInstanceReportService:
and_(
HumanTaskModel.process_instance_id == ProcessInstanceModel.id,
HumanTaskModel.lane_assignment_id.is_(None), # type: ignore
+ HumanTaskModel.completed.is_(False) # type: ignore
),
).join(
HumanTaskUserModel,
and_(HumanTaskUserModel.human_task_id == HumanTaskModel.id, HumanTaskUserModel.user_id == user.id),
)
- if has_active_status:
- process_instance_query = process_instance_query.filter(
- HumanTaskModel.completed.is_(False) # type: ignore
- )
if with_tasks_assigned_to_my_group is True:
+ user_group_identifier = cls.get_filter_value(filters, "user_group_identifier")
group_model_join_conditions = [GroupModel.id == HumanTaskModel.lane_assignment_id]
if user_group_identifier:
group_model_join_conditions.append(GroupModel.identifier == user_group_identifier)
@@ -528,10 +539,10 @@ class ProcessInstanceReportService:
for value in cls.check_filter_value(filters, "oldest_open_human_task_fields"):
results = cls.add_human_task_fields(results, value.split(","))
+ report_metadata['filter_by'] = filters
response_json = {
"report_metadata": report_metadata,
"results": results,
- "filters": filters,
"pagination": {
"count": len(results),
"total": process_instances.total,
diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
index 4430a500..942d7ed8 100644
--- a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
+++ b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
@@ -1,5 +1,10 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
-import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
+import {
+ Link,
+ useNavigate,
+ useParams,
+ useSearchParams,
+} from 'react-router-dom';
// @ts-ignore
import { Close, AddAlt } from '@carbon/icons-react';
@@ -91,6 +96,7 @@ type OwnProps = {
variant?: string;
canCompleteAllTasks?: boolean;
showActionsColumn?: boolean;
+ showLinkToReport?: boolean;
};
interface dateParameters {
@@ -111,6 +117,7 @@ export default function ProcessInstanceListTable({
variant = 'for-me',
canCompleteAllTasks = false,
showActionsColumn = false,
+ showLinkToReport = false,
}: OwnProps) {
let processInstanceApiSearchPath = '/process-instances/for-me';
if (variant === 'all') {
@@ -200,6 +207,8 @@ export default function ProcessInstanceListTable({
string | null
>(null);
+ const [reportHash, setReportHash] = useState(null);
+
const [
processInitiatorNotFoundErrorText,
setProcessInitiatorNotFoundErrorText,
@@ -280,6 +289,9 @@ export default function ProcessInstanceListTable({
setPagination(result.pagination);
setReportMetadata(result.report_metadata);
+ if (result.report_hash) {
+ setReportHash(result.report_hash);
+ }
}, []);
const setProcessInstancesFromApplyFilter = (result: any) => {
@@ -1550,6 +1562,15 @@ export default function ProcessInstanceListTable({
return null;
};
+ const linkToReport = () => {
+ if (!showLinkToReport) {
+ return null;
+ }
+ return (
+ Hey
+ );
+ };
+
let resultsTable = null;
if (pagination && (!textToShowIfEmpty || pagination.total > 0)) {
// eslint-disable-next-line prefer-const
@@ -1598,6 +1619,7 @@ export default function ProcessInstanceListTable({
<>
{reportColumnForm()}
{processInstanceReportSaveTag()}
+ {linkToReport()}
>
);
@@ -67,9 +67,11 @@ export default function InProgressInstances() {
showReports={false}
textToShowIfEmpty="There are no open instances you started at this time."
paginationClassName="with-large-bottom-margin"
+ showLinkToReport
showActionsColumn
- autoReload
+ autoReload={false}
/>
+ {/*
{groupTableComponents()}
+ */}
>
);
}
From 1f17e58a07a34d7ab9f8e4d7962fce5317aa1c77 Mon Sep 17 00:00:00 2001
From: jasquat
Date: Fri, 28 Apr 2023 15:42:35 -0400
Subject: [PATCH 12/18] only return the compiled report metadata to the
frontend and remove most usages of the internal filters w/ burnettk
---
.../models/process_instance.py | 5 +
.../process_instance_report_service.py | 102 +++++++++---------
.../components/ProcessInstanceListTable.tsx | 5 +
3 files changed, 64 insertions(+), 48 deletions(-)
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py
index 009a7486..7f95bdca 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py
@@ -163,6 +163,11 @@ class ProcessInstanceModel(SpiffworkflowBaseDBModel):
def terminal_statuses(cls) -> list[str]:
return ["complete", "error", "terminated"]
+ @classmethod
+ def non_terminal_statuses(cls) -> list[str]:
+ terminal_status_values = cls.terminal_statuses()
+ return [s for s in ProcessInstanceStatus.list() if s not in terminal_status_values]
+
@classmethod
def active_statuses(cls) -> list[str]:
return ["user_input_required", "waiting"]
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py
index fa342cb5..9df4355d 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py
@@ -1,5 +1,6 @@
"""Process_instance_report_service."""
import re
+import copy
from typing import Any
from typing import Generator
from typing import Optional
@@ -41,8 +42,10 @@ class ProcessInstanceReportService:
@classmethod
def system_metadata_map(cls, metadata_key: str) -> Optional[ReportMetadata]:
- """System_metadata_map."""
# TODO replace with system reports that are loaded on launch (or similar)
+ terminal_status_values = ','.join(ProcessInstanceModel.terminal_statuses())
+ non_terminal_status_values = ','.join(ProcessInstanceModel.non_terminal_statuses())
+ active_status_values = ','.join(ProcessInstanceModel.active_statuses())
default: ReportMetadata = {
"columns": cls.builtin_column_options(),
"filter_by": [],
@@ -61,7 +64,7 @@ class ProcessInstanceReportService:
],
"filter_by": [
{"field_name": "initiated_by_me", "field_value": True},
- {"field_name": "has_terminal_status", "field_value": True},
+ {"field_name": "process_status", "field_value": terminal_status_values},
],
"order_by": ["-start_in_seconds", "-id"],
}
@@ -69,18 +72,14 @@ class ProcessInstanceReportService:
"columns": cls.builtin_column_options(),
"filter_by": [
{"field_name": "with_tasks_completed_by_me", "field_value": True},
- {"field_name": "has_terminal_status", "field_value": True},
+ {"field_name": "process_status", "field_value": terminal_status_values},
],
"order_by": ["-start_in_seconds", "-id"],
}
system_report_completed_instances_with_tasks_completed_by_my_groups: ReportMetadata = {
"columns": cls.builtin_column_options(),
"filter_by": [
- {
- "field_name": "with_tasks_assigned_to_my_group",
- "field_value": True,
- },
- {"field_name": "has_terminal_status", "field_value": True},
+ {"field_name": "process_status", "field_value": terminal_status_values},
],
"order_by": ["-start_in_seconds", "-id"],
}
@@ -99,12 +98,10 @@ class ProcessInstanceReportService:
],
"filter_by": [
{"field_name": "initiated_by_me", "field_value": True},
- {"field_name": "has_terminal_status", "field_value": False},
+ {"field_name": "process_status", "field_value": non_terminal_status_values},
{
- "field_name": "oldest_open_human_task_fields",
- "field_value": (
- "task_id,task_title,task_name,potential_owner_usernames,assigned_user_group_identifier"
- ),
+ "field_name": "with_oldest_open_task",
+ "field_value": True,
},
],
"order_by": ["-start_in_seconds", "-id"],
@@ -123,10 +120,10 @@ class ProcessInstanceReportService:
],
"filter_by": [
{"field_name": "with_tasks_i_can_complete", "field_value": True},
- {"field_name": "has_active_status", "field_value": True},
+ {"field_name": "process_status", "field_value": active_status_values},
{
- "field_name": "oldest_open_human_task_fields",
- "field_value": "task_id,task_title,task_name",
+ "field_name": "with_oldest_open_task",
+ "field_value": True,
},
],
"order_by": ["-start_in_seconds", "-id"],
@@ -144,15 +141,11 @@ class ProcessInstanceReportService:
{"Header": "Last Updated", "accessor": "updated_at_in_seconds"},
],
"filter_by": [
+ {"field_name": "process_status", "field_value": active_status_values},
{
- "field_name": "with_tasks_assigned_to_my_group",
+ "field_name": "with_oldest_open_task",
"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"],
}
@@ -178,6 +171,18 @@ class ProcessInstanceReportService:
return None
return temp_system_metadata_map[metadata_key]
+ @classmethod
+ def compile_report(cls, report_metadata: ReportMetadata, user: UserModel) -> None:
+ compiled_filters: list[FilterValue] = []
+ old_filters = copy.deepcopy(report_metadata['filter_by'])
+ for filter in old_filters:
+ if filter['field_name'] == 'initiated_by_me':
+ compiled_filters.append({'field_name': 'process_initiator_username', 'field_value': user.username})
+ else:
+ compiled_filters.append(filter)
+
+ report_metadata['filter_by'] = compiled_filters
+
@classmethod
def report_with_identifier(
cls,
@@ -206,6 +211,7 @@ class ProcessInstanceReportService:
raise ProcessInstanceReportNotFoundError(
f"Could not find a report with identifier '{report_identifier}' for user '{user.username}'"
)
+ cls.compile_report(report_metadata, user=user)
process_instance_report = ProcessInstanceReportModel(
identifier=report_identifier,
@@ -238,8 +244,9 @@ class ProcessInstanceReportService:
@classmethod
def add_human_task_fields(
- cls, process_instance_dicts: list[dict], oldest_open_human_task_fields: list
+ cls, process_instance_dicts: list[dict]
) -> list[dict]:
+ fields_to_return = ["task_id", "task_title", "task_name", "potential_owner_usernames", "assigned_user_group_identifier"]
for process_instance_dict in process_instance_dicts:
assigned_user = aliased(UserModel)
human_task_query = (
@@ -265,7 +272,7 @@ class ProcessInstanceReportService:
.first()
)
if human_task is not None:
- for field in oldest_open_human_task_fields:
+ for field in fields_to_return:
process_instance_dict[field] = getattr(human_task, field)
return process_instance_dicts
@@ -378,25 +385,18 @@ class ProcessInstanceReportService:
process_instance_query = process_instance_query.filter(ProcessInstanceModel.end_in_seconds >= value)
for value in cls.check_filter_value(filters, "end_to"):
process_instance_query = process_instance_query.filter(ProcessInstanceModel.end_in_seconds <= value)
- for value in cls.check_filter_value(filters, "process_status"):
+
+ process_status = cls.get_filter_value(filters, "process_status")
+ if process_status is not None:
process_instance_query = process_instance_query.filter(
- ProcessInstanceModel.status.in_(value.split(",")) # type: ignore
+ ProcessInstanceModel.status.in_(process_status.split(",")) # type: ignore
)
- for value in cls.check_filter_value(filters, "initiated_by_me"):
- if value is True:
- # process_instance_query = process_instance_query.filter_by(process_initiator=user)
- cls.add_or_update_filter(filters, {'field_name': 'process_initiator_username', 'field_value': user.username})
+ for _value in cls.check_filter_value(filters, "initiated_by_me"):
+ raise Exception("DEPRECATED: initiated_by_me")
for value in cls.check_filter_value(filters, "has_terminal_status"):
- if value is True:
- process_instance_query = process_instance_query.filter(
- ProcessInstanceModel.status.in_(ProcessInstanceModel.terminal_statuses()) # type: ignore
- )
- elif value is False:
- process_instance_query = process_instance_query.filter(
- ProcessInstanceModel.status.not_in(ProcessInstanceModel.terminal_statuses()) # type: ignore
- )
+ raise Exception("DEPRECATED: has_terminal_status")
has_active_status = cls.get_filter_value(filters, "has_active_status")
if has_active_status:
@@ -412,12 +412,15 @@ class ProcessInstanceReportService:
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")
- with_tasks_assigned_to_my_group = cls.get_filter_value(filters, "with_tasks_assigned_to_my_group")
with_tasks_i_can_complete = cls.get_filter_value(filters, "with_tasks_i_can_complete")
+ user_group_identifier = cls.get_filter_value(filters, "user_group_identifier")
+
+ # builtin only - for the for-me paths
with_relation_to_me = cls.get_filter_value(filters, "with_relation_to_me")
+
if (
not with_tasks_completed_by_me
- and not with_tasks_assigned_to_my_group
+ and not user_group_identifier
and not with_tasks_i_can_complete
and with_relation_to_me is True
):
@@ -465,17 +468,18 @@ class ProcessInstanceReportService:
and_(HumanTaskUserModel.human_task_id == HumanTaskModel.id, HumanTaskUserModel.user_id == user.id),
)
- if with_tasks_assigned_to_my_group is True:
- user_group_identifier = cls.get_filter_value(filters, "user_group_identifier")
+ if user_group_identifier is not None:
group_model_join_conditions = [GroupModel.id == HumanTaskModel.lane_assignment_id]
if user_group_identifier:
group_model_join_conditions.append(GroupModel.identifier == user_group_identifier)
process_instance_query = process_instance_query.join(HumanTaskModel)
- if has_active_status:
- process_instance_query = process_instance_query.filter(
- HumanTaskModel.completed.is_(False) # type: ignore
- )
+ if process_status is not None:
+ non_active_statuses = [s for s in process_status.split(',') if s not in ProcessInstanceModel.active_statuses()]
+ if len(non_active_statuses) == 0:
+ process_instance_query = process_instance_query.filter(
+ HumanTaskModel.completed.is_(False) # type: ignore
+ )
process_instance_query = process_instance_query.join(GroupModel, and_(*group_model_join_conditions))
process_instance_query = process_instance_query.join(
@@ -537,8 +541,10 @@ class ProcessInstanceReportService:
)
results = cls.add_metadata_columns_to_process_instance(process_instances.items, report_metadata["columns"])
- for value in cls.check_filter_value(filters, "oldest_open_human_task_fields"):
- results = cls.add_human_task_fields(results, value.split(","))
+ for value in cls.check_filter_value(filters, "with_oldest_open_task"):
+ if value is True:
+ results = cls.add_human_task_fields(results)
+
report_metadata['filter_by'] = filters
response_json = {
"report_metadata": report_metadata,
diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
index 942d7ed8..0e074ef9 100644
--- a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
+++ b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
@@ -1,3 +1,8 @@
+// TODO:
+// add drop down for user_group_identifier and one for the 2 system reports:
+// with_tasks_completed_by_me
+// with_tasks_i_can_complete
+// add checkbox to show with_oldest_open_task
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import {
Link,
From 607abf97c2dbed60851999944e4be9130bda4e43 Mon Sep 17 00:00:00 2001
From: jasquat
Date: Mon, 1 May 2023 12:25:22 -0400
Subject: [PATCH 13/18] added links to filtered tables on homepage and added
advanced options modal to use them w/ burnettk
---
.../components/ProcessInstanceListTable.tsx | 335 ++++++++++++------
spiffworkflow-frontend/src/index.css | 6 +
spiffworkflow-frontend/src/interfaces.ts | 5 +-
.../src/routes/InProgressInstances.tsx | 75 ++--
4 files changed, 281 insertions(+), 140 deletions(-)
diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
index 0e074ef9..0b36b6f1 100644
--- a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
+++ b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
@@ -1,23 +1,19 @@
-// TODO:
-// add drop down for user_group_identifier and one for the 2 system reports:
-// with_tasks_completed_by_me
-// with_tasks_i_can_complete
-// add checkbox to show with_oldest_open_task
-import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
-import {
- Link,
- useNavigate,
- useParams,
- useSearchParams,
-} from 'react-router-dom';
+import React, {
+ useCallback,
+ useEffect,
+ useMemo,
+ useRef,
+ useState,
+} from 'react';
+import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
-// @ts-ignore
-import { Close, AddAlt } from '@carbon/icons-react';
+import { Close, AddAlt, ArrowRight } from '@carbon/icons-react';
import {
Button,
ButtonSet,
DatePicker,
DatePickerInput,
+ Dropdown,
Table,
Grid,
Column,
@@ -32,7 +28,7 @@ import {
ComboBox,
TextInput,
FormLabel,
- // @ts-ignore
+ Checkbox,
} from '@carbon/react';
import { useDebouncedCallback } from 'use-debounce';
import {
@@ -102,6 +98,7 @@ type OwnProps = {
canCompleteAllTasks?: boolean;
showActionsColumn?: boolean;
showLinkToReport?: boolean;
+ headerElement?: React.ReactElement;
};
interface dateParameters {
@@ -123,13 +120,13 @@ export default function ProcessInstanceListTable({
canCompleteAllTasks = false,
showActionsColumn = false,
showLinkToReport = false,
+ headerElement,
}: OwnProps) {
let processInstanceApiSearchPath = '/process-instances/for-me';
if (variant === 'all') {
processInstanceApiSearchPath = '/process-instances';
}
- const params = useParams();
const [searchParams, setSearchParams] = useSearchParams();
const navigate = useNavigate();
const { addError, removeError } = useAPIError();
@@ -147,8 +144,6 @@ export default function ProcessInstanceListTable({
const [reportMetadata, setReportMetadata] = useState();
const [pagination, setPagination] = useState(null);
- const oneHourInSeconds = 3600;
- const oneMonthInSeconds = oneHourInSeconds * 24 * 30;
const [startFromDate, setStartFromDate] = useState('');
const [startToDate, setStartToDate] = useState('');
const [endFromDate, setEndFromDate] = useState('');
@@ -157,12 +152,13 @@ export default function ProcessInstanceListTable({
const [startToTime, setStartToTime] = useState('');
const [endFromTime, setEndFromTime] = useState('');
const [endToTime, setEndToTime] = useState('');
- const [showFilterOptions, setShowFilterOptions] = useState(false);
const [startFromTimeInvalid, setStartFromTimeInvalid] =
useState(false);
const [startToTimeInvalid, setStartToTimeInvalid] = useState(false);
const [endFromTimeInvalid, setEndFromTimeInvalid] = useState(false);
const [endToTimeInvalid, setEndToTimeInvalid] = useState(false);
+
+ const [showFilterOptions, setShowFilterOptions] = useState(false);
const [requiresRefilter, setRequiresRefilter] = useState(false);
const [lastColumnFilter, setLastColumnFilter] = useState('');
@@ -212,6 +208,18 @@ export default function ProcessInstanceListTable({
string | null
>(null);
+ const [showAdvancedOptions, setShowAdvancedOptions] =
+ useState(false);
+ const [withOldestOpenTask, setWithOldestOpenTask] = useState(false);
+ const [systemReport, setSystemReport] = useState(null);
+ const [selectedUserGroup, setSelectedUserGroup] = useState(
+ null
+ );
+ const [userGroups, setUserGroups] = useState([]);
+ const systemReportOptions: string[] = useMemo(() => {
+ return ['with_tasks_i_can_complete', 'with_tasks_completed_by_me'];
+ }, []);
+
const [reportHash, setReportHash] = useState(null);
const [
@@ -274,19 +282,6 @@ export default function ProcessInstanceListTable({
250
);
- const parametersToGetFromSearchParams = useMemo(() => {
- const figureOutProcessInitiator = (processInitiatorSearchText: string) => {
- searchForProcessInitiator(processInitiatorSearchText);
- };
-
- return {
- process_model_identifier: null,
- process_status: null,
- process_initiator_username: figureOutProcessInitiator,
- };
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
-
const setProcessInstancesFromResult = useCallback((result: any) => {
setRequiresRefilter(false);
const processInstancesFromApi = result.results;
@@ -319,19 +314,15 @@ export default function ProcessInstanceListTable({
}
}, []);
- // eslint-disable-next-line sonarjs/cognitive-complexity
- useEffect(() => {
- if (!permissionsLoaded) {
- return undefined;
- }
+ // we apparently cannot use a state set in a useEffect from within that same useEffect
+ // so use a variable instead
+ const processModelSelectionItemsForUseEffect = useRef([]);
- // we apparently cannot use a state set in a useEffect from within that same useEffect
- // so use a variable instead
- let processModelSelectionItemsForUseEffect: ProcessModel[] = [];
-
- function getProcessInstances(
+ const getProcessInstances = useCallback(
+ (
processInstanceReport: ProcessInstanceReport | null = null
- ) {
+ // eslint-disable-next-line sonarjs/cognitive-complexity
+ ) => {
if (listHasBeenFiltered) {
return;
}
@@ -359,6 +350,12 @@ export default function ProcessInstanceListTable({
);
} else if (reportFilter.field_name === 'process_initiator_username') {
setProcessInitiatorSelection(reportFilter.field_value || '');
+ } else if (reportFilter.field_name === 'with_oldest_open_task') {
+ setWithOldestOpenTask(reportFilter.field_value);
+ } else if (reportFilter.field_name === 'user_group_identifier') {
+ setSelectedUserGroup(reportFilter.field_value);
+ } else if (systemReportOptions.includes(reportFilter.field_name)) {
+ setSystemReport(reportFilter.field_name);
} else if (reportFilter.field_name === 'process_model_identifier') {
selectedProcessModelIdentifier =
reportFilter.field_value || undefined;
@@ -380,7 +377,7 @@ export default function ProcessInstanceListTable({
}
}
);
- processModelSelectionItemsForUseEffect.forEach(
+ processModelSelectionItemsForUseEffect.current.forEach(
(processModel: ProcessModel) => {
if (processModel.id === selectedProcessModelIdentifier) {
setProcessModelSelection(processModel);
@@ -412,6 +409,13 @@ export default function ProcessInstanceListTable({
});
}
+ if (filtersEnabled) {
+ HttpService.makeCallToBackend({
+ path: `/user-groups/for-current-user`,
+ successCallback: setUserGroups,
+ });
+ }
+
HttpService.makeCallToBackend({
path: `${processInstanceApiSearchPath}?${queryParamString}`,
successCallback: setProcessInstancesFromResult,
@@ -422,7 +426,28 @@ export default function ProcessInstanceListTable({
report_metadata: reportMetadataBodyToUse,
},
});
+ },
+ [
+ additionalReportFilters,
+ dateParametersToAlwaysFilterBy,
+ filtersEnabled,
+ listHasBeenFiltered,
+ paginationQueryParamPrefix,
+ perPageOptions,
+ processInstanceApiSearchPath,
+ processModelFullIdentifier,
+ searchParams,
+ setProcessInstancesFromResult,
+ stopRefreshing,
+ systemReportOptions,
+ ]
+ );
+
+ useEffect(() => {
+ if (!permissionsLoaded) {
+ return undefined;
}
+
function getReportMetadataWithReportHash() {
if (listHasBeenFiltered) {
return;
@@ -453,7 +478,7 @@ export default function ProcessInstanceListTable({
Object.assign(item, { label });
return item;
});
- processModelSelectionItemsForUseEffect = selectionArray;
+ processModelSelectionItemsForUseEffect.current = selectionArray;
setProcessModelAvailableItems(selectionArray);
const processStatusAllOptionsArray = PROCESS_STATUSES.map(
@@ -489,23 +514,12 @@ export default function ProcessInstanceListTable({
return undefined;
}, [
autoReload,
- searchParams,
- params,
- oneMonthInSeconds,
- oneHourInSeconds,
- dateParametersToAlwaysFilterBy,
- parametersToGetFromSearchParams,
filtersEnabled,
- paginationQueryParamPrefix,
- processModelFullIdentifier,
- perPageOptions,
- reportIdentifier,
- additionalReportFilters,
- processInstanceApiSearchPath,
- permissionsLoaded,
+ getProcessInstances,
listHasBeenFiltered,
- setProcessInstancesFromResult,
- stopRefreshing,
+ permissionsLoaded,
+ reportIdentifier,
+ searchParams,
]);
const processInstanceReportSaveTag = () => {
@@ -620,7 +634,7 @@ export default function ProcessInstanceListTable({
const insertOrUpdateFieldInReportMetadata = (
reportMetadataToUse: ReportMetadata,
fieldName: string,
- fieldValue: string
+ fieldValue: any
) => {
removeFieldFromReportMetadata(reportMetadataToUse, fieldName);
if (fieldValue) {
@@ -644,7 +658,7 @@ export default function ProcessInstanceListTable({
return null;
}
- let newReportMetadata = null;
+ let newReportMetadata: ReportMetadata | null = null;
if (reportMetadata) {
newReportMetadata = { ...reportMetadata };
}
@@ -677,41 +691,44 @@ export default function ProcessInstanceListTable({
endToSeconds
);
- if (processStatusSelection.length > 0) {
- insertOrUpdateFieldInReportMetadata(
- newReportMetadata,
- 'process_status',
- processStatusSelection.join(',')
- );
- } else {
- removeFieldFromReportMetadata(newReportMetadata, 'process_status');
- }
+ insertOrUpdateFieldInReportMetadata(
+ newReportMetadata,
+ 'process_status',
+ processStatusSelection.length > 0
+ ? processStatusSelection.join(',')
+ : null
+ );
+ insertOrUpdateFieldInReportMetadata(
+ newReportMetadata,
+ 'process_model_identifier',
+ processModelSelection ? processModelSelection.id : null
+ );
+ insertOrUpdateFieldInReportMetadata(
+ newReportMetadata,
+ 'process_initiator_username',
+ processInitiatorSelection
+ );
- if (processModelSelection) {
- insertOrUpdateFieldInReportMetadata(
- newReportMetadata,
- 'process_model_identifier',
- processModelSelection.id
- );
- } else {
- removeFieldFromReportMetadata(
- newReportMetadata,
- 'process_model_identifier'
- );
- }
+ insertOrUpdateFieldInReportMetadata(
+ newReportMetadata,
+ 'with_oldest_open_task',
+ withOldestOpenTask
+ );
+ insertOrUpdateFieldInReportMetadata(
+ newReportMetadata,
+ 'user_group_identifier',
+ selectedUserGroup
+ );
+ systemReportOptions.forEach((systemReportOption: string) => {
+ if (newReportMetadata) {
+ insertOrUpdateFieldInReportMetadata(
+ newReportMetadata,
+ systemReportOption,
+ systemReport === systemReportOption
+ );
+ }
+ });
- if (processInitiatorSelection) {
- insertOrUpdateFieldInReportMetadata(
- newReportMetadata,
- 'process_initiator_username',
- processInitiatorSelection
- );
- } else {
- removeFieldFromReportMetadata(
- newReportMetadata,
- 'process_initiator_username'
- );
- }
return newReportMetadata;
};
@@ -843,14 +860,19 @@ export default function ProcessInstanceListTable({
clearFilters();
const selectedReport = selection.selectedItem;
setProcessInstanceReportSelection(selectedReport);
+ removeError();
+ setProcessInstanceReportJustSaved(mode || null);
+ setListHasBeenFiltered(false);
let queryParamString = '';
if (selectedReport) {
queryParamString = `?report_id=${selectedReport.id}`;
- }
- removeError();
- setProcessInstanceReportJustSaved(mode || null);
+ HttpService.makeCallToBackend({
+ path: `/process-instances/report-metadata${queryParamString}`,
+ successCallback: getProcessInstances,
+ });
+ }
navigate(`${processInstanceListPathPrefix}${queryParamString}`);
};
@@ -1181,6 +1203,75 @@ export default function ProcessInstanceListTable({
return null;
};
+ const handleAdvancedOptionsClose = () => {
+ setShowAdvancedOptions(false);
+ };
+
+ const advancedOptionsModal = () => {
+ if (!showAdvancedOptions) {
+ return null;
+ }
+ const formElements = (
+ <>
+
+
+ item}
+ selectedItem={systemReport}
+ onChange={(value: any) => {
+ setSystemReport(value.selectedItem);
+ setRequiresRefilter(true);
+ }}
+ />
+
+
+ item}
+ selectedItem={selectedUserGroup}
+ onChange={(value: any) => {
+ setSelectedUserGroup(value.selectedItem);
+ setRequiresRefilter(true);
+ }}
+ />
+
+
+
+
+
+ {
+ setWithOldestOpenTask(value.target.checked);
+ setRequiresRefilter(true);
+ }}
+ />
+
+
+
+ >
+ );
+ return (
+
+ {formElements}
+
+ );
+ };
+
const filterOptions = () => {
if (!showFilterOptions) {
return null;
@@ -1366,10 +1457,20 @@ export default function ProcessInstanceListTable({
-
+
{saveAsReportComponent()}
{deleteReportComponent()}
+
+
+
>
);
@@ -1567,12 +1668,39 @@ export default function ProcessInstanceListTable({
return null;
};
- const linkToReport = () => {
- if (!showLinkToReport) {
+ const tableTitleLine = () => {
+ if (!showLinkToReport && !headerElement) {
return null;
}
+ let filterButtonLink = null;
+ if (showLinkToReport) {
+ filterButtonLink = (
+
+
+ );
+ }
return (
- Hey
+
+
+ {headerElement}
+
+ {filterButtonLink}
+
);
};
@@ -1623,8 +1751,9 @@ export default function ProcessInstanceListTable({
return (
<>
{reportColumnForm()}
+ {advancedOptionsModal()}
{processInstanceReportSaveTag()}
- {linkToReport()}
+ {tableTitleLine()}
{
const titleText = `This is a list of instances with tasks that are waiting for the ${userGroup} group.`;
+ const headerElement = (
+
+ Waiting for {userGroup}
+
+ );
return (
- <>
-
- Waiting for {userGroup}
-
-
- >
+
);
});
};
const startedByMeTitleText =
'This is a list of open instances that you started.';
+ const startedByMeHeaderElement = (
+
+ Started by me
+
+ );
+
const waitingForMeTitleText =
'This is a list of instances that have tasks that you can complete.';
+ const waitingForMeHeaderElement = (
+
+ Waiting for me
+
+ );
+
return (
<>
-
- Started by me
-
- {/*
-
- Waiting for me
-
{groupTableComponents()}
- */}
>
);
}
From c84aa44acfd9858058bd141d12f8d15cbcc45383 Mon Sep 17 00:00:00 2001
From: jasquat
Date: Mon, 1 May 2023 12:29:48 -0400
Subject: [PATCH 14/18] lint fixes w/ burnettk
---
.../codemod/update_file_to_remove_function.py | 8 ++--
.../bin/import_tickets_for_command_line.py | 2 +
.../routes/process_instances_controller.py | 4 +-
.../process_instance_report_service.py | 47 +++++++++----------
.../components/ProcessInstanceListTable.tsx | 2 +-
5 files changed, 34 insertions(+), 29 deletions(-)
diff --git a/spiffworkflow-backend/bin/codemod/update_file_to_remove_function.py b/spiffworkflow-backend/bin/codemod/update_file_to_remove_function.py
index 82d571fa..47d55c57 100644
--- a/spiffworkflow-backend/bin/codemod/update_file_to_remove_function.py
+++ b/spiffworkflow-backend/bin/codemod/update_file_to_remove_function.py
@@ -5,15 +5,17 @@ from bowler.types import Leaf
# actually found unused stuff, and I wanted to remove it.
# See also https://github.com/craigds/decrapify
-def remove_function(filename: str, function_name: str) -> None:
+def remove_function(filename: str, function_name: str) -> None:
def remove_statement(node, capture, filename):
node.remove()
- bowler_query = (Query(filename)
+ bowler_query = (
+ Query(filename)
.select_function(function_name)
.modify(remove_statement)
- .execute(write=True, silent=True, interactive=False))
+ .execute(write=True, silent=True, interactive=False)
+ )
if len(bowler_query.exceptions) > 0:
print(f"Failed to remove function {function_name} from {filename}.")
diff --git a/spiffworkflow-backend/bin/import_tickets_for_command_line.py b/spiffworkflow-backend/bin/import_tickets_for_command_line.py
index 1392c111..0780f209 100644
--- a/spiffworkflow-backend/bin/import_tickets_for_command_line.py
+++ b/spiffworkflow-backend/bin/import_tickets_for_command_line.py
@@ -11,6 +11,8 @@ from spiffworkflow_backend.services.process_instance_processor import (
from spiffworkflow_backend.services.process_instance_service import (
ProcessInstanceService,
)
+
+
def main():
"""Main."""
app = get_hacked_up_app_for_script()
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py
index 9f849bd8..45847f58 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py
@@ -231,7 +231,9 @@ def process_instance_list_for_me(
page: int = 1,
per_page: int = 100,
) -> flask.wrappers.Response:
- ProcessInstanceReportService.add_or_update_filter(body['report_metadata']['filter_by'], {"field_name": 'with_relation_to_me', "field_value": True})
+ ProcessInstanceReportService.add_or_update_filter(
+ body["report_metadata"]["filter_by"], {"field_name": "with_relation_to_me", "field_value": True}
+ )
return process_instance_list(
process_model_identifier=process_model_identifier,
page=page,
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py
index 9df4355d..9fd976ff 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py
@@ -1,6 +1,6 @@
"""Process_instance_report_service."""
-import re
import copy
+import re
from typing import Any
from typing import Generator
from typing import Optional
@@ -43,9 +43,9 @@ class ProcessInstanceReportService:
@classmethod
def system_metadata_map(cls, metadata_key: str) -> Optional[ReportMetadata]:
# TODO replace with system reports that are loaded on launch (or similar)
- terminal_status_values = ','.join(ProcessInstanceModel.terminal_statuses())
- non_terminal_status_values = ','.join(ProcessInstanceModel.non_terminal_statuses())
- active_status_values = ','.join(ProcessInstanceModel.active_statuses())
+ terminal_status_values = ",".join(ProcessInstanceModel.terminal_statuses())
+ non_terminal_status_values = ",".join(ProcessInstanceModel.non_terminal_statuses())
+ active_status_values = ",".join(ProcessInstanceModel.active_statuses())
default: ReportMetadata = {
"columns": cls.builtin_column_options(),
"filter_by": [],
@@ -174,14 +174,14 @@ class ProcessInstanceReportService:
@classmethod
def compile_report(cls, report_metadata: ReportMetadata, user: UserModel) -> None:
compiled_filters: list[FilterValue] = []
- old_filters = copy.deepcopy(report_metadata['filter_by'])
+ old_filters = copy.deepcopy(report_metadata["filter_by"])
for filter in old_filters:
- if filter['field_name'] == 'initiated_by_me':
- compiled_filters.append({'field_name': 'process_initiator_username', 'field_value': user.username})
+ if filter["field_name"] == "initiated_by_me":
+ compiled_filters.append({"field_name": "process_initiator_username", "field_value": user.username})
else:
compiled_filters.append(filter)
- report_metadata['filter_by'] = compiled_filters
+ report_metadata["filter_by"] = compiled_filters
@classmethod
def report_with_identifier(
@@ -243,10 +243,14 @@ class ProcessInstanceReportService:
return results
@classmethod
- def add_human_task_fields(
- cls, process_instance_dicts: list[dict]
- ) -> list[dict]:
- fields_to_return = ["task_id", "task_title", "task_name", "potential_owner_usernames", "assigned_user_group_identifier"]
+ def add_human_task_fields(cls, process_instance_dicts: list[dict]) -> list[dict]:
+ fields_to_return = [
+ "task_id",
+ "task_title",
+ "task_name",
+ "potential_owner_usernames",
+ "assigned_user_group_identifier",
+ ]
for process_instance_dict in process_instance_dicts:
assigned_user = aliased(UserModel)
human_task_query = (
@@ -341,13 +345,12 @@ class ProcessInstanceReportService:
def add_or_update_filter(cls, filters: list[FilterValue], new_filter: FilterValue) -> None:
filter_found = False
for filter in filters:
- if filter["field_name"] == new_filter['field_name']:
- filter['field_value'] = new_filter['field_value']
+ if filter["field_name"] == new_filter["field_name"]:
+ filter["field_value"] = new_filter["field_value"]
filter_found = True
if filter_found is False:
filters.append(new_filter)
-
@classmethod
def run_process_instance_report(
cls,
@@ -392,12 +395,6 @@ class ProcessInstanceReportService:
ProcessInstanceModel.status.in_(process_status.split(",")) # type: ignore
)
- for _value in cls.check_filter_value(filters, "initiated_by_me"):
- raise Exception("DEPRECATED: initiated_by_me")
-
- for value in cls.check_filter_value(filters, "has_terminal_status"):
- raise Exception("DEPRECATED: has_terminal_status")
-
has_active_status = cls.get_filter_value(filters, "has_active_status")
if has_active_status:
process_instance_query = process_instance_query.filter(
@@ -461,7 +458,7 @@ class ProcessInstanceReportService:
and_(
HumanTaskModel.process_instance_id == ProcessInstanceModel.id,
HumanTaskModel.lane_assignment_id.is_(None), # type: ignore
- HumanTaskModel.completed.is_(False) # type: ignore
+ HumanTaskModel.completed.is_(False), # type: ignore
),
).join(
HumanTaskUserModel,
@@ -475,7 +472,9 @@ class ProcessInstanceReportService:
process_instance_query = process_instance_query.join(HumanTaskModel)
if process_status is not None:
- non_active_statuses = [s for s in process_status.split(',') if s not in ProcessInstanceModel.active_statuses()]
+ non_active_statuses = [
+ s for s in process_status.split(",") if s not in ProcessInstanceModel.active_statuses()
+ ]
if len(non_active_statuses) == 0:
process_instance_query = process_instance_query.filter(
HumanTaskModel.completed.is_(False) # type: ignore
@@ -545,7 +544,7 @@ class ProcessInstanceReportService:
if value is True:
results = cls.add_human_task_fields(results)
- report_metadata['filter_by'] = filters
+ report_metadata["filter_by"] = filters
response_json = {
"report_metadata": report_metadata,
"results": results,
diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
index 0b36b6f1..6b17d6a9 100644
--- a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
+++ b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
@@ -5,7 +5,7 @@ import React, {
useRef,
useState,
} from 'react';
-import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
+import { useNavigate, useSearchParams } from 'react-router-dom';
import { Close, AddAlt, ArrowRight } from '@carbon/icons-react';
import {
From 07b4616d45d1812d336343b905fff2f9dce33834 Mon Sep 17 00:00:00 2001
From: jasquat
Date: Mon, 1 May 2023 12:51:58 -0400
Subject: [PATCH 15/18] fixed tests w/ burnettk
---
.../migrations/versions/68adb1d504e1_.py | 2 +-
.../services/process_instance_report_service.py | 12 ++++--------
.../unit/test_process_instance_report_service.py | 5 ++++-
.../src/routes/CompletedInstances.tsx | 2 +-
.../src/routes/InProgressInstances.tsx | 2 +-
5 files changed, 11 insertions(+), 12 deletions(-)
diff --git a/spiffworkflow-backend/migrations/versions/68adb1d504e1_.py b/spiffworkflow-backend/migrations/versions/68adb1d504e1_.py
index dbeca2ba..7cf74222 100644
--- a/spiffworkflow-backend/migrations/versions/68adb1d504e1_.py
+++ b/spiffworkflow-backend/migrations/versions/68adb1d504e1_.py
@@ -11,7 +11,7 @@ import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '68adb1d504e1'
-down_revision = '0c7428378d6e'
+down_revision = '664bb2f00694'
branch_labels = None
depends_on = None
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py
index fd254601..7f67051e 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py
@@ -76,7 +76,7 @@ class ProcessInstanceReportService:
],
"order_by": ["-start_in_seconds", "-id"],
}
- system_report_completed_instances_with_tasks_completed_by_my_groups: ReportMetadata = {
+ system_report_completed_instances: ReportMetadata = {
"columns": cls.builtin_column_options(),
"filter_by": [
{"field_name": "process_status", "field_value": terminal_status_values},
@@ -128,7 +128,7 @@ class ProcessInstanceReportService:
],
"order_by": ["-start_in_seconds", "-id"],
}
- system_report_in_progress_instances_with_tasks_for_my_group: ReportMetadata = {
+ system_report_in_progress_instances_with_tasks: ReportMetadata = {
"columns": [
{"Header": "id", "accessor": "id"},
{
@@ -156,16 +156,12 @@ class ProcessInstanceReportService:
"system_report_completed_instances_with_tasks_completed_by_me": (
system_report_completed_instances_with_tasks_completed_by_me
),
- "system_report_completed_instances_with_tasks_completed_by_my_groups": (
- system_report_completed_instances_with_tasks_completed_by_my_groups
- ),
+ "system_report_completed_instances": system_report_completed_instances,
"system_report_in_progress_instances_initiated_by_me": system_report_in_progress_instances_initiated_by_me,
"system_report_in_progress_instances_with_tasks_for_me": (
system_report_in_progress_instances_with_tasks_for_me
),
- "system_report_in_progress_instances_with_tasks_for_my_group": (
- system_report_in_progress_instances_with_tasks_for_my_group
- ),
+ "system_report_in_progress_instances_with_tasks": system_report_in_progress_instances_with_tasks,
}
if metadata_key not in temp_system_metadata_map:
return None
diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_report_service.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_report_service.py
index e7e2ad61..95eab41e 100644
--- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_report_service.py
+++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_report_service.py
@@ -197,7 +197,10 @@ class TestProcessInstanceReportService(BaseTest):
process_instance_report = ProcessInstanceReportService.report_with_identifier(
user=user_one,
- report_identifier="system_report_completed_instances_with_tasks_completed_by_my_groups",
+ report_identifier="system_report_completed_instances",
+ )
+ process_instance_report.report_metadata["filter_by"].append(
+ {"field_name": "user_group_identifier", "field_value": user_one.groups[0].identifier}
)
response_json = ProcessInstanceReportService.run_process_instance_report(
report_metadata=process_instance_report.report_metadata,
diff --git a/spiffworkflow-frontend/src/routes/CompletedInstances.tsx b/spiffworkflow-frontend/src/routes/CompletedInstances.tsx
index 581dbe04..6b8e0883 100644
--- a/spiffworkflow-frontend/src/routes/CompletedInstances.tsx
+++ b/spiffworkflow-frontend/src/routes/CompletedInstances.tsx
@@ -29,7 +29,7 @@ export default function CompletedInstances() {
paginationQueryParamPrefix="group_completed_instances"
paginationClassName="with-large-bottom-margin"
perPageOptions={[2, 5, 25]}
- reportIdentifier="system_report_completed_instances_with_tasks_completed_by_my_groups"
+ reportIdentifier="system_report_completed_instances"
showReports={false}
textToShowIfEmpty="This group has no completed instances at this time."
additionalReportFilters={[
diff --git a/spiffworkflow-frontend/src/routes/InProgressInstances.tsx b/spiffworkflow-frontend/src/routes/InProgressInstances.tsx
index cc66a51b..eb68a05b 100644
--- a/spiffworkflow-frontend/src/routes/InProgressInstances.tsx
+++ b/spiffworkflow-frontend/src/routes/InProgressInstances.tsx
@@ -35,7 +35,7 @@ export default function InProgressInstances() {
).replace('-', '_')}`}
paginationClassName="with-large-bottom-margin"
perPageOptions={[2, 5, 25]}
- reportIdentifier="system_report_in_progress_instances_with_tasks_for_my_group"
+ reportIdentifier="system_report_in_progress_instances_with_tasks"
showReports={false}
textToShowIfEmpty="This group has no instances waiting on it at this time."
additionalReportFilters={[
From 7e3ad411e4b1564ac641bdcc763d7df0e3afd10b Mon Sep 17 00:00:00 2001
From: jasquat
Date: Mon, 1 May 2023 14:00:09 -0400
Subject: [PATCH 16/18] load correct NotRequired if python < 3.11
---
.../src/spiffworkflow_backend/api.yml | 14 --------
.../models/process_instance_report.py | 8 +++--
.../routes/process_instances_controller.py | 35 -------------------
3 files changed, 6 insertions(+), 51 deletions(-)
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml
index cab7621d..0ea5291c 100755
--- a/spiffworkflow-backend/src/spiffworkflow_backend/api.yml
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/api.yml
@@ -1191,20 +1191,6 @@ paths:
description: The page number to return. Defaults to page 1.
schema:
type: integer
- # get:
- # operationId: spiffworkflow_backend.routes.process_instances_controller.process_instance_report_show
- # summary: Returns a report of process instances for a given process model
- # tags:
- # - Process Instances
- # responses:
- # "200":
- # description: Workflow.
- # content:
- # application/json:
- # schema:
- # type: array
- # items:
- # $ref: "#/components/schemas/Workflow"
put:
operationId: spiffworkflow_backend.routes.process_instances_controller.process_instance_report_update
summary: Updates a process instance report
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance_report.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance_report.py
index e5b8c5c6..79ff0adf 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance_report.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance_report.py
@@ -1,13 +1,17 @@
"""Process_instance."""
from __future__ import annotations
+import sys
import typing
from dataclasses import dataclass
from typing import Any
from typing import cast
-from typing import NotRequired
+
+if sys.version_info < (3, 11):
+ from typing_extensions import TypedDict, NotRequired
+else:
+ from typing import TypedDict, NotRequired
from typing import Optional
-from typing import TypedDict
from sqlalchemy import ForeignKey
from sqlalchemy.orm import relationship
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py
index 45847f58..dab93e8a 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py
@@ -427,41 +427,6 @@ def process_instance_report_delete(
return Response(json.dumps({"ok": True}), status=200, mimetype="application/json")
-# def process_instance_report_show(
-# report_id: int,
-# page: int = 1,
-# per_page: int = 100,
-# ) -> flask.wrappers.Response:
-# """Process_instance_report_show."""
-# process_instances = ProcessInstanceModel.query.order_by(
-# ProcessInstanceModel.start_in_seconds.desc(), ProcessInstanceModel.id.desc() # type: ignore
-# ).paginate(page=page, per_page=per_page, error_out=False)
-#
-# process_instance_report = ProcessInstanceReportModel.query.filter_by(
-# id=report_id,
-# created_by_id=g.user.id,
-# ).first()
-# if process_instance_report is None:
-# raise ApiError(
-# error_code="unknown_process_instance_report",
-# message="Unknown process instance report",
-# status_code=404,
-# )
-#
-# substitution_variables = request.args.to_dict()
-# result_dict = process_instance_report.generate_report(process_instances.items, substitution_variables)
-#
-# # update this if we go back to a database query instead of filtering in memory
-# result_dict["pagination"] = {
-# "count": len(result_dict["results"]),
-# "total": len(result_dict["results"]),
-# "pages": 1,
-# }
-#
-# return Response(json.dumps(result_dict), status=200, mimetype="application/json")
-#
-
-
def process_instance_task_list_without_task_data_for_me(
modified_process_model_identifier: str,
process_instance_id: int,
From ca8af1bce05dc122bfb45209689871d040fb66af Mon Sep 17 00:00:00 2001
From: jasquat
Date: Mon, 1 May 2023 14:54:11 -0400
Subject: [PATCH 17/18] fixed cypress tests w/ burnettk
---
.../cypress/e2e/process_instances.cy.js | 2 +-
.../cypress/e2e/process_models.cy.js | 28 +++++-----
.../cypress/e2e/tasks.cy.js | 55 +++++++------------
.../components/ProcessInstanceListTable.tsx | 2 +-
.../src/routes/ProcessInstanceLogList.tsx | 2 +-
.../src/routes/ReactFormEditor.tsx | 1 +
6 files changed, 37 insertions(+), 53 deletions(-)
diff --git a/spiffworkflow-frontend/cypress/e2e/process_instances.cy.js b/spiffworkflow-frontend/cypress/e2e/process_instances.cy.js
index db6f53d3..406a55c5 100644
--- a/spiffworkflow-frontend/cypress/e2e/process_instances.cy.js
+++ b/spiffworkflow-frontend/cypress/e2e/process_instances.cy.js
@@ -161,7 +161,7 @@ describe('process-instances', () => {
cy.getBySel('process-instance-list-link').click();
cy.getBySel('process-instance-show-link-id').first().click();
cy.getBySel('process-instance-log-list-link').click();
- cy.getBySel('process-instance-log-detailed').click();
+ cy.getBySel('process-instance-log-events').click();
cy.contains('process_model_one');
cy.contains('task_completed');
cy.basicPaginationTest();
diff --git a/spiffworkflow-frontend/cypress/e2e/process_models.cy.js b/spiffworkflow-frontend/cypress/e2e/process_models.cy.js
index 9e7bd55d..06986080 100644
--- a/spiffworkflow-frontend/cypress/e2e/process_models.cy.js
+++ b/spiffworkflow-frontend/cypress/e2e/process_models.cy.js
@@ -1,9 +1,6 @@
-import { slowCypressDown } from 'cypress-slow-down';
import { modifyProcessIdentifierForPathParam } from '../../src/helpers';
import { miscDisplayName } from '../support/helpers';
-// slowCypressDown(500);
-
describe('process-models', () => {
beforeEach(() => {
cy.login();
@@ -15,7 +12,7 @@ describe('process-models', () => {
const groupDisplayName = 'Acceptance Tests Group One';
const deleteProcessModelButtonId = 'delete-process-model-button';
const saveChangesButtonText = 'Save Changes';
- const fileNameInputSelector = 'input[name=file_name]';
+ const fileNameInputSelector = 'input#process_model_file_name';
it('can perform crud operations', () => {
const uuid = () => Cypress._.random(0, 1e6);
@@ -36,7 +33,8 @@ describe('process-models', () => {
cy.contains(`Process Model: ${modelDisplayName}`);
cy.getBySel('edit-process-model-button').click();
- cy.get('input[name=display_name]').clear().type(newModelDisplayName);
+ cy.get('input[name=display_name]').clear();
+ cy.get('input[name=display_name]').type(newModelDisplayName);
cy.contains('Submit').click();
cy.contains(`Process Model: ${newModelDisplayName}`);
@@ -50,7 +48,6 @@ describe('process-models', () => {
const uuid = () => Cypress._.random(0, 1e6);
const id = uuid();
const directParentGroupId = 'acceptance-tests-group-one';
- const directParentGroupName = 'Acceptance Tests Group One';
const groupId = `misc/${directParentGroupId}`;
const modelDisplayName = `Test Model 2 ${id}`;
const modelId = `test-model-2-${id}`;
@@ -58,7 +55,7 @@ describe('process-models', () => {
const bpmnFileName = `bpmn_test_file_${id}`;
const dmnFileName = `dmn_test_file_${id}`;
const jsonFileName = `json_test_file_${id}`;
- const decision_acceptance_test_id = `decision_acceptance_test_${id}`;
+ const decisionAcceptanceTestId = `decision_acceptance_test_${id}`;
cy.contains(miscDisplayName).click();
cy.contains(groupDisplayName).click();
@@ -79,9 +76,10 @@ describe('process-models', () => {
// add new bpmn file
cy.contains('New BPMN File').click();
cy.contains(/^Process Model File$/);
- cy.get('g[data-element-id=StartEvent_1]').click().should('exist');
+ cy.get('g[data-element-id=StartEvent_1]').click();
cy.contains('General').click();
- cy.get('#bio-properties-panel-name').clear().type('Start Event Name');
+ cy.get('#bio-properties-panel-name').clear();
+ cy.get('#bio-properties-panel-name').type('Start Event Name');
cy.wait(500);
cy.contains('Save').click();
cy.contains('Start Event Name');
@@ -96,11 +94,10 @@ describe('process-models', () => {
// add new dmn file
cy.contains('New DMN File').click();
cy.contains(/^Process Model File$/);
- cy.get('g[data-element-id=decision_1]').click().should('exist');
+ cy.get('g[data-element-id=decision_1]').click();
cy.contains('General').click();
- cy.get('#bio-properties-panel-id')
- .clear()
- .type(decision_acceptance_test_id);
+ cy.get('#bio-properties-panel-id').clear();
+ cy.get('#bio-properties-panel-id').type(decisionAcceptanceTestId);
cy.contains('General').click();
cy.contains('Save').click();
cy.get(fileNameInputSelector).type(dmnFileName);
@@ -135,7 +132,7 @@ describe('process-models', () => {
cy.get('.tile-process-group-content-container').should('exist');
});
- it.only('can upload and run a bpmn file', () => {
+ it('can upload and run a bpmn file', () => {
const uuid = () => Cypress._.random(0, 1e6);
const id = uuid();
const directParentGroupId = 'acceptance-tests-group-one';
@@ -192,7 +189,8 @@ describe('process-models', () => {
});
it('can allow searching for model', () => {
- cy.getBySel('process-model-selection').click().type('model-3');
+ cy.getBySel('process-model-selection').click();
+ cy.getBySel('process-model-selection').type('model-3');
cy.contains('acceptance-tests-group-one/acceptance-tests-model-3').click();
cy.contains('Acceptance Tests Model 3');
});
diff --git a/spiffworkflow-frontend/cypress/e2e/tasks.cy.js b/spiffworkflow-frontend/cypress/e2e/tasks.cy.js
index 50d24899..fde67873 100644
--- a/spiffworkflow-frontend/cypress/e2e/tasks.cy.js
+++ b/spiffworkflow-frontend/cypress/e2e/tasks.cy.js
@@ -1,13 +1,14 @@
const submitInputIntoFormField = (taskName, fieldKey, fieldValue) => {
cy.contains(`Task: ${taskName}`, { timeout: 10000 });
- cy.get(fieldKey).clear().type(fieldValue);
+ cy.get(fieldKey).clear();
+ cy.get(fieldKey).type(fieldValue);
cy.contains('Submit').click();
};
-const checkFormFieldIsReadOnly = (formName, fieldKey) => {
- cy.contains(`Task: ${formName}`);
- cy.get(fieldKey).invoke('attr', 'disabled').should('exist');
-};
+// const checkFormFieldIsReadOnly = (formName, fieldKey) => {
+// cy.contains(`Task: ${formName}`);
+// cy.get(fieldKey).invoke('attr', 'disabled').should('exist');
+// };
const checkTaskHasClass = (taskName, className) => {
cy.get(`g[data-element-id=${taskName}]`).should('have.class', className);
@@ -38,38 +39,26 @@ describe('tasks', () => {
cy.navigateToProcessModel(groupDisplayName, modelDisplayName);
cy.runPrimaryBpmnFile(true);
- submitInputIntoFormField(
- 'get_user_generated_number_one',
- '#root_user_generated_number_1',
- 2
- );
- submitInputIntoFormField(
- 'get_user_generated_number_two',
- '#root_user_generated_number_2',
- 3
- );
+ submitInputIntoFormField('get_form_num_one', '#root_form_num_1', 2);
+ submitInputIntoFormField('get_form_num_two', '#root_form_num_2', 3);
- cy.contains('Task: get_user_generated_number_three');
+ cy.contains('Task: get_form_num_three');
// TODO: remove this if we decide to completely kill form navigation
// cy.getBySel('form-nav-form2').click();
// checkFormFieldIsReadOnly(
- // 'get_user_generated_number_two',
- // '#root_user_generated_number_2'
+ // 'get_form_num_two',
+ // '#root_form_num_2'
// );
// cy.getBySel('form-nav-form1').click();
// checkFormFieldIsReadOnly(
- // 'get_user_generated_number_one',
- // '#root_user_generated_number_1'
+ // 'get_form_num_one',
+ // '#root_form_num_1'
// );
//
// cy.getBySel('form-nav-form3').click();
- submitInputIntoFormField(
- 'get_user_generated_number_three',
- '#root_user_generated_number_3',
- 4
- );
+ submitInputIntoFormField('get_form_num_three', '#root_form_num_3', 4);
- cy.contains('Task: get_user_generated_number_four');
+ cy.contains('Task: get_form_num_four');
cy.navigateToProcessModel(groupDisplayName, modelDisplayName);
cy.getBySel('process-instance-list-link').click();
cy.assertAtLeastOneItemInPaginatedResults();
@@ -79,10 +68,10 @@ describe('tasks', () => {
cy.contains('Process Instance Id: ');
cy.get(`g[data-element-id=form3]`).click();
- cy.contains('"user_generated_number_1": 2');
- cy.contains('"user_generated_number_2": 3');
- cy.contains('"user_generated_number_3": 4');
- cy.contains('"user_generated_number_4": 5').should('not.exist');
+ cy.contains('"form_num_1": 2');
+ cy.contains('"form_num_2": 3');
+ cy.contains('"form_num_3": 4');
+ cy.contains('"form_num_4": 5').should('not.exist');
checkTaskHasClass('form1', completedTaskClassName);
checkTaskHasClass('form2', completedTaskClassName);
checkTaskHasClass('form3', completedTaskClassName);
@@ -97,11 +86,7 @@ describe('tasks', () => {
// FIXME: this will probably need a better way to link to the proper form that we want
cy.contains('Go').click();
- submitInputIntoFormField(
- 'get_user_generated_number_four',
- '#root_user_generated_number_4',
- 5
- );
+ submitInputIntoFormField('get_form_num_four', '#root_form_num_4', 5);
cy.url().should('include', '/tasks');
cy.navigateToProcessModel(groupDisplayName, modelDisplayName);
diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
index 74d1012c..6f12d0e7 100644
--- a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
+++ b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx
@@ -1692,7 +1692,7 @@ export default function ProcessInstanceListTable({
data-qa="process-instance-list-link"
kind="ghost"
renderIcon={ArrowRight}
- iconDescription="Go to Filterable List"
+ iconDescription="View Filterable List"
hasIconOnly
size="lg"
onClick={() =>
diff --git a/spiffworkflow-frontend/src/routes/ProcessInstanceLogList.tsx b/spiffworkflow-frontend/src/routes/ProcessInstanceLogList.tsx
index efb5bd9f..a38276b9 100644
--- a/spiffworkflow-frontend/src/routes/ProcessInstanceLogList.tsx
+++ b/spiffworkflow-frontend/src/routes/ProcessInstanceLogList.tsx
@@ -494,7 +494,7 @@ export default function ProcessInstanceLogList({ variant }: OwnProps) {
{
resetFilters();
searchParams.set('events', 'false');
diff --git a/spiffworkflow-frontend/src/routes/ReactFormEditor.tsx b/spiffworkflow-frontend/src/routes/ReactFormEditor.tsx
index f9e83f35..e03253da 100644
--- a/spiffworkflow-frontend/src/routes/ReactFormEditor.tsx
+++ b/spiffworkflow-frontend/src/routes/ReactFormEditor.tsx
@@ -174,6 +174,7 @@ export default function ReactFormEditor() {
setNewFileName(e.target.value)}
From 418ec7d9c9fb6b0bbc89bba38563cf0156526628 Mon Sep 17 00:00:00 2001
From: jasquat
Date: Mon, 1 May 2023 15:26:29 -0400
Subject: [PATCH 18/18] updated typeguard and fixed issues w/ burnettk
---
spiffworkflow-backend/conftest.py | 2 +-
spiffworkflow-backend/poetry.lock | 18 +++--
spiffworkflow-backend/pyproject.toml | 2 +-
.../models/process_instance_report.py | 81 -------------------
.../routes/process_instances_controller.py | 2 +-
.../routes/process_models_controller.py | 2 +-
.../process_instance_report_service.py | 74 +++++++++--------
.../integration/test_process_api.py | 43 +++++++---
8 files changed, 87 insertions(+), 137 deletions(-)
diff --git a/spiffworkflow-backend/conftest.py b/spiffworkflow-backend/conftest.py
index df002ff4..45e9fd54 100644
--- a/spiffworkflow-backend/conftest.py
+++ b/spiffworkflow-backend/conftest.py
@@ -23,7 +23,7 @@ from spiffworkflow_backend.services.process_model_service import ProcessModelSer
# We need to call this before importing spiffworkflow_backend
# otherwise typeguard cannot work. hence the noqa: E402
if os.environ.get("RUN_TYPEGUARD") == "true":
- from typeguard.importhook import install_import_hook
+ from typeguard import install_import_hook
install_import_hook(packages="spiffworkflow_backend")
diff --git a/spiffworkflow-backend/poetry.lock b/spiffworkflow-backend/poetry.lock
index 53a5f1f8..9c0a426d 100644
--- a/spiffworkflow-backend/poetry.lock
+++ b/spiffworkflow-backend/poetry.lock
@@ -3502,19 +3502,23 @@ files = [
[[package]]
name = "typeguard"
-version = "2.13.3"
+version = "3.0.2"
description = "Run-time type checker for Python"
category = "dev"
optional = false
-python-versions = ">=3.5.3"
+python-versions = ">=3.7.4"
files = [
- {file = "typeguard-2.13.3-py3-none-any.whl", hash = "sha256:5e3e3be01e887e7eafae5af63d1f36c849aaa94e3a0112097312aabfa16284f1"},
- {file = "typeguard-2.13.3.tar.gz", hash = "sha256:00edaa8da3a133674796cf5ea87d9f4b4c367d77476e185e80251cc13dfbb8c4"},
+ {file = "typeguard-3.0.2-py3-none-any.whl", hash = "sha256:bbe993854385284ab42fd5bd3bee6f6556577ce8b50696d6cb956d704f286c8e"},
+ {file = "typeguard-3.0.2.tar.gz", hash = "sha256:fee5297fdb28f8e9efcb8142b5ee219e02375509cd77ea9d270b5af826358d5a"},
]
+[package.dependencies]
+importlib-metadata = {version = ">=3.6", markers = "python_version < \"3.10\""}
+typing-extensions = {version = ">=4.4.0", markers = "python_version < \"3.11\""}
+
[package.extras]
-doc = ["sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
-test = ["mypy", "pytest", "typing-extensions"]
+doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
+test = ["mypy (>=0.991)", "pytest (>=7)"]
[[package]]
name = "types-click"
@@ -3934,4 +3938,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more
[metadata]
lock-version = "2.0"
python-versions = ">=3.9,<3.12"
-content-hash = "994c36ab39238500b4fd05bc1ccdd2d729dd5f66749ab77b1028371147bdf753"
+content-hash = "53f3340f73de770b4fbebff3fcd396cdf1bc2c082b929ade350f31a9df6c3860"
diff --git a/spiffworkflow-backend/pyproject.toml b/spiffworkflow-backend/pyproject.toml
index d4288f5b..b8ce9115 100644
--- a/spiffworkflow-backend/pyproject.toml
+++ b/spiffworkflow-backend/pyproject.toml
@@ -93,7 +93,7 @@ pytest = "^7.1.2"
coverage = {extras = ["toml"], version = "^6.1"}
safety = "^2.3.1"
mypy = ">=0.961"
-typeguard = "^2"
+typeguard = "^3"
xdoctest = {extras = ["colors"], version = "^1.0.1"}
sphinx = "^5.0.2"
sphinx-autobuild = ">=2021.3.14"
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance_report.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance_report.py
index 79ff0adf..8ad87ea1 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance_report.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance_report.py
@@ -5,13 +5,11 @@ import sys
import typing
from dataclasses import dataclass
from typing import Any
-from typing import cast
if sys.version_info < (3, 11):
from typing_extensions import TypedDict, NotRequired
else:
from typing import TypedDict, NotRequired
-from typing import Optional
from sqlalchemy import ForeignKey
from sqlalchemy.orm import relationship
@@ -19,11 +17,7 @@ from sqlalchemy.orm import relationship
from spiffworkflow_backend.models.db import db
from spiffworkflow_backend.models.db import SpiffworkflowBaseDBModel
from spiffworkflow_backend.models.json_data import JsonDataModel # noqa: F401
-from spiffworkflow_backend.models.process_instance import ProcessInstanceModel
from spiffworkflow_backend.models.user import UserModel
-from spiffworkflow_backend.services.process_instance_processor import (
- ProcessInstanceProcessor,
-)
class FilterValue(TypedDict):
@@ -149,78 +143,3 @@ class ProcessInstanceReportModel(SpiffworkflowBaseDBModel):
if isinstance(value, str) or isinstance(value, int):
field_value = str(field_value).replace("{{" + key + "}}", str(value))
return field_value
-
- # modeled after https://github.com/suyash248/sqlalchemy-json-querybuilder
- # just supports "equals" operator for now.
- # perhaps we will use the database instead of filtering in memory in the future and then we might use this lib directly.
- def passes_filter(self, process_instance_dict: dict, substitution_variables: dict) -> bool:
- """Passes_filter."""
- if "filter_by" in self.report_metadata:
- for filter_by in self.report_metadata["filter_by"]:
- field_name = filter_by["field_name"]
- operator = filter_by["operator"]
- field_value = self.with_substitutions(filter_by["field_value"], substitution_variables)
- if operator == "equals":
- if str(process_instance_dict.get(field_name)) != str(field_value):
- return False
-
- return True
-
- def order_things(self, process_instance_dicts: list) -> list:
- """Order_things."""
- order_by = self.report_metadata["order_by"]
-
- def order_by_function_for_lambda(
- process_instance_dict: dict,
- ) -> list[Reversor | str | None]:
- """Order_by_function_for_lambda."""
- comparison_values: list[Reversor | str | None] = []
- for order_by_item in order_by:
- if order_by_item.startswith("-"):
- # remove leading - from order_by_item
- order_by_item = order_by_item[1:]
- sort_value = process_instance_dict.get(order_by_item)
- comparison_values.append(Reversor(sort_value))
- else:
- sort_value = cast(Optional[str], process_instance_dict.get(order_by_item))
- comparison_values.append(sort_value)
- return comparison_values
-
- return sorted(process_instance_dicts, key=order_by_function_for_lambda)
-
- def generate_report(
- self,
- process_instances: list[ProcessInstanceModel],
- substitution_variables: dict | None,
- ) -> ProcessInstanceReportResult:
- """Generate_report."""
- if substitution_variables is None:
- substitution_variables = {}
-
- def to_serialized(process_instance: ProcessInstanceModel) -> dict:
- """To_serialized."""
- processor = ProcessInstanceProcessor(process_instance)
- process_instance.data = processor.get_current_data()
- return process_instance.serialized_flat
-
- process_instance_dicts = map(to_serialized, process_instances)
- results = []
- for process_instance_dict in process_instance_dicts:
- if self.passes_filter(process_instance_dict, substitution_variables):
- results.append(process_instance_dict)
-
- if "order_by" in self.report_metadata:
- results = self.order_things(results)
-
- if "columns" in self.report_metadata:
- column_keys_to_keep = [c["accessor"] for c in self.report_metadata["columns"]]
-
- pruned_results = []
- for result in results:
- dict_you_want = {
- your_key: result[your_key] for your_key in column_keys_to_keep if result.get(your_key)
- }
- pruned_results.append(dict_you_want)
- results = pruned_results
-
- return ProcessInstanceReportResult(report_metadata=self.report_metadata, results=results)
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py
index dab93e8a..fa8f62de 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_instances_controller.py
@@ -692,7 +692,7 @@ def _find_process_instance_for_me_or_raise(
process_instance_id: int,
) -> ProcessInstanceModel:
"""_find_process_instance_for_me_or_raise."""
- process_instance: ProcessInstanceModel = (
+ process_instance: Optional[ProcessInstanceModel] = (
ProcessInstanceModel.query.filter_by(id=process_instance_id)
.outerjoin(HumanTaskModel)
.outerjoin(
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_models_controller.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_models_controller.py
index 192e7f11..880cabda 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_models_controller.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_models_controller.py
@@ -438,7 +438,7 @@ def process_model_create_with_natural_language(
def _get_file_from_request() -> FileStorage:
"""Get_file_from_request."""
- request_file: FileStorage = connexion.request.files.get("file")
+ request_file: Optional[FileStorage] = connexion.request.files.get("file")
if not request_file:
raise ApiError(
error_code="no_file_given",
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py
index 7f67051e..91f35938 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_report_service.py
@@ -53,98 +53,105 @@ class ProcessInstanceReportService:
}
system_report_completed_instances_initiated_by_me: ReportMetadata = {
"columns": [
- {"Header": "id", "accessor": "id"},
+ {"Header": "id", "accessor": "id", "filterable": False},
{
"Header": "process_model_display_name",
"accessor": "process_model_display_name",
+ "filterable": False,
},
- {"Header": "start_in_seconds", "accessor": "start_in_seconds"},
- {"Header": "end_in_seconds", "accessor": "end_in_seconds"},
- {"Header": "status", "accessor": "status"},
+ {"Header": "start_in_seconds", "accessor": "start_in_seconds", "filterable": False},
+ {"Header": "end_in_seconds", "accessor": "end_in_seconds", "filterable": False},
+ {"Header": "status", "accessor": "status", "filterable": False},
],
"filter_by": [
- {"field_name": "initiated_by_me", "field_value": True},
- {"field_name": "process_status", "field_value": terminal_status_values},
+ {"field_name": "initiated_by_me", "field_value": True, "operator": "equals"},
+ {"field_name": "process_status", "field_value": terminal_status_values, "operator": "equals"},
],
"order_by": ["-start_in_seconds", "-id"],
}
system_report_completed_instances_with_tasks_completed_by_me: ReportMetadata = {
"columns": cls.builtin_column_options(),
"filter_by": [
- {"field_name": "with_tasks_completed_by_me", "field_value": True},
- {"field_name": "process_status", "field_value": terminal_status_values},
+ {"field_name": "with_tasks_completed_by_me", "field_value": True, "operator": "equals"},
+ {"field_name": "process_status", "field_value": terminal_status_values, "operator": "equals"},
],
"order_by": ["-start_in_seconds", "-id"],
}
system_report_completed_instances: ReportMetadata = {
"columns": cls.builtin_column_options(),
"filter_by": [
- {"field_name": "process_status", "field_value": terminal_status_values},
+ {"field_name": "process_status", "field_value": terminal_status_values, "operator": "equals"},
],
"order_by": ["-start_in_seconds", "-id"],
}
system_report_in_progress_instances_initiated_by_me: ReportMetadata = {
"columns": [
- {"Header": "id", "accessor": "id"},
+ {"Header": "id", "accessor": "id", "filterable": False},
{
"Header": "process_model_display_name",
"accessor": "process_model_display_name",
+ "filterable": False,
},
- {"Header": "Task", "accessor": "task_title"},
- {"Header": "Waiting For", "accessor": "waiting_for"},
- {"Header": "Started", "accessor": "start_in_seconds"},
- {"Header": "Last Updated", "accessor": "task_updated_at_in_seconds"},
- {"Header": "status", "accessor": "status"},
+ {"Header": "Task", "accessor": "task_title", "filterable": False},
+ {"Header": "Waiting For", "accessor": "waiting_for", "filterable": False},
+ {"Header": "Started", "accessor": "start_in_seconds", "filterable": False},
+ {"Header": "Last Updated", "accessor": "task_updated_at_in_seconds", "filterable": False},
+ {"Header": "status", "accessor": "status", "filterable": False},
],
"filter_by": [
- {"field_name": "initiated_by_me", "field_value": True},
- {"field_name": "process_status", "field_value": non_terminal_status_values},
+ {"field_name": "initiated_by_me", "field_value": True, "operator": "equals"},
+ {"field_name": "process_status", "field_value": non_terminal_status_values, "operator": "equals"},
{
"field_name": "with_oldest_open_task",
"field_value": True,
+ "operator": "equals",
},
],
"order_by": ["-start_in_seconds", "-id"],
}
system_report_in_progress_instances_with_tasks_for_me: ReportMetadata = {
"columns": [
- {"Header": "id", "accessor": "id"},
+ {"Header": "id", "accessor": "id", "filterable": False},
{
"Header": "process_model_display_name",
"accessor": "process_model_display_name",
+ "filterable": False,
},
- {"Header": "Task", "accessor": "task_title"},
- {"Header": "Started By", "accessor": "process_initiator_username"},
- {"Header": "Started", "accessor": "start_in_seconds"},
- {"Header": "Last Updated", "accessor": "task_updated_at_in_seconds"},
+ {"Header": "Task", "accessor": "task_title", "filterable": False},
+ {"Header": "Started By", "accessor": "process_initiator_username", "filterable": False},
+ {"Header": "Started", "accessor": "start_in_seconds", "filterable": False},
+ {"Header": "Last Updated", "accessor": "task_updated_at_in_seconds", "filterable": False},
],
"filter_by": [
- {"field_name": "with_tasks_i_can_complete", "field_value": True},
- {"field_name": "process_status", "field_value": active_status_values},
+ {"field_name": "with_tasks_i_can_complete", "field_value": True, "operator": "equals"},
+ {"field_name": "process_status", "field_value": active_status_values, "operator": "equals"},
{
"field_name": "with_oldest_open_task",
"field_value": True,
+ "operator": "equals",
},
],
"order_by": ["-start_in_seconds", "-id"],
}
system_report_in_progress_instances_with_tasks: ReportMetadata = {
"columns": [
- {"Header": "id", "accessor": "id"},
+ {"Header": "id", "accessor": "id", "filterable": False},
{
"Header": "process_model_display_name",
"accessor": "process_model_display_name",
+ "filterable": False,
},
- {"Header": "Task", "accessor": "task_title"},
- {"Header": "Started By", "accessor": "process_initiator_username"},
- {"Header": "Started", "accessor": "start_in_seconds"},
- {"Header": "Last Updated", "accessor": "task_updated_at_in_seconds"},
+ {"Header": "Task", "accessor": "task_title", "filterable": False},
+ {"Header": "Started By", "accessor": "process_initiator_username", "filterable": False},
+ {"Header": "Started", "accessor": "start_in_seconds", "filterable": False},
+ {"Header": "Last Updated", "accessor": "task_updated_at_in_seconds", "filterable": False},
],
"filter_by": [
- {"field_name": "process_status", "field_value": active_status_values},
+ {"field_name": "process_status", "field_value": active_status_values, "operator": "equals"},
{
"field_name": "with_oldest_open_task",
"field_value": True,
+ "operator": "equals",
},
],
"order_by": ["-start_in_seconds", "-id"],
@@ -165,7 +172,8 @@ class ProcessInstanceReportService:
}
if metadata_key not in temp_system_metadata_map:
return None
- return temp_system_metadata_map[metadata_key]
+ return_value: ReportMetadata = temp_system_metadata_map[metadata_key]
+ return return_value
@classmethod
def compile_report(cls, report_metadata: ReportMetadata, user: UserModel) -> None:
@@ -173,7 +181,9 @@ class ProcessInstanceReportService:
old_filters = copy.deepcopy(report_metadata["filter_by"])
for filter in old_filters:
if filter["field_name"] == "initiated_by_me":
- compiled_filters.append({"field_name": "process_initiator_username", "field_value": user.username})
+ compiled_filters.append(
+ {"field_name": "process_initiator_username", "field_value": user.username, "operator": "equals"}
+ )
else:
compiled_filters.append(filter)
diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py
index 20ef475a..ce72759b 100644
--- a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py
+++ b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py
@@ -792,7 +792,6 @@ class TestProcessApi(BaseTest):
content_type="multipart/form-data",
headers=self.logged_in_headers(with_super_admin_user),
)
-
assert response.status_code == 400
assert response.json is not None
assert response.json["error_code"] == "no_file_given"
@@ -1853,7 +1852,13 @@ class TestProcessApi(BaseTest):
# Without filtering we should get all 5 instances
report_metadata_body: ReportMetadata = {
- "filter_by": [{"field_name": "process_model_identifier", "field_value": process_model_identifier}],
+ "filter_by": [
+ {
+ "field_name": "process_model_identifier",
+ "field_value": process_model_identifier,
+ "operator": "equals",
+ }
+ ],
"columns": [],
"order_by": [],
}
@@ -1868,8 +1873,16 @@ class TestProcessApi(BaseTest):
for i in range(5):
report_metadata_body = {
"filter_by": [
- {"field_name": "process_model_identifier", "field_value": process_model_identifier},
- {"field_name": "process_status", "field_value": ProcessInstanceStatus[statuses[i]].value},
+ {
+ "field_name": "process_model_identifier",
+ "field_value": process_model_identifier,
+ "operator": "equals",
+ },
+ {
+ "field_name": "process_status",
+ "field_value": ProcessInstanceStatus[statuses[i]].value,
+ "operator": "equals",
+ },
],
"columns": [],
"order_by": [],
@@ -1883,8 +1896,12 @@ class TestProcessApi(BaseTest):
report_metadata_body = {
"filter_by": [
- {"field_name": "process_model_identifier", "field_value": process_model_identifier},
- {"field_name": "process_status", "field_value": "not_started,complete"},
+ {
+ "field_name": "process_model_identifier",
+ "field_value": process_model_identifier,
+ "operator": "equals",
+ },
+ {"field_name": "process_status", "field_value": "not_started,complete", "operator": "equals"},
],
"columns": [],
"order_by": [],
@@ -1900,7 +1917,7 @@ class TestProcessApi(BaseTest):
# filter by start/end seconds
# start > 1000 - this should eliminate the first
report_metadata_body = {
- "filter_by": [{"field_name": "start_from", "field_value": 1001}],
+ "filter_by": [{"field_name": "start_from", "field_value": 1001, "operator": "equals"}],
"columns": [],
"order_by": [],
}
@@ -1920,8 +1937,8 @@ class TestProcessApi(BaseTest):
# start > 2000, end < 5000 - this should eliminate the first 2 and the last
report_metadata_body = {
"filter_by": [
- {"field_name": "start_from", "field_value": 2001},
- {"field_name": "end_to", "field_value": 5999},
+ {"field_name": "start_from", "field_value": 2001, "operator": "equals"},
+ {"field_name": "end_to", "field_value": 5999, "operator": "equals"},
],
"columns": [],
"order_by": [],
@@ -1937,8 +1954,8 @@ class TestProcessApi(BaseTest):
# start > 1000, start < 4000 - this should eliminate the first and the last 2
report_metadata_body = {
"filter_by": [
- {"field_name": "start_from", "field_value": 1001},
- {"field_name": "start_to", "field_value": 3999},
+ {"field_name": "start_from", "field_value": 1001, "operator": "equals"},
+ {"field_name": "start_to", "field_value": 3999, "operator": "equals"},
],
"columns": [],
"order_by": [],
@@ -1954,8 +1971,8 @@ class TestProcessApi(BaseTest):
# end > 2000, end < 6000 - this should eliminate the first and the last
report_metadata_body = {
"filter_by": [
- {"field_name": "end_from", "field_value": 2001},
- {"field_name": "end_to", "field_value": 5999},
+ {"field_name": "end_from", "field_value": 2001, "operator": "equals"},
+ {"field_name": "end_to", "field_value": 5999, "operator": "equals"},
],
"columns": [],
"order_by": [],