mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-11-11 09:06:43 -05:00
Fix: ensure custom field query propagation, change detection (#11291)
This commit is contained in:
parent
74f72e417d
commit
85027dbffd
@ -354,5 +354,13 @@ describe('CustomFieldsQueryDropdownComponent', () => {
|
|||||||
model.removeElement(atom)
|
model.removeElement(atom)
|
||||||
expect(completeSpy).toHaveBeenCalled()
|
expect(completeSpy).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should subscribe to existing elements when queries are assigned', () => {
|
||||||
|
const expression = new CustomFieldQueryExpression()
|
||||||
|
const nextSpy = jest.spyOn(model.changed, 'next')
|
||||||
|
model.queries = [expression]
|
||||||
|
expression.changed.next(expression)
|
||||||
|
expect(nextSpy).toHaveBeenCalledWith(model)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -17,7 +17,7 @@ import {
|
|||||||
} from '@ng-bootstrap/ng-bootstrap'
|
} from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { NgSelectComponent, NgSelectModule } from '@ng-select/ng-select'
|
import { NgSelectComponent, NgSelectModule } from '@ng-select/ng-select'
|
||||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||||
import { first, Subject, takeUntil } from 'rxjs'
|
import { first, Subject, Subscription, takeUntil } from 'rxjs'
|
||||||
import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field'
|
import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field'
|
||||||
import {
|
import {
|
||||||
CUSTOM_FIELD_QUERY_MAX_ATOMS,
|
CUSTOM_FIELD_QUERY_MAX_ATOMS,
|
||||||
@ -41,10 +41,27 @@ import { ClearableBadgeComponent } from '../clearable-badge/clearable-badge.comp
|
|||||||
import { DocumentLinkComponent } from '../input/document-link/document-link.component'
|
import { DocumentLinkComponent } from '../input/document-link/document-link.component'
|
||||||
|
|
||||||
export class CustomFieldQueriesModel {
|
export class CustomFieldQueriesModel {
|
||||||
public queries: CustomFieldQueryElement[] = []
|
private _queries: CustomFieldQueryElement[] = []
|
||||||
|
private rootSubscriptions: Subscription[] = []
|
||||||
|
|
||||||
public readonly changed = new Subject<CustomFieldQueriesModel>()
|
public readonly changed = new Subject<CustomFieldQueriesModel>()
|
||||||
|
|
||||||
|
public get queries(): CustomFieldQueryElement[] {
|
||||||
|
return this._queries
|
||||||
|
}
|
||||||
|
|
||||||
|
public set queries(value: CustomFieldQueryElement[]) {
|
||||||
|
this.teardownRootSubscriptions()
|
||||||
|
this._queries = value ?? []
|
||||||
|
for (const element of this._queries) {
|
||||||
|
this.rootSubscriptions.push(
|
||||||
|
element.changed.subscribe(() => {
|
||||||
|
this.changed.next(this)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public clear(fireEvent = true) {
|
public clear(fireEvent = true) {
|
||||||
this.queries = []
|
this.queries = []
|
||||||
if (fireEvent) {
|
if (fireEvent) {
|
||||||
@ -107,14 +124,14 @@ export class CustomFieldQueriesModel {
|
|||||||
public addExpression(
|
public addExpression(
|
||||||
expression: CustomFieldQueryExpression = new CustomFieldQueryExpression()
|
expression: CustomFieldQueryExpression = new CustomFieldQueryExpression()
|
||||||
) {
|
) {
|
||||||
if (this.queries.length > 0) {
|
if (this.queries.length === 0) {
|
||||||
;(
|
this.queries = [expression]
|
||||||
(this.queries[0] as CustomFieldQueryExpression)
|
return
|
||||||
.value as CustomFieldQueryElement[]
|
|
||||||
).push(expression)
|
|
||||||
} else {
|
|
||||||
this.queries.push(expression)
|
|
||||||
}
|
}
|
||||||
|
;(
|
||||||
|
(this.queries[0] as CustomFieldQueryExpression)
|
||||||
|
.value as CustomFieldQueryElement[]
|
||||||
|
).push(expression)
|
||||||
expression.changed.subscribe(() => {
|
expression.changed.subscribe(() => {
|
||||||
this.changed.next(this)
|
this.changed.next(this)
|
||||||
})
|
})
|
||||||
@ -166,6 +183,13 @@ export class CustomFieldQueriesModel {
|
|||||||
this.changed.next(this)
|
this.changed.next(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private teardownRootSubscriptions() {
|
||||||
|
for (const subscription of this.rootSubscriptions) {
|
||||||
|
subscription.unsubscribe()
|
||||||
|
}
|
||||||
|
this.rootSubscriptions = []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { fakeAsync, tick } from '@angular/core/testing'
|
|
||||||
import {
|
import {
|
||||||
CustomFieldQueryElementType,
|
CustomFieldQueryElementType,
|
||||||
CustomFieldQueryLogicalOperator,
|
CustomFieldQueryLogicalOperator,
|
||||||
@ -111,13 +110,38 @@ describe('CustomFieldQueryAtom', () => {
|
|||||||
expect(atom.serialize()).toEqual([1, 'operator', 'value'])
|
expect(atom.serialize()).toEqual([1, 'operator', 'value'])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should emit changed on value change after debounce', fakeAsync(() => {
|
it('should emit changed on value change immediately', () => {
|
||||||
const atom = new CustomFieldQueryAtom()
|
const atom = new CustomFieldQueryAtom()
|
||||||
const changeSpy = jest.spyOn(atom.changed, 'next')
|
const changeSpy = jest.spyOn(atom.changed, 'next')
|
||||||
atom.value = 'new value'
|
atom.value = 'new value'
|
||||||
tick(1000)
|
|
||||||
expect(changeSpy).toHaveBeenCalled()
|
expect(changeSpy).toHaveBeenCalled()
|
||||||
}))
|
})
|
||||||
|
|
||||||
|
it('should ignore duplicate array emissions', () => {
|
||||||
|
const atom = new CustomFieldQueryAtom()
|
||||||
|
atom.operator = CustomFieldQueryOperator.In
|
||||||
|
const changeSpy = jest.fn()
|
||||||
|
atom.changed.subscribe(changeSpy)
|
||||||
|
|
||||||
|
atom.value = [1, 2]
|
||||||
|
expect(changeSpy).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
|
changeSpy.mockClear()
|
||||||
|
atom.value = [1, 2]
|
||||||
|
expect(changeSpy).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should emit when array values differ while length matches', () => {
|
||||||
|
const atom = new CustomFieldQueryAtom()
|
||||||
|
atom.operator = CustomFieldQueryOperator.In
|
||||||
|
const changeSpy = jest.fn()
|
||||||
|
atom.changed.subscribe(changeSpy)
|
||||||
|
|
||||||
|
atom.value = [1, 2]
|
||||||
|
changeSpy.mockClear()
|
||||||
|
atom.value = [1, 3]
|
||||||
|
expect(changeSpy).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('CustomFieldQueryExpression', () => {
|
describe('CustomFieldQueryExpression', () => {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Subject, debounceTime, distinctUntilChanged } from 'rxjs'
|
import { Subject, distinctUntilChanged } from 'rxjs'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import {
|
import {
|
||||||
CUSTOM_FIELD_QUERY_VALUE_TYPES_BY_OPERATOR,
|
CUSTOM_FIELD_QUERY_VALUE_TYPES_BY_OPERATOR,
|
||||||
@ -110,7 +110,22 @@ export class CustomFieldQueryAtom extends CustomFieldQueryElement {
|
|||||||
|
|
||||||
protected override connectValueModelChanged(): void {
|
protected override connectValueModelChanged(): void {
|
||||||
this.valueModelChanged
|
this.valueModelChanged
|
||||||
.pipe(debounceTime(1000), distinctUntilChanged())
|
.pipe(
|
||||||
|
distinctUntilChanged((previous, current) => {
|
||||||
|
if (Array.isArray(previous) && Array.isArray(current)) {
|
||||||
|
if (previous.length !== current.length) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for (let i = 0; i < previous.length; i++) {
|
||||||
|
if (previous[i] !== current[i]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return previous === current
|
||||||
|
})
|
||||||
|
)
|
||||||
.subscribe(() => {
|
.subscribe(() => {
|
||||||
this.changed.next(this)
|
this.changed.next(this)
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user