diff --git a/src-ui/src/app/components/document-detail/document-history/document-history.component.html b/src-ui/src/app/components/document-detail/document-history/document-history.component.html
index e23ca6c07e..2c2ed0a7fb 100644
--- a/src-ui/src/app/components/document-detail/document-history/document-history.component.html
+++ b/src-ui/src/app/components/document-detail/document-history/document-history.component.html
@@ -47,6 +47,8 @@
{{ change.key | titlecase }}:
@if (change.key === 'content') {
{{ change.value[1]?.substring(0,100) }}...
+ } @else if (change.key === 'tags') {
+ {{ getTagNames($any(change.value[1])) | async }}
} @else {
{{ getPrettyName(change.key, change.value[1]) | async }}
}
diff --git a/src-ui/src/app/components/document-detail/document-history/document-history.component.spec.ts b/src-ui/src/app/components/document-detail/document-history/document-history.component.spec.ts
index c1845c8e47..1b00482101 100644
--- a/src-ui/src/app/components/document-detail/document-history/document-history.component.spec.ts
+++ b/src-ui/src/app/components/document-detail/document-history/document-history.component.spec.ts
@@ -12,6 +12,7 @@ import { CorrespondentService } from 'src/app/services/rest/correspondent.servic
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
import { DocumentService } from 'src/app/services/rest/document.service'
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
+import { TagService } from 'src/app/services/rest/tag.service'
import { UserService } from 'src/app/services/rest/user.service'
import { DocumentHistoryComponent } from './document-history.component'
@@ -174,6 +175,39 @@ describe('DocumentHistoryComponent', () => {
})
})
+ it('should render bulk tag history with names and preserve missing tag IDs', () => {
+ const getCachedSpy = jest
+ .spyOn(TestBed.inject(TagService), 'getCached')
+ .mockImplementation((id) =>
+ of(id === 1 ? { id: 1, name: 'Invoice' } : null)
+ )
+ component.entries.set([
+ {
+ id: 1,
+ action: AuditLogAction.Update,
+ timestamp: '2021-01-01T00:00:00Z',
+ remote_addr: null,
+ changes: JSON.parse('{"tags":[[],[1,2345]]}'),
+ },
+ ])
+ component.loading.set(false)
+ fixture.detectChanges()
+
+ expect(fixture.nativeElement.querySelector('code').textContent).toBe(
+ 'Invoice, 2345'
+ )
+ fixture.detectChanges()
+ expect(getCachedSpy).toHaveBeenCalledTimes(2)
+ })
+
+ it('should handle removal of all tags without a lookup', () => {
+ const getCachedSpy = jest.spyOn(TestBed.inject(TagService), 'getCached')
+ component.getTagNames([]).subscribe((result) => {
+ expect(result).toBe('')
+ })
+ expect(getCachedSpy).not.toHaveBeenCalled()
+ })
+
it('getPrettyName should return the value as is for other types', () => {
const id = '123'
component.getPrettyName('other', id).subscribe((result) => {
diff --git a/src-ui/src/app/components/document-detail/document-history/document-history.component.ts b/src-ui/src/app/components/document-detail/document-history/document-history.component.ts
index d1cb069f47..37d999139c 100644
--- a/src-ui/src/app/components/document-detail/document-history/document-history.component.ts
+++ b/src-ui/src/app/components/document-detail/document-history/document-history.component.ts
@@ -2,7 +2,7 @@ import { AsyncPipe, KeyValuePipe, TitleCasePipe } from '@angular/common'
import { Component, Input, OnInit, inject, signal } from '@angular/core'
import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
-import { Observable, first, map, of, shareReplay } from 'rxjs'
+import { Observable, first, forkJoin, map, of, shareReplay } from 'rxjs'
import { AuditLogAction, AuditLogEntry } from 'src/app/data/auditlog-entry'
import { DataType } from 'src/app/data/datatype'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
@@ -10,6 +10,7 @@ import { CorrespondentService } from 'src/app/services/rest/correspondent.servic
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
import { DocumentService } from 'src/app/services/rest/document.service'
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
+import { TagService } from 'src/app/services/rest/tag.service'
import { UserService } from 'src/app/services/rest/user.service'
@Component({
@@ -30,6 +31,7 @@ export class DocumentHistoryComponent implements OnInit {
private readonly correspondentService = inject(CorrespondentService)
private readonly storagePathService = inject(StoragePathService)
private readonly documentTypeService = inject(DocumentTypeService)
+ private readonly tagService = inject(TagService)
private readonly userService = inject(UserService)
public AuditLogAction = AuditLogAction
@@ -63,6 +65,30 @@ export class DocumentHistoryComponent implements OnInit {
}
}
+ getTagNames(tagIds: number[]): Observable {
+ const cacheKey = `tags:${JSON.stringify(tagIds)}`
+ const cachedNames = this.prettyNameCache.get(cacheKey)
+ if (cachedNames) {
+ return cachedNames
+ }
+
+ const tagNames$ = tagIds.length
+ ? forkJoin(
+ tagIds.map((tagId) =>
+ this.tagService.getCached(tagId).pipe(
+ first(),
+ map((tag) => tag?.name ?? String(tagId))
+ )
+ )
+ ).pipe(map((names) => names.join(', ')))
+ : of('')
+ const names$ = tagNames$.pipe(
+ shareReplay({ bufferSize: 1, refCount: true })
+ )
+ this.prettyNameCache.set(cacheKey, names$)
+ return names$
+ }
+
getPrettyName(type: DataType | string, id: string): Observable {
const cacheKey = `${type}:${id}`
const cached = this.prettyNameCache.get(cacheKey)