mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-08-14 00:26:21 +00:00
Feature: custom fields queries (#7761)
This commit is contained in:
@@ -140,7 +140,7 @@
|
||||
} @else {
|
||||
@if (list.displayMode === DisplayMode.LARGE_CARDS) {
|
||||
<div>
|
||||
@for (d of list.documents; track trackByDocumentId($index, d)) {
|
||||
@for (d of list.documents; track d.id) {
|
||||
<pngx-document-card-large
|
||||
[selected]="list.isSelected(d)"
|
||||
(toggleSelected)="toggleSelected(d, $event)"
|
||||
@@ -269,7 +269,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (d of list.documents; track trackByDocumentId($index, d)) {
|
||||
@for (d of list.documents; track d.id) {
|
||||
<tr (click)="toggleSelected(d, $event); $event.stopPropagation();" (dblclick)="openDocumentDetail(d)" [ngClass]="list.isSelected(d) ? 'table-row-selected' : ''">
|
||||
<td>
|
||||
<div class="form-check">
|
||||
@@ -364,7 +364,7 @@
|
||||
}
|
||||
@if (list.displayMode === DisplayMode.SMALL_CARDS) {
|
||||
<div class="row row-cols-paperless-cards">
|
||||
@for (d of list.documents; track trackByDocumentId($index, d)) {
|
||||
@for (d of list.documents; track d.id) {
|
||||
<pngx-document-card-small class="p-0"
|
||||
[selected]="list.isSelected(d)"
|
||||
(toggleSelected)="toggleSelected(d, $event)"
|
||||
|
@@ -383,10 +383,6 @@ export class DocumentListComponent
|
||||
])
|
||||
}
|
||||
|
||||
trackByDocumentId(index, item: Document) {
|
||||
return item.id
|
||||
}
|
||||
|
||||
get notesEnabled(): boolean {
|
||||
return this.settingsService.get(SETTINGS_KEYS.NOTES_ENABLED)
|
||||
}
|
||||
|
@@ -86,15 +86,10 @@
|
||||
}
|
||||
|
||||
@if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.CustomField) && customFields.length > 0) {
|
||||
<pngx-filterable-dropdown class="flex-fill" title="Custom fields" icon="ui-radios" i18n-title
|
||||
filterPlaceholder="Filter custom fields" i18n-filterPlaceholder
|
||||
[items]="customFields"
|
||||
[manyToOne]="true"
|
||||
[(selectionModel)]="customFieldSelectionModel"
|
||||
<pngx-custom-fields-query-dropdown class="flex-fill" title="Custom fields" icon="ui-radios" i18n-title
|
||||
[(selectionModel)]="customFieldQueriesModel"
|
||||
(selectionModelChange)="updateRules()"
|
||||
(opened)="onCustomFieldsDropdownOpen()"
|
||||
[documentCounts]="customFieldDocumentCounts"
|
||||
[allowSelectNone]="true"></pngx-filterable-dropdown>
|
||||
></pngx-custom-fields-query-dropdown>
|
||||
}
|
||||
<pngx-dates-dropdown
|
||||
title="Dates" i18n-title
|
||||
|
@@ -17,7 +17,7 @@ import {
|
||||
NgbDropdownItem,
|
||||
NgbTypeaheadModule,
|
||||
} from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgSelectComponent } from '@ng-select/ng-select'
|
||||
import { NgSelectComponent, NgSelectModule } from '@ng-select/ng-select'
|
||||
import { of, throwError } from 'rxjs'
|
||||
import {
|
||||
FILTER_TITLE,
|
||||
@@ -55,6 +55,7 @@ import {
|
||||
FILTER_HAS_ANY_CUSTOM_FIELDS,
|
||||
FILTER_DOES_NOT_HAVE_CUSTOM_FIELDS,
|
||||
FILTER_HAS_CUSTOM_FIELDS_ALL,
|
||||
FILTER_CUSTOM_FIELDS_QUERY,
|
||||
} from 'src/app/data/filter-rule-type'
|
||||
import { Correspondent } from 'src/app/data/correspondent'
|
||||
import { DocumentType } from 'src/app/data/document-type'
|
||||
@@ -95,6 +96,12 @@ import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service
|
||||
import { RouterModule } from '@angular/router'
|
||||
import { SearchService } from 'src/app/services/rest/search.service'
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
import { CustomFieldsQueryDropdownComponent } from '../../common/custom-fields-query-dropdown/custom-fields-query-dropdown.component'
|
||||
import {
|
||||
CustomFieldQueryLogicalOperator,
|
||||
CustomFieldQueryOperator,
|
||||
} from 'src/app/data/custom-field-query'
|
||||
import { CustomFieldQueryAtom } from 'src/app/utils/custom-field-query-element'
|
||||
|
||||
const tags: Tag[] = [
|
||||
{
|
||||
@@ -181,6 +188,7 @@ describe('FilterEditorComponent', () => {
|
||||
ToggleableDropdownButtonComponent,
|
||||
DatesDropdownComponent,
|
||||
CustomDatePipe,
|
||||
CustomFieldsQueryDropdownComponent,
|
||||
],
|
||||
imports: [
|
||||
RouterModule,
|
||||
@@ -190,6 +198,7 @@ describe('FilterEditorComponent', () => {
|
||||
NgbDatepickerModule,
|
||||
NgxBootstrapIconsModule.pick(allIcons),
|
||||
NgbTypeaheadModule,
|
||||
NgSelectModule,
|
||||
],
|
||||
providers: [
|
||||
FilterPipe,
|
||||
@@ -838,108 +847,79 @@ describe('FilterEditorComponent', () => {
|
||||
]
|
||||
}))
|
||||
|
||||
it('should ingest filter rules for has all custom fields', fakeAsync(() => {
|
||||
expect(component.customFieldSelectionModel.getSelectedItems()).toHaveLength(
|
||||
0
|
||||
)
|
||||
it('should ingest filter rules for custom fields all', fakeAsync(() => {
|
||||
expect(component.customFieldQueriesModel.isEmpty()).toBeTruthy()
|
||||
component.filterRules = [
|
||||
{
|
||||
rule_type: FILTER_HAS_CUSTOM_FIELDS_ALL,
|
||||
value: '42',
|
||||
},
|
||||
{
|
||||
rule_type: FILTER_HAS_CUSTOM_FIELDS_ALL,
|
||||
value: '43',
|
||||
value: '42,43',
|
||||
},
|
||||
]
|
||||
expect(component.customFieldSelectionModel.logicalOperator).toEqual(
|
||||
LogicalOperator.And
|
||||
expect(component.customFieldQueriesModel.queries[0].operator).toEqual(
|
||||
CustomFieldQueryLogicalOperator.And
|
||||
)
|
||||
expect(component.customFieldSelectionModel.getSelectedItems()).toEqual(
|
||||
custom_fields
|
||||
)
|
||||
// coverage
|
||||
component.filterRules = [
|
||||
{
|
||||
rule_type: FILTER_HAS_CUSTOM_FIELDS_ALL,
|
||||
value: null,
|
||||
},
|
||||
]
|
||||
component.toggleTag(2) // coverage
|
||||
expect(component.customFieldQueriesModel.queries[0].value.length).toEqual(2)
|
||||
expect(
|
||||
(
|
||||
component.customFieldQueriesModel.queries[0]
|
||||
.value[0] as CustomFieldQueryAtom
|
||||
).serialize()
|
||||
).toEqual(['42', CustomFieldQueryOperator.Exists, 'true'])
|
||||
}))
|
||||
|
||||
it('should ingest filter rules for has any custom fields', fakeAsync(() => {
|
||||
expect(component.customFieldSelectionModel.getSelectedItems()).toHaveLength(
|
||||
0
|
||||
)
|
||||
expect(component.customFieldQueriesModel.isEmpty()).toBeTruthy()
|
||||
component.filterRules = [
|
||||
{
|
||||
rule_type: FILTER_HAS_CUSTOM_FIELDS_ANY,
|
||||
value: '42',
|
||||
},
|
||||
{
|
||||
rule_type: FILTER_HAS_CUSTOM_FIELDS_ANY,
|
||||
value: '43',
|
||||
value: '42,43',
|
||||
},
|
||||
]
|
||||
expect(component.customFieldSelectionModel.logicalOperator).toEqual(
|
||||
LogicalOperator.Or
|
||||
expect(component.customFieldQueriesModel.queries[0].operator).toEqual(
|
||||
CustomFieldQueryLogicalOperator.Or
|
||||
)
|
||||
expect(component.customFieldSelectionModel.getSelectedItems()).toEqual(
|
||||
custom_fields
|
||||
)
|
||||
// coverage
|
||||
component.filterRules = [
|
||||
{
|
||||
rule_type: FILTER_HAS_CUSTOM_FIELDS_ANY,
|
||||
value: null,
|
||||
},
|
||||
]
|
||||
expect(component.customFieldQueriesModel.queries[0].value.length).toEqual(2)
|
||||
expect(
|
||||
(
|
||||
component.customFieldQueriesModel.queries[0]
|
||||
.value[0] as CustomFieldQueryAtom
|
||||
).serialize()
|
||||
).toEqual(['42', CustomFieldQueryOperator.Exists, 'true'])
|
||||
}))
|
||||
|
||||
it('should ingest filter rules for has any custom field', fakeAsync(() => {
|
||||
expect(component.customFieldSelectionModel.getSelectedItems()).toHaveLength(
|
||||
0
|
||||
)
|
||||
it('should ingest filter rules for custom field queries', fakeAsync(() => {
|
||||
expect(component.customFieldQueriesModel.isEmpty()).toBeTruthy()
|
||||
component.filterRules = [
|
||||
{
|
||||
rule_type: FILTER_HAS_ANY_CUSTOM_FIELDS,
|
||||
value: '1',
|
||||
rule_type: FILTER_CUSTOM_FIELDS_QUERY,
|
||||
value: '["AND", [[42, "exists", "true"],[43, "exists", "true"]]]',
|
||||
},
|
||||
]
|
||||
expect(component.customFieldSelectionModel.getSelectedItems()).toHaveLength(
|
||||
1
|
||||
expect(component.customFieldQueriesModel.queries[0].operator).toEqual(
|
||||
CustomFieldQueryLogicalOperator.And
|
||||
)
|
||||
expect(component.customFieldSelectionModel.get(null)).toBeTruthy()
|
||||
}))
|
||||
expect(component.customFieldQueriesModel.queries[0].value.length).toEqual(2)
|
||||
expect(
|
||||
(
|
||||
component.customFieldQueriesModel.queries[0]
|
||||
.value[0] as CustomFieldQueryAtom
|
||||
).serialize()
|
||||
).toEqual([42, CustomFieldQueryOperator.Exists, 'true'])
|
||||
|
||||
it('should ingest filter rules for exclude tag(s)', fakeAsync(() => {
|
||||
expect(component.customFieldSelectionModel.getExcludedItems()).toHaveLength(
|
||||
0
|
||||
)
|
||||
// atom
|
||||
component.filterRules = [
|
||||
{
|
||||
rule_type: FILTER_DOES_NOT_HAVE_CUSTOM_FIELDS,
|
||||
value: '42',
|
||||
},
|
||||
{
|
||||
rule_type: FILTER_DOES_NOT_HAVE_CUSTOM_FIELDS,
|
||||
value: '43',
|
||||
},
|
||||
]
|
||||
expect(component.customFieldSelectionModel.logicalOperator).toEqual(
|
||||
LogicalOperator.And
|
||||
)
|
||||
expect(component.customFieldSelectionModel.getExcludedItems()).toEqual(
|
||||
custom_fields
|
||||
)
|
||||
// coverage
|
||||
component.filterRules = [
|
||||
{
|
||||
rule_type: FILTER_DOES_NOT_HAVE_CUSTOM_FIELDS,
|
||||
value: null,
|
||||
rule_type: FILTER_CUSTOM_FIELDS_QUERY,
|
||||
value: '[42, "exists", "true"]',
|
||||
},
|
||||
]
|
||||
expect(component.customFieldQueriesModel.queries[0].value.length).toEqual(1)
|
||||
expect(
|
||||
(
|
||||
component.customFieldQueriesModel.queries[0]
|
||||
.value[0] as CustomFieldQueryAtom
|
||||
).serialize()
|
||||
).toEqual([42, CustomFieldQueryOperator.Exists, 'true'])
|
||||
}))
|
||||
|
||||
it('should ingest filter rules for owner', fakeAsync(() => {
|
||||
@@ -1453,71 +1433,37 @@ describe('FilterEditorComponent', () => {
|
||||
])
|
||||
}))
|
||||
|
||||
it('should convert user input to correct filter rules on custom field select not assigned', fakeAsync(() => {
|
||||
const customFieldsFilterableDropdown = fixture.debugElement.queryAll(
|
||||
By.directive(FilterableDropdownComponent)
|
||||
)[4]
|
||||
customFieldsFilterableDropdown.triggerEventHandler('opened')
|
||||
const customFieldButton = customFieldsFilterableDropdown.queryAll(
|
||||
By.directive(ToggleableDropdownButtonComponent)
|
||||
)[0]
|
||||
customFieldButton.triggerEventHandler('toggle')
|
||||
fixture.detectChanges()
|
||||
expect(component.filterRules).toEqual([
|
||||
{
|
||||
rule_type: FILTER_HAS_ANY_CUSTOM_FIELDS,
|
||||
value: 'false',
|
||||
},
|
||||
])
|
||||
}))
|
||||
|
||||
it('should convert user input to correct filter rules on custom field selections', fakeAsync(() => {
|
||||
const customFieldsFilterableDropdown = fixture.debugElement.queryAll(
|
||||
By.directive(FilterableDropdownComponent)
|
||||
)[4] // CF dropdown
|
||||
customFieldsFilterableDropdown.triggerEventHandler('opened')
|
||||
const customFieldButtons = customFieldsFilterableDropdown.queryAll(
|
||||
By.directive(ToggleableDropdownButtonComponent)
|
||||
const customFieldsQueryDropdown = fixture.debugElement.queryAll(
|
||||
By.directive(CustomFieldsQueryDropdownComponent)
|
||||
)[0]
|
||||
const customFieldToggleButton = customFieldsQueryDropdown.query(
|
||||
By.css('button')
|
||||
)
|
||||
customFieldButtons[1].triggerEventHandler('toggle')
|
||||
customFieldButtons[2].triggerEventHandler('toggle')
|
||||
customFieldToggleButton.triggerEventHandler('click')
|
||||
fixture.detectChanges()
|
||||
expect(component.filterRules).toEqual([
|
||||
{
|
||||
rule_type: FILTER_HAS_CUSTOM_FIELDS_ALL,
|
||||
value: custom_fields[0].id.toString(),
|
||||
},
|
||||
{
|
||||
rule_type: FILTER_HAS_CUSTOM_FIELDS_ALL,
|
||||
value: custom_fields[1].id.toString(),
|
||||
},
|
||||
])
|
||||
const toggleOperatorButtons = customFieldsFilterableDropdown.queryAll(
|
||||
By.css('input[type=radio]')
|
||||
const customFieldButtons = customFieldsQueryDropdown.queryAll(
|
||||
By.css('button')
|
||||
)
|
||||
toggleOperatorButtons[1].nativeElement.checked = true
|
||||
toggleOperatorButtons[1].triggerEventHandler('change')
|
||||
customFieldButtons[1].triggerEventHandler('click')
|
||||
fixture.detectChanges()
|
||||
const query = component.customFieldQueriesModel
|
||||
.queries[0] as CustomFieldQueryAtom
|
||||
query.field = custom_fields[0].id
|
||||
const fieldSelect: NgSelectComponent = customFieldsQueryDropdown.queryAll(
|
||||
By.directive(NgSelectComponent)
|
||||
)[0].componentInstance
|
||||
fieldSelect.open()
|
||||
const options = customFieldsQueryDropdown.queryAll(By.css('.ng-option'))
|
||||
options[0].nativeElement.click()
|
||||
expect(component.customFieldQueriesModel.queries[0].value.length).toEqual(1)
|
||||
expect(component.filterRules).toEqual([
|
||||
{
|
||||
rule_type: FILTER_HAS_CUSTOM_FIELDS_ANY,
|
||||
value: custom_fields[0].id.toString(),
|
||||
},
|
||||
{
|
||||
rule_type: FILTER_HAS_CUSTOM_FIELDS_ANY,
|
||||
value: custom_fields[1].id.toString(),
|
||||
},
|
||||
])
|
||||
customFieldButtons[2].triggerEventHandler('exclude')
|
||||
fixture.detectChanges()
|
||||
expect(component.filterRules).toEqual([
|
||||
{
|
||||
rule_type: FILTER_HAS_CUSTOM_FIELDS_ALL,
|
||||
value: custom_fields[0].id.toString(),
|
||||
},
|
||||
{
|
||||
rule_type: FILTER_DOES_NOT_HAVE_CUSTOM_FIELDS,
|
||||
value: custom_fields[1].id.toString(),
|
||||
rule_type: FILTER_CUSTOM_FIELDS_QUERY,
|
||||
value: JSON.stringify([
|
||||
CustomFieldQueryLogicalOperator.Or,
|
||||
[[custom_fields[0].id, 'exists', 'true']],
|
||||
]),
|
||||
},
|
||||
])
|
||||
}))
|
||||
@@ -1930,21 +1876,11 @@ describe('FilterEditorComponent', () => {
|
||||
|
||||
component.filterRules = [
|
||||
{
|
||||
rule_type: FILTER_HAS_CUSTOM_FIELDS_ALL,
|
||||
value: '42',
|
||||
rule_type: FILTER_CUSTOM_FIELDS_QUERY,
|
||||
value: '["AND",[["42","exists","true"],["43","exists","true"]]]',
|
||||
},
|
||||
]
|
||||
expect(component.generateFilterName()).toEqual(
|
||||
`Custom fields: ${custom_fields[0].name}`
|
||||
)
|
||||
|
||||
component.filterRules = [
|
||||
{
|
||||
rule_type: FILTER_HAS_ANY_CUSTOM_FIELDS,
|
||||
value: 'false',
|
||||
},
|
||||
]
|
||||
expect(component.generateFilterName()).toEqual('Without any custom field')
|
||||
expect(component.generateFilterName()).toEqual(`Custom fields query`)
|
||||
|
||||
component.filterRules = [
|
||||
{
|
||||
|
@@ -12,7 +12,7 @@ import {
|
||||
import { Tag } from 'src/app/data/tag'
|
||||
import { Correspondent } from 'src/app/data/correspondent'
|
||||
import { DocumentType } from 'src/app/data/document-type'
|
||||
import { Observable, Subject, Subscription, from } from 'rxjs'
|
||||
import { Observable, Subject, from } from 'rxjs'
|
||||
import {
|
||||
catchError,
|
||||
debounceTime,
|
||||
@@ -62,7 +62,7 @@ import {
|
||||
FILTER_HAS_CUSTOM_FIELDS_ANY,
|
||||
FILTER_HAS_CUSTOM_FIELDS_ALL,
|
||||
FILTER_HAS_ANY_CUSTOM_FIELDS,
|
||||
FILTER_DOES_NOT_HAVE_CUSTOM_FIELDS,
|
||||
FILTER_CUSTOM_FIELDS_QUERY,
|
||||
} from 'src/app/data/filter-rule-type'
|
||||
import {
|
||||
FilterableDropdownSelectionModel,
|
||||
@@ -92,6 +92,15 @@ import { ComponentWithPermissions } from '../../with-permissions/with-permission
|
||||
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
||||
import { CustomField } from 'src/app/data/custom-field'
|
||||
import { SearchService } from 'src/app/services/rest/search.service'
|
||||
import {
|
||||
CustomFieldQueryLogicalOperator,
|
||||
CustomFieldQueryOperator,
|
||||
} from 'src/app/data/custom-field-query'
|
||||
import { CustomFieldQueriesModel } from '../../common/custom-fields-query-dropdown/custom-fields-query-dropdown.component'
|
||||
import {
|
||||
CustomFieldQueryExpression,
|
||||
CustomFieldQueryAtom,
|
||||
} from 'src/app/utils/custom-field-query-element'
|
||||
|
||||
const TEXT_FILTER_TARGET_TITLE = 'title'
|
||||
const TEXT_FILTER_TARGET_TITLE_CONTENT = 'title-content'
|
||||
@@ -225,15 +234,8 @@ export class FilterEditorComponent
|
||||
return $localize`Without any tag`
|
||||
}
|
||||
|
||||
case FILTER_HAS_CUSTOM_FIELDS_ALL:
|
||||
return $localize`Custom fields: ${
|
||||
this.customFields.find((f) => f.id == +rule.value)?.name
|
||||
}`
|
||||
|
||||
case FILTER_HAS_ANY_CUSTOM_FIELDS:
|
||||
if (rule.value == 'false') {
|
||||
return $localize`Without any custom field`
|
||||
}
|
||||
case FILTER_CUSTOM_FIELDS_QUERY:
|
||||
return $localize`Custom fields query`
|
||||
|
||||
case FILTER_TITLE:
|
||||
return $localize`Title: ${rule.value}`
|
||||
@@ -321,7 +323,7 @@ export class FilterEditorComponent
|
||||
correspondentSelectionModel = new FilterableDropdownSelectionModel()
|
||||
documentTypeSelectionModel = new FilterableDropdownSelectionModel()
|
||||
storagePathSelectionModel = new FilterableDropdownSelectionModel()
|
||||
customFieldSelectionModel = new FilterableDropdownSelectionModel()
|
||||
customFieldQueriesModel = new CustomFieldQueriesModel()
|
||||
|
||||
dateCreatedBefore: string
|
||||
dateCreatedAfter: string
|
||||
@@ -356,7 +358,7 @@ export class FilterEditorComponent
|
||||
this.storagePathSelectionModel.clear(false)
|
||||
this.tagSelectionModel.clear(false)
|
||||
this.correspondentSelectionModel.clear(false)
|
||||
this.customFieldSelectionModel.clear(false)
|
||||
this.customFieldQueriesModel.clear(false)
|
||||
this._textFilter = null
|
||||
this._moreLikeId = null
|
||||
this.dateAddedBefore = null
|
||||
@@ -523,34 +525,45 @@ export class FilterEditorComponent
|
||||
false
|
||||
)
|
||||
break
|
||||
case FILTER_CUSTOM_FIELDS_QUERY:
|
||||
try {
|
||||
const query = JSON.parse(rule.value)
|
||||
if (Array.isArray(query)) {
|
||||
if (query.length === 2) {
|
||||
// expression
|
||||
this.customFieldQueriesModel.addExpression(
|
||||
new CustomFieldQueryExpression(query as any)
|
||||
)
|
||||
} else if (query.length === 3) {
|
||||
// atom
|
||||
this.customFieldQueriesModel.addAtom(
|
||||
new CustomFieldQueryAtom(query as any)
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// error handled by list view service
|
||||
}
|
||||
break
|
||||
// Legacy custom field filters
|
||||
case FILTER_HAS_CUSTOM_FIELDS_ALL:
|
||||
this.customFieldSelectionModel.logicalOperator = LogicalOperator.And
|
||||
this.customFieldSelectionModel.set(
|
||||
rule.value ? +rule.value : null,
|
||||
ToggleableItemState.Selected,
|
||||
false
|
||||
this.customFieldQueriesModel.addExpression(
|
||||
new CustomFieldQueryExpression([
|
||||
CustomFieldQueryLogicalOperator.And,
|
||||
rule.value
|
||||
.split(',')
|
||||
.map((id) => [id, CustomFieldQueryOperator.Exists, 'true']),
|
||||
])
|
||||
)
|
||||
break
|
||||
case FILTER_HAS_CUSTOM_FIELDS_ANY:
|
||||
this.customFieldSelectionModel.logicalOperator = LogicalOperator.Or
|
||||
this.customFieldSelectionModel.set(
|
||||
rule.value ? +rule.value : null,
|
||||
ToggleableItemState.Selected,
|
||||
false
|
||||
)
|
||||
break
|
||||
case FILTER_HAS_ANY_CUSTOM_FIELDS:
|
||||
this.customFieldSelectionModel.set(
|
||||
null,
|
||||
ToggleableItemState.Selected,
|
||||
false
|
||||
)
|
||||
break
|
||||
case FILTER_DOES_NOT_HAVE_CUSTOM_FIELDS:
|
||||
this.customFieldSelectionModel.set(
|
||||
rule.value ? +rule.value : null,
|
||||
ToggleableItemState.Excluded,
|
||||
false
|
||||
this.customFieldQueriesModel.addExpression(
|
||||
new CustomFieldQueryExpression([
|
||||
CustomFieldQueryLogicalOperator.Or,
|
||||
rule.value
|
||||
.split(',')
|
||||
.map((id) => [id, CustomFieldQueryOperator.Exists, 'true']),
|
||||
])
|
||||
)
|
||||
break
|
||||
case FILTER_ASN_ISNULL:
|
||||
@@ -768,34 +781,14 @@ export class FilterEditorComponent
|
||||
})
|
||||
})
|
||||
}
|
||||
if (this.customFieldSelectionModel.isNoneSelected()) {
|
||||
let queries = this.customFieldQueriesModel.queries.map((query) =>
|
||||
query.serialize()
|
||||
)
|
||||
if (queries.length > 0) {
|
||||
filterRules.push({
|
||||
rule_type: FILTER_HAS_ANY_CUSTOM_FIELDS,
|
||||
value: 'false',
|
||||
rule_type: FILTER_CUSTOM_FIELDS_QUERY,
|
||||
value: JSON.stringify(queries[0]),
|
||||
})
|
||||
} else {
|
||||
const customFieldFilterType =
|
||||
this.customFieldSelectionModel.logicalOperator == LogicalOperator.And
|
||||
? FILTER_HAS_CUSTOM_FIELDS_ALL
|
||||
: FILTER_HAS_CUSTOM_FIELDS_ANY
|
||||
this.customFieldSelectionModel
|
||||
.getSelectedItems()
|
||||
.filter((field) => field.id)
|
||||
.forEach((field) => {
|
||||
filterRules.push({
|
||||
rule_type: customFieldFilterType,
|
||||
value: field.id?.toString(),
|
||||
})
|
||||
})
|
||||
this.customFieldSelectionModel
|
||||
.getExcludedItems()
|
||||
.filter((field) => field.id)
|
||||
.forEach((field) => {
|
||||
filterRules.push({
|
||||
rule_type: FILTER_DOES_NOT_HAVE_CUSTOM_FIELDS,
|
||||
value: field.id?.toString(),
|
||||
})
|
||||
})
|
||||
}
|
||||
if (this.dateCreatedBefore) {
|
||||
filterRules.push({
|
||||
@@ -1079,10 +1072,6 @@ export class FilterEditorComponent
|
||||
this.storagePathSelectionModel.apply()
|
||||
}
|
||||
|
||||
onCustomFieldsDropdownOpen() {
|
||||
this.customFieldSelectionModel.apply()
|
||||
}
|
||||
|
||||
updateTextFilter(text, updateRules = true) {
|
||||
this._textFilter = text
|
||||
if (updateRules) {
|
||||
|
Reference in New Issue
Block a user