mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-02 13:45:10 -05:00
Fix/refactor: remove doc observables, fix username async (#8908)
This commit is contained in:
parent
8f9a294529
commit
f3cda54cd1
@ -324,7 +324,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
<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>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/manage/custom-fields/custom-fields.component.html</context>
|
<context context-type="sourcefile">src/app/components/manage/custom-fields/custom-fields.component.html</context>
|
||||||
@ -1128,11 +1128,11 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/data/document.ts</context>
|
<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>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/data/document.ts</context>
|
<context context-type="sourcefile">src/app/data/document.ts</context>
|
||||||
<context context-type="linenumber">100</context>
|
<context context-type="linenumber">95</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="293524471897878391" datatype="html">
|
<trans-unit id="293524471897878391" datatype="html">
|
||||||
@ -1712,11 +1712,11 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/data/document.ts</context>
|
<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>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/data/document.ts</context>
|
<context context-type="sourcefile">src/app/data/document.ts</context>
|
||||||
<context context-type="linenumber">97</context>
|
<context context-type="linenumber">92</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5968132631442328843" datatype="html">
|
<trans-unit id="5968132631442328843" datatype="html">
|
||||||
@ -2738,7 +2738,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/data/document.ts</context>
|
<context context-type="sourcefile">src/app/data/document.ts</context>
|
||||||
<context context-type="linenumber">47</context>
|
<context context-type="linenumber">42</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4369111787961525769" datatype="html">
|
<trans-unit id="4369111787961525769" datatype="html">
|
||||||
@ -3439,11 +3439,11 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/data/document.ts</context>
|
<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>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/data/document.ts</context>
|
<context context-type="sourcefile">src/app/data/document.ts</context>
|
||||||
<context context-type="linenumber">98</context>
|
<context context-type="linenumber">93</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4873149362496451858" datatype="html">
|
<trans-unit id="4873149362496451858" datatype="html">
|
||||||
@ -4842,7 +4842,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/data/document.ts</context>
|
<context context-type="sourcefile">src/app/data/document.ts</context>
|
||||||
<context context-type="linenumber">99</context>
|
<context context-type="linenumber">94</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8686921715946540725" datatype="html">
|
<trans-unit id="8686921715946540725" datatype="html">
|
||||||
@ -5188,8 +5188,8 @@
|
|||||||
<context context-type="linenumber">13</context>
|
<context context-type="linenumber">13</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<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="sourcefile">src/app/pipes/object-name.pipe.ts</context>
|
||||||
<context context-type="linenumber">121</context>
|
<context context-type="linenumber">43</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2504502765849142619" datatype="html">
|
<trans-unit id="2504502765849142619" datatype="html">
|
||||||
@ -6278,11 +6278,11 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/data/document.ts</context>
|
<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>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/data/document.ts</context>
|
<context context-type="sourcefile">src/app/data/document.ts</context>
|
||||||
<context context-type="linenumber">95</context>
|
<context context-type="linenumber">90</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1379170675585571971" datatype="html">
|
<trans-unit id="1379170675585571971" datatype="html">
|
||||||
@ -6319,11 +6319,11 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/data/document.ts</context>
|
<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>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/data/document.ts</context>
|
<context context-type="sourcefile">src/app/data/document.ts</context>
|
||||||
<context context-type="linenumber">94</context>
|
<context context-type="linenumber">89</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5066119607229701477" datatype="html">
|
<trans-unit id="5066119607229701477" datatype="html">
|
||||||
@ -6346,11 +6346,11 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/data/document.ts</context>
|
<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>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/data/document.ts</context>
|
<context context-type="sourcefile">src/app/data/document.ts</context>
|
||||||
<context context-type="linenumber">96</context>
|
<context context-type="linenumber">91</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2091353339965748767" datatype="html">
|
<trans-unit id="2091353339965748767" datatype="html">
|
||||||
@ -6373,7 +6373,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/data/document.ts</context>
|
<context context-type="sourcefile">src/app/data/document.ts</context>
|
||||||
<context context-type="linenumber">59</context>
|
<context context-type="linenumber">54</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5607669932062416162" datatype="html">
|
<trans-unit id="5607669932062416162" datatype="html">
|
||||||
@ -7305,11 +7305,11 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/data/document.ts</context>
|
<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>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/pipes/username.pipe.ts</context>
|
<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>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2332107018974972998" datatype="html">
|
<trans-unit id="2332107018974972998" datatype="html">
|
||||||
@ -7373,7 +7373,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
<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>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1494518490116523821" datatype="html">
|
<trans-unit id="1494518490116523821" datatype="html">
|
||||||
@ -7384,7 +7384,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
<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>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8461842260159597706" datatype="html">
|
<trans-unit id="8461842260159597706" datatype="html">
|
||||||
@ -7491,11 +7491,11 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/data/document.ts</context>
|
<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>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/data/document.ts</context>
|
<context context-type="sourcefile">src/app/data/document.ts</context>
|
||||||
<context context-type="linenumber">93</context>
|
<context context-type="linenumber">88</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6954625430271090777" datatype="html">
|
<trans-unit id="6954625430271090777" datatype="html">
|
||||||
@ -7527,11 +7527,11 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/data/document.ts</context>
|
<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>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/data/document.ts</context>
|
<context context-type="sourcefile">src/app/data/document.ts</context>
|
||||||
<context context-type="linenumber">101</context>
|
<context context-type="linenumber">96</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3557446856808034218" datatype="html">
|
<trans-unit id="3557446856808034218" datatype="html">
|
||||||
@ -7584,11 +7584,11 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/data/document.ts</context>
|
<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>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/data/document.ts</context>
|
<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>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/data/paperless-config.ts</context>
|
<context context-type="sourcefile">src/app/data/paperless-config.ts</context>
|
||||||
@ -7627,42 +7627,42 @@
|
|||||||
<source>Reset filters / selection</source>
|
<source>Reset filters / selection</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
<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>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4135055128446167640" datatype="html">
|
<trans-unit id="4135055128446167640" datatype="html">
|
||||||
<source>Open first [selected] document</source>
|
<source>Open first [selected] document</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
<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>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3629960544875360046" datatype="html">
|
<trans-unit id="3629960544875360046" datatype="html">
|
||||||
<source>Previous page</source>
|
<source>Previous page</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
<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>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3337301694210287595" datatype="html">
|
<trans-unit id="3337301694210287595" datatype="html">
|
||||||
<source>Next page</source>
|
<source>Next page</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
<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>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2155249406916744630" datatype="html">
|
<trans-unit id="2155249406916744630" datatype="html">
|
||||||
<source>View "<x id="PH" equiv-text="this.list.activeSavedViewTitle"/>" saved successfully.</source>
|
<source>View "<x id="PH" equiv-text="this.list.activeSavedViewTitle"/>" saved successfully.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
<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>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6837554170707123455" datatype="html">
|
<trans-unit id="6837554170707123455" datatype="html">
|
||||||
<source>View "<x id="PH" equiv-text="savedView.name"/>" created successfully.</source>
|
<source>View "<x id="PH" equiv-text="savedView.name"/>" created successfully.</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
<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>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="739880801667335279" datatype="html">
|
<trans-unit id="739880801667335279" datatype="html">
|
||||||
@ -8829,7 +8829,7 @@
|
|||||||
<source>Search score</source>
|
<source>Search score</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">src/app/data/document.ts</context>
|
<context context-type="sourcefile">src/app/data/document.ts</context>
|
||||||
<context context-type="linenumber">108</context>
|
<context context-type="linenumber">103</context>
|
||||||
</context-group>
|
</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>
|
<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>
|
</trans-unit>
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||||
|
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||||
import {
|
import {
|
||||||
ComponentFixture,
|
ComponentFixture,
|
||||||
TestBed,
|
TestBed,
|
||||||
@ -51,7 +53,11 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
providers: [FilterPipe],
|
providers: [
|
||||||
|
FilterPipe,
|
||||||
|
provideHttpClient(withInterceptorsFromDi()),
|
||||||
|
provideHttpClientTesting(),
|
||||||
|
],
|
||||||
imports: [NgxBootstrapIconsModule.pick(allIcons)],
|
imports: [NgxBootstrapIconsModule.pick(allIcons)],
|
||||||
}).compileComponents()
|
}).compileComponents()
|
||||||
|
|
||||||
|
@ -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 { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||||
import { Tag } from 'src/app/data/tag'
|
import { Tag } from 'src/app/data/tag'
|
||||||
import { TagComponent } from '../../tag/tag.component'
|
import { TagComponent } from '../../tag/tag.component'
|
||||||
@ -12,7 +14,10 @@ describe('ToggleableDropdownButtonComponent', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
providers: [],
|
providers: [
|
||||||
|
provideHttpClient(withInterceptorsFromDi()),
|
||||||
|
provideHttpClientTesting(),
|
||||||
|
],
|
||||||
imports: [ToggleableDropdownButtonComponent, TagComponent],
|
imports: [ToggleableDropdownButtonComponent, TagComponent],
|
||||||
}).compileComponents()
|
}).compileComponents()
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
@if (tag !== undefined) {
|
@if (tag) {
|
||||||
@if (!clickable) {
|
@if (!clickable) {
|
||||||
<span class="badge" [style.background]="tag.color" [style.color]="tag.text_color">{{tag.name}}</span>
|
<span class="badge" [style.background]="tag.color" [style.color]="tag.text_color">{{tag.name}}</span>
|
||||||
}
|
}
|
||||||
|
@ -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 { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||||
import { By } from '@angular/platform-browser'
|
import { By } from '@angular/platform-browser'
|
||||||
|
import { of } from 'rxjs'
|
||||||
import { Tag } from 'src/app/data/tag'
|
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'
|
import { TagComponent } from './tag.component'
|
||||||
|
|
||||||
const tag: Tag = {
|
const tag: Tag = {
|
||||||
@ -12,13 +17,20 @@ const tag: Tag = {
|
|||||||
describe('TagComponent', () => {
|
describe('TagComponent', () => {
|
||||||
let component: TagComponent
|
let component: TagComponent
|
||||||
let fixture: ComponentFixture<TagComponent>
|
let fixture: ComponentFixture<TagComponent>
|
||||||
|
let permissionsService: PermissionsService
|
||||||
|
let tagService: TagService
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
providers: [],
|
providers: [
|
||||||
|
provideHttpClient(withInterceptorsFromDi()),
|
||||||
|
provideHttpClientTesting(),
|
||||||
|
],
|
||||||
imports: [TagComponent],
|
imports: [TagComponent],
|
||||||
}).compileComponents()
|
}).compileComponents()
|
||||||
|
|
||||||
|
permissionsService = TestBed.inject(PermissionsService)
|
||||||
|
tagService = TestBed.inject(TagService)
|
||||||
fixture = TestBed.createComponent(TagComponent)
|
fixture = TestBed.createComponent(TagComponent)
|
||||||
component = fixture.componentInstance
|
component = fixture.componentInstance
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
@ -47,4 +59,13 @@ describe('TagComponent', () => {
|
|||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
expect(fixture.debugElement.query(By.css('a.badge'))).not.toBeNull()
|
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)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
import { Component, Input } from '@angular/core'
|
import { Component, Input } from '@angular/core'
|
||||||
import { Tag } from 'src/app/data/tag'
|
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({
|
@Component({
|
||||||
selector: 'pngx-tag',
|
selector: 'pngx-tag',
|
||||||
@ -7,10 +13,39 @@ import { Tag } from 'src/app/data/tag'
|
|||||||
styleUrls: ['./tag.component.scss'],
|
styleUrls: ['./tag.component.scss'],
|
||||||
})
|
})
|
||||||
export class TagComponent {
|
export class TagComponent {
|
||||||
constructor() {}
|
private _tag: Tag
|
||||||
|
private _tagID: number
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private permissionsService: PermissionsService,
|
||||||
|
private tagService: TagService
|
||||||
|
) {}
|
||||||
|
|
||||||
@Input()
|
@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()
|
@Input()
|
||||||
linkTitle: string = ''
|
linkTitle: string = ''
|
||||||
|
@ -50,27 +50,27 @@
|
|||||||
}
|
}
|
||||||
@case (DisplayField.CORRESPONDENT) {
|
@case (DisplayField.CORRESPONDENT) {
|
||||||
@if (doc.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) {
|
@case (DisplayField.TAGS) {
|
||||||
@for (t of doc.tags$ | async; track t) {
|
@for (tagID of doc.tags; track tagID) {
|
||||||
<pngx-tag [tag]="t" class="ms-1" (click)="clickTag(t.id, $event)" [clickable]="true" linkTitle="Filter by tag" i18n-title></pngx-tag>
|
<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) {
|
@case (DisplayField.DOCUMENT_TYPE) {
|
||||||
@if (doc.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) {
|
@case (DisplayField.STORAGE_PATH) {
|
||||||
@if (doc.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) {
|
@case (DisplayField.OWNER) {
|
||||||
@if (doc.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) {
|
@case (DisplayField.ASN) {
|
||||||
|
@ -36,8 +36,11 @@ import {
|
|||||||
} from 'src/app/data/filter-rule-type'
|
} from 'src/app/data/filter-rule-type'
|
||||||
import { SavedView } from 'src/app/data/saved-view'
|
import { SavedView } from 'src/app/data/saved-view'
|
||||||
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
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 { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
|
||||||
import { DocumentTitlePipe } from 'src/app/pipes/document-title.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 { UsernamePipe } from 'src/app/pipes/username.pipe'
|
||||||
import { ConsumerStatusService } from 'src/app/services/consumer-status.service'
|
import { ConsumerStatusService } from 'src/app/services/consumer-status.service'
|
||||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
||||||
@ -65,6 +68,9 @@ import { WidgetFrameComponent } from '../widget-frame/widget-frame.component'
|
|||||||
WidgetFrameComponent,
|
WidgetFrameComponent,
|
||||||
IfPermissionsDirective,
|
IfPermissionsDirective,
|
||||||
UsernamePipe,
|
UsernamePipe,
|
||||||
|
CorrespondentNamePipe,
|
||||||
|
DocumentTypeNamePipe,
|
||||||
|
StoragePathNamePipe,
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
DocumentTitlePipe,
|
DocumentTitlePipe,
|
||||||
CustomDatePipe,
|
CustomDatePipe,
|
||||||
|
@ -22,9 +22,9 @@
|
|||||||
@if (document) {
|
@if (document) {
|
||||||
@if (displayFields.includes(DisplayField.CORRESPONDENT) && document.correspondent) {
|
@if (displayFields.includes(DisplayField.CORRESPONDENT) && document.correspondent) {
|
||||||
@if (clickCorrespondent.observers.length ) {
|
@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 {
|
} @else {
|
||||||
{{(document.correspondent$ | async)?.name}}
|
{{document.correspondent | correspondentName | async}}
|
||||||
}
|
}
|
||||||
@if (displayFields.includes(DisplayField.TITLE)) {:}
|
@if (displayFields.includes(DisplayField.TITLE)) {:}
|
||||||
}
|
}
|
||||||
@ -32,8 +32,8 @@
|
|||||||
{{document.title | documentTitle}}
|
{{document.title | documentTitle}}
|
||||||
}
|
}
|
||||||
@if (displayFields.includes(DisplayField.TAGS)) {
|
@if (displayFields.includes(DisplayField.TAGS)) {
|
||||||
@for (t of document.tags$ | async; track t) {
|
@for (tagID of document.tags; 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>
|
<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 {
|
} @else {
|
||||||
@ -95,13 +95,13 @@
|
|||||||
@if (displayFields.includes(DisplayField.DOCUMENT_TYPE) && document.document_type) {
|
@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
|
<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()">
|
(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>
|
</button>
|
||||||
}
|
}
|
||||||
@if (displayFields.includes(DisplayField.STORAGE_PATH) && document.storage_path) {
|
@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
|
<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()">
|
(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>
|
</button>
|
||||||
}
|
}
|
||||||
@if (displayFields.includes(DisplayField.ASN) && document.archive_serial_number | isNumber) {
|
@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) {
|
@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">
|
<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>
|
</div>
|
||||||
}
|
}
|
||||||
@if (displayFields.includes(DisplayField.SHARED) && document.is_shared_by_requester) {
|
@if (displayFields.includes(DisplayField.SHARED) && document.is_shared_by_requester) {
|
||||||
|
@ -21,9 +21,12 @@ import {
|
|||||||
} from 'src/app/data/document'
|
} from 'src/app/data/document'
|
||||||
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
|
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
|
||||||
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
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 { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
|
||||||
import { DocumentTitlePipe } from 'src/app/pipes/document-title.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 { 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 { UsernamePipe } from 'src/app/pipes/username.pipe'
|
||||||
import { DocumentService } from 'src/app/services/rest/document.service'
|
import { DocumentService } from 'src/app/services/rest/document.service'
|
||||||
import { SettingsService } from 'src/app/services/settings.service'
|
import { SettingsService } from 'src/app/services/settings.service'
|
||||||
@ -44,6 +47,9 @@ import { LoadingComponentWithPermissions } from '../../loading-component/loading
|
|||||||
CustomFieldDisplayComponent,
|
CustomFieldDisplayComponent,
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
UsernamePipe,
|
UsernamePipe,
|
||||||
|
CorrespondentNamePipe,
|
||||||
|
DocumentTypeNamePipe,
|
||||||
|
StoragePathNamePipe,
|
||||||
IfPermissionsDirective,
|
IfPermissionsDirective,
|
||||||
CustomDatePipe,
|
CustomDatePipe,
|
||||||
RouterModule,
|
RouterModule,
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
|
|
||||||
@if (document && displayFields?.includes(DisplayField.TAGS)) {
|
@if (document && displayFields?.includes(DisplayField.TAGS)) {
|
||||||
<div class="tags d-flex flex-column text-end position-absolute me-1 fs-6">
|
<div class="tags d-flex flex-column text-end position-absolute me-1 fs-6">
|
||||||
@for (t of getTagsLimited$() | async; track t) {
|
@for (tagID of tagIDs; track tagID) {
|
||||||
<pngx-tag [tag]="t" (click)="clickTag.emit(t.id);$event.stopPropagation()" [clickable]="true" linkTitle="Toggle tag filter" i18n-linkTitle></pngx-tag>
|
<pngx-tag [tagID]="tagID" (click)="clickTag.emit(tagID);$event.stopPropagation()" [clickable]="true" linkTitle="Toggle tag filter" i18n-linkTitle></pngx-tag>
|
||||||
}
|
}
|
||||||
@if (moreTags) {
|
@if (moreTags) {
|
||||||
<div>
|
<div>
|
||||||
@ -40,7 +40,7 @@
|
|||||||
<p class="card-text">
|
<p class="card-text">
|
||||||
@if (document) {
|
@if (document) {
|
||||||
@if (displayFields.includes(DisplayField.CORRESPONDENT) && document.correspondent) {
|
@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)) {:}
|
||||||
}
|
}
|
||||||
@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
|
<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()">
|
(click)="clickDocumentType.emit(document.document_type);$event.stopPropagation()">
|
||||||
<i-bs width="1em" height="1em" class="me-2 text-muted" name="file-earmark"></i-bs>
|
<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>
|
</button>
|
||||||
}
|
}
|
||||||
@if (displayFields.includes(DisplayField.STORAGE_PATH) && document.storage_path) {
|
@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
|
<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()">
|
(click)="clickStoragePath.emit(document.storage_path);$event.stopPropagation()">
|
||||||
<i-bs width="1em" height="1em" class="me-2 text-muted" name="folder"></i-bs>
|
<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>
|
</button>
|
||||||
}
|
}
|
||||||
@if (displayFields.includes(DisplayField.CREATED)) {
|
@if (displayFields.includes(DisplayField.CREATED)) {
|
||||||
@ -116,7 +116,7 @@
|
|||||||
@if (displayFields.includes(DisplayField.OWNER) && document.owner && document.owner !== settingsService.currentUser.id) {
|
@if (displayFields.includes(DisplayField.OWNER) && document.owner && document.owner !== settingsService.currentUser.id) {
|
||||||
<div class="ps-0 p-1">
|
<div class="ps-0 p-1">
|
||||||
<i-bs width="1em" height="1em" class="me-2 text-muted" name="person-fill-lock"></i-bs>
|
<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>
|
</div>
|
||||||
}
|
}
|
||||||
@if (displayFields.includes(DisplayField.SHARED) && document.is_shared_by_requester) {
|
@if (displayFields.includes(DisplayField.SHARED) && document.is_shared_by_requester) {
|
||||||
|
@ -5,8 +5,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'
|
|||||||
import { By } from '@angular/platform-browser'
|
import { By } from '@angular/platform-browser'
|
||||||
import { RouterTestingModule } from '@angular/router/testing'
|
import { RouterTestingModule } from '@angular/router/testing'
|
||||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
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 { TagComponent } from '../../common/tag/tag.component'
|
||||||
import { DocumentCardSmallComponent } from './document-card-small.component'
|
import { DocumentCardSmallComponent } from './document-card-small.component'
|
||||||
|
|
||||||
@ -24,16 +22,6 @@ const doc = {
|
|||||||
note: 'This is some note content bananas',
|
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:
|
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.',
|
'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))
|
fixture.debugElement.queryAll(By.directive(TagComponent))
|
||||||
).toHaveLength(5)
|
).toHaveLength(5)
|
||||||
component.document.tags = [1, 2]
|
component.document.tags = [1, 2]
|
||||||
component.document.tags$ = of([{ id: 1 } as Tag, { id: 2 } as Tag])
|
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
expect(
|
expect(
|
||||||
fixture.debugElement.queryAll(By.directive(TagComponent))
|
fixture.debugElement.queryAll(By.directive(TagComponent))
|
||||||
|
@ -14,7 +14,7 @@ import {
|
|||||||
} from '@ng-bootstrap/ng-bootstrap'
|
} from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||||
import { of } from 'rxjs'
|
import { of } from 'rxjs'
|
||||||
import { delay, map } from 'rxjs/operators'
|
import { delay } from 'rxjs/operators'
|
||||||
import {
|
import {
|
||||||
DEFAULT_DISPLAY_FIELDS,
|
DEFAULT_DISPLAY_FIELDS,
|
||||||
DisplayField,
|
DisplayField,
|
||||||
@ -22,9 +22,12 @@ import {
|
|||||||
} from 'src/app/data/document'
|
} from 'src/app/data/document'
|
||||||
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
|
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
|
||||||
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
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 { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
|
||||||
import { DocumentTitlePipe } from 'src/app/pipes/document-title.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 { 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 { UsernamePipe } from 'src/app/pipes/username.pipe'
|
||||||
import { DocumentService } from 'src/app/services/rest/document.service'
|
import { DocumentService } from 'src/app/services/rest/document.service'
|
||||||
import { SettingsService } from 'src/app/services/settings.service'
|
import { SettingsService } from 'src/app/services/settings.service'
|
||||||
@ -45,6 +48,9 @@ import { LoadingComponentWithPermissions } from '../../loading-component/loading
|
|||||||
CustomFieldDisplayComponent,
|
CustomFieldDisplayComponent,
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
UsernamePipe,
|
UsernamePipe,
|
||||||
|
CorrespondentNamePipe,
|
||||||
|
DocumentTypeNamePipe,
|
||||||
|
StoragePathNamePipe,
|
||||||
IfPermissionsDirective,
|
IfPermissionsDirective,
|
||||||
CustomDatePipe,
|
CustomDatePipe,
|
||||||
RouterModule,
|
RouterModule,
|
||||||
@ -117,22 +123,14 @@ export class DocumentCardSmallComponent
|
|||||||
return this.documentService.getDownloadUrl(this.document.id)
|
return this.documentService.getDownloadUrl(this.document.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
get privateName() {
|
get tagIDs() {
|
||||||
return $localize`Private`
|
|
||||||
}
|
|
||||||
|
|
||||||
getTagsLimited$() {
|
|
||||||
const limit = this.document.notes.length > 0 ? 6 : 7
|
const limit = this.document.notes.length > 0 ? 6 : 7
|
||||||
return this.document.tags$?.pipe(
|
if (this.document.tags.length > limit) {
|
||||||
map((tags) => {
|
this.moreTags = this.document.tags.length - (limit - 1)
|
||||||
if (tags.length > limit) {
|
return this.document.tags.slice(0, limit - 1)
|
||||||
this.moreTags = tags.length - (limit - 1)
|
|
||||||
return tags.slice(0, limit - 1)
|
|
||||||
} else {
|
} else {
|
||||||
return tags
|
return this.document.tags
|
||||||
}
|
}
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mouseLeaveCard() {
|
mouseLeaveCard() {
|
||||||
|
@ -295,7 +295,7 @@
|
|||||||
@if (activeDisplayFields.includes(DisplayField.CORRESPONDENT) && permissionService.currentUserCan(PermissionAction.View, PermissionType.Correspondent)) {
|
@if (activeDisplayFields.includes(DisplayField.CORRESPONDENT) && permissionService.currentUserCan(PermissionAction.View, PermissionType.Correspondent)) {
|
||||||
<td class="">
|
<td class="">
|
||||||
@if (d.correspondent) {
|
@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>
|
</td>
|
||||||
}
|
}
|
||||||
@ -310,15 +310,15 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@if (activeDisplayFields.includes(DisplayField.TAGS)) {
|
@if (activeDisplayFields.includes(DisplayField.TAGS)) {
|
||||||
@for (t of d.tags$ | async; track t) {
|
@for (tagID of d.tags; 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>
|
<pngx-tag [tagID]="tagID" class="ms-1" clickable="true" linkTitle="Filter by tag" i18n-linkTitle (click)="clickTag(tagID);$event.stopPropagation()"></pngx-tag>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
}
|
}
|
||||||
@if (activeDisplayFields.includes(DisplayField.OWNER) && permissionService.currentUserCan(PermissionAction.View, PermissionType.User)) {
|
@if (activeDisplayFields.includes(DisplayField.OWNER) && permissionService.currentUserCan(PermissionAction.View, PermissionType.User)) {
|
||||||
<td>
|
<td>
|
||||||
{{d.owner | username}}
|
{{d.owner | username | async}}
|
||||||
</td>
|
</td>
|
||||||
}
|
}
|
||||||
@if (activeDisplayFields.includes(DisplayField.NOTES) && notesEnabled) {
|
@if (activeDisplayFields.includes(DisplayField.NOTES) && notesEnabled) {
|
||||||
@ -335,14 +335,14 @@
|
|||||||
@if (activeDisplayFields.includes(DisplayField.DOCUMENT_TYPE) && permissionService.currentUserCan(PermissionAction.View, PermissionType.DocumentType)) {
|
@if (activeDisplayFields.includes(DisplayField.DOCUMENT_TYPE) && permissionService.currentUserCan(PermissionAction.View, PermissionType.DocumentType)) {
|
||||||
<td class="">
|
<td class="">
|
||||||
@if (d.document_type) {
|
@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>
|
</td>
|
||||||
}
|
}
|
||||||
@if (activeDisplayFields.includes(DisplayField.STORAGE_PATH) && permissionService.currentUserCan(PermissionAction.View, PermissionType.StoragePath)) {
|
@if (activeDisplayFields.includes(DisplayField.STORAGE_PATH) && permissionService.currentUserCan(PermissionAction.View, PermissionType.StoragePath)) {
|
||||||
<td class="">
|
<td class="">
|
||||||
@if (d.storage_path) {
|
@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>
|
</td>
|
||||||
}
|
}
|
||||||
|
@ -57,21 +57,21 @@ const docs: Document[] = [
|
|||||||
id: 1,
|
id: 1,
|
||||||
title: 'Doc1',
|
title: 'Doc1',
|
||||||
notes: [],
|
notes: [],
|
||||||
tags$: new Subject(),
|
tags: [],
|
||||||
content: 'document content 1',
|
content: 'document content 1',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
title: 'Doc2',
|
title: 'Doc2',
|
||||||
notes: [],
|
notes: [],
|
||||||
tags$: new Subject(),
|
tags: [],
|
||||||
content: 'document content 2',
|
content: 'document content 2',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
title: 'Doc3',
|
title: 'Doc3',
|
||||||
notes: [],
|
notes: [],
|
||||||
tags$: new Subject(),
|
tags: [],
|
||||||
content: 'document content 3',
|
content: 'document content 3',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@ -650,7 +650,6 @@ describe('DocumentListComponent', () => {
|
|||||||
id: i + 1,
|
id: i + 1,
|
||||||
title: `Doc${i + 1}`,
|
title: `Doc${i + 1}`,
|
||||||
notes: [],
|
notes: [],
|
||||||
tags$: new Subject(),
|
|
||||||
content: `document content ${i + 1}`,
|
content: `document content ${i + 1}`,
|
||||||
}))
|
}))
|
||||||
jest
|
jest
|
||||||
|
@ -37,8 +37,11 @@ import {
|
|||||||
SortableDirective,
|
SortableDirective,
|
||||||
SortEvent,
|
SortEvent,
|
||||||
} from 'src/app/directives/sortable.directive'
|
} 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 { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
|
||||||
import { DocumentTitlePipe } from 'src/app/pipes/document-title.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 { UsernamePipe } from 'src/app/pipes/username.pipe'
|
||||||
import { ConsumerStatusService } from 'src/app/services/consumer-status.service'
|
import { ConsumerStatusService } from 'src/app/services/consumer-status.service'
|
||||||
import { DocumentListViewService } from 'src/app/services/document-list-view.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,
|
IfPermissionsDirective,
|
||||||
SortableDirective,
|
SortableDirective,
|
||||||
UsernamePipe,
|
UsernamePipe,
|
||||||
|
CorrespondentNamePipe,
|
||||||
|
DocumentTypeNamePipe,
|
||||||
|
StoragePathNamePipe,
|
||||||
NgxBootstrapIconsModule,
|
NgxBootstrapIconsModule,
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
import { Observable } from 'rxjs'
|
|
||||||
import { Correspondent } from './correspondent'
|
|
||||||
import { CustomFieldInstance } from './custom-field-instance'
|
import { CustomFieldInstance } from './custom-field-instance'
|
||||||
import { DocumentNote } from './document-note'
|
import { DocumentNote } from './document-note'
|
||||||
import { DocumentType } from './document-type'
|
|
||||||
import { ObjectWithPermissions } from './object-with-permissions'
|
import { ObjectWithPermissions } from './object-with-permissions'
|
||||||
import { StoragePath } from './storage-path'
|
|
||||||
import { Tag } from './tag'
|
|
||||||
|
|
||||||
export enum DisplayMode {
|
export enum DisplayMode {
|
||||||
TABLE = 'table',
|
TABLE = 'table',
|
||||||
@ -118,24 +113,16 @@ export interface SearchHit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Document extends ObjectWithPermissions {
|
export interface Document extends ObjectWithPermissions {
|
||||||
correspondent$?: Observable<Correspondent>
|
|
||||||
|
|
||||||
correspondent?: number
|
correspondent?: number
|
||||||
|
|
||||||
document_type$?: Observable<DocumentType>
|
|
||||||
|
|
||||||
document_type?: number
|
document_type?: number
|
||||||
|
|
||||||
storage_path$?: Observable<StoragePath>
|
|
||||||
|
|
||||||
storage_path?: number
|
storage_path?: number
|
||||||
|
|
||||||
title?: string
|
title?: string
|
||||||
|
|
||||||
content?: string
|
content?: string
|
||||||
|
|
||||||
tags$?: Observable<Tag[]>
|
|
||||||
|
|
||||||
tags?: number[]
|
tags?: number[]
|
||||||
|
|
||||||
checksum?: string
|
checksum?: string
|
||||||
|
28
src-ui/src/app/pipes/correspondent-name.pipe.spec.ts
Normal file
28
src-ui/src/app/pipes/correspondent-name.pipe.spec.ts
Normal 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()
|
||||||
|
})
|
||||||
|
})
|
22
src-ui/src/app/pipes/correspondent-name.pipe.ts
Normal file
22
src-ui/src/app/pipes/correspondent-name.pipe.ts
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
28
src-ui/src/app/pipes/document-type-name.pipe.spec.ts
Normal file
28
src-ui/src/app/pipes/document-type-name.pipe.spec.ts
Normal 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()
|
||||||
|
})
|
||||||
|
})
|
22
src-ui/src/app/pipes/document-type-name.pipe.ts
Normal file
22
src-ui/src/app/pipes/document-type-name.pipe.ts
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
88
src-ui/src/app/pipes/object-name.pipe.spec.ts
Normal file
88
src-ui/src/app/pipes/object-name.pipe.spec.ts
Normal 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()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
46
src-ui/src/app/pipes/object-name.pipe.ts
Normal file
46
src-ui/src/app/pipes/object-name.pipe.ts
Normal 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`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
src-ui/src/app/pipes/storage-path-name.pipe.spec.ts
Normal file
28
src-ui/src/app/pipes/storage-path-name.pipe.spec.ts
Normal 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()
|
||||||
|
})
|
||||||
|
})
|
22
src-ui/src/app/pipes/storage-path-name.pipe.ts
Normal file
22
src-ui/src/app/pipes/storage-path-name.pipe.ts
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
@ -37,7 +37,11 @@ describe('UsernamePipe', () => {
|
|||||||
httpTestingController.verify()
|
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(
|
const req = httpTestingController.expectOne(
|
||||||
`${environment.apiBaseUrl}users/?page=1&page_size=100000`
|
`${environment.apiBaseUrl}users/?page=1&page_size=100000`
|
||||||
)
|
)
|
||||||
@ -55,24 +59,39 @@ describe('UsernamePipe', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
pipe.transform(3).subscribe((username) => {
|
||||||
let username = pipe.transform(2)
|
|
||||||
expect(username).toEqual('username2')
|
|
||||||
|
|
||||||
username = pipe.transform(3)
|
|
||||||
expect(username).toEqual('User Name3')
|
expect(username).toEqual('User Name3')
|
||||||
|
|
||||||
username = pipe.transform(4)
|
|
||||||
expect(username).toEqual('')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should show generic label when no users retrieved', () => {
|
pipe.transform(4).subscribe((username) => {
|
||||||
|
expect(username).toEqual('')
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
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(
|
const req = httpTestingController.expectOne(
|
||||||
`${environment.apiBaseUrl}users/?page=1&page_size=100000`
|
`${environment.apiBaseUrl}users/?page=1&page_size=100000`
|
||||||
)
|
)
|
||||||
req.flush(null)
|
req.error(new ProgressEvent('error'))
|
||||||
|
|
||||||
let username = pipe.transform(4)
|
|
||||||
expect(username).toEqual('Shared')
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { Pipe, PipeTransform } from '@angular/core'
|
import { Pipe, PipeTransform } from '@angular/core'
|
||||||
|
import { catchError, map, Observable, of } from 'rxjs'
|
||||||
import { User } from '../data/user'
|
import { User } from '../data/user'
|
||||||
import {
|
import {
|
||||||
PermissionAction,
|
PermissionAction,
|
||||||
PermissionType,
|
|
||||||
PermissionsService,
|
PermissionsService,
|
||||||
|
PermissionType,
|
||||||
} from '../services/permissions.service'
|
} from '../services/permissions.service'
|
||||||
import { UserService } from '../services/rest/user.service'
|
import { UserService } from '../services/rest/user.service'
|
||||||
|
|
||||||
@ -14,25 +15,29 @@ export class UsernamePipe implements PipeTransform {
|
|||||||
users: User[]
|
users: User[]
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
permissionsService: PermissionsService,
|
private permissionsService: PermissionsService,
|
||||||
userService: UserService
|
private userService: UserService
|
||||||
) {
|
) {}
|
||||||
|
|
||||||
|
transform(userID: number): Observable<string> {
|
||||||
if (
|
if (
|
||||||
permissionsService.currentUserCan(
|
this.permissionsService.currentUserCan(
|
||||||
PermissionAction.View,
|
PermissionAction.View,
|
||||||
PermissionType.User
|
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 {
|
getName(user: User): string {
|
||||||
if (!user) return ''
|
if (!user) return ''
|
||||||
const name = [user.first_name, user.last_name].join(' ')
|
const name = [user.first_name, user.last_name].join(' ')
|
||||||
|
@ -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', () => {
|
it('should set search query', () => {
|
||||||
const searchQuery = 'hello'
|
const searchQuery = 'hello'
|
||||||
service.searchQuery = searchQuery
|
service.searchQuery = searchQuery
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { HttpClient } from '@angular/common/http'
|
import { HttpClient } from '@angular/common/http'
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { Observable } from 'rxjs'
|
import { Observable } from 'rxjs'
|
||||||
import { map, tap } from 'rxjs/operators'
|
import { map } from 'rxjs/operators'
|
||||||
import { AuditLogEntry } from 'src/app/data/auditlog-entry'
|
import { AuditLogEntry } from 'src/app/data/auditlog-entry'
|
||||||
import { CustomField } from 'src/app/data/custom-field'
|
import { CustomField } from 'src/app/data/custom-field'
|
||||||
import {
|
import {
|
||||||
@ -22,11 +22,7 @@ import {
|
|||||||
} from '../permissions.service'
|
} from '../permissions.service'
|
||||||
import { SettingsService } from '../settings.service'
|
import { SettingsService } from '../settings.service'
|
||||||
import { AbstractPaperlessService } from './abstract-paperless-service'
|
import { AbstractPaperlessService } from './abstract-paperless-service'
|
||||||
import { CorrespondentService } from './correspondent.service'
|
|
||||||
import { CustomFieldsService } from './custom-fields.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 {
|
export interface SelectionDataItem {
|
||||||
id: number
|
id: number
|
||||||
@ -61,10 +57,6 @@ export class DocumentService extends AbstractPaperlessService<Document> {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
http: HttpClient,
|
http: HttpClient,
|
||||||
private correspondentService: CorrespondentService,
|
|
||||||
private documentTypeService: DocumentTypeService,
|
|
||||||
private tagService: TagService,
|
|
||||||
private storagePathService: StoragePathService,
|
|
||||||
private permissionsService: PermissionsService,
|
private permissionsService: PermissionsService,
|
||||||
private settingsService: SettingsService,
|
private settingsService: SettingsService,
|
||||||
private customFieldService: CustomFieldsService
|
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(
|
listFiltered(
|
||||||
page?: number,
|
page?: number,
|
||||||
pageSize?: number,
|
pageSize?: number,
|
||||||
@ -199,11 +143,6 @@ export class DocumentService extends AbstractPaperlessService<Document> {
|
|||||||
sortField,
|
sortField,
|
||||||
sortReverse,
|
sortReverse,
|
||||||
Object.assign(extraParams, queryParamsFromFilterRules(filterRules))
|
Object.assign(extraParams, queryParamsFromFilterRules(filterRules))
|
||||||
).pipe(
|
|
||||||
map((results) => {
|
|
||||||
results.results.forEach((doc) => this.addObservablesToDocument(doc))
|
|
||||||
return results
|
|
||||||
})
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,6 +180,9 @@ import localeSv from '@angular/common/locales/sv'
|
|||||||
import localeTr from '@angular/common/locales/tr'
|
import localeTr from '@angular/common/locales/tr'
|
||||||
import localeUk from '@angular/common/locales/uk'
|
import localeUk from '@angular/common/locales/uk'
|
||||||
import localeZh from '@angular/common/locales/zh'
|
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(localeAf)
|
||||||
registerLocaleData(localeAr)
|
registerLocaleData(localeAr)
|
||||||
@ -375,6 +378,9 @@ bootstrapApplication(AppComponent, {
|
|||||||
DirtyDocGuard,
|
DirtyDocGuard,
|
||||||
DirtySavedViewGuard,
|
DirtySavedViewGuard,
|
||||||
UsernamePipe,
|
UsernamePipe,
|
||||||
|
CorrespondentNamePipe,
|
||||||
|
DocumentTypeNamePipe,
|
||||||
|
StoragePathNamePipe,
|
||||||
provideHttpClient(withInterceptorsFromDi()),
|
provideHttpClient(withInterceptorsFromDi()),
|
||||||
],
|
],
|
||||||
}).catch((err) => console.error(err))
|
}).catch((err) => console.error(err))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user