diff --git a/src/spiffworkflow_backend/api.yml b/src/spiffworkflow_backend/api.yml index e5e0db97..f85cc74f 100755 --- a/src/spiffworkflow_backend/api.yml +++ b/src/spiffworkflow_backend/api.yml @@ -514,6 +514,24 @@ paths: description: For filtering - not_started, user_input_required, waiting, complete, error, or suspended schema: type: string + - name: initiated_by_me + in: query + required: false + description: For filtering - show instances initiated by me + schema: + type: boolean + - name: with_tasks_completed_by_me + in: query + required: false + description: For filtering - show instances with tasks completed by me + schema: + type: boolean + - name: with_tasks_completed_by_my_group + in: query + required: false + description: For filtering - show instances with tasks completed by my group + schema: + type: boolean - name: user_filter in: query required: false diff --git a/src/spiffworkflow_backend/routes/process_api_blueprint.py b/src/spiffworkflow_backend/routes/process_api_blueprint.py index f1135843..31eff335 100644 --- a/src/spiffworkflow_backend/routes/process_api_blueprint.py +++ b/src/spiffworkflow_backend/routes/process_api_blueprint.py @@ -30,6 +30,7 @@ from SpiffWorkflow.task import TaskState from sqlalchemy import and_ from sqlalchemy import asc from sqlalchemy import desc +from sqlalchemy import select from spiffworkflow_backend.exceptions.process_entity_not_found_error import ( ProcessEntityNotFoundError, @@ -63,6 +64,7 @@ from spiffworkflow_backend.models.spec_reference import SpecReferenceSchema from spiffworkflow_backend.models.spiff_logging import SpiffLoggingModel from spiffworkflow_backend.models.spiff_step_details import SpiffStepDetailsModel from spiffworkflow_backend.models.user import UserModel +from spiffworkflow_backend.models.user_group_assignment import UserGroupAssignmentModel from spiffworkflow_backend.routes.user import verify_token from spiffworkflow_backend.services.authorization_service import AuthorizationService from spiffworkflow_backend.services.error_handling_service import ErrorHandlingService @@ -762,6 +764,9 @@ def process_instance_list( end_from: Optional[int] = None, end_to: Optional[int] = None, process_status: Optional[str] = None, + initiated_by_me: Optional[bool] = None, + with_tasks_completed_by_me: Optional[bool] = None, + with_tasks_completed_by_my_group: Optional[bool] = None, user_filter: Optional[bool] = False, report_identifier: Optional[str] = None, ) -> flask.wrappers.Response: @@ -778,6 +783,9 @@ def process_instance_list( end_from, end_to, process_status.split(",") if process_status else None, + initiated_by_me, + with_tasks_completed_by_me, + with_tasks_completed_by_my_group, ) else: report_filter = ( @@ -789,6 +797,9 @@ def process_instance_list( end_from, end_to, process_status, + initiated_by_me, + with_tasks_completed_by_me, + with_tasks_completed_by_my_group, ) ) @@ -837,9 +848,79 @@ def process_instance_list( ProcessInstanceModel.status.in_(report_filter.process_status) # type: ignore ) - process_instances = process_instance_query.order_by( - ProcessInstanceModel.start_in_seconds.desc(), ProcessInstanceModel.id.desc() # type: ignore - ).paginate(page=page, per_page=per_page, error_out=False) + if report_filter.initiated_by_me is True: + process_instance_query = process_instance_query.filter( + ProcessInstanceModel.status == "complete" + ) + process_instance_query = process_instance_query.filter_by( + process_initiator=g.user + ) + + # TODO: not sure if this is exactly what is wanted + if report_filter.with_tasks_completed_by_me is True: + process_instance_query = process_instance_query.filter( + ProcessInstanceModel.status == "complete" + ) + process_instance_query = process_instance_query.join( + SpiffStepDetailsModel, + ProcessInstanceModel.id == SpiffStepDetailsModel.process_instance_id, + ) + process_instance_query = process_instance_query.join( + SpiffLoggingModel, + ProcessInstanceModel.id == SpiffLoggingModel.process_instance_id, + ) + process_instance_query = process_instance_query.filter( + SpiffLoggingModel.message.contains("COMPLETED") # type: ignore + ) + process_instance_query = process_instance_query.filter( + SpiffLoggingModel.spiff_step == SpiffStepDetailsModel.spiff_step + ) + process_instance_query = process_instance_query.filter( + SpiffStepDetailsModel.completed_by_user_id == g.user.id + ) + + # TODO: not sure if this is exactly what is wanted + if report_filter.with_tasks_completed_by_my_group is True: + process_instance_query = process_instance_query.filter( + ProcessInstanceModel.status == "complete" + ) + process_instance_query = process_instance_query.join( + SpiffStepDetailsModel, + ProcessInstanceModel.id == SpiffStepDetailsModel.process_instance_id, + ) + process_instance_query = process_instance_query.join( + SpiffLoggingModel, + ProcessInstanceModel.id == SpiffLoggingModel.process_instance_id, + ) + process_instance_query = process_instance_query.filter( + SpiffLoggingModel.message.contains("COMPLETED") # type: ignore + ) + process_instance_query = process_instance_query.filter( + SpiffLoggingModel.spiff_step == SpiffStepDetailsModel.spiff_step + ) + + my_groups = ( + select(UserGroupAssignmentModel) # type: ignore + .where(UserGroupAssignmentModel.user_id == g.user.id) + .with_only_columns(UserGroupAssignmentModel.group_id) + ) + users_in_my_groups = ( + select(UserGroupAssignmentModel) # type: ignore + .where(UserGroupAssignmentModel.group_id.in_(my_groups)) + .with_only_columns(UserGroupAssignmentModel.user_id) + ) + + process_instance_query = process_instance_query.filter( + SpiffStepDetailsModel.completed_by_user_id.in_(users_in_my_groups) # type: ignore + ) + + process_instances = ( + process_instance_query.distinct() + .order_by( + ProcessInstanceModel.start_in_seconds.desc(), ProcessInstanceModel.id.desc() # type: ignore + ) + .paginate(page=page, per_page=per_page, error_out=False) + ) results = list( map( diff --git a/src/spiffworkflow_backend/services/process_instance_report_service.py b/src/spiffworkflow_backend/services/process_instance_report_service.py index 9c398514..731abcdb 100644 --- a/src/spiffworkflow_backend/services/process_instance_report_service.py +++ b/src/spiffworkflow_backend/services/process_instance_report_service.py @@ -18,6 +18,9 @@ class ProcessInstanceReportFilter: end_from: Optional[int] = None end_to: Optional[int] = None process_status: Optional[list[str]] = None + initiated_by_me: Optional[bool] = None + with_tasks_completed_by_me: Optional[bool] = None + with_tasks_completed_by_my_group: Optional[bool] = None def to_dict(self) -> dict[str, str]: """To_dict.""" @@ -35,6 +38,16 @@ class ProcessInstanceReportFilter: 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.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_completed_by_my_group is not None: + d["with_tasks_completed_by_my_group"] = str( + self.with_tasks_completed_by_my_group + ).lower() return d @@ -73,30 +86,35 @@ class ProcessInstanceReportService: }, "system_report_instances_initiated_by_me": { "columns": [ + {"Header": "id", "accessor": "id"}, { "Header": "process_model_identifier", "accessor": "process_model_identifier", }, {"Header": "start_in_seconds", "accessor": "start_in_seconds"}, - {"Header": "id", "accessor": "id"}, {"Header": "end_in_seconds", "accessor": "end_in_seconds"}, {"Header": "status", "accessor": "status"}, ], + "filter_by": [{"field_name": "initiated_by_me", "field_value": True}], }, "system_report_instances_with_tasks_completed_by_me": { "columns": [ - {"Header": "start_in_seconds", "accessor": "start_in_seconds"}, - {"Header": "end_in_seconds", "accessor": "end_in_seconds"}, - {"Header": "status", "accessor": "status"}, {"Header": "id", "accessor": "id"}, { "Header": "process_model_identifier", "accessor": "process_model_identifier", }, + {"Header": "start_in_seconds", "accessor": "start_in_seconds"}, + {"Header": "end_in_seconds", "accessor": "end_in_seconds"}, + {"Header": "status", "accessor": "status"}, + ], + "filter_by": [ + {"field_name": "with_tasks_completed_by_me", "field_value": True} ], }, "system_report_instances_with_tasks_completed_by_my_groups": { "columns": [ + {"Header": "id", "accessor": "id"}, { "Header": "process_model_identifier", "accessor": "process_model_identifier", @@ -104,7 +122,12 @@ class ProcessInstanceReportService: {"Header": "start_in_seconds", "accessor": "start_in_seconds"}, {"Header": "end_in_seconds", "accessor": "end_in_seconds"}, {"Header": "status", "accessor": "status"}, - {"Header": "id", "accessor": "id"}, + ], + "filter_by": [ + { + "field_name": "with_tasks_completed_by_my_group", + "field_value": True, + } ], }, } @@ -112,7 +135,7 @@ class ProcessInstanceReportService: process_instance_report = ProcessInstanceReportModel( identifier=report_identifier, created_by_id=user.id, - report_metadata=temp_system_metadata_map[report_identifier], + report_metadata=temp_system_metadata_map[report_identifier], # type: ignore ) return process_instance_report # type: ignore @@ -138,6 +161,10 @@ class ProcessInstanceReportService: """Filter_from_metadata.""" filters = cls.filter_by_to_dict(process_instance_report) + def bool_value(key: str) -> Optional[bool]: + """Bool_value.""" + return bool(filters[key]) if key in filters else None + def int_value(key: str) -> Optional[int]: """Int_value.""" return int(filters[key]) if key in filters else None @@ -152,6 +179,11 @@ class ProcessInstanceReportService: 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") + with_tasks_completed_by_me = bool_value("with_tasks_completed_by_me") + with_tasks_completed_by_my_group = bool_value( + "with_tasks_completed_by_my_group" + ) report_filter = ProcessInstanceReportFilter( process_model_identifier, @@ -160,6 +192,9 @@ class ProcessInstanceReportService: end_from, end_to, process_status, + initiated_by_me, + with_tasks_completed_by_me, + with_tasks_completed_by_my_group, ) return report_filter @@ -174,6 +209,9 @@ class ProcessInstanceReportService: end_from: Optional[int] = None, end_to: Optional[int] = None, process_status: Optional[str] = None, + initiated_by_me: Optional[bool] = None, + with_tasks_completed_by_me: Optional[bool] = None, + with_tasks_completed_by_my_group: Optional[bool] = None, ) -> ProcessInstanceReportFilter: """Filter_from_metadata_with_overrides.""" report_filter = cls.filter_from_metadata(process_instance_report) @@ -190,5 +228,13 @@ class ProcessInstanceReportService: 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 with_tasks_completed_by_me is not None: + report_filter.with_tasks_completed_by_me = with_tasks_completed_by_me + if with_tasks_completed_by_my_group is not None: + report_filter.with_tasks_completed_by_my_group = ( + with_tasks_completed_by_my_group + ) return report_filter