From a4fd81cb9af27c5dcc6cefbefa0053040cbee933 Mon Sep 17 00:00:00 2001 From: jasquat Date: Thu, 27 Apr 2023 17:33:25 -0400 Subject: [PATCH] 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 9550699c9..000000000 --- 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 5c094b86d..be4eb8d7a 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 25497b1c4..74ee1a481 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 a9471c9a5..dd2493727 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 b62cd1c1b..bf2d8bc91 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 ab180fee0..a95cec341 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 ec088dd3f..4b22605c2 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 fa49cac07..8fec7d265 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 57e1cbfc8..e89a959a3 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 3542e7dd1..e7e2ad61b 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 5ed209007..75ad6cbc3 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 78186d971..b196c2ee4 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 39f7871bf..7284eaf6a 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); }} />