Enhancement: filter by file type (#8946)

This commit is contained in:
shamoon
2025-02-10 08:09:50 -08:00
committed by GitHub
parent 880f08599a
commit 63bb3644f6
11 changed files with 175 additions and 52 deletions

View File

@@ -56,6 +56,7 @@
[ngbPopover]="getFileTypeName(filetype)"
i18n-ngbPopover
triggers="mouseenter:mouseleave"
(click)="filterByFileType(filetype)"
[attr.aria-label]="getFileTypeName(filetype)"
[class.me-1px]="!last"
[style.width]="getFileTypePercent(filetype) + '%'"
@@ -70,7 +71,7 @@
<div class="d-flex flex-wrap align-items-start">
@for (filetype of statistics?.document_file_type_counts; track filetype; let i = $index) {
<div class="d-flex">
<div class="text-nowrap me-2">
<div class="text-nowrap me-2" [class.cursor-pointer]="!filetype.is_other" (click)="filterByFileType(filetype)">
<span class="badge rounded-pill bg-primary d-inline-block p-0 me-1" [style.opacity]="getItemOpacity(i)"></span>
<small class="text-nowrap"><span class="fw-bold">{{ getFileTypeExtension(filetype) }}</span>&nbsp;<span class="text-muted">({{getFileTypePercent(filetype) | number: '1.0-1'}}%)</span></small>
</div>

View File

@@ -9,8 +9,10 @@ import { RouterTestingModule } from '@angular/router/testing'
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { Subject } from 'rxjs'
import { routes } from 'src/app/app-routing.module'
import { FILTER_MIME_TYPE } from 'src/app/data/filter-rule-type'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import {
FileStatus,
WebsocketStatusService,
@@ -24,6 +26,7 @@ describe('StatisticsWidgetComponent', () => {
let fixture: ComponentFixture<StatisticsWidgetComponent>
let httpTestingController: HttpTestingController
let websocketStatusService: WebsocketStatusService
let documentListViewService: DocumentListViewService
const fileStatusSubject = new Subject<FileStatus>()
beforeEach(async () => {
@@ -48,6 +51,7 @@ describe('StatisticsWidgetComponent', () => {
jest
.spyOn(websocketStatusService, 'onDocumentConsumptionFinished')
.mockReturnValue(fileStatusSubject)
documentListViewService = TestBed.inject(DocumentListViewService)
component = fixture.componentInstance
httpTestingController = TestBed.inject(HttpTestingController)
@@ -231,4 +235,26 @@ describe('StatisticsWidgetComponent', () => {
'CurrentASN:'
)
})
it('should support quick filter by mime type', () => {
const qfSpy = jest.spyOn(documentListViewService, 'quickFilter')
component.filterByFileType({
mime_type: 'application/pdf',
mime_type_count: 160,
})
expect(qfSpy).toHaveBeenCalledWith([
{
rule_type: FILTER_MIME_TYPE,
value: 'application/pdf',
},
])
qfSpy.mockClear()
component.filterByFileType({
mime_type: 'Other',
mime_type_count: 160,
is_other: true,
})
expect(qfSpy).not.toHaveBeenCalled()
})
})

View File

@@ -6,7 +6,10 @@ import { NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap'
import * as mimeTypeNames from 'mime-names'
import { first, Subject, Subscription, takeUntil } from 'rxjs'
import { ComponentWithPermissions } from 'src/app/components/with-permissions/with-permissions.component'
import { FILTER_HAS_TAGS_ANY } from 'src/app/data/filter-rule-type'
import {
FILTER_HAS_TAGS_ANY,
FILTER_MIME_TYPE,
} from 'src/app/data/filter-rule-type'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { WebsocketStatusService } from 'src/app/services/websocket-status.service'
@@ -29,6 +32,7 @@ export interface Statistics {
interface DocumentFileType {
mime_type: string
mime_type_count: number
is_other?: boolean
}
@Component({
@@ -77,6 +81,7 @@ export class StatisticsWidgetComponent
statistics.document_file_type_counts.slice(0, fileTypeMax)
statistics.document_file_type_counts.push({
mime_type: $localize`Other`,
is_other: true,
mime_type_count: others.reduce(
(currentValue, documentFileType) =>
documentFileType.mime_type_count + currentValue,
@@ -132,4 +137,14 @@ export class StatisticsWidgetComponent
},
])
}
filterByFileType(filetype: DocumentFileType) {
if (filetype.is_other) return
this.documentListViewService.quickFilter([
{
rule_type: FILTER_MIME_TYPE,
value: filetype.mime_type,
},
])
}
}

View File

@@ -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,12 +1235,30 @@ 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)
)[4]
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'))
const textFieldTargetDropdown = fixture.debugElement.queryAll(
By.directive(NgbDropdownItem)
)[4]
)[5]
textFieldTargetDropdown.triggerEventHandler('click') // TEXT_FILTER_TARGET_ASN
fixture.detectChanges()
tick(400)
@@ -1594,7 +1625,7 @@ describe('FilterEditorComponent', () => {
component.textFilterInput.nativeElement.dispatchEvent(new Event('input'))
const textFieldTargetDropdown = fixture.debugElement.queryAll(
By.directive(NgbDropdownItem)
)[4]
)[5]
textFieldTargetDropdown.triggerEventHandler('click')
fixture.detectChanges()
tick(400)

View File

@@ -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'
@@ -165,6 +167,7 @@ const DEFAULT_TEXT_FILTER_TARGET_OPTIONS = [
id: TEXT_FILTER_TARGET_CUSTOM_FIELDS,
name: $localize`Custom fields`,
},
{ id: TEXT_FILTER_TARGET_MIME_TYPE, name: $localize`File type` },
{
id: TEXT_FILTER_TARGET_FULLTEXT_QUERY,
name: $localize`Advanced search`,
@@ -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

View File

@@ -62,6 +62,8 @@ export const FILTER_HAS_ANY_CUSTOM_FIELDS = 41
export const FILTER_CUSTOM_FIELDS_QUERY = 42
export const FILTER_MIME_TYPE = 47
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 {