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 93c91b54..00872fdc 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 @@ -563,30 +563,30 @@ class ProcessInstanceReportService: None, ) isouter = True - conditions = [ + join_conditions = [ ProcessInstanceModel.id == instance_metadata_alias.process_instance_id, instance_metadata_alias.key == column["accessor"], ] if filter_for_column: isouter = False if "operator" not in filter_for_column or filter_for_column["operator"] == "equals": - conditions.append(instance_metadata_alias.value == filter_for_column["field_value"]) - elif filter_for_column["operator"] == "not equals": - conditions.append(instance_metadata_alias.value != filter_for_column["field_value"]) + join_conditions.append(instance_metadata_alias.value == filter_for_column["field_value"]) + elif filter_for_column["operator"] == "not_equals": + join_conditions.append(instance_metadata_alias.value != filter_for_column["field_value"]) elif filter_for_column["operator"] == "contains": - conditions.append(instance_metadata_alias.value.like(f"%{filter_for_column['field_value']}%")) + join_conditions.append(instance_metadata_alias.value.like(f"%{filter_for_column['field_value']}%")) elif filter_for_column["operator"] == "is_empty": # we still need to return results if the metadata value is null so make sure it's outer join isouter = True - conditions.append( + process_instance_query = process_instance_query.filter( or_(instance_metadata_alias.value.is_(None), instance_metadata_alias.value == "") ) elif filter_for_column["operator"] == "is_not_empty": - conditions.append( + join_conditions.append( or_(instance_metadata_alias.value.is_not(None), instance_metadata_alias.value != "") ) process_instance_query = process_instance_query.join( - instance_metadata_alias, and_(*conditions), isouter=isouter + instance_metadata_alias, and_(*join_conditions), isouter=isouter ).add_columns(func.max(instance_metadata_alias.value).label(column["accessor"])) order_by_query_array = [] diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/base_test.py b/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/base_test.py index d41b3ecd..67aaa705 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/base_test.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/base_test.py @@ -17,6 +17,8 @@ from spiffworkflow_backend.models.permission_target import PermissionTargetModel from spiffworkflow_backend.models.process_group import ProcessGroup from spiffworkflow_backend.models.process_group import ProcessGroupSchema from spiffworkflow_backend.models.process_instance import ProcessInstanceModel +from spiffworkflow_backend.models.process_instance_metadata import ProcessInstanceMetadataModel +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 ProcessModelInfo @@ -453,6 +455,52 @@ class BaseTest: assert po_curr is not None assert customer_curr is not None + def create_process_instance_with_synthetic_metadata( + self, process_model: ProcessModelInfo, process_instance_metadata_dict: dict + ) -> ProcessInstanceModel: + process_instance = self.create_process_instance_from_process_model(process_model=process_model) + processor = ProcessInstanceProcessor(process_instance) + processor.do_engine_steps(save=True) + for key, value in process_instance_metadata_dict.items(): + process_instance_metadata = ProcessInstanceMetadataModel( + process_instance_id=process_instance.id, + key=key, + value=value, + ) + db.session.add(process_instance_metadata) + db.session.commit() + return process_instance + + def assert_report_with_process_metadata_operator_includes_instance( + self, + client: FlaskClient, + user: UserModel, + process_instance: ProcessInstanceModel, + operator: str, + filter_field_value: str = "", + ) -> None: + report_metadata: ReportMetadata = { + "columns": [ + {"Header": "ID", "accessor": "id", "filterable": False}, + {"Header": "Key one", "accessor": "key1", "filterable": False}, + {"Header": "Key two", "accessor": "key2", "filterable": False}, + ], + "order_by": ["status"], + "filter_by": [{"field_name": "key1", "field_value": filter_field_value, "operator": operator}], + } + process_instance_report = ProcessInstanceReportModel.create_report( + identifier=f"{process_instance.id}_sure", + report_metadata=report_metadata, + user=user, + ) + response = self.post_to_process_instance_list( + client, user, report_metadata=process_instance_report.get_report_metadata() + ) + assert len(response.json["results"]) == 1 + assert response.json["results"][0]["id"] == process_instance.id + db.session.delete(process_instance_report) + db.session.commit() + @contextmanager def app_config_mock(self, app: Flask, config_identifier: str, new_config_value: Any) -> Generator: initial_value = app.config[config_identifier] 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 8c1d3907..459d1073 100644 --- a/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py +++ b/spiffworkflow-backend/tests/spiffworkflow_backend/integration/test_process_api.py @@ -2848,6 +2848,57 @@ class TestProcessApi(BaseTest): assert response.json["pagination"]["pages"] == 1 assert response.json["pagination"]["total"] == 1 + def test_can_get_process_instance_list_with_report_metadata_using_different_operators( + self, + app: Flask, + client: FlaskClient, + with_db_and_bpmn_file_cleanup: None, + with_super_admin_user: UserModel, + ) -> None: + process_model = load_test_spec( + process_model_id="save_process_instance_metadata/save_process_instance_metadata", + bpmn_file_name="save_process_instance_metadata.bpmn", + process_model_source_directory="save_process_instance_metadata", + ) + + process_instance_one_metadata = {"key1": "value1"} + process_instance_one = self.create_process_instance_with_synthetic_metadata( + process_model=process_model, process_instance_metadata_dict=process_instance_one_metadata + ) + + process_instance_two_metadata = {"key2": "value2"} + process_instance_two = self.create_process_instance_with_synthetic_metadata( + process_model=process_model, process_instance_metadata_dict=process_instance_two_metadata + ) + + self.assert_report_with_process_metadata_operator_includes_instance( + client=client, user=with_super_admin_user, process_instance=process_instance_one, operator="is_not_empty" + ) + self.assert_report_with_process_metadata_operator_includes_instance( + client=client, + user=with_super_admin_user, + process_instance=process_instance_one, + operator="equals", + filter_field_value="value1", + ) + self.assert_report_with_process_metadata_operator_includes_instance( + client=client, + user=with_super_admin_user, + process_instance=process_instance_one, + operator="contains", + filter_field_value="alu", + ) + self.assert_report_with_process_metadata_operator_includes_instance( + client=client, + user=with_super_admin_user, + process_instance=process_instance_one, + operator="not_equals", + filter_field_value="hey", + ) + self.assert_report_with_process_metadata_operator_includes_instance( + client=client, user=with_super_admin_user, process_instance=process_instance_two, operator="is_empty" + ) + def test_can_get_process_instance_list_with_report_metadata_and_process_initiator( self, app: Flask, diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx index 8c5f4835..a427c2b5 100644 --- a/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx +++ b/spiffworkflow-frontend/src/components/ProcessInstanceListTable.tsx @@ -168,7 +168,7 @@ export default function ProcessInstanceListTable({ const filterOperatorMappings: FilterOperatorMapping = { Is: { id: 'equals', requires_value: true }, - 'Is Not': { id: 'not equals', requires_value: true }, + 'Is Not': { id: 'not_equals', requires_value: true }, Contains: { id: 'contains', requires_value: true }, 'Is Empty': { id: 'is_empty', requires_value: false }, 'Is Not Empty': { id: 'is_not_empty', requires_value: false },