- @for (t of getTagsLimited$() | async; track t) {
-
+ @for (tagID of tagIDs; track tagID) {
+
}
@if (moreTags) {
@@ -40,7 +40,7 @@
@if (document) {
@if (displayFields.includes(DisplayField.CORRESPONDENT) && document.correspondent) {
- {{(document.correspondent$ | async)?.name ?? privateName}}
+ {{document.correspondent | correspondentName | async}}
@if (displayFields.includes(DisplayField.TITLE)) {:}
}
@if (displayFields.includes(DisplayField.TITLE)) {
@@ -59,14 +59,14 @@
}
@if (displayFields.includes(DisplayField.STORAGE_PATH) && document.storage_path) {
}
@if (displayFields.includes(DisplayField.CREATED)) {
@@ -116,7 +116,7 @@
@if (displayFields.includes(DisplayField.OWNER) && document.owner && document.owner !== settingsService.currentUser.id) {
- {{document.owner | username}}
+ {{document.owner | username | async}}
}
@if (displayFields.includes(DisplayField.SHARED) && document.is_shared_by_requester) {
diff --git a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.spec.ts b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.spec.ts
index 7e6d9da201..63cfc5a50c 100644
--- a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.spec.ts
+++ b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.spec.ts
@@ -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))
diff --git a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.ts b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.ts
index 9d5bcf7040..6e4e3943ee 100644
--- a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.ts
+++ b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.ts
@@ -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() {
diff --git a/src-ui/src/app/components/document-list/document-list.component.html b/src-ui/src/app/components/document-list/document-list.component.html
index 73f7a1ce88..1bcee8a819 100644
--- a/src-ui/src/app/components/document-list/document-list.component.html
+++ b/src-ui/src/app/components/document-list/document-list.component.html
@@ -295,7 +295,7 @@
@if (activeDisplayFields.includes(DisplayField.CORRESPONDENT) && permissionService.currentUserCan(PermissionAction.View, PermissionType.Correspondent)) {
@if (d.correspondent) {
- {{(d.correspondent$ | async)?.name}}
+ {{d.correspondent | correspondentName | async}}
}
|
}
@@ -310,15 +310,15 @@
}
@if (activeDisplayFields.includes(DisplayField.TAGS)) {
- @for (t of d.tags$ | async; track t) {
-
+ @for (tagID of d.tags; track t) {
+
}
}
}
@if (activeDisplayFields.includes(DisplayField.OWNER) && permissionService.currentUserCan(PermissionAction.View, PermissionType.User)) {
- {{d.owner | username}}
+ {{d.owner | username | async}}
|
}
@if (activeDisplayFields.includes(DisplayField.NOTES) && notesEnabled) {
@@ -335,14 +335,14 @@
@if (activeDisplayFields.includes(DisplayField.DOCUMENT_TYPE) && permissionService.currentUserCan(PermissionAction.View, PermissionType.DocumentType)) {
@if (d.document_type) {
- {{(d.document_type$ | async)?.name}}
+ {{d.document_type | documentTypeName | async}}
}
|
}
@if (activeDisplayFields.includes(DisplayField.STORAGE_PATH) && permissionService.currentUserCan(PermissionAction.View, PermissionType.StoragePath)) {
@if (d.storage_path) {
- {{(d.storage_path$ | async)?.name}}
+ {{d.storage_path | storagePathName | async}}
}
|
}
diff --git a/src-ui/src/app/components/document-list/document-list.component.spec.ts b/src-ui/src/app/components/document-list/document-list.component.spec.ts
index dfdab018f5..805a658467 100644
--- a/src-ui/src/app/components/document-list/document-list.component.spec.ts
+++ b/src-ui/src/app/components/document-list/document-list.component.spec.ts
@@ -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
diff --git a/src-ui/src/app/components/document-list/document-list.component.ts b/src-ui/src/app/components/document-list/document-list.component.ts
index a19ac341df..b845a524a0 100644
--- a/src-ui/src/app/components/document-list/document-list.component.ts
+++ b/src-ui/src/app/components/document-list/document-list.component.ts
@@ -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,
diff --git a/src-ui/src/app/data/document.ts b/src-ui/src/app/data/document.ts
index 168fcff922..e5f00148ed 100644
--- a/src-ui/src/app/data/document.ts
+++ b/src-ui/src/app/data/document.ts
@@ -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?: number
- document_type$?: Observable
-
document_type?: number
- storage_path$?: Observable
-
storage_path?: number
title?: string
content?: string
- tags$?: Observable
-
tags?: number[]
checksum?: string
diff --git a/src-ui/src/app/pipes/correspondent-name.pipe.spec.ts b/src-ui/src/app/pipes/correspondent-name.pipe.spec.ts
new file mode 100644
index 0000000000..701011c5b4
--- /dev/null
+++ b/src-ui/src/app/pipes/correspondent-name.pipe.spec.ts
@@ -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()
+ })
+})
diff --git a/src-ui/src/app/pipes/correspondent-name.pipe.ts b/src-ui/src/app/pipes/correspondent-name.pipe.ts
new file mode 100644
index 0000000000..c068c5ccf0
--- /dev/null
+++ b/src-ui/src/app/pipes/correspondent-name.pipe.ts
@@ -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)
+ }
+}
diff --git a/src-ui/src/app/pipes/document-type-name.pipe.spec.ts b/src-ui/src/app/pipes/document-type-name.pipe.spec.ts
new file mode 100644
index 0000000000..20219dc92f
--- /dev/null
+++ b/src-ui/src/app/pipes/document-type-name.pipe.spec.ts
@@ -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()
+ })
+})
diff --git a/src-ui/src/app/pipes/document-type-name.pipe.ts b/src-ui/src/app/pipes/document-type-name.pipe.ts
new file mode 100644
index 0000000000..8bb65206a6
--- /dev/null
+++ b/src-ui/src/app/pipes/document-type-name.pipe.ts
@@ -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)
+ }
+}
diff --git a/src-ui/src/app/pipes/object-name.pipe.spec.ts b/src-ui/src/app/pipes/object-name.pipe.spec.ts
new file mode 100644
index 0000000000..ebe44af7da
--- /dev/null
+++ b/src-ui/src/app/pipes/object-name.pipe.spec.ts
@@ -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
+
+ 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()
+ })
+ })
+})
diff --git a/src-ui/src/app/pipes/object-name.pipe.ts b/src-ui/src/app/pipes/object-name.pipe.ts
new file mode 100644
index 0000000000..89c8613b7b
--- /dev/null
+++ b/src-ui/src/app/pipes/object-name.pipe.ts
@@ -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
+ ) {}
+
+ transform(obejctId: number): Observable {
+ 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`)
+ }
+ }
+}
diff --git a/src-ui/src/app/pipes/storage-path-name.pipe.spec.ts b/src-ui/src/app/pipes/storage-path-name.pipe.spec.ts
new file mode 100644
index 0000000000..d49f99080c
--- /dev/null
+++ b/src-ui/src/app/pipes/storage-path-name.pipe.spec.ts
@@ -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()
+ })
+})
diff --git a/src-ui/src/app/pipes/storage-path-name.pipe.ts b/src-ui/src/app/pipes/storage-path-name.pipe.ts
new file mode 100644
index 0000000000..5a166857ec
--- /dev/null
+++ b/src-ui/src/app/pipes/storage-path-name.pipe.ts
@@ -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)
+ }
+}
diff --git a/src-ui/src/app/pipes/username.pipe.spec.ts b/src-ui/src/app/pipes/username.pipe.spec.ts
index 0838163262..33b21c950d 100644
--- a/src-ui/src/app/pipes/username.pipe.spec.ts
+++ b/src-ui/src/app/pipes/username.pipe.spec.ts
@@ -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')
+ pipe.transform(4).subscribe((username) => {
+ expect(username).toEqual('')
+ done()
+ })
+ })
- username = pipe.transform(4)
- expect(username).toEqual('')
+ 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 generic label when no users retrieved', () => {
+ 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'))
})
})
diff --git a/src-ui/src/app/pipes/username.pipe.ts b/src-ui/src/app/pipes/username.pipe.ts
index 54e8385d80..f8a3be9872 100644
--- a/src-ui/src/app/pipes/username.pipe.ts
+++ b/src-ui/src/app/pipes/username.pipe.ts
@@ -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 {
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(' ')
diff --git a/src-ui/src/app/services/rest/document.service.spec.ts b/src-ui/src/app/services/rest/document.service.spec.ts
index dc358d6c71..4d7d7cef7f 100644
--- a/src-ui/src/app/services/rest/document.service.spec.ts
+++ b/src-ui/src/app/services/rest/document.service.spec.ts
@@ -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
diff --git a/src-ui/src/app/services/rest/document.service.ts b/src-ui/src/app/services/rest/document.service.ts
index e05ab8373f..6bc29276bf 100644
--- a/src-ui/src/app/services/rest/document.service.ts
+++ b/src-ui/src/app/services/rest/document.service.ts
@@ -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 {
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 {
]
}
- 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 {
sortField,
sortReverse,
Object.assign(extraParams, queryParamsFromFilterRules(filterRules))
- ).pipe(
- map((results) => {
- results.results.forEach((doc) => this.addObservablesToDocument(doc))
- return results
- })
)
}
diff --git a/src-ui/src/main.ts b/src-ui/src/main.ts
index 998ebf2609..83aa12dc21 100644
--- a/src-ui/src/main.ts
+++ b/src-ui/src/main.ts
@@ -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))