Add to the filter editor

This commit is contained in:
shamoon
2026-09-05 09:45:47 -07:00
parent 28f33763cc
commit 4ab91dfea4
3 changed files with 139 additions and 4 deletions
@@ -10,7 +10,7 @@
}
</div>
</div>
@if (textFilterTarget === 'asn') {
@if (textFilterTarget === 'asn' || textFilterTarget === 'duplicates') {
<select class="form-select flex-grow-0 w-auto" [(ngModel)]="textFilterModifier" (change)="textFilterModifierChange()">
@for (m of textFilterModifiers; track m) {
<option ngbDropdownItem [value]="m.id">{{m.label}}</option>
@@ -23,7 +23,7 @@
</button>
}
<input #textFilterInput class="form-control form-control-sm" type="text"
[disabled]="textFilterModifierIsNull"
[disabled]="textFilterInputDisabled"
[(ngModel)]="textFilter"
(keydown)="textFilterKeydown($event)"
[ngbTypeahead]="searchAutoComplete"
@@ -53,6 +53,7 @@ import {
FILTER_HAS_CUSTOM_FIELDS_ALL,
FILTER_HAS_CUSTOM_FIELDS_ANY,
FILTER_HAS_DOCUMENT_TYPE_ANY,
FILTER_HAS_DUPLICATES,
FILTER_HAS_STORAGE_PATH_ANY,
FILTER_HAS_TAGS_ALL,
FILTER_HAS_TAGS_ANY,
@@ -427,6 +428,38 @@ describe('FilterEditorComponent', () => {
expect(component.textFilterTarget).toEqual('mime-type') // TEXT_FILTER_TARGET_MIME_TYPE
})
it('should ingest filter rules for documents with duplicates', () => {
component.filterRules = [
{
rule_type: FILTER_HAS_DUPLICATES,
value: 'true',
},
]
fixture.detectChanges()
expect(component.textFilterTarget).toEqual('duplicates')
expect(component.textFilterModifier).toEqual('has-duplicates')
expect(component.textFilterInputDisabled).toBeTruthy()
})
it('should ingest filter rules for documents without duplicates', () => {
component.filterRules = [
{
rule_type: FILTER_HAS_DUPLICATES,
value: 'false',
},
]
expect(component.textFilterTarget).toEqual('duplicates')
expect(component.textFilterModifier).toEqual('does-not-have-duplicates')
expect(component.filterRules).toEqual([
{
rule_type: FILTER_HAS_DUPLICATES,
value: 'false',
},
])
})
it('should ingest text filter rules for fulltext query', () => {
expect(component.textFilter).toEqual(null)
component.filterRules = [
@@ -1390,6 +1423,33 @@ describe('FilterEditorComponent', () => {
])
})
it('should convert duplicate target input to the correct filter rule', () => {
const textFieldTargetDropdown = fixture.debugElement.queryAll(
By.directive(NgbDropdownItem)
)[5]
textFieldTargetDropdown.triggerEventHandler('click')
fixture.detectChanges()
expect(component.textFilterTarget).toEqual('duplicates')
expect(component.filterRules).toEqual([
{
rule_type: FILTER_HAS_DUPLICATES,
value: 'true',
},
])
const textFieldModifierSelect = fixture.debugElement.query(By.css('select'))
textFieldModifierSelect.nativeElement.value = 'does-not-have-duplicates'
textFieldModifierSelect.nativeElement.dispatchEvent(new Event('change'))
fixture.detectChanges()
expect(component.filterRules).toEqual([
{
rule_type: FILTER_HAS_DUPLICATES,
value: 'false',
},
])
})
it('should convert user input to correct filter rules on full text query', () => {
component.textFilterInput.nativeElement.value = 'foo'
component.textFilterInput.nativeElement.dispatchEvent(new Event('input'))
@@ -2178,6 +2238,22 @@ describe('FilterEditorComponent', () => {
]
expect(component.generateFilterName()).toEqual('Without any tag')
component.filterRules = [
{
rule_type: FILTER_HAS_DUPLICATES,
value: 'true',
},
]
expect(component.generateFilterName()).toEqual('With duplicates')
component.filterRules = [
{
rule_type: FILTER_HAS_DUPLICATES,
value: 'false',
},
]
expect(component.generateFilterName()).toEqual('Without duplicates')
component.filterRules = [
{
rule_type: FILTER_CUSTOM_FIELDS_QUERY,
@@ -65,6 +65,7 @@ import {
FILTER_HAS_CUSTOM_FIELDS_ALL,
FILTER_HAS_CUSTOM_FIELDS_ANY,
FILTER_HAS_DOCUMENT_TYPE_ANY,
FILTER_HAS_DUPLICATES,
FILTER_HAS_STORAGE_PATH_ANY,
FILTER_HAS_TAGS_ALL,
FILTER_HAS_TAGS_ANY,
@@ -129,12 +130,15 @@ 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_TARGET_DUPLICATES = 'duplicates'
const TEXT_FILTER_MODIFIER_EQUALS = 'equals'
const TEXT_FILTER_MODIFIER_NULL = 'is null'
const TEXT_FILTER_MODIFIER_NOTNULL = 'not null'
const TEXT_FILTER_MODIFIER_GT = 'greater'
const TEXT_FILTER_MODIFIER_LT = 'less'
const TEXT_FILTER_MODIFIER_HAS_DUPLICATES = 'has-duplicates'
const TEXT_FILTER_MODIFIER_DOES_NOT_HAVE_DUPLICATES = 'does-not-have-duplicates'
const RELATIVE_DATE_QUERY_REGEXP_CREATED = /created:[\["]([^\]]+)[\]"]/g
const RELATIVE_DATE_QUERY_REGEXP_ADDED = /added:[\["]([^\]]+)[\]"]/g
@@ -205,6 +209,7 @@ const DEFAULT_TEXT_FILTER_TARGET_OPTIONS = [
id: TEXT_FILTER_TARGET_FULLTEXT_QUERY,
name: $localize`Advanced search`,
},
{ id: TEXT_FILTER_TARGET_DUPLICATES, name: $localize`Duplicates` },
]
const DEPRECATED_CUSTOM_FIELDS_TEXT_FILTER_TARGET_OPTION = {
@@ -241,6 +246,17 @@ const DEFAULT_TEXT_FILTER_MODIFIER_OPTIONS = [
},
]
const DUPLICATES_FILTER_MODIFIER_OPTIONS = [
{
id: TEXT_FILTER_MODIFIER_HAS_DUPLICATES,
label: $localize`exist`,
},
{
id: TEXT_FILTER_MODIFIER_DOES_NOT_HAVE_DUPLICATES,
label: $localize`do not exist`,
},
]
@Component({
selector: 'pngx-filter-editor',
templateUrl: './filter-editor.component.html',
@@ -320,6 +336,12 @@ export class FilterEditorComponent
if (rule.value == 'false') {
return $localize`Without any tag`
}
break
case FILTER_HAS_DUPLICATES:
return rule.value == 'false'
? $localize`Without duplicates`
: $localize`With duplicates`
case FILTER_CUSTOM_FIELDS_QUERY:
return $localize`Custom fields query`
@@ -390,7 +412,9 @@ export class FilterEditorComponent
public textFilterModifier: string
get textFilterModifiers() {
return DEFAULT_TEXT_FILTER_MODIFIER_OPTIONS
return this.textFilterTarget === TEXT_FILTER_TARGET_DUPLICATES
? DUPLICATES_FILTER_MODIFIER_OPTIONS
: DEFAULT_TEXT_FILTER_MODIFIER_OPTIONS
}
get textFilterModifierIsNull(): boolean {
@@ -399,6 +423,13 @@ export class FilterEditorComponent
)
}
get textFilterInputDisabled(): boolean {
return (
this.textFilterModifierIsNull ||
this.textFilterTarget === TEXT_FILTER_TARGET_DUPLICATES
)
}
tagSelectionModel = new FilterableDropdownSelectionModel(true)
correspondentSelectionModel = new FilterableDropdownSelectionModel()
documentTypeSelectionModel = new FilterableDropdownSelectionModel()
@@ -444,6 +475,7 @@ export class FilterEditorComponent
this.customFieldQueriesModel.clear(false)
this._textFilter = null
this._moreLikeId = null
this.textFilterTarget = TEXT_FILTER_TARGET_TITLE_CONTENT
this.dateAddedTo = null
this.dateAddedFrom = null
this.dateCreatedTo = null
@@ -477,6 +509,13 @@ export class FilterEditorComponent
this.textFilterTarget = TEXT_FILTER_TARGET_MIME_TYPE
this._textFilter = rule.value
break
case FILTER_HAS_DUPLICATES:
this.textFilterTarget = TEXT_FILTER_TARGET_DUPLICATES
this.textFilterModifier =
rule.value == 'false' || rule.value == '0'
? TEXT_FILTER_MODIFIER_DOES_NOT_HAVE_DUPLICATES
: TEXT_FILTER_MODIFIER_HAS_DUPLICATES
break
case FILTER_FULLTEXT_QUERY:
let allQueryArgs = rule.value.split(',')
let textQueryArgs = []
@@ -800,6 +839,14 @@ export class FilterEditorComponent
value: this._textFilter.trim(),
})
}
if (this.textFilterTarget == TEXT_FILTER_TARGET_DUPLICATES) {
filterRules.push({
rule_type: FILTER_HAS_DUPLICATES,
value: (
this.textFilterModifier == TEXT_FILTER_MODIFIER_HAS_DUPLICATES
).toString(),
})
}
if (this._textFilter && this.textFilterTarget == TEXT_FILTER_TARGET_TITLE) {
filterRules.push({
rule_type: FILTER_SIMPLE_TITLE,
@@ -1163,7 +1210,7 @@ export class FilterEditorComponent
}
get textFilter() {
return this.textFilterModifierIsNull ? '' : this._textFilter
return this.textFilterInputDisabled ? '' : this._textFilter
}
set textFilter(value) {
@@ -1363,12 +1410,24 @@ export class FilterEditorComponent
this._textFilter = ''
}
this.textFilterTarget = target
if (target == TEXT_FILTER_TARGET_DUPLICATES) {
this._textFilter = ''
this.textFilterModifier = TEXT_FILTER_MODIFIER_HAS_DUPLICATES
} else if (
[
TEXT_FILTER_MODIFIER_HAS_DUPLICATES,
TEXT_FILTER_MODIFIER_DOES_NOT_HAVE_DUPLICATES,
].includes(this.textFilterModifier)
) {
this.textFilterModifier = TEXT_FILTER_MODIFIER_EQUALS
}
this.textFilterInput.nativeElement.focus()
this.updateRules()
}
textFilterModifierChange() {
if (
this.textFilterTarget == TEXT_FILTER_TARGET_DUPLICATES ||
this.textFilterModifierIsNull ||
([
TEXT_FILTER_MODIFIER_EQUALS,