diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/config/__init__.py b/spiffworkflow-backend/src/spiffworkflow_backend/config/__init__.py index eaf67f6c..7711c36f 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/config/__init__.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/config/__init__.py @@ -18,13 +18,13 @@ def setup_database_uri(app: Flask) -> None: if app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_URI") is None: database_name = f"spiffworkflow_backend_{app.config['ENV_IDENTIFIER']}" if app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_TYPE") == "sqlite": - app.config[ - "SQLALCHEMY_DATABASE_URI" - ] = f"sqlite:///{app.instance_path}/db_{app.config['ENV_IDENTIFIER']}.sqlite3" + app.config["SQLALCHEMY_DATABASE_URI"] = ( + f"sqlite:///{app.instance_path}/db_{app.config['ENV_IDENTIFIER']}.sqlite3" + ) elif app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_TYPE") == "postgres": - app.config[ - "SQLALCHEMY_DATABASE_URI" - ] = f"postgresql://spiffworkflow_backend:spiffworkflow_backend@localhost:5432/{database_name}" + app.config["SQLALCHEMY_DATABASE_URI"] = ( + f"postgresql://spiffworkflow_backend:spiffworkflow_backend@localhost:5432/{database_name}" + ) else: # use pswd to trick flake8 with hardcoded passwords db_pswd = app.config.get("SPIFFWORKFLOW_BACKEND_DATABASE_PASSWORD") diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py b/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py index 303532af..b3ab709d 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/models/process_instance.py @@ -129,9 +129,9 @@ class ProcessInstanceModel(SpiffworkflowBaseDBModel): def serialized_with_metadata(self) -> dict[str, Any]: process_instance_attributes = self.serialized process_instance_attributes["process_metadata"] = self.process_metadata - process_instance_attributes[ - "process_model_with_diagram_identifier" - ] = self.process_model_with_diagram_identifier + process_instance_attributes["process_model_with_diagram_identifier"] = ( + self.process_model_with_diagram_identifier + ) return process_instance_attributes @property diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py index 2ff469b3..39d37c2c 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_processor.py @@ -423,9 +423,9 @@ class ProcessInstanceProcessor: tld.process_instance_id = process_instance_model.id # we want this to be the fully qualified path to the process model including all group subcomponents - current_app.config[ - "THREAD_LOCAL_DATA" - ].process_model_identifier = f"{process_instance_model.process_model_identifier}" + current_app.config["THREAD_LOCAL_DATA"].process_model_identifier = ( + f"{process_instance_model.process_model_identifier}" + ) self.process_instance_model = process_instance_model self.process_model_service = ProcessModelService() @@ -585,9 +585,9 @@ class ProcessInstanceProcessor: bpmn_subprocess_definition.bpmn_identifier ] = bpmn_process_definition_dict spiff_bpmn_process_dict["subprocess_specs"][bpmn_subprocess_definition.bpmn_identifier]["task_specs"] = {} - bpmn_subprocess_definition_bpmn_identifiers[ - bpmn_subprocess_definition.id - ] = bpmn_subprocess_definition.bpmn_identifier + bpmn_subprocess_definition_bpmn_identifiers[bpmn_subprocess_definition.id] = ( + bpmn_subprocess_definition.bpmn_identifier + ) task_definitions = TaskDefinitionModel.query.filter( TaskDefinitionModel.bpmn_process_definition_id.in_( # type: ignore 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 c9617ce6..fc7b9d43 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 @@ -34,7 +34,11 @@ from spiffworkflow_backend.services.process_model_service import ProcessModelSer class ProcessInstanceReportNotFoundError(Exception): - """ProcessInstanceReportNotFoundError.""" + pass + + +class ProcessInstanceReportMetadataInvalidError(Exception): + pass class ProcessInstanceReportService: @@ -441,6 +445,15 @@ class ProcessInstanceReportService: ) ) + if with_tasks_completed_by_me is True and with_tasks_i_can_complete is True: + raise ProcessInstanceReportMetadataInvalidError( + "Cannot set both 'with_tasks_completed_by_me' and 'with_tasks_i_can_complete' to true. You must choose" + " one." + ) + + # ensure we only join with HumanTaskModel once + human_task_already_joined = False + if with_tasks_completed_by_me is True: process_instance_query = process_instance_query.filter( ProcessInstanceModel.process_initiator_id != user.id @@ -452,6 +465,7 @@ class ProcessInstanceReportService: HumanTaskModel.completed_by_user_id == user.id, ), ) + human_task_already_joined = True # this excludes some tasks you can complete, because that's the way the requirements were described. # if it's assigned to one of your groups, it does not get returned by this query. @@ -470,13 +484,15 @@ class ProcessInstanceReportService: HumanTaskUserModel, and_(HumanTaskUserModel.human_task_id == HumanTaskModel.id, HumanTaskUserModel.user_id == user.id), ) + human_task_already_joined = True if user_group_identifier is not None: group_model_join_conditions = [GroupModel.id == HumanTaskModel.lane_assignment_id] if user_group_identifier: group_model_join_conditions.append(GroupModel.identifier == user_group_identifier) - process_instance_query = process_instance_query.join(HumanTaskModel) + if human_task_already_joined is False: + process_instance_query = process_instance_query.join(HumanTaskModel) if process_status is not None: non_active_statuses = [ s for s in process_status.split(",") if s not in ProcessInstanceModel.active_statuses() 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 95eab41e..9c36de92 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,4 +1,5 @@ """Test_process_instance_report_service.""" +import pytest from flask import Flask from flask.testing import FlaskClient from tests.spiffworkflow_backend.helpers.base_test import BaseTest @@ -7,9 +8,9 @@ from tests.spiffworkflow_backend.helpers.test_data import load_test_spec from spiffworkflow_backend.models.db import db from spiffworkflow_backend.models.group import GroupModel from spiffworkflow_backend.models.human_task import HumanTaskModel -from spiffworkflow_backend.services.process_instance_report_service import ( - ProcessInstanceReportService, -) +from spiffworkflow_backend.models.process_instance_report import ReportMetadata +from spiffworkflow_backend.services.process_instance_report_service import ProcessInstanceReportMetadataInvalidError +from spiffworkflow_backend.services.process_instance_report_service import ProcessInstanceReportService from spiffworkflow_backend.services.user_service import UserService @@ -52,6 +53,62 @@ class TestProcessInstanceReportService(BaseTest): assert response_json["results"][0]["status"] == "complete" assert response_json["results"][1]["status"] == "complete" + def test_raises_if_filtering_with_both_task_i_can_complete_and_tasks_completed_by_me( + self, + app: Flask, + client: FlaskClient, + with_db_and_bpmn_file_cleanup: None, + ) -> None: + user_one = self.find_or_create_user(username="user_one") + report_metadata: ReportMetadata = { + "columns": [], + "filter_by": [ + {"field_name": "with_tasks_i_can_complete", "field_value": True, "operator": "equals"}, + {"field_name": "with_tasks_completed_by_me", "field_value": True, "operator": "equals"}, + ], + "order_by": [], + } + with pytest.raises(ProcessInstanceReportMetadataInvalidError): + ProcessInstanceReportService.run_process_instance_report( + report_metadata=report_metadata, + user=user_one, + ) + + def test_with_group_identifier_does_not_conflict_with_system_filters( + self, + app: Flask, + client: FlaskClient, + with_db_and_bpmn_file_cleanup: None, + ) -> None: + user_one = self.find_or_create_user(username="user_one") + report_metadata: ReportMetadata = { + "columns": [], + "filter_by": [ + {"field_name": "with_tasks_i_can_complete", "field_value": True, "operator": "equals"}, + {"field_name": "user_group_identifier", "field_value": "group_one", "operator": "equals"}, + ], + "order_by": [], + } + result = ProcessInstanceReportService.run_process_instance_report( + report_metadata=report_metadata, + user=user_one, + ) + assert result is not None + + report_metadata = { + "columns": [], + "filter_by": [ + {"field_name": "with_tasks_completed_by_me", "field_value": True, "operator": "equals"}, + {"field_name": "user_group_identifier", "field_value": "group_one", "operator": "equals"}, + ], + "order_by": [], + } + result = ProcessInstanceReportService.run_process_instance_report( + report_metadata=report_metadata, + user=user_one, + ) + assert result is not None + def test_can_filter_by_completed_instances_with_tasks_completed_by_me( self, app: Flask,