Fix: add root tag filtering for tag list page consistency, fix toggle all (#11208)

This commit is contained in:
shamoon
2025-10-28 11:04:22 -07:00
committed by GitHub
parent ce112cda0e
commit d718d7d29f
6 changed files with 117 additions and 3 deletions

View File

@@ -361,4 +361,11 @@ describe('ManagementListComponent', () => {
const original = component.getOriginalObject({ id: 4 } as Tag)
expect(original).toEqual(childTag)
})
it('getSelectableIDs should return flat ids when not overridden', () => {
const ids = (
ManagementListComponent.prototype as any
).getSelectableIDs.call({}, [{ id: 1 }, { id: 5 }] as any)
expect(ids).toEqual([1, 5])
})
})

View File

@@ -297,13 +297,19 @@ export abstract class ManagementListComponent<T extends MatchingModel>
}
toggleAll(event: PointerEvent) {
if ((event.target as HTMLInputElement).checked) {
this.selectedObjects = new Set(this.data.map((o) => o.id))
const checked = (event.target as HTMLInputElement).checked
this.togggleAll = checked
if (checked) {
this.selectedObjects = new Set(this.getSelectableIDs(this.data))
} else {
this.clearSelection()
}
}
protected getSelectableIDs(objects: T[]): number[] {
return objects.map((o) => o.id)
}
clearSelection() {
this.togggleAll = false
this.selectedObjects.clear()

View File

@@ -17,6 +17,7 @@ describe('TagListComponent', () => {
let component: TagListComponent
let fixture: ComponentFixture<TagListComponent>
let tagService: TagService
let listFilteredSpy: jest.SpyInstance
beforeEach(async () => {
TestBed.configureTestingModule({
@@ -39,7 +40,7 @@ describe('TagListComponent', () => {
}).compileComponents()
tagService = TestBed.inject(TagService)
jest.spyOn(tagService, 'listFiltered').mockReturnValue(
listFilteredSpy = jest.spyOn(tagService, 'listFiltered').mockReturnValue(
of({
count: 3,
all: [1, 2, 3],
@@ -87,4 +88,57 @@ describe('TagListComponent', () => {
const filteredWithName = component.filterData(tags as any)
expect(filteredWithName.length).toBe(3)
})
it('should request only parent tags when no name filter is applied', () => {
expect(tagService.listFiltered).toHaveBeenCalledWith(
1,
null,
undefined,
undefined,
undefined,
true,
{ is_root: true }
)
})
it('should include child tags when a name filter is applied', () => {
listFilteredSpy.mockClear()
component['_nameFilter'] = 'Tag'
component.reloadData()
expect(tagService.listFiltered).toHaveBeenCalledWith(
1,
null,
undefined,
undefined,
'Tag',
true,
null
)
})
it('should include child tags when selecting all', () => {
const parent = {
id: 10,
name: 'Parent',
children: [
{
id: 11,
name: 'Child',
},
],
}
component.data = [parent as any]
const selectEvent = { target: { checked: true } } as unknown as PointerEvent
component.toggleAll(selectEvent)
expect(component.selectedObjects.has(10)).toBe(true)
expect(component.selectedObjects.has(11)).toBe(true)
const deselectEvent = {
target: { checked: false },
} as unknown as PointerEvent
component.toggleAll(deselectEvent)
expect(component.selectedObjects.size).toBe(0)
})
})

View File

@@ -61,9 +61,29 @@ export class TagListComponent extends ManagementListComponent<Tag> {
return $localize`Do you really want to delete the tag "${object.name}"?`
}
override reloadData(extraParams: { [key: string]: any } = null) {
const params = this.nameFilter?.length
? extraParams
: { ...extraParams, is_root: true }
super.reloadData(params)
}
filterData(data: Tag[]) {
return this.nameFilter?.length
? [...data]
: data.filter((tag) => !tag.parent)
}
protected override getSelectableIDs(tags: Tag[]): number[] {
const ids: number[] = []
for (const tag of tags.filter(Boolean)) {
if (tag.id != null) {
ids.push(tag.id)
}
if (Array.isArray(tag.children) && tag.children.length) {
ids.push(...this.getSelectableIDs(tag.children))
}
}
return ids
}
}

View File

@@ -92,6 +92,12 @@ class TagFilterSet(FilterSet):
"name": CHAR_KWARGS,
}
is_root = BooleanFilter(
label="Is root tag",
field_name="tn_parent",
lookup_expr="isnull",
)
class DocumentTypeFilterSet(FilterSet):
class Meta:

View File

@@ -229,3 +229,24 @@ class TestTagHierarchy(APITestCase):
assert resp_ok.status_code in (200, 202)
x.refresh_from_db()
assert x.parent_pk == c.id
def test_is_root_filter_returns_only_root_tags(self):
other_root = Tag.objects.create(name="Other parent")
response = self.client.get(
"/api/tags/",
{"is_root": "true"},
)
assert response.status_code == 200
assert response.data["count"] == 2
returned_ids = {row["id"] for row in response.data["results"]}
assert self.child.pk not in returned_ids
assert self.parent.pk in returned_ids
assert other_root.pk in returned_ids
parent_entry = next(
row for row in response.data["results"] if row["id"] == self.parent.pk
)
assert any(child["id"] == self.child.pk for child in parent_entry["children"])