Compare commits

..
Author SHA1 Message Date
dependabot[bot] 02a7af248a docker-compose(deps): Bump gotenberg/gotenberg in /docker/compose
Bumps gotenberg/gotenberg from 8.37 to 8.38.

---
updated-dependencies:
- dependency-name: gotenberg/gotenberg
  dependency-version: '8.38'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-11-01 20:20:08 +00:00
shamoon 5b63c9a0ff Fixhancement: add tag names to document history list (#14625) 2026-11-01 05:39:52 -08:00
shamoon 217c5e681d Fix: add button type to zoom buttons to prevent form submission (#14624) 2026-11-01 05:35:38 -08:00
14 changed files with 75 additions and 19 deletions

No files matched your search

+1 -2
View File
@@ -8,8 +8,7 @@ on:
- 'translations**'
workflow_dispatch:
concurrency:
# Let dev/main commits finish, PR and feature-branch runs supersede older ones
group: backend-${{ github.event.pull_request.number || ((github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main') && github.run_id) || github.ref }}
group: backend-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
env:
DEFAULT_UV_VERSION: "0.13.x"
+1 -2
View File
@@ -13,8 +13,7 @@ on:
- main
workflow_dispatch:
concurrency:
# Let dev/main commits finish, PR and feature-branch runs supersede older ones
group: docker-${{ github.event.pull_request.number || ((github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main') && github.run_id) || github.ref }}
group: docker-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
env:
REGISTRY: ghcr.io
+1 -2
View File
@@ -6,8 +6,7 @@ on:
pull_request:
workflow_dispatch:
concurrency:
# Let dev/main commits finish, PR and feature-branch runs supersede older ones
group: docs-${{ github.event.pull_request.number || ((github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main') && github.run_id) || github.ref }}
group: docs-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
+1 -2
View File
@@ -8,8 +8,7 @@ on:
- 'translations**'
workflow_dispatch:
concurrency:
# Let dev/main commits finish, PR and feature-branch runs supersede older ones
group: frontend-${{ github.event.pull_request.number || ((github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main') && github.run_id) || github.ref }}
group: frontend-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions: {}
jobs:
+1 -2
View File
@@ -7,8 +7,7 @@ on:
branches-ignore:
- 'translations**'
concurrency:
# Let dev/main commits finish, PR and feature-branch runs supersede older ones
group: lint-${{ github.event.pull_request.number || ((github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main') && github.run_id) || github.ref }}
group: lint-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
+1 -2
View File
@@ -8,8 +8,7 @@ on:
- 'translations**'
workflow_dispatch:
concurrency:
# Let dev/main commits finish, PR and feature-branch runs supersede older ones
group: static-analysis-${{ github.event.pull_request.number || ((github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main') && github.run_id) || github.ref }}
group: static-analysis-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
+1 -1
View File
@@ -4,7 +4,7 @@
# correct networking for the tests
services:
gotenberg:
image: docker.io/gotenberg/gotenberg:8.37
image: docker.io/gotenberg/gotenberg:8.38
hostname: gotenberg
container_name: gotenberg
network_mode: host
@@ -71,7 +71,7 @@ services:
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
gotenberg:
image: docker.io/gotenberg/gotenberg:8.37
image: docker.io/gotenberg/gotenberg:8.38
restart: unless-stopped
# The gotenberg chromium route is used to convert .eml files. We do not
# want to allow external content like tracking pixels or even javascript.
@@ -67,7 +67,7 @@ services:
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
gotenberg:
image: docker.io/gotenberg/gotenberg:8.37
image: docker.io/gotenberg/gotenberg:8.38
restart: unless-stopped
# The gotenberg chromium route is used to convert .eml files. We do not
# want to allow external content like tracking pixels or even javascript.
@@ -56,7 +56,7 @@ services:
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
gotenberg:
image: docker.io/gotenberg/gotenberg:8.37
image: docker.io/gotenberg/gotenberg:8.38
restart: unless-stopped
# The gotenberg chromium route is used to convert .eml files. We do not
# want to allow external content like tracking pixels or even javascript.
@@ -485,7 +485,7 @@
</div>
}
<div class="input-group input-group-sm w-auto flex-nowrap">
<button class="btn btn-outline-secondary" (click)="decreaseZoom()" aria-label="Zoom out" i18n-aria-label>
<button class="btn btn-outline-secondary" type="button" (click)="decreaseZoom()" aria-label="Zoom out" i18n-aria-label>
<i-bs width="1em" height="1em" name="dash"></i-bs>
</button>
<select class="form-select" (change)="setZoom($event.target.value)" [ngModelOptions]="{standalone: true}" [ngModel]="currentZoom" aria-label="Zoom" i18n-aria-label>
@@ -495,7 +495,7 @@
</option>
}
</select>
<button class="btn btn-outline-secondary" (click)="increaseZoom()" aria-label="Zoom in" i18n-aria-label>
<button class="btn btn-outline-secondary" type="button" (click)="increaseZoom()" aria-label="Zoom in" i18n-aria-label>
<i-bs width="1em" height="1em" name="plus"></i-bs>
</button>
</div>
@@ -47,6 +47,8 @@
<span>{{ change.key | titlecase }}</span>:&nbsp;
@if (change.key === 'content') {
<code class="text-primary">{{ change.value[1]?.substring(0,100) }}...</code>
} @else if (change.key === 'tags') {
<code class="text-primary">{{ getTagNames($any(change.value[1])) | async }}</code>
} @else {
<code class="text-primary">{{ getPrettyName(change.key, change.value[1]) | async }}</code>
}
@@ -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) => {
@@ -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<string> {
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<string> {
const cacheKey = `${type}:${id}`
const cached = this.prettyNameCache.get(cacheKey)