mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-11-01 20:45:35 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aba16584fe | ||
|
|
5b63c9a0ff | ||
|
|
217c5e681d |
No files matched your search
+1
-1
@@ -56,7 +56,7 @@
|
||||
"@angular/build": "22.2.2",
|
||||
"@angular/cli": "22.2.2",
|
||||
"@angular/compiler-cli": "~22.2.2",
|
||||
"@playwright/test": "^1.62.1",
|
||||
"@playwright/test": "^1.64.1",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/node": "^26.6.2",
|
||||
"@typescript-eslint/eslint-plugin": "^8.70.1",
|
||||
|
||||
Generated
+13
-23
@@ -131,8 +131,8 @@ importers:
|
||||
specifier: ~22.2.2
|
||||
version: 22.2.2(@angular/compiler@22.2.2)(typescript@6.0.3)
|
||||
'@playwright/test':
|
||||
specifier: ^1.62.1
|
||||
version: 1.62.1
|
||||
specifier: ^1.64.1
|
||||
version: 1.64.1
|
||||
'@types/jest':
|
||||
specifier: ^30.0.0
|
||||
version: 30.0.0
|
||||
@@ -1747,8 +1747,8 @@ packages:
|
||||
resolution: {integrity: sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
|
||||
'@playwright/test@1.62.1':
|
||||
resolution: {integrity: sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==}
|
||||
'@playwright/test@1.64.1':
|
||||
resolution: {integrity: sha512-IaYGml/nSOiZWROfPtuIL7neuu9qDOZ3TNvNljUIrhDMxiGTgPjzj/g9p9RxpS2tSepPDRY+UwAaEeiKtUvIAg==}
|
||||
engines: {node: '>=20'}
|
||||
hasBin: true
|
||||
|
||||
@@ -2746,11 +2746,6 @@ packages:
|
||||
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
fsevents@2.3.2:
|
||||
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
@@ -3595,13 +3590,13 @@ packages:
|
||||
resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
playwright-core@1.62.1:
|
||||
resolution: {integrity: sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==}
|
||||
playwright-core@1.64.1:
|
||||
resolution: {integrity: sha512-BC4s8y6SEVA6Q1rSxZTto8hI1KUI3xXlHGZgvGqP4IqPdI/a6FX9H29IDNz7vKo9l21b6LfYosScPL6M3xxyDQ==}
|
||||
engines: {node: '>=20'}
|
||||
hasBin: true
|
||||
|
||||
playwright@1.62.1:
|
||||
resolution: {integrity: sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==}
|
||||
playwright@1.64.1:
|
||||
resolution: {integrity: sha512-HCrVhHr5QQuwYoswEUGTb7gGp522yflbv/ST8nF4sFWGuLjQCt17Q/EubpGGSNKYn1tsTCPZxqHhHCfVURu7UA==}
|
||||
engines: {node: '>=20'}
|
||||
hasBin: true
|
||||
|
||||
@@ -5832,9 +5827,9 @@ snapshots:
|
||||
|
||||
'@pkgr/core@0.3.6': {}
|
||||
|
||||
'@playwright/test@1.62.1':
|
||||
'@playwright/test@1.64.1':
|
||||
dependencies:
|
||||
playwright: 1.62.1
|
||||
playwright: 1.64.1
|
||||
|
||||
'@popperjs/core@2.11.8': {}
|
||||
|
||||
@@ -6752,9 +6747,6 @@ snapshots:
|
||||
cross-spawn: 7.0.6
|
||||
signal-exit: 4.1.0
|
||||
|
||||
fsevents@2.3.2:
|
||||
optional: true
|
||||
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
@@ -7822,13 +7814,11 @@ snapshots:
|
||||
dependencies:
|
||||
find-up: 4.1.0
|
||||
|
||||
playwright-core@1.62.1: {}
|
||||
playwright-core@1.64.1: {}
|
||||
|
||||
playwright@1.62.1:
|
||||
playwright@1.64.1:
|
||||
dependencies:
|
||||
playwright-core: 1.62.1
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
playwright-core: 1.64.1
|
||||
|
||||
postcss-media-query-parser@0.2.3: {}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
+2
@@ -47,6 +47,8 @@
|
||||
<span>{{ change.key | titlecase }}</span>:
|
||||
@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>
|
||||
}
|
||||
|
||||
+34
@@ -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) => {
|
||||
|
||||
+27
-1
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user