diff --git a/src/documents/filters.py b/src/documents/filters.py index ddc784204..d4e8fbecb 100644 --- a/src/documents/filters.py +++ b/src/documents/filters.py @@ -900,6 +900,16 @@ class ShareLinkBundleFilterSet(FilterSet): class PaperlessTaskFilterSet(FilterSet): + name = Filter( + method="filter_name", + label="Name", + ) + + result = Filter( + method="filter_result", + label="Result", + ) + task_type = MultipleChoiceFilter( choices=PaperlessTask.TaskType.choices, label="Task Type", @@ -939,7 +949,58 @@ class PaperlessTaskFilterSet(FilterSet): class Meta: model = PaperlessTask - fields = ["task_type", "trigger_source", "status", "acknowledged", "owner"] + fields = [ + "task_type", + "trigger_source", + "status", + "acknowledged", + "owner", + "name", + "result", + ] + + def filter_name(self, queryset, name, value): + if not value: + return queryset + + matching_task_types = [ + task_type + for task_type, label in PaperlessTask.TaskType.choices + if value.lower() in str(label).lower() + ] + matching_trigger_sources = [ + trigger_source + for trigger_source, label in PaperlessTask.TriggerSource.choices + if value.lower() in str(label).lower() + ] + + return queryset.filter( + Q(input_data__filename__icontains=value) + | Q(task_type__in=matching_task_types) + | Q(trigger_source__in=matching_trigger_sources), + ) + + def filter_result(self, queryset, name, value): + if not value: + return queryset + + query = Q(result_data__reason__icontains=value) | Q( + result_data__error_message__icontains=value, + ) + + try: + numeric_value = int(value) + except (TypeError, ValueError): + pass + else: + query |= Q(result_data__document_id=numeric_value) | Q( + result_data__duplicate_of=numeric_value, + ) + + if "duplicate" in value.lower(): + query |= Q(result_data__duplicate_of__isnull=False) + + return queryset.filter(query) def filter_is_complete(self, queryset, name, value): if value: diff --git a/src/documents/tests/test_api_tasks.py b/src/documents/tests/test_api_tasks.py index f9b6c4538..b1ec10203 100644 --- a/src/documents/tests/test_api_tasks.py +++ b/src/documents/tests/test_api_tasks.py @@ -169,6 +169,61 @@ class TestGetTasksV10: PaperlessTask.Status.STARTED, } + def test_filter_by_task_name(self, admin_client: APIClient) -> None: + """?name= searches task filenames, task types, and trigger sources.""" + filename_task = PaperlessTaskFactory(input_data={"filename": "invoice-123.pdf"}) + type_task = PaperlessTaskFactory(task_type=PaperlessTask.TaskType.SANITY_CHECK) + source_task = PaperlessTaskFactory( + trigger_source=PaperlessTask.TriggerSource.EMAIL_CONSUME, + ) + PaperlessTaskFactory(input_data={"filename": "unrelated.pdf"}) + + response = admin_client.get(ENDPOINT, {"name": "invoice"}) + + assert response.status_code == status.HTTP_200_OK + assert response.data["count"] == 1 + assert response.data["results"][0]["task_id"] == filename_task.task_id + + response = admin_client.get(ENDPOINT, {"name": "sanity"}) + + assert response.status_code == status.HTTP_200_OK + assert response.data["count"] == 1 + assert response.data["results"][0]["task_id"] == type_task.task_id + + response = admin_client.get(ENDPOINT, {"name": "email"}) + + assert response.status_code == status.HTTP_200_OK + assert response.data["count"] == 1 + assert response.data["results"][0]["task_id"] == source_task.task_id + + def test_filter_by_task_result(self, admin_client: APIClient) -> None: + """?result= searches common structured task result messages.""" + reason_task = PaperlessTaskFactory(result_data={"reason": "Manual review"}) + error_task = PaperlessTaskFactory( + result_data={"error_message": "Duplicate detected"}, + ) + document_task = PaperlessTaskFactory(result_data={"document_id": 321}) + duplicate_task = PaperlessTaskFactory(result_data={"duplicate_of": 123}) + PaperlessTaskFactory(result_data={"reason": "unrelated"}) + + response = admin_client.get(ENDPOINT, {"result": "manual"}) + + assert response.status_code == status.HTTP_200_OK + assert response.data["count"] == 1 + assert response.data["results"][0]["task_id"] == reason_task.task_id + + response = admin_client.get(ENDPOINT, {"result": "duplicate"}) + + assert response.status_code == status.HTTP_200_OK + returned_ids = {task["task_id"] for task in response.data["results"]} + assert returned_ids == {error_task.task_id, duplicate_task.task_id} + + response = admin_client.get(ENDPOINT, {"result": "321"}) + + assert response.status_code == status.HTTP_200_OK + assert response.data["count"] == 1 + assert response.data["results"][0]["task_id"] == document_task.task_id + def test_default_ordering_is_newest_first(self, admin_client: APIClient) -> None: """Tasks are returned in descending date_created order (newest first).""" base = timezone.now()