Fix/refactor: remove doc observables, fix username async (#8908)

This commit is contained in:
shamoon 2025-01-25 12:38:36 -08:00 committed by GitHub
parent 8f9a294529
commit f3cda54cd1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 509 additions and 220 deletions

View File

@ -324,7 +324,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
<context context-type="linenumber">187</context>
<context context-type="linenumber">193</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/custom-fields/custom-fields.component.html</context>
@ -1128,11 +1128,11 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">63</context>
<context context-type="linenumber">58</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">100</context>
<context context-type="linenumber">95</context>
</context-group>
</trans-unit>
<trans-unit id="293524471897878391" datatype="html">
@ -1712,11 +1712,11 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">39</context>
<context context-type="linenumber">34</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">97</context>
<context context-type="linenumber">92</context>
</context-group>
</trans-unit>
<trans-unit id="5968132631442328843" datatype="html">
@ -2738,7 +2738,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">47</context>
<context context-type="linenumber">42</context>
</context-group>
</trans-unit>
<trans-unit id="4369111787961525769" datatype="html">
@ -3439,11 +3439,11 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">43</context>
<context context-type="linenumber">38</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">98</context>
<context context-type="linenumber">93</context>
</context-group>
</trans-unit>
<trans-unit id="4873149362496451858" datatype="html">
@ -4842,7 +4842,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">99</context>
<context context-type="linenumber">94</context>
</context-group>
</trans-unit>
<trans-unit id="8686921715946540725" datatype="html">
@ -5188,8 +5188,8 @@
<context context-type="linenumber">13</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.ts</context>
<context context-type="linenumber">121</context>
<context context-type="sourcefile">src/app/pipes/object-name.pipe.ts</context>
<context context-type="linenumber">43</context>
</context-group>
</trans-unit>
<trans-unit id="2504502765849142619" datatype="html">
@ -6278,11 +6278,11 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">35</context>
<context context-type="linenumber">30</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">95</context>
<context context-type="linenumber">90</context>
</context-group>
</trans-unit>
<trans-unit id="1379170675585571971" datatype="html">
@ -6319,11 +6319,11 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">51</context>
<context context-type="linenumber">46</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">94</context>
<context context-type="linenumber">89</context>
</context-group>
</trans-unit>
<trans-unit id="5066119607229701477" datatype="html">
@ -6346,11 +6346,11 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">55</context>
<context context-type="linenumber">50</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">96</context>
<context context-type="linenumber">91</context>
</context-group>
</trans-unit>
<trans-unit id="2091353339965748767" datatype="html">
@ -6373,7 +6373,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">59</context>
<context context-type="linenumber">54</context>
</context-group>
</trans-unit>
<trans-unit id="5607669932062416162" datatype="html">
@ -7305,11 +7305,11 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">71</context>
<context context-type="linenumber">66</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/pipes/username.pipe.ts</context>
<context context-type="linenumber">33</context>
<context context-type="linenumber">37</context>
</context-group>
</trans-unit>
<trans-unit id="2332107018974972998" datatype="html">
@ -7373,7 +7373,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
<context context-type="linenumber">304</context>
<context context-type="linenumber">310</context>
</context-group>
</trans-unit>
<trans-unit id="1494518490116523821" datatype="html">
@ -7384,7 +7384,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
<context context-type="linenumber">297</context>
<context context-type="linenumber">303</context>
</context-group>
</trans-unit>
<trans-unit id="8461842260159597706" datatype="html">
@ -7491,11 +7491,11 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">75</context>
<context context-type="linenumber">70</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">93</context>
<context context-type="linenumber">88</context>
</context-group>
</trans-unit>
<trans-unit id="6954625430271090777" datatype="html">
@ -7527,11 +7527,11 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">67</context>
<context context-type="linenumber">62</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">101</context>
<context context-type="linenumber">96</context>
</context-group>
</trans-unit>
<trans-unit id="3557446856808034218" datatype="html">
@ -7584,11 +7584,11 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">79</context>
<context context-type="linenumber">74</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">102</context>
<context context-type="linenumber">97</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/paperless-config.ts</context>
@ -7627,42 +7627,42 @@
<source>Reset filters / selection</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
<context context-type="linenumber">285</context>
<context context-type="linenumber">291</context>
</context-group>
</trans-unit>
<trans-unit id="4135055128446167640" datatype="html">
<source>Open first [selected] document</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
<context context-type="linenumber">313</context>
<context context-type="linenumber">319</context>
</context-group>
</trans-unit>
<trans-unit id="3629960544875360046" datatype="html">
<source>Previous page</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
<context context-type="linenumber">329</context>
<context context-type="linenumber">335</context>
</context-group>
</trans-unit>
<trans-unit id="3337301694210287595" datatype="html">
<source>Next page</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
<context context-type="linenumber">341</context>
<context context-type="linenumber">347</context>
</context-group>
</trans-unit>
<trans-unit id="2155249406916744630" datatype="html">
<source>View &quot;<x id="PH" equiv-text="this.list.activeSavedViewTitle"/>&quot; saved successfully.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
<context context-type="linenumber">373</context>
<context context-type="linenumber">379</context>
</context-group>
</trans-unit>
<trans-unit id="6837554170707123455" datatype="html">
<source>View &quot;<x id="PH" equiv-text="savedView.name"/>&quot; created successfully.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
<context context-type="linenumber">416</context>
<context context-type="linenumber">422</context>
</context-group>
</trans-unit>
<trans-unit id="739880801667335279" datatype="html">
@ -8829,7 +8829,7 @@
<source>Search score</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/document.ts</context>
<context context-type="linenumber">108</context>
<context context-type="linenumber">103</context>
</context-group>
<note priority="1" from="description">Score is a value returned by the full text search engine and specifies how well a result matches the given query</note>
</trans-unit>

View File

@ -1,3 +1,5 @@
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { provideHttpClientTesting } from '@angular/common/http/testing'
import {
ComponentFixture,
TestBed,
@ -51,7 +53,11 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
beforeEach(async () => {
TestBed.configureTestingModule({
providers: [FilterPipe],
providers: [
FilterPipe,
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
],
imports: [NgxBootstrapIconsModule.pick(allIcons)],
}).compileComponents()

View File

@ -1,3 +1,5 @@
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { provideHttpClientTesting } from '@angular/common/http/testing'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { Tag } from 'src/app/data/tag'
import { TagComponent } from '../../tag/tag.component'
@ -12,7 +14,10 @@ describe('ToggleableDropdownButtonComponent', () => {
beforeEach(async () => {
TestBed.configureTestingModule({
providers: [],
providers: [
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
],
imports: [ToggleableDropdownButtonComponent, TagComponent],
}).compileComponents()

View File

@ -1,4 +1,4 @@
@if (tag !== undefined) {
@if (tag) {
@if (!clickable) {
<span class="badge" [style.background]="tag.color" [style.color]="tag.text_color">{{tag.name}}</span>
}

View File

@ -1,6 +1,11 @@
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { provideHttpClientTesting } from '@angular/common/http/testing'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { By } from '@angular/platform-browser'
import { of } from 'rxjs'
import { Tag } from 'src/app/data/tag'
import { PermissionsService } from 'src/app/services/permissions.service'
import { TagService } from 'src/app/services/rest/tag.service'
import { TagComponent } from './tag.component'
const tag: Tag = {
@ -12,13 +17,20 @@ const tag: Tag = {
describe('TagComponent', () => {
let component: TagComponent
let fixture: ComponentFixture<TagComponent>
let permissionsService: PermissionsService
let tagService: TagService
beforeEach(async () => {
TestBed.configureTestingModule({
providers: [],
providers: [
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
],
imports: [TagComponent],
}).compileComponents()
permissionsService = TestBed.inject(PermissionsService)
tagService = TestBed.inject(TagService)
fixture = TestBed.createComponent(TagComponent)
component = fixture.componentInstance
fixture.detectChanges()
@ -47,4 +59,13 @@ describe('TagComponent', () => {
fixture.detectChanges()
expect(fixture.debugElement.query(By.css('a.badge'))).not.toBeNull()
})
it('should support retrieving tag by ID', () => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
const getCachedSpy = jest.spyOn(tagService, 'getCached')
getCachedSpy.mockReturnValue(of(tag))
component.tagID = 1
expect(getCachedSpy).toHaveBeenCalledWith(1)
expect(component.tag).toEqual(tag)
})
})

View File

@ -1,5 +1,11 @@
import { Component, Input } from '@angular/core'
import { Tag } from 'src/app/data/tag'
import {
PermissionAction,
PermissionsService,
PermissionType,
} from 'src/app/services/permissions.service'
import { TagService } from 'src/app/services/rest/tag.service'
@Component({
selector: 'pngx-tag',
@ -7,10 +13,39 @@ import { Tag } from 'src/app/data/tag'
styleUrls: ['./tag.component.scss'],
})
export class TagComponent {
constructor() {}
private _tag: Tag
private _tagID: number
constructor(
private permissionsService: PermissionsService,
private tagService: TagService
) {}
@Input()
tag: Tag
public set tag(tag: Tag) {
this._tag = tag
}
public get tag(): Tag {
return this._tag
}
@Input()
set tagID(tagID: number) {
if (tagID !== this._tagID) {
this._tagID = tagID
if (
this.permissionsService.currentUserCan(
PermissionAction.View,
PermissionType.Tag
)
) {
this.tagService.getCached(this._tagID).subscribe((tag) => {
this.tag = tag
})
}
}
}
@Input()
linkTitle: string = ''

View File

@ -50,27 +50,27 @@
}
@case (DisplayField.CORRESPONDENT) {
@if (doc.correspondent) {
<a class="btn-link text-dark text-decoration-none" type="button" (click)="clickCorrespondent(doc.correspondent, $event)" title="Filter by correspondent" i18n-title>{{(doc.correspondent$ | async)?.name}}</a>
<a class="btn-link text-dark text-decoration-none" type="button" (click)="clickCorrespondent(doc.correspondent, $event)" title="Filter by correspondent" i18n-title>{{doc.correspondent | correspondentName | async}}</a>
}
}
@case (DisplayField.TAGS) {
@for (t of doc.tags$ | async; track t) {
<pngx-tag [tag]="t" class="ms-1" (click)="clickTag(t.id, $event)" [clickable]="true" linkTitle="Filter by tag" i18n-title></pngx-tag>
@for (tagID of doc.tags; track tagID) {
<pngx-tag [tagID]="tagID" class="ms-1" (click)="clickTag(tagID, $event)" [clickable]="true" linkTitle="Filter by tag" i18n-title></pngx-tag>
}
}
@case (DisplayField.DOCUMENT_TYPE) {
@if (doc.document_type) {
<a class="btn-link text-dark text-decoration-none" type="button" (click)="clickDocType(doc.document_type, $event)" title="Filter by document type" i18n-title>{{(doc.document_type$ | async)?.name}}</a>
<a class="btn-link text-dark text-decoration-none" type="button" (click)="clickDocType(doc.document_type, $event)" title="Filter by document type" i18n-title>{{doc.document_type | documentTypeName | async}}</a>
}
}
@case (DisplayField.STORAGE_PATH) {
@if (doc.storage_path) {
<a class="btn-link text-dark text-decoration-none" type="button" (click)="clickStoragePath(doc.storage_path, $event)" title="Filter by storage path" i18n-title>{{(doc.storage_path$ | async)?.name}}</a>
<a class="btn-link text-dark text-decoration-none" type="button" (click)="clickStoragePath(doc.storage_path, $event)" title="Filter by storage path" i18n-title>{{doc.storage_path | storagePathName | async}}</a>
}
}
@case (DisplayField.OWNER) {
@if (doc.owner) {
<a class="btn-link text-dark text-decoration-none" type="button" (click)="clickOwner(doc.owner, $event)" title="Filter by owner" i18n-title>{{doc.owner | username}}</a>
<a class="btn-link text-dark text-decoration-none" type="button" (click)="clickOwner(doc.owner, $event)" title="Filter by owner" i18n-title>{{doc.owner | username | async}}</a>
}
}
@case (DisplayField.ASN) {

View File

@ -36,8 +36,11 @@ import {
} from 'src/app/data/filter-rule-type'
import { SavedView } from 'src/app/data/saved-view'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { CorrespondentNamePipe } from 'src/app/pipes/correspondent-name.pipe'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
import { DocumentTypeNamePipe } from 'src/app/pipes/document-type-name.pipe'
import { StoragePathNamePipe } from 'src/app/pipes/storage-path-name.pipe'
import { UsernamePipe } from 'src/app/pipes/username.pipe'
import { ConsumerStatusService } from 'src/app/services/consumer-status.service'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
@ -65,6 +68,9 @@ import { WidgetFrameComponent } from '../widget-frame/widget-frame.component'
WidgetFrameComponent,
IfPermissionsDirective,
UsernamePipe,
CorrespondentNamePipe,
DocumentTypeNamePipe,
StoragePathNamePipe,
AsyncPipe,
DocumentTitlePipe,
CustomDatePipe,

View File

@ -22,9 +22,9 @@
@if (document) {
@if (displayFields.includes(DisplayField.CORRESPONDENT) && document.correspondent) {
@if (clickCorrespondent.observers.length ) {
<a title="Filter by correspondent" i18n-title (click)="clickCorrespondent.emit(document.correspondent);$event.stopPropagation()" class="fw-bold btn-link">{{(document.correspondent$ | async)?.name}}</a>
<a title="Filter by correspondent" i18n-title (click)="clickCorrespondent.emit(document.correspondent);$event.stopPropagation()" class="fw-bold btn-link">{{document.correspondent | correspondentName | async}}</a>
} @else {
{{(document.correspondent$ | async)?.name}}
{{document.correspondent | correspondentName | async}}
}
@if (displayFields.includes(DisplayField.TITLE)) {:}
}
@ -32,8 +32,8 @@
{{document.title | documentTitle}}
}
@if (displayFields.includes(DisplayField.TAGS)) {
@for (t of document.tags$ | async; track t) {
<pngx-tag [tag]="t" linkTitle="Filter by tag" i18n-linkTitle class="ms-1" (click)="clickTag.emit(t.id);$event.stopPropagation()" [clickable]="clickTag.observers.length"></pngx-tag>
@for (tagID of document.tags; track t) {
<pngx-tag [tagID]="tagID" linkTitle="Filter by tag" i18n-linkTitle class="ms-1" (click)="clickTag.emit(tagID);$event.stopPropagation()" [clickable]="clickTag.observers.length"></pngx-tag>
}
}
} @else {
@ -95,13 +95,13 @@
@if (displayFields.includes(DisplayField.DOCUMENT_TYPE) && document.document_type) {
<button type="button" class="list-group-item btn btn-sm bg-light text-dark p-1 border-0 me-2 d-flex align-items-center" title="Filter by document type" i18n-title
(click)="clickDocumentType.emit(document.document_type);$event.stopPropagation()">
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="file-earmark"></i-bs><small>{{(document.document_type$ | async)?.name}}</small>
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="file-earmark"></i-bs><small>{{document.document_type | documentTypeName | async}}</small>
</button>
}
@if (displayFields.includes(DisplayField.STORAGE_PATH) && document.storage_path) {
<button type="button" class="list-group-item btn btn-sm bg-light text-dark p-1 border-0 me-2 d-flex align-items-center" title="Filter by storage path" i18n-title
(click)="clickStoragePath.emit(document.storage_path);$event.stopPropagation()">
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="archive"></i-bs><small>{{(document.storage_path$ | async)?.name}}</small>
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="archive"></i-bs><small>{{document.storage_path | storagePathName | async}}</small>
</button>
}
@if (displayFields.includes(DisplayField.ASN) && document.archive_serial_number | isNumber) {
@ -136,7 +136,7 @@
}
@if (displayFields.includes(DisplayField.OWNER) && document.owner && document.owner !== settingsService.currentUser.id) {
<div class="list-group-item bg-light text-dark p-1 border-0 d-flex align-items-center">
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="person-fill-lock"></i-bs><small>{{document.owner | username}}</small>
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="person-fill-lock"></i-bs><small>{{document.owner | username | async}}</small>
</div>
}
@if (displayFields.includes(DisplayField.SHARED) && document.is_shared_by_requester) {

View File

@ -21,9 +21,12 @@ import {
} from 'src/app/data/document'
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { CorrespondentNamePipe } from 'src/app/pipes/correspondent-name.pipe'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
import { DocumentTypeNamePipe } from 'src/app/pipes/document-type-name.pipe'
import { IsNumberPipe } from 'src/app/pipes/is-number.pipe'
import { StoragePathNamePipe } from 'src/app/pipes/storage-path-name.pipe'
import { UsernamePipe } from 'src/app/pipes/username.pipe'
import { DocumentService } from 'src/app/services/rest/document.service'
import { SettingsService } from 'src/app/services/settings.service'
@ -44,6 +47,9 @@ import { LoadingComponentWithPermissions } from '../../loading-component/loading
CustomFieldDisplayComponent,
AsyncPipe,
UsernamePipe,
CorrespondentNamePipe,
DocumentTypeNamePipe,
StoragePathNamePipe,
IfPermissionsDirective,
CustomDatePipe,
RouterModule,

View File

@ -16,8 +16,8 @@
@if (document && displayFields?.includes(DisplayField.TAGS)) {
<div class="tags d-flex flex-column text-end position-absolute me-1 fs-6">
@for (t of getTagsLimited$() | async; track t) {
<pngx-tag [tag]="t" (click)="clickTag.emit(t.id);$event.stopPropagation()" [clickable]="true" linkTitle="Toggle tag filter" i18n-linkTitle></pngx-tag>
@for (tagID of tagIDs; track tagID) {
<pngx-tag [tagID]="tagID" (click)="clickTag.emit(tagID);$event.stopPropagation()" [clickable]="true" linkTitle="Toggle tag filter" i18n-linkTitle></pngx-tag>
}
@if (moreTags) {
<div>
@ -40,7 +40,7 @@
<p class="card-text">
@if (document) {
@if (displayFields.includes(DisplayField.CORRESPONDENT) && document.correspondent) {
<a title="Toggle correspondent filter" i18n-title (click)="clickCorrespondent.emit(document.correspondent);$event.stopPropagation()" class="fw-bold btn-link">{{(document.correspondent$ | async)?.name ?? privateName}}</a>
<a title="Toggle correspondent filter" i18n-title (click)="clickCorrespondent.emit(document.correspondent);$event.stopPropagation()" class="fw-bold btn-link">{{document.correspondent | correspondentName | async}}</a>
@if (displayFields.includes(DisplayField.TITLE)) {:}
}
@if (displayFields.includes(DisplayField.TITLE)) {
@ -59,14 +59,14 @@
<button type="button" class="list-group-item list-group-item-action bg-transparent ps-0 p-1 border-0" title="Toggle document type filter" i18n-title
(click)="clickDocumentType.emit(document.document_type);$event.stopPropagation()">
<i-bs width="1em" height="1em" class="me-2 text-muted" name="file-earmark"></i-bs>
<small>{{(document.document_type$ | async)?.name ?? privateName}}</small>
<small>{{document.document_type | documentTypeName | async}}</small>
</button>
}
@if (displayFields.includes(DisplayField.STORAGE_PATH) && document.storage_path) {
<button type="button" class="list-group-item list-group-item-action bg-transparent ps-0 p-1 border-0" title="Toggle storage path filter" i18n-title
(click)="clickStoragePath.emit(document.storage_path);$event.stopPropagation()">
<i-bs width="1em" height="1em" class="me-2 text-muted" name="folder"></i-bs>
<small>{{(document.storage_path$ | async)?.name ?? privateName}}</small>
<small>{{document.storage_path | storagePathName | async}}</small>
</button>
}
@if (displayFields.includes(DisplayField.CREATED)) {
@ -116,7 +116,7 @@
@if (displayFields.includes(DisplayField.OWNER) && document.owner && document.owner !== settingsService.currentUser.id) {
<div class="ps-0 p-1">
<i-bs width="1em" height="1em" class="me-2 text-muted" name="person-fill-lock"></i-bs>
<small>{{document.owner | username}}</small>
<small>{{document.owner | username | async}}</small>
</div>
}
@if (displayFields.includes(DisplayField.SHARED) && document.is_shared_by_requester) {

View File

@ -5,8 +5,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'
import { By } from '@angular/platform-browser'
import { RouterTestingModule } from '@angular/router/testing'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { of } from 'rxjs'
import { Tag } from 'src/app/data/tag'
import { TagComponent } from '../../common/tag/tag.component'
import { DocumentCardSmallComponent } from './document-card-small.component'
@ -24,16 +22,6 @@ const doc = {
note: 'This is some note content bananas',
},
],
tags$: of([
{ id: 1, name: 'Tag1' },
{ id: 2, name: 'Tag2' },
{ id: 3, name: 'Tag3' },
{ id: 4, name: 'Tag4' },
{ id: 5, name: 'Tag5' },
{ id: 6, name: 'Tag6' },
{ id: 7, name: 'Tag7' },
{ id: 8, name: 'Tag8' },
]),
content:
'Cupcake ipsum dolor sit amet ice cream. Donut shortbread cheesecake caramels tiramisu pastry caramels chocolate bar. Tart tootsie roll muffin icing cotton candy topping sweet roll. Pie lollipop dragée sesame snaps donut tart pudding. Oat cake apple pie danish danish candy canes. Shortbread candy canes sesame snaps muffin tiramisu marshmallow chocolate bar halvah. Cake lemon drops candy apple pie carrot cake bonbon halvah pastry gummi bears. Sweet roll candy ice cream sesame snaps marzipan cookie ice cream. Cake cheesecake apple pie muffin candy toffee lollipop. Carrot cake oat cake cookie biscuit cupcake cake marshmallow. Sweet roll jujubes carrot cake cheesecake cake candy canes sweet roll gingerbread jelly beans. Apple pie sugar plum oat cake halvah cake. Pie oat cake chocolate cake cookie gingerbread marzipan. Lemon drops cheesecake lollipop danish marzipan candy.',
}
@ -80,7 +68,6 @@ describe('DocumentCardSmallComponent', () => {
fixture.debugElement.queryAll(By.directive(TagComponent))
).toHaveLength(5)
component.document.tags = [1, 2]
component.document.tags$ = of([{ id: 1 } as Tag, { id: 2 } as Tag])
fixture.detectChanges()
expect(
fixture.debugElement.queryAll(By.directive(TagComponent))

View File

@ -14,7 +14,7 @@ import {
} from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { of } from 'rxjs'
import { delay, map } from 'rxjs/operators'
import { delay } from 'rxjs/operators'
import {
DEFAULT_DISPLAY_FIELDS,
DisplayField,
@ -22,9 +22,12 @@ import {
} from 'src/app/data/document'
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { CorrespondentNamePipe } from 'src/app/pipes/correspondent-name.pipe'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
import { DocumentTypeNamePipe } from 'src/app/pipes/document-type-name.pipe'
import { IsNumberPipe } from 'src/app/pipes/is-number.pipe'
import { StoragePathNamePipe } from 'src/app/pipes/storage-path-name.pipe'
import { UsernamePipe } from 'src/app/pipes/username.pipe'
import { DocumentService } from 'src/app/services/rest/document.service'
import { SettingsService } from 'src/app/services/settings.service'
@ -45,6 +48,9 @@ import { LoadingComponentWithPermissions } from '../../loading-component/loading
CustomFieldDisplayComponent,
AsyncPipe,
UsernamePipe,
CorrespondentNamePipe,
DocumentTypeNamePipe,
StoragePathNamePipe,
IfPermissionsDirective,
CustomDatePipe,
RouterModule,
@ -117,22 +123,14 @@ export class DocumentCardSmallComponent
return this.documentService.getDownloadUrl(this.document.id)
}
get privateName() {
return $localize`Private`
}
getTagsLimited$() {
get tagIDs() {
const limit = this.document.notes.length > 0 ? 6 : 7
return this.document.tags$?.pipe(
map((tags) => {
if (tags.length > limit) {
this.moreTags = tags.length - (limit - 1)
return tags.slice(0, limit - 1)
} else {
return tags
}
})
)
if (this.document.tags.length > limit) {
this.moreTags = this.document.tags.length - (limit - 1)
return this.document.tags.slice(0, limit - 1)
} else {
return this.document.tags
}
}
mouseLeaveCard() {

View File

@ -295,7 +295,7 @@
@if (activeDisplayFields.includes(DisplayField.CORRESPONDENT) && permissionService.currentUserCan(PermissionAction.View, PermissionType.Correspondent)) {
<td class="">
@if (d.correspondent) {
<a (click)="clickCorrespondent(d.correspondent);$event.stopPropagation()" title="Filter by correspondent" i18n-title>{{(d.correspondent$ | async)?.name}}</a>
<a (click)="clickCorrespondent(d.correspondent);$event.stopPropagation()" title="Filter by correspondent" i18n-title>{{d.correspondent | correspondentName | async}}</a>
}
</td>
}
@ -310,15 +310,15 @@
</div>
}
@if (activeDisplayFields.includes(DisplayField.TAGS)) {
@for (t of d.tags$ | async; track t) {
<pngx-tag [tag]="t" class="ms-1" clickable="true" linkTitle="Filter by tag" i18n-linkTitle (click)="clickTag(t.id);$event.stopPropagation()"></pngx-tag>
@for (tagID of d.tags; track t) {
<pngx-tag [tagID]="tagID" class="ms-1" clickable="true" linkTitle="Filter by tag" i18n-linkTitle (click)="clickTag(tagID);$event.stopPropagation()"></pngx-tag>
}
}
</td>
}
@if (activeDisplayFields.includes(DisplayField.OWNER) && permissionService.currentUserCan(PermissionAction.View, PermissionType.User)) {
<td>
{{d.owner | username}}
{{d.owner | username | async}}
</td>
}
@if (activeDisplayFields.includes(DisplayField.NOTES) && notesEnabled) {
@ -335,14 +335,14 @@
@if (activeDisplayFields.includes(DisplayField.DOCUMENT_TYPE) && permissionService.currentUserCan(PermissionAction.View, PermissionType.DocumentType)) {
<td class="">
@if (d.document_type) {
<a (click)="clickDocumentType(d.document_type);$event.stopPropagation()" title="Filter by document type" i18n-title>{{(d.document_type$ | async)?.name}}</a>
<a (click)="clickDocumentType(d.document_type);$event.stopPropagation()" title="Filter by document type" i18n-title>{{d.document_type | documentTypeName | async}}</a>
}
</td>
}
@if (activeDisplayFields.includes(DisplayField.STORAGE_PATH) && permissionService.currentUserCan(PermissionAction.View, PermissionType.StoragePath)) {
<td class="">
@if (d.storage_path) {
<a (click)="clickStoragePath(d.storage_path);$event.stopPropagation()" title="Filter by storage path" i18n-title>{{(d.storage_path$ | async)?.name}}</a>
<a (click)="clickStoragePath(d.storage_path);$event.stopPropagation()" title="Filter by storage path" i18n-title>{{d.storage_path | storagePathName | async}}</a>
}
</td>
}

View File

@ -57,21 +57,21 @@ const docs: Document[] = [
id: 1,
title: 'Doc1',
notes: [],
tags$: new Subject(),
tags: [],
content: 'document content 1',
},
{
id: 2,
title: 'Doc2',
notes: [],
tags$: new Subject(),
tags: [],
content: 'document content 2',
},
{
id: 3,
title: 'Doc3',
notes: [],
tags$: new Subject(),
tags: [],
content: 'document content 3',
},
]
@ -650,7 +650,6 @@ describe('DocumentListComponent', () => {
id: i + 1,
title: `Doc${i + 1}`,
notes: [],
tags$: new Subject(),
content: `document content ${i + 1}`,
}))
jest

View File

@ -37,8 +37,11 @@ import {
SortableDirective,
SortEvent,
} from 'src/app/directives/sortable.directive'
import { CorrespondentNamePipe } from 'src/app/pipes/correspondent-name.pipe'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
import { DocumentTypeNamePipe } from 'src/app/pipes/document-type-name.pipe'
import { StoragePathNamePipe } from 'src/app/pipes/storage-path-name.pipe'
import { UsernamePipe } from 'src/app/pipes/username.pipe'
import { ConsumerStatusService } from 'src/app/services/consumer-status.service'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
@ -81,6 +84,9 @@ import { SaveViewConfigDialogComponent } from './save-view-config-dialog/save-vi
IfPermissionsDirective,
SortableDirective,
UsernamePipe,
CorrespondentNamePipe,
DocumentTypeNamePipe,
StoragePathNamePipe,
NgxBootstrapIconsModule,
AsyncPipe,
FormsModule,

View File

@ -1,11 +1,6 @@
import { Observable } from 'rxjs'
import { Correspondent } from './correspondent'
import { CustomFieldInstance } from './custom-field-instance'
import { DocumentNote } from './document-note'
import { DocumentType } from './document-type'
import { ObjectWithPermissions } from './object-with-permissions'
import { StoragePath } from './storage-path'
import { Tag } from './tag'
export enum DisplayMode {
TABLE = 'table',
@ -118,24 +113,16 @@ export interface SearchHit {
}
export interface Document extends ObjectWithPermissions {
correspondent$?: Observable<Correspondent>
correspondent?: number
document_type$?: Observable<DocumentType>
document_type?: number
storage_path$?: Observable<StoragePath>
storage_path?: number
title?: string
content?: string
tags$?: Observable<Tag[]>
tags?: number[]
checksum?: string

View File

@ -0,0 +1,28 @@
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { provideHttpClientTesting } from '@angular/common/http/testing'
import { TestBed } from '@angular/core/testing'
import { PermissionsService } from '../services/permissions.service'
import { CorrespondentService } from '../services/rest/correspondent.service'
import { CorrespondentNamePipe } from './correspondent-name.pipe'
describe('CorrespondentNamePipe', () => {
let pipe: CorrespondentNamePipe
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
],
})
})
// The pipe is a simple wrapper around ObjectNamePipe, see ObjectNamePipe for the actual tests.
it('should be created', () => {
pipe = new CorrespondentNamePipe(
TestBed.inject(PermissionsService),
TestBed.inject(CorrespondentService)
)
expect(pipe).toBeTruthy()
})
})

View File

@ -0,0 +1,22 @@
import { Pipe, PipeTransform } from '@angular/core'
import {
PermissionsService,
PermissionType,
} from '../services/permissions.service'
import { CorrespondentService } from '../services/rest/correspondent.service'
import { ObjectNamePipe } from './object-name.pipe'
@Pipe({
name: 'correspondentName',
})
export class CorrespondentNamePipe
extends ObjectNamePipe
implements PipeTransform
{
constructor(
permissionsService: PermissionsService,
objectService: CorrespondentService
) {
super(permissionsService, PermissionType.Correspondent, objectService)
}
}

View File

@ -0,0 +1,28 @@
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { provideHttpClientTesting } from '@angular/common/http/testing'
import { TestBed } from '@angular/core/testing'
import { PermissionsService } from '../services/permissions.service'
import { DocumentTypeService } from '../services/rest/document-type.service'
import { DocumentTypeNamePipe } from './document-type-name.pipe'
describe('DocumentTypeNamePipe', () => {
let pipe: DocumentTypeNamePipe
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
],
})
})
// The pipe is a simple wrapper around ObjectNamePipe, see ObjectNamePipe for the actual tests.
it('should be created', () => {
pipe = new DocumentTypeNamePipe(
TestBed.inject(PermissionsService),
TestBed.inject(DocumentTypeService)
)
expect(pipe).toBeTruthy()
})
})

View File

@ -0,0 +1,22 @@
import { Pipe, PipeTransform } from '@angular/core'
import {
PermissionsService,
PermissionType,
} from '../services/permissions.service'
import { DocumentTypeService } from '../services/rest/document-type.service'
import { ObjectNamePipe } from './object-name.pipe'
@Pipe({
name: 'documentTypeName',
})
export class DocumentTypeNamePipe
extends ObjectNamePipe
implements PipeTransform
{
constructor(
permissionsService: PermissionsService,
objectService: DocumentTypeService
) {
super(permissionsService, PermissionType.DocumentType, objectService)
}
}

View File

@ -0,0 +1,88 @@
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { provideHttpClientTesting } from '@angular/common/http/testing'
import { TestBed } from '@angular/core/testing'
import { of, throwError } from 'rxjs'
import { MatchingModel } from '../data/matching-model'
import { PermissionsService } from '../services/permissions.service'
import { AbstractNameFilterService } from '../services/rest/abstract-name-filter-service'
import { CorrespondentService } from '../services/rest/correspondent.service'
import { CorrespondentNamePipe } from './correspondent-name.pipe'
import { ObjectNamePipe } from './object-name.pipe'
describe('ObjectNamePipe', () => {
/*
ObjectNamePipe is an abstract class to prevent instantiation,
so we test the concrete implementation CorrespondentNamePipe instead.
*/
let pipe: CorrespondentNamePipe
let permissionsService: PermissionsService
let objectService: AbstractNameFilterService<MatchingModel>
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
ObjectNamePipe,
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
],
})
permissionsService = TestBed.inject(PermissionsService)
objectService = TestBed.inject(CorrespondentService)
pipe = new CorrespondentNamePipe(permissionsService, objectService)
})
it('should return object name if user has permission', (done) => {
const mockObjects = {
results: [
{ id: 1, name: 'Object 1' },
{ id: 2, name: 'Object 2' },
],
count: 2,
all: [1, 2],
}
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
jest.spyOn(objectService, 'listAll').mockReturnValue(of(mockObjects))
pipe.transform(1).subscribe((result) => {
expect(result).toBe('Object 1')
done()
})
})
it('should return empty string if object not found', (done) => {
const mockObjects = {
results: [{ id: 2, name: 'Object 2' }],
count: 1,
all: [2],
}
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
jest.spyOn(objectService, 'listAll').mockReturnValue(of(mockObjects))
pipe.transform(1).subscribe((result) => {
expect(result).toBe('')
done()
})
})
it('should return "Private" if user does not have permission', (done) => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(false)
pipe.transform(1).subscribe((result) => {
expect(result).toBe('Private')
done()
})
})
it('should handle error and return empty string', (done) => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
jest
.spyOn(objectService, 'listAll')
.mockReturnValueOnce(throwError(() => new Error('Error getting objects')))
pipe.transform(1).subscribe((result) => {
expect(result).toBe('')
done()
})
})
})

View File

@ -0,0 +1,46 @@
import { Pipe, PipeTransform } from '@angular/core'
import { catchError, map, Observable, of } from 'rxjs'
import { MatchingModel } from '../data/matching-model'
import {
PermissionAction,
PermissionsService,
PermissionType,
} from '../services/permissions.service'
import { AbstractNameFilterService } from '../services/rest/abstract-name-filter-service'
@Pipe({
name: 'objectName',
})
export abstract class ObjectNamePipe implements PipeTransform {
/*
ObjectNamePipe is an abstract class to prevent instantiation,
object-specific pipes extend this class and provide the
correct permission type, and object service.
*/
protected objects: MatchingModel[]
constructor(
protected permissionsService: PermissionsService,
protected permissionType: PermissionType,
protected objectService: AbstractNameFilterService<MatchingModel>
) {}
transform(obejctId: number): Observable<string> {
if (
this.permissionsService.currentUserCan(
PermissionAction.View,
this.permissionType
)
) {
return this.objectService.listAll().pipe(
map((objects) => {
this.objects = objects.results
return this.objects.find((o) => o.id === obejctId)?.name || ''
}),
catchError(() => of(''))
)
} else {
return of($localize`Private`)
}
}
}

View File

@ -0,0 +1,28 @@
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { provideHttpClientTesting } from '@angular/common/http/testing'
import { TestBed } from '@angular/core/testing'
import { PermissionsService } from '../services/permissions.service'
import { StoragePathService } from '../services/rest/storage-path.service'
import { StoragePathNamePipe } from './storage-path-name.pipe'
describe('StoragePathNamePipe', () => {
let pipe: StoragePathNamePipe
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
],
})
})
// The pipe is a simple wrapper around ObjectNamePipe, see ObjectNamePipe for the actual tests.
it('should be created', () => {
pipe = new StoragePathNamePipe(
TestBed.inject(PermissionsService),
TestBed.inject(StoragePathService)
)
expect(pipe).toBeTruthy()
})
})

View File

@ -0,0 +1,22 @@
import { Pipe, PipeTransform } from '@angular/core'
import {
PermissionsService,
PermissionType,
} from '../services/permissions.service'
import { StoragePathService } from '../services/rest/storage-path.service'
import { ObjectNamePipe } from './object-name.pipe'
@Pipe({
name: 'storagePathName',
})
export class StoragePathNamePipe
extends ObjectNamePipe
implements PipeTransform
{
constructor(
permissionsService: PermissionsService,
objectService: StoragePathService
) {
super(permissionsService, PermissionType.StoragePath, objectService)
}
}

View File

@ -37,7 +37,11 @@ describe('UsernamePipe', () => {
httpTestingController.verify()
})
it('should transform user id to username', () => {
it('should transform user id to username', (done) => {
pipe.transform(2).subscribe((username) => {
expect(username).toEqual('username2')
})
const req = httpTestingController.expectOne(
`${environment.apiBaseUrl}users/?page=1&page_size=100000`
)
@ -55,24 +59,39 @@ describe('UsernamePipe', () => {
},
],
})
pipe.transform(3).subscribe((username) => {
expect(username).toEqual('User Name3')
})
let username = pipe.transform(2)
expect(username).toEqual('username2')
username = pipe.transform(3)
expect(username).toEqual('User Name3')
username = pipe.transform(4)
expect(username).toEqual('')
pipe.transform(4).subscribe((username) => {
expect(username).toEqual('')
done()
})
})
it('should show generic label when no users retrieved', () => {
it('should show generic label when insufficient permissions', (done) => {
jest
.spyOn(permissionsService, 'currentUserCan')
.mockImplementation((action, type) => {
return false
})
pipe.transform(4).subscribe((username) => {
expect(username).toEqual('Shared')
done()
})
httpTestingController.expectNone(
`${environment.apiBaseUrl}users/?page=1&page_size=100000`
)
})
it('should show empty string when no users retrieved due to error', (done) => {
pipe.transform(4).subscribe((username) => {
expect(username).toEqual('')
done()
})
const req = httpTestingController.expectOne(
`${environment.apiBaseUrl}users/?page=1&page_size=100000`
)
req.flush(null)
let username = pipe.transform(4)
expect(username).toEqual('Shared')
req.error(new ProgressEvent('error'))
})
})

View File

@ -1,9 +1,10 @@
import { Pipe, PipeTransform } from '@angular/core'
import { catchError, map, Observable, of } from 'rxjs'
import { User } from '../data/user'
import {
PermissionAction,
PermissionType,
PermissionsService,
PermissionType,
} from '../services/permissions.service'
import { UserService } from '../services/rest/user.service'
@ -14,25 +15,29 @@ export class UsernamePipe implements PipeTransform {
users: User[]
constructor(
permissionsService: PermissionsService,
userService: UserService
) {
private permissionsService: PermissionsService,
private userService: UserService
) {}
transform(userID: number): Observable<string> {
if (
permissionsService.currentUserCan(
this.permissionsService.currentUserCan(
PermissionAction.View,
PermissionType.User
)
) {
userService.listAll().subscribe((r) => (this.users = r.results))
return this.userService.listAll().pipe(
map((users) => {
this.users = users.results
return this.getName(this.users.find((u) => u.id === userID))
}),
catchError(() => of(''))
)
} else {
return of($localize`Shared`)
}
}
transform(userID: number): string {
return this.users
? (this.getName(this.users.find((u) => u.id === userID)) ?? '')
: $localize`Shared`
}
getName(user: User): string {
if (!user) return ''
const name = [user.first_name, user.last_name].join(' ')

View File

@ -251,26 +251,6 @@ describe(`DocumentService`, () => {
)
})
it('should add observables to document', () => {
subscription = service
.listFiltered(1, 25, 'title', false, [])
.subscribe((result) => {
expect(result.results).toHaveLength(3)
const doc = result.results[0]
expect(doc.correspondent$).not.toBeNull()
expect(doc.document_type$).not.toBeNull()
expect(doc.tags$).not.toBeNull()
expect(doc.storage_path$).not.toBeNull()
})
httpTestingController
.expectOne(
`${environment.apiBaseUrl}${endpoint}/?page=1&page_size=25&ordering=title`
)
.flush({
results: documents,
})
})
it('should set search query', () => {
const searchQuery = 'hello'
service.searchQuery = searchQuery

View File

@ -1,7 +1,7 @@
import { HttpClient } from '@angular/common/http'
import { Injectable } from '@angular/core'
import { Observable } from 'rxjs'
import { map, tap } from 'rxjs/operators'
import { map } from 'rxjs/operators'
import { AuditLogEntry } from 'src/app/data/auditlog-entry'
import { CustomField } from 'src/app/data/custom-field'
import {
@ -22,11 +22,7 @@ import {
} from '../permissions.service'
import { SettingsService } from '../settings.service'
import { AbstractPaperlessService } from './abstract-paperless-service'
import { CorrespondentService } from './correspondent.service'
import { CustomFieldsService } from './custom-fields.service'
import { DocumentTypeService } from './document-type.service'
import { StoragePathService } from './storage-path.service'
import { TagService } from './tag.service'
export interface SelectionDataItem {
id: number
@ -61,10 +57,6 @@ export class DocumentService extends AbstractPaperlessService<Document> {
constructor(
http: HttpClient,
private correspondentService: CorrespondentService,
private documentTypeService: DocumentTypeService,
private tagService: TagService,
private storagePathService: StoragePathService,
private permissionsService: PermissionsService,
private settingsService: SettingsService,
private customFieldService: CustomFieldsService
@ -137,54 +129,6 @@ export class DocumentService extends AbstractPaperlessService<Document> {
]
}
addObservablesToDocument(doc: Document) {
if (
doc.correspondent &&
this.permissionsService.currentUserCan(
PermissionAction.View,
PermissionType.Correspondent
)
) {
doc.correspondent$ = this.correspondentService.getCached(
doc.correspondent
)
}
if (
doc.document_type &&
this.permissionsService.currentUserCan(
PermissionAction.View,
PermissionType.DocumentType
)
) {
doc.document_type$ = this.documentTypeService.getCached(doc.document_type)
}
if (
doc.tags &&
this.permissionsService.currentUserCan(
PermissionAction.View,
PermissionType.Tag
)
) {
doc.tags$ = this.tagService
.getCachedMany(doc.tags)
.pipe(
tap((tags) =>
tags.sort((tagA, tagB) => tagA.name.localeCompare(tagB.name))
)
)
}
if (
doc.storage_path &&
this.permissionsService.currentUserCan(
PermissionAction.View,
PermissionType.StoragePath
)
) {
doc.storage_path$ = this.storagePathService.getCached(doc.storage_path)
}
return doc
}
listFiltered(
page?: number,
pageSize?: number,
@ -199,11 +143,6 @@ export class DocumentService extends AbstractPaperlessService<Document> {
sortField,
sortReverse,
Object.assign(extraParams, queryParamsFromFilterRules(filterRules))
).pipe(
map((results) => {
results.results.forEach((doc) => this.addObservablesToDocument(doc))
return results
})
)
}

View File

@ -180,6 +180,9 @@ import localeSv from '@angular/common/locales/sv'
import localeTr from '@angular/common/locales/tr'
import localeUk from '@angular/common/locales/uk'
import localeZh from '@angular/common/locales/zh'
import { CorrespondentNamePipe } from './app/pipes/correspondent-name.pipe'
import { DocumentTypeNamePipe } from './app/pipes/document-type-name.pipe'
import { StoragePathNamePipe } from './app/pipes/storage-path-name.pipe'
registerLocaleData(localeAf)
registerLocaleData(localeAr)
@ -375,6 +378,9 @@ bootstrapApplication(AppComponent, {
DirtyDocGuard,
DirtySavedViewGuard,
UsernamePipe,
CorrespondentNamePipe,
DocumentTypeNamePipe,
StoragePathNamePipe,
provideHttpClient(withInterceptorsFromDi()),
],
}).catch((err) => console.error(err))