From bb026c64c4f9ff712c6d09ef23ea877ca9595d82 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Thu, 16 Jan 2025 00:30:21 -0800 Subject: [PATCH] Feature: filter by mime type --- .../filter-editor.component.spec.ts | 31 +++++++++++++++++++ .../filter-editor/filter-editor.component.ts | 16 ++++++++++ src-ui/src/app/data/filter-rule-type.ts | 8 +++++ src/documents/filters.py | 10 ++++++ src/documents/tests/test_api_documents.py | 7 +++++ 5 files changed, 72 insertions(+) diff --git a/src-ui/src/app/components/document-list/filter-editor/filter-editor.component.spec.ts b/src-ui/src/app/components/document-list/filter-editor/filter-editor.component.spec.ts index 3a5cedccb..5979127e7 100644 --- a/src-ui/src/app/components/document-list/filter-editor/filter-editor.component.spec.ts +++ b/src-ui/src/app/components/document-list/filter-editor/filter-editor.component.spec.ts @@ -60,6 +60,7 @@ import { FILTER_HAS_STORAGE_PATH_ANY, FILTER_HAS_TAGS_ALL, FILTER_HAS_TAGS_ANY, + FILTER_MIME_TYPE, FILTER_OWNER, FILTER_OWNER_ANY, FILTER_OWNER_DOES_NOT_INCLUDE, @@ -389,6 +390,18 @@ describe('FilterEditorComponent', () => { expect(component.textFilterModifier).toEqual('less') // TEXT_FILTER_MODIFIER_LT })) + it('should ingest text filter rules for mime type', fakeAsync(() => { + expect(component.textFilter).toEqual(null) + component.filterRules = [ + { + rule_type: FILTER_MIME_TYPE, + value: 'pdf', + }, + ] + expect(component.textFilter).toEqual('pdf') + expect(component.textFilterTarget).toEqual('mime-type') // TEXT_FILTER_TARGET_MIME_TYPE + })) + it('should ingest text filter rules for fulltext query', fakeAsync(() => { expect(component.textFilter).toEqual(null) component.filterRules = [ @@ -1222,6 +1235,24 @@ describe('FilterEditorComponent', () => { ]) })) + it('should convert user input to correct filter rules on mime type', fakeAsync(() => { + component.textFilterInput.nativeElement.value = 'pdf' + component.textFilterInput.nativeElement.dispatchEvent(new Event('input')) + const textFieldTargetDropdown = fixture.debugElement.queryAll( + By.directive(NgbDropdownItem) + )[5] + textFieldTargetDropdown.triggerEventHandler('click') // TEXT_FILTER_TARGET_MIME_TYPE + fixture.detectChanges() + tick(400) + expect(component.textFilterTarget).toEqual('mime-type') + expect(component.filterRules).toEqual([ + { + rule_type: FILTER_MIME_TYPE, + value: 'pdf', + }, + ]) + })) + it('should convert user input to correct filter rules on full text query', fakeAsync(() => { component.textFilterInput.nativeElement.value = 'foo' component.textFilterInput.nativeElement.dispatchEvent(new Event('input')) diff --git a/src-ui/src/app/components/document-list/filter-editor/filter-editor.component.ts b/src-ui/src/app/components/document-list/filter-editor/filter-editor.component.ts index 2179efaf4..de40772c1 100644 --- a/src-ui/src/app/components/document-list/filter-editor/filter-editor.component.ts +++ b/src-ui/src/app/components/document-list/filter-editor/filter-editor.component.ts @@ -66,6 +66,7 @@ import { FILTER_HAS_STORAGE_PATH_ANY, FILTER_HAS_TAGS_ALL, FILTER_HAS_TAGS_ANY, + FILTER_MIME_TYPE, FILTER_OWNER, FILTER_OWNER_ANY, FILTER_OWNER_DOES_NOT_INCLUDE, @@ -126,6 +127,7 @@ const TEXT_FILTER_TARGET_ASN = 'asn' const TEXT_FILTER_TARGET_FULLTEXT_QUERY = 'fulltext-query' const TEXT_FILTER_TARGET_FULLTEXT_MORELIKE = 'fulltext-morelike' const TEXT_FILTER_TARGET_CUSTOM_FIELDS = 'custom-fields' +const TEXT_FILTER_TARGET_MIME_TYPE = 'mime-type' const TEXT_FILTER_MODIFIER_EQUALS = 'equals' const TEXT_FILTER_MODIFIER_NULL = 'is null' @@ -169,6 +171,7 @@ const DEFAULT_TEXT_FILTER_TARGET_OPTIONS = [ id: TEXT_FILTER_TARGET_FULLTEXT_QUERY, name: $localize`Advanced search`, }, + { id: TEXT_FILTER_TARGET_MIME_TYPE, name: $localize`MIME type` }, ] const TEXT_FILTER_TARGET_MORELIKE_OPTION = { @@ -416,6 +419,10 @@ export class FilterEditorComponent this._textFilter = rule.value this.textFilterTarget = TEXT_FILTER_TARGET_CUSTOM_FIELDS break + case FILTER_MIME_TYPE: + this.textFilterTarget = TEXT_FILTER_TARGET_MIME_TYPE + this._textFilter = rule.value + break case FILTER_FULLTEXT_QUERY: let allQueryArgs = rule.value.split(',') let textQueryArgs = [] @@ -729,6 +736,15 @@ export class FilterEditorComponent value: this._textFilter, }) } + if ( + this._textFilter && + this.textFilterTarget == TEXT_FILTER_TARGET_MIME_TYPE + ) { + filterRules.push({ + rule_type: FILTER_MIME_TYPE, + value: this._textFilter, + }) + } if ( this._textFilter && this.textFilterTarget == TEXT_FILTER_TARGET_FULLTEXT_QUERY diff --git a/src-ui/src/app/data/filter-rule-type.ts b/src-ui/src/app/data/filter-rule-type.ts index dd9d8731a..daf7936b8 100644 --- a/src-ui/src/app/data/filter-rule-type.ts +++ b/src-ui/src/app/data/filter-rule-type.ts @@ -62,6 +62,8 @@ export const FILTER_HAS_ANY_CUSTOM_FIELDS = 41 export const FILTER_CUSTOM_FIELDS_QUERY = 42 +export const FILTER_MIME_TYPE = 43 + export const FILTER_RULE_TYPES: FilterRuleType[] = [ { id: FILTER_TITLE, @@ -354,6 +356,12 @@ export const FILTER_RULE_TYPES: FilterRuleType[] = [ datatype: 'string', multi: false, }, + { + id: FILTER_MIME_TYPE, + filtervar: 'mime_type', + datatype: 'string', + multi: false, + }, ] export interface FilterRuleType { diff --git a/src/documents/filters.py b/src/documents/filters.py index fab029312..21a9422ad 100644 --- a/src/documents/filters.py +++ b/src/documents/filters.py @@ -215,6 +215,14 @@ class CustomFieldsFilter(Filter): return qs +class MimeTypeFilter(Filter): + def filter(self, qs, value): + if value: + return qs.filter(mime_type__icontains=value) + else: + return qs + + class SelectField(serializers.CharField): def __init__(self, custom_field: CustomField): self._options = custom_field.extra_data["select_options"] @@ -710,6 +718,8 @@ class DocumentFilterSet(FilterSet): shared_by__id = SharedByUser() + mime_type = MimeTypeFilter() + class Meta: model = Document fields = { diff --git a/src/documents/tests/test_api_documents.py b/src/documents/tests/test_api_documents.py index b7a4f4e2f..7010c5095 100644 --- a/src/documents/tests/test_api_documents.py +++ b/src/documents/tests/test_api_documents.py @@ -639,6 +639,13 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): self.assertEqual(len(results), 1) self.assertEqual(results[0]["id"], doc3.id) + response = self.client.get( + "/api/documents/?mime_type=pdf", + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + results = response.data["results"] + self.assertEqual(len(results), 3) + def test_custom_field_select_filter(self): """ GIVEN: