</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">793</context>
+ <context context-type="linenumber">796</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">826</context>
+ <context context-type="linenumber">829</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">845</context>
+ <context context-type="linenumber">848</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/custom-fields/custom-fields.component.ts</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">436</context>
+ <context context-type="linenumber">439</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">476</context>
+ <context context-type="linenumber">479</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">514</context>
+ <context context-type="linenumber">517</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">552</context>
+ <context context-type="linenumber">555</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">614</context>
+ <context context-type="linenumber">617</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">747</context>
+ <context context-type="linenumber">750</context>
</context-group>
</trans-unit>
<trans-unit id="1407560924967345762" datatype="html">
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">381</context>
+ <context context-type="linenumber">384</context>
</context-group>
<note priority="1" from="description">this string is used to separate processing, failed and added on the file upload widget</note>
</trans-unit>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">749</context>
+ <context context-type="linenumber">752</context>
</context-group>
</trans-unit>
<trans-unit id="2048798344356757326" datatype="html">
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">751</context>
+ <context context-type="linenumber">754</context>
</context-group>
</trans-unit>
<trans-unit id="7295637485862454066" datatype="html">
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">789</context>
+ <context context-type="linenumber">792</context>
</context-group>
</trans-unit>
<trans-unit id="2951161989614003846" datatype="html">
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">823</context>
+ <context context-type="linenumber">826</context>
</context-group>
</trans-unit>
<trans-unit id="857641176955257111" datatype="html">
<source>Error executing bulk operation</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">285</context>
+ <context context-type="linenumber">288</context>
</context-group>
</trans-unit>
<trans-unit id="7894972847287473517" datatype="html">
<source>"<x id="PH" equiv-text="items[0].name"/>"</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">373</context>
+ <context context-type="linenumber">376</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">379</context>
+ <context context-type="linenumber">382</context>
</context-group>
</trans-unit>
<trans-unit id="8639884465898458690" datatype="html">
<source>"<x id="PH" equiv-text="items[0].name"/>" and "<x id="PH_1" equiv-text="items[1].name"/>"</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">375</context>
+ <context context-type="linenumber">378</context>
</context-group>
<note priority="1" from="description">This is for messages like 'modify "tag1" and "tag2"'</note>
</trans-unit>
<source><x id="PH" equiv-text="list"/> and "<x id="PH_1" equiv-text="items[items.length - 1].name"/>"</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">383,385</context>
+ <context context-type="linenumber">386,388</context>
</context-group>
<note priority="1" from="description">this is for messages like 'modify "tag1", "tag2" and "tag3"'</note>
</trans-unit>
<source>Confirm tags assignment</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">400</context>
+ <context context-type="linenumber">403</context>
</context-group>
</trans-unit>
<trans-unit id="6619516195038467207" datatype="html">
<source>This operation will add the tag "<x id="PH" equiv-text="tag.name"/>" to <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">406</context>
+ <context context-type="linenumber">409</context>
</context-group>
</trans-unit>
<trans-unit id="1894412783609570695" datatype="html">
)"/> to <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">411,413</context>
+ <context context-type="linenumber">414,416</context>
</context-group>
</trans-unit>
<trans-unit id="7181166515756808573" datatype="html">
<source>This operation will remove the tag "<x id="PH" equiv-text="tag.name"/>" from <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">419</context>
+ <context context-type="linenumber">422</context>
</context-group>
</trans-unit>
<trans-unit id="3819792277998068944" datatype="html">
)"/> from <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">424,426</context>
+ <context context-type="linenumber">427,429</context>
</context-group>
</trans-unit>
<trans-unit id="2739066218579571288" datatype="html">
)"/> on <x id="PH_2" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">428,432</context>
+ <context context-type="linenumber">431,435</context>
</context-group>
</trans-unit>
<trans-unit id="2996713129519325161" datatype="html">
<source>Confirm correspondent assignment</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">469</context>
+ <context context-type="linenumber">472</context>
</context-group>
</trans-unit>
<trans-unit id="6900893559485781849" datatype="html">
<source>This operation will assign the correspondent "<x id="PH" equiv-text="correspondent.name"/>" to <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">471</context>
+ <context context-type="linenumber">474</context>
</context-group>
</trans-unit>
<trans-unit id="1257522660364398440" datatype="html">
<source>This operation will remove the correspondent from <x id="PH" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">473</context>
+ <context context-type="linenumber">476</context>
</context-group>
</trans-unit>
<trans-unit id="5393409374423140648" datatype="html">
<source>Confirm document type assignment</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">507</context>
+ <context context-type="linenumber">510</context>
</context-group>
</trans-unit>
<trans-unit id="332180123895325027" datatype="html">
<source>This operation will assign the document type "<x id="PH" equiv-text="documentType.name"/>" to <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">509</context>
+ <context context-type="linenumber">512</context>
</context-group>
</trans-unit>
<trans-unit id="2236642492594872779" datatype="html">
<source>This operation will remove the document type from <x id="PH" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">511</context>
+ <context context-type="linenumber">514</context>
</context-group>
</trans-unit>
<trans-unit id="6386555513013840736" datatype="html">
<source>Confirm storage path assignment</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">545</context>
+ <context context-type="linenumber">548</context>
</context-group>
</trans-unit>
<trans-unit id="8750527458618415924" datatype="html">
<source>This operation will assign the storage path "<x id="PH" equiv-text="storagePath.name"/>" to <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">547</context>
+ <context context-type="linenumber">550</context>
</context-group>
</trans-unit>
<trans-unit id="60728365335056946" datatype="html">
<source>This operation will remove the storage path from <x id="PH" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">549</context>
+ <context context-type="linenumber">552</context>
</context-group>
</trans-unit>
<trans-unit id="4187352575310415704" datatype="html">
<source>Confirm custom field assignment</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">578</context>
+ <context context-type="linenumber">581</context>
</context-group>
</trans-unit>
<trans-unit id="7966494636326273856" datatype="html">
<source>This operation will assign the custom field "<x id="PH" equiv-text="customField.name"/>" to <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">584</context>
+ <context context-type="linenumber">587</context>
</context-group>
</trans-unit>
<trans-unit id="5789455969634598553" datatype="html">
)"/> to <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">589,591</context>
+ <context context-type="linenumber">592,594</context>
</context-group>
</trans-unit>
<trans-unit id="5648572354333199245" datatype="html">
<source>This operation will remove the custom field "<x id="PH" equiv-text="customField.name"/>" from <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">597</context>
+ <context context-type="linenumber">600</context>
</context-group>
</trans-unit>
<trans-unit id="6666899594015948817" datatype="html">
)"/> from <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">602,604</context>
+ <context context-type="linenumber">605,607</context>
</context-group>
</trans-unit>
<trans-unit id="8050047262594964176" datatype="html">
)"/> on <x id="PH_2" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">606,610</context>
+ <context context-type="linenumber">609,613</context>
</context-group>
</trans-unit>
<trans-unit id="8615059324209654051" datatype="html">
<source>Move <x id="PH" equiv-text="this.list.selected.size"/> selected document(s) to the trash?</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">748</context>
+ <context context-type="linenumber">751</context>
</context-group>
</trans-unit>
<trans-unit id="8585195717323764335" datatype="html">
<source>This operation will permanently recreate the archive files for <x id="PH" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">790</context>
+ <context context-type="linenumber">793</context>
</context-group>
</trans-unit>
<trans-unit id="7366623494074776040" datatype="html">
<source>The archive files will be re-generated with the current settings.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">791</context>
+ <context context-type="linenumber">794</context>
</context-group>
</trans-unit>
<trans-unit id="6390006284731990222" datatype="html">
<source>This operation will permanently rotate the original version of <x id="PH" equiv-text="this.list.selected.size"/> document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">824</context>
+ <context context-type="linenumber">827</context>
</context-group>
</trans-unit>
<trans-unit id="7910756456450124185" datatype="html">
<source>Merge confirm</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">843</context>
+ <context context-type="linenumber">846</context>
</context-group>
</trans-unit>
<trans-unit id="7643543647233874431" datatype="html">
<source>This operation will merge <x id="PH" equiv-text="this.list.selected.size"/> selected documents into a new document.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">844</context>
+ <context context-type="linenumber">847</context>
</context-group>
</trans-unit>
<trans-unit id="7869008840945899895" datatype="html">
<source>Merged document will be queued for consumption.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">860</context>
+ <context context-type="linenumber">863</context>
</context-group>
</trans-unit>
<trans-unit id="476913782630693351" datatype="html">
<source>Custom fields updated.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">882</context>
+ <context context-type="linenumber">885</context>
</context-group>
</trans-unit>
<trans-unit id="3873496751167944011" datatype="html">
<source>Error updating custom fields.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
- <context context-type="linenumber">891</context>
+ <context context-type="linenumber">894</context>
</context-group>
</trans-unit>
<trans-unit id="6307402210351946694" datatype="html">
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
- <context context-type="linenumber">310</context>
+ <context context-type="linenumber">314</context>
</context-group>
</trans-unit>
<trans-unit id="1494518490116523821" datatype="html">
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
- <context context-type="linenumber">303</context>
+ <context context-type="linenumber">307</context>
</context-group>
</trans-unit>
<trans-unit id="8461842260159597706" datatype="html">
<source>Reset filters / selection</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
- <context context-type="linenumber">291</context>
+ <context context-type="linenumber">295</context>
</context-group>
</trans-unit>
<trans-unit id="4135055128446167640" datatype="html">
<source>Open first [selected] document</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
- <context context-type="linenumber">319</context>
+ <context context-type="linenumber">323</context>
</context-group>
</trans-unit>
<trans-unit id="3629960544875360046" datatype="html">
<source>Previous page</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
- <context context-type="linenumber">335</context>
+ <context context-type="linenumber">339</context>
</context-group>
</trans-unit>
<trans-unit id="3337301694210287595" datatype="html">
<source>Next page</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
- <context context-type="linenumber">347</context>
+ <context context-type="linenumber">351</context>
</context-group>
</trans-unit>
<trans-unit id="2155249406916744630" datatype="html">
<source>View "<x id="PH" equiv-text="this.list.activeSavedViewTitle"/>" saved successfully.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
- <context context-type="linenumber">379</context>
+ <context context-type="linenumber">383</context>
</context-group>
</trans-unit>
<trans-unit id="6837554170707123455" datatype="html">
<source>View "<x id="PH" equiv-text="savedView.name"/>" created successfully.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
- <context context-type="linenumber">422</context>
+ <context context-type="linenumber">426</context>
</context-group>
</trans-unit>
<trans-unit id="739880801667335279" datatype="html">
<context context-type="linenumber">11</context>
</context-group>
</trans-unit>
- <trans-unit id="2119857572761283468" datatype="html">
- <source>Document already exists.</source>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
- <context context-type="linenumber">17</context>
- </context-group>
- </trans-unit>
- <trans-unit id="5103087968344279314" datatype="html">
- <source>Document already exists. Note: existing document is in the trash.</source>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
- <context context-type="linenumber">18</context>
- </context-group>
- </trans-unit>
- <trans-unit id="6108404046106249255" datatype="html">
- <source>Document with ASN already exists.</source>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
- <context context-type="linenumber">19</context>
- </context-group>
- </trans-unit>
- <trans-unit id="65951081560571094" datatype="html">
- <source>Document with ASN already exists. Note: existing document is in the trash.</source>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
- <context context-type="linenumber">20</context>
- </context-group>
- </trans-unit>
- <trans-unit id="148389968432135849" datatype="html">
- <source>File not found.</source>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
- <context context-type="linenumber">21</context>
- </context-group>
- </trans-unit>
- <trans-unit id="1520671543092565667" datatype="html">
- <source>Pre-consume script does not exist.</source>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
- <context context-type="linenumber">22</context>
- </context-group>
- <note priority="1" from="description">Pre-Consume is a term that appears like that in the documentation as well and does not need a specific translation</note>
- </trans-unit>
- <trans-unit id="7742915911032564889" datatype="html">
- <source>Error while executing pre-consume script.</source>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
- <context context-type="linenumber">23</context>
- </context-group>
- <note priority="1" from="description">Pre-Consume is a term that appears like that in the documentation as well and does not need a specific translation</note>
- </trans-unit>
- <trans-unit id="8995193730018060346" datatype="html">
- <source>Post-consume script does not exist.</source>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
- <context context-type="linenumber">24</context>
- </context-group>
- <note priority="1" from="description">Post-Consume is a term that appears like that in the documentation as well and does not need a specific translation</note>
- </trans-unit>
- <trans-unit id="256773668518189604" datatype="html">
- <source>Error while executing post-consume script.</source>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
- <context context-type="linenumber">25</context>
- </context-group>
- <note priority="1" from="description">Post-Consume is a term that appears like that in the documentation as well and does not need a specific translation</note>
- </trans-unit>
- <trans-unit id="6252258095055634191" datatype="html">
- <source>Received new file.</source>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
- <context context-type="linenumber">26</context>
- </context-group>
- </trans-unit>
- <trans-unit id="7337565919209746135" datatype="html">
- <source>File type not supported.</source>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
- <context context-type="linenumber">27</context>
- </context-group>
- </trans-unit>
- <trans-unit id="5002399167376099234" datatype="html">
- <source>Processing document...</source>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
- <context context-type="linenumber">28</context>
- </context-group>
- </trans-unit>
- <trans-unit id="1085975194762600381" datatype="html">
- <source>Generating thumbnail...</source>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
- <context context-type="linenumber">29</context>
- </context-group>
- </trans-unit>
- <trans-unit id="3280851677698431426" datatype="html">
- <source>Retrieving date from document...</source>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
- <context context-type="linenumber">30</context>
- </context-group>
- </trans-unit>
- <trans-unit id="7162102384876037296" datatype="html">
- <source>Saving document...</source>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
- <context context-type="linenumber">31</context>
- </context-group>
- </trans-unit>
- <trans-unit id="4550450765009165976" datatype="html">
- <source>Finished.</source>
- <context-group purpose="location">
- <context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
- <context context-type="linenumber">32</context>
- </context-group>
- </trans-unit>
<trans-unit id="5523607037798226031" datatype="html">
<source>You have unsaved changes to the document</source>
<context-group purpose="location">
<context context-type="linenumber">70</context>
</context-group>
</trans-unit>
+ <trans-unit id="2119857572761283468" datatype="html">
+ <source>Document already exists.</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/services/websocket-status.service.ts</context>
+ <context context-type="linenumber">23</context>
+ </context-group>
+ </trans-unit>
+ <trans-unit id="5103087968344279314" datatype="html">
+ <source>Document already exists. Note: existing document is in the trash.</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/services/websocket-status.service.ts</context>
+ <context context-type="linenumber">24</context>
+ </context-group>
+ </trans-unit>
+ <trans-unit id="6108404046106249255" datatype="html">
+ <source>Document with ASN already exists.</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/services/websocket-status.service.ts</context>
+ <context context-type="linenumber">25</context>
+ </context-group>
+ </trans-unit>
+ <trans-unit id="65951081560571094" datatype="html">
+ <source>Document with ASN already exists. Note: existing document is in the trash.</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/services/websocket-status.service.ts</context>
+ <context context-type="linenumber">26</context>
+ </context-group>
+ </trans-unit>
+ <trans-unit id="148389968432135849" datatype="html">
+ <source>File not found.</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/services/websocket-status.service.ts</context>
+ <context context-type="linenumber">27</context>
+ </context-group>
+ </trans-unit>
+ <trans-unit id="1520671543092565667" datatype="html">
+ <source>Pre-consume script does not exist.</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/services/websocket-status.service.ts</context>
+ <context context-type="linenumber">28</context>
+ </context-group>
+ <note priority="1" from="description">Pre-Consume is a term that appears like that in the documentation as well and does not need a specific translation</note>
+ </trans-unit>
+ <trans-unit id="7742915911032564889" datatype="html">
+ <source>Error while executing pre-consume script.</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/services/websocket-status.service.ts</context>
+ <context context-type="linenumber">29</context>
+ </context-group>
+ <note priority="1" from="description">Pre-Consume is a term that appears like that in the documentation as well and does not need a specific translation</note>
+ </trans-unit>
+ <trans-unit id="8995193730018060346" datatype="html">
+ <source>Post-consume script does not exist.</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/services/websocket-status.service.ts</context>
+ <context context-type="linenumber">30</context>
+ </context-group>
+ <note priority="1" from="description">Post-Consume is a term that appears like that in the documentation as well and does not need a specific translation</note>
+ </trans-unit>
+ <trans-unit id="256773668518189604" datatype="html">
+ <source>Error while executing post-consume script.</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/services/websocket-status.service.ts</context>
+ <context context-type="linenumber">31</context>
+ </context-group>
+ <note priority="1" from="description">Post-Consume is a term that appears like that in the documentation as well and does not need a specific translation</note>
+ </trans-unit>
+ <trans-unit id="6252258095055634191" datatype="html">
+ <source>Received new file.</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/services/websocket-status.service.ts</context>
+ <context context-type="linenumber">32</context>
+ </context-group>
+ </trans-unit>
+ <trans-unit id="7337565919209746135" datatype="html">
+ <source>File type not supported.</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/services/websocket-status.service.ts</context>
+ <context context-type="linenumber">33</context>
+ </context-group>
+ </trans-unit>
+ <trans-unit id="5002399167376099234" datatype="html">
+ <source>Processing document...</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/services/websocket-status.service.ts</context>
+ <context context-type="linenumber">34</context>
+ </context-group>
+ </trans-unit>
+ <trans-unit id="1085975194762600381" datatype="html">
+ <source>Generating thumbnail...</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/services/websocket-status.service.ts</context>
+ <context context-type="linenumber">35</context>
+ </context-group>
+ </trans-unit>
+ <trans-unit id="3280851677698431426" datatype="html">
+ <source>Retrieving date from document...</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/services/websocket-status.service.ts</context>
+ <context context-type="linenumber">36</context>
+ </context-group>
+ </trans-unit>
+ <trans-unit id="7162102384876037296" datatype="html">
+ <source>Saving document...</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/services/websocket-status.service.ts</context>
+ <context context-type="linenumber">37</context>
+ </context-group>
+ </trans-unit>
+ <trans-unit id="4550450765009165976" datatype="html">
+ <source>Finished.</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/services/websocket-status.service.ts</context>
+ <context context-type="linenumber">38</context>
+ </context-group>
+ </trans-unit>
</body>
</file>
</xliff>
import { FileDropComponent } from './components/file-drop/file-drop.component'
import { DirtySavedViewGuard } from './guards/dirty-saved-view.guard'
import { PermissionsGuard } from './guards/permissions.guard'
-import {
- ConsumerStatusService,
- FileStatus,
-} from './services/consumer-status.service'
import { HotKeyService } from './services/hot-key.service'
import { PermissionsService } from './services/permissions.service'
import { SettingsService } from './services/settings.service'
import { Toast, ToastService } from './services/toast.service'
+import {
+ FileStatus,
+ WebsocketStatusService,
+} from './services/websocket-status.service'
describe('AppComponent', () => {
let component: AppComponent
let fixture: ComponentFixture<AppComponent>
let tourService: TourService
- let consumerStatusService: ConsumerStatusService
+ let websocketStatusService: WebsocketStatusService
let permissionsService: PermissionsService
let toastService: ToastService
let router: Router
}).compileComponents()
tourService = TestBed.inject(TourService)
- consumerStatusService = TestBed.inject(ConsumerStatusService)
+ websocketStatusService = TestBed.inject(WebsocketStatusService)
permissionsService = TestBed.inject(PermissionsService)
settingsService = TestBed.inject(SettingsService)
toastService = TestBed.inject(ToastService)
const toastSpy = jest.spyOn(toastService, 'show')
const fileStatusSubject = new Subject<FileStatus>()
jest
- .spyOn(consumerStatusService, 'onDocumentConsumptionFinished')
+ .spyOn(websocketStatusService, 'onDocumentConsumptionFinished')
.mockReturnValue(fileStatusSubject)
component.ngOnInit()
const status = new FileStatus()
const toastSpy = jest.spyOn(toastService, 'show')
const fileStatusSubject = new Subject<FileStatus>()
jest
- .spyOn(consumerStatusService, 'onDocumentConsumptionFinished')
+ .spyOn(websocketStatusService, 'onDocumentConsumptionFinished')
.mockReturnValue(fileStatusSubject)
component.ngOnInit()
fileStatusSubject.next(new FileStatus())
const toastSpy = jest.spyOn(toastService, 'show')
const fileStatusSubject = new Subject<FileStatus>()
jest
- .spyOn(consumerStatusService, 'onDocumentDetected')
+ .spyOn(websocketStatusService, 'onDocumentDetected')
.mockReturnValue(fileStatusSubject)
component.ngOnInit()
fileStatusSubject.next(new FileStatus())
const toastSpy = jest.spyOn(toastService, 'show')
const fileStatusSubject = new Subject<FileStatus>()
jest
- .spyOn(consumerStatusService, 'onDocumentDetected')
+ .spyOn(websocketStatusService, 'onDocumentDetected')
.mockReturnValue(fileStatusSubject)
component.ngOnInit()
fileStatusSubject.next(new FileStatus())
const toastSpy = jest.spyOn(toastService, 'showError')
const fileStatusSubject = new Subject<FileStatus>()
jest
- .spyOn(consumerStatusService, 'onDocumentConsumptionFailed')
+ .spyOn(websocketStatusService, 'onDocumentConsumptionFailed')
.mockReturnValue(fileStatusSubject)
component.ngOnInit()
fileStatusSubject.next(new FileStatus())
import { FileDropComponent } from './components/file-drop/file-drop.component'
import { SETTINGS_KEYS } from './data/ui-settings'
import { ComponentRouterService } from './services/component-router.service'
-import { ConsumerStatusService } from './services/consumer-status.service'
import { HotKeyService } from './services/hot-key.service'
import {
PermissionAction,
import { SettingsService } from './services/settings.service'
import { TasksService } from './services/tasks.service'
import { ToastService } from './services/toast.service'
+import { WebsocketStatusService } from './services/websocket-status.service'
@Component({
selector: 'pngx-root',
constructor(
private settings: SettingsService,
- private consumerStatusService: ConsumerStatusService,
+ private websocketStatusService: WebsocketStatusService,
private toastService: ToastService,
private router: Router,
private tasksService: TasksService,
}
ngOnDestroy(): void {
- this.consumerStatusService.disconnect()
+ this.websocketStatusService.disconnect()
if (this.successSubscription) {
this.successSubscription.unsubscribe()
}
}
ngOnInit(): void {
- this.consumerStatusService.connect()
+ this.websocketStatusService.connect()
- this.successSubscription = this.consumerStatusService
+ this.successSubscription = this.websocketStatusService
.onDocumentConsumptionFinished()
.subscribe((status) => {
this.tasksService.reload()
}
})
- this.failedSubscription = this.consumerStatusService
+ this.failedSubscription = this.websocketStatusService
.onDocumentConsumptionFailed()
.subscribe((status) => {
this.tasksService.reload()
}
})
- this.newDocumentSubscription = this.consumerStatusService
+ this.newDocumentSubscription = this.websocketStatusService
.onDocumentDetected()
.subscribe((status) => {
this.tasksService.reload()
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
import { SafeUrlPipe } from 'src/app/pipes/safeurl.pipe'
-import {
- ConsumerStatusService,
- FileStatus,
-} from 'src/app/services/consumer-status.service'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { PermissionsService } from 'src/app/services/permissions.service'
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
import { DocumentService } from 'src/app/services/rest/document.service'
+import {
+ FileStatus,
+ WebsocketStatusService,
+} from 'src/app/services/websocket-status.service'
import { WidgetFrameComponent } from '../widget-frame/widget-frame.component'
import { SavedViewWidgetComponent } from './saved-view-widget.component'
let component: SavedViewWidgetComponent
let fixture: ComponentFixture<SavedViewWidgetComponent>
let documentService: DocumentService
- let consumerStatusService: ConsumerStatusService
+ let websocketStatusService: WebsocketStatusService
let documentListViewService: DocumentListViewService
let router: Router
}).compileComponents()
documentService = TestBed.inject(DocumentService)
- consumerStatusService = TestBed.inject(ConsumerStatusService)
+ websocketStatusService = TestBed.inject(WebsocketStatusService)
documentListViewService = TestBed.inject(DocumentListViewService)
router = TestBed.inject(Router)
fixture = TestBed.createComponent(SavedViewWidgetComponent)
it('should reload on document consumption finished', () => {
const fileStatusSubject = new Subject<FileStatus>()
jest
- .spyOn(consumerStatusService, 'onDocumentConsumptionFinished')
+ .spyOn(websocketStatusService, 'onDocumentConsumptionFinished')
.mockReturnValue(fileStatusSubject)
const reloadSpy = jest.spyOn(component, 'reload')
component.ngOnInit()
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'
import { OpenDocumentsService } from 'src/app/services/open-documents.service'
import {
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
import { DocumentService } from 'src/app/services/rest/document.service'
import { SettingsService } from 'src/app/services/settings.service'
+import { WebsocketStatusService } from 'src/app/services/websocket-status.service'
import { WidgetFrameComponent } from '../widget-frame/widget-frame.component'
@Component({
private documentService: DocumentService,
private router: Router,
private list: DocumentListViewService,
- private consumerStatusService: ConsumerStatusService,
+ private websocketStatusService: WebsocketStatusService,
public openDocumentsService: OpenDocumentsService,
public documentListViewService: DocumentListViewService,
public permissionsService: PermissionsService,
ngOnInit(): void {
this.reload()
this.displayMode = this.savedView.display_mode ?? DisplayMode.TABLE
- this.consumerStatusService
+ this.websocketStatusService
.onDocumentConsumptionFinished()
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe(() => {
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
import {
- ConsumerStatusService,
FileStatus,
-} from 'src/app/services/consumer-status.service'
+ WebsocketStatusService,
+} from 'src/app/services/websocket-status.service'
import { environment } from 'src/environments/environment'
import { WidgetFrameComponent } from '../widget-frame/widget-frame.component'
import { StatisticsWidgetComponent } from './statistics-widget.component'
let component: StatisticsWidgetComponent
let fixture: ComponentFixture<StatisticsWidgetComponent>
let httpTestingController: HttpTestingController
- let consumerStatusService: ConsumerStatusService
+ let websocketStatusService: WebsocketStatusService
const fileStatusSubject = new Subject<FileStatus>()
beforeEach(async () => {
}).compileComponents()
fixture = TestBed.createComponent(StatisticsWidgetComponent)
- consumerStatusService = TestBed.inject(ConsumerStatusService)
+ websocketStatusService = TestBed.inject(WebsocketStatusService)
jest
- .spyOn(consumerStatusService, 'onDocumentConsumptionFinished')
+ .spyOn(websocketStatusService, 'onDocumentConsumptionFinished')
.mockReturnValue(fileStatusSubject)
component = fixture.componentInstance
import { ComponentWithPermissions } from 'src/app/components/with-permissions/with-permissions.component'
import { FILTER_HAS_TAGS_ANY } from 'src/app/data/filter-rule-type'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
-import { ConsumerStatusService } from 'src/app/services/consumer-status.service'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
+import { WebsocketStatusService } from 'src/app/services/websocket-status.service'
import { environment } from 'src/environments/environment'
import { WidgetFrameComponent } from '../widget-frame/widget-frame.component'
constructor(
private http: HttpClient,
- private consumerStatusService: ConsumerStatusService,
+ private websocketConnectionService: WebsocketStatusService,
private documentListViewService: DocumentListViewService
) {
super()
ngOnInit(): void {
this.reload()
- this.subscription = this.consumerStatusService
+ this.subscription = this.websocketConnectionService
.onDocumentConsumptionFinished()
.subscribe(() => {
this.reload()
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { routes } from 'src/app/app-routing.module'
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
+import { PermissionsService } from 'src/app/services/permissions.service'
+import { UploadDocumentsService } from 'src/app/services/upload-documents.service'
import {
- ConsumerStatusService,
FileStatus,
FileStatusPhase,
-} from 'src/app/services/consumer-status.service'
-import { PermissionsService } from 'src/app/services/permissions.service'
-import { UploadDocumentsService } from 'src/app/services/upload-documents.service'
+ WebsocketStatusService,
+} from 'src/app/services/websocket-status.service'
import { UploadFileWidgetComponent } from './upload-file-widget.component'
const FAILED_STATUSES = [new FileStatus()]
describe('UploadFileWidgetComponent', () => {
let component: UploadFileWidgetComponent
let fixture: ComponentFixture<UploadFileWidgetComponent>
- let consumerStatusService: ConsumerStatusService
+ let websocketStatusService: WebsocketStatusService
let uploadDocumentsService: UploadDocumentsService
beforeEach(async () => {
],
}).compileComponents()
- consumerStatusService = TestBed.inject(ConsumerStatusService)
+ websocketStatusService = TestBed.inject(WebsocketStatusService)
uploadDocumentsService = TestBed.inject(UploadDocumentsService)
fixture = TestBed.createComponent(UploadFileWidgetComponent)
component = fixture.componentInstance
})
it('should generate stats summary', () => {
- mockConsumerStatuses(consumerStatusService)
+ mockConsumerStatuses(websocketStatusService)
expect(component.getStatusSummary()).toEqual(
'Processing: 6, Failed: 1, Added: 4'
)
})
it('should report an upload progress summary', () => {
- mockConsumerStatuses(consumerStatusService)
+ mockConsumerStatuses(websocketStatusService)
expect(component.getTotalUploadProgress()).toEqual(0.75)
})
})
it('should enforce a maximum number of alerts', () => {
- mockConsumerStatuses(consumerStatusService)
+ mockConsumerStatuses(websocketStatusService)
fixture.detectChanges()
// 5 total, 1 hidden
expect(fixture.debugElement.queryAll(By.directive(NgbAlert))).toHaveLength(
})
it('should allow dismissing an alert', () => {
- const dismissSpy = jest.spyOn(consumerStatusService, 'dismiss')
+ const dismissSpy = jest.spyOn(websocketStatusService, 'dismiss')
component.dismiss(new FileStatus())
expect(dismissSpy).toHaveBeenCalled()
})
it('should allow dismissing completed alerts', fakeAsync(() => {
- mockConsumerStatuses(consumerStatusService)
+ mockConsumerStatuses(websocketStatusService)
component.alertsExpanded = true
fixture.detectChanges()
jest
.spyOn(component, 'getStatusCompleted')
.mockImplementation(() => SUCCESS_STATUSES)
- const dismissSpy = jest.spyOn(consumerStatusService, 'dismiss')
+ const dismissSpy = jest.spyOn(websocketStatusService, 'dismiss')
component.dismissCompleted()
tick(1000)
fixture.detectChanges()
import { ComponentWithPermissions } from 'src/app/components/with-permissions/with-permissions.component'
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
+import { SettingsService } from 'src/app/services/settings.service'
+import { UploadDocumentsService } from 'src/app/services/upload-documents.service'
import {
- ConsumerStatusService,
FileStatus,
FileStatusPhase,
-} from 'src/app/services/consumer-status.service'
-import { SettingsService } from 'src/app/services/settings.service'
-import { UploadDocumentsService } from 'src/app/services/upload-documents.service'
+ WebsocketStatusService,
+} from 'src/app/services/websocket-status.service'
import { WidgetFrameComponent } from '../widget-frame/widget-frame.component'
const MAX_ALERTS = 5
@ViewChildren(NgbAlert) alerts: QueryList<NgbAlert>
constructor(
- private consumerStatusService: ConsumerStatusService,
+ private websocketStatusService: WebsocketStatusService,
private uploadDocumentsService: UploadDocumentsService,
public settingsService: SettingsService
) {
}
getStatus() {
- return this.consumerStatusService.getConsumerStatus().slice(0, MAX_ALERTS)
+ return this.websocketStatusService.getConsumerStatus().slice(0, MAX_ALERTS)
}
getStatusSummary() {
let strings = []
let countUploadingAndProcessing =
- this.consumerStatusService.getConsumerStatusNotCompleted().length
+ this.websocketStatusService.getConsumerStatusNotCompleted().length
let countFailed = this.getStatusFailed().length
let countSuccess = this.getStatusSuccess().length
if (countUploadingAndProcessing > 0) {
}
getStatusHidden() {
- if (this.consumerStatusService.getConsumerStatus().length < MAX_ALERTS)
+ if (this.websocketStatusService.getConsumerStatus().length < MAX_ALERTS)
return []
- else return this.consumerStatusService.getConsumerStatus().slice(MAX_ALERTS)
+ else
+ return this.websocketStatusService.getConsumerStatus().slice(MAX_ALERTS)
}
getStatusUploading() {
- return this.consumerStatusService.getConsumerStatus(
+ return this.websocketStatusService.getConsumerStatus(
FileStatusPhase.UPLOADING
)
}
getStatusFailed() {
- return this.consumerStatusService.getConsumerStatus(FileStatusPhase.FAILED)
+ return this.websocketStatusService.getConsumerStatus(FileStatusPhase.FAILED)
}
getStatusSuccess() {
- return this.consumerStatusService.getConsumerStatus(FileStatusPhase.SUCCESS)
+ return this.websocketStatusService.getConsumerStatus(
+ FileStatusPhase.SUCCESS
+ )
}
getStatusCompleted() {
- return this.consumerStatusService.getConsumerStatusCompleted()
+ return this.websocketStatusService.getConsumerStatusCompleted()
}
getTotalUploadProgress() {
}
dismiss(status: FileStatus) {
- this.consumerStatusService.dismiss(status)
+ this.websocketStatusService.dismiss(status)
}
dismissCompleted() {
this.getStatusCompleted().forEach((status) =>
- this.consumerStatusService.dismiss(status)
+ this.websocketStatusService.dismiss(status)
)
}
httpTestingController.match(
`${environment.apiBaseUrl}documents/?page=1&page_size=100000&fields=id`
) // listAllFilteredIds
+ expect(documentListViewService.selected.size).toEqual(0)
})
it('should support bulk download with archive, originals or both and file formatting', () => {
.pipe(first())
.subscribe({
next: () => {
+ if (args['delete_originals']) {
+ this.list.selected.clear()
+ }
this.list.reload()
this.list.reduceSelectionToFilter()
this.list.selected.forEach((id) => {
import { FilterPipe } from 'src/app/pipes/filter.pipe'
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
import { UsernamePipe } from 'src/app/pipes/username.pipe'
-import {
- ConsumerStatusService,
- FileStatus,
-} from 'src/app/services/consumer-status.service'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { PermissionsService } from 'src/app/services/permissions.service'
import { DocumentService } from 'src/app/services/rest/document.service'
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
+import {
+ FileStatus,
+ WebsocketStatusService,
+} from 'src/app/services/websocket-status.service'
import { DocumentCardLargeComponent } from './document-card-large/document-card-large.component'
import { DocumentCardSmallComponent } from './document-card-small/document-card-small.component'
import { DocumentListComponent } from './document-list.component'
let fixture: ComponentFixture<DocumentListComponent>
let documentListService: DocumentListViewService
let documentService: DocumentService
- let consumerStatusService: ConsumerStatusService
+ let websocketStatusService: WebsocketStatusService
let savedViewService: SavedViewService
let router: Router
let activatedRoute: ActivatedRoute
documentListService = TestBed.inject(DocumentListViewService)
documentService = TestBed.inject(DocumentService)
- consumerStatusService = TestBed.inject(ConsumerStatusService)
+ websocketStatusService = TestBed.inject(WebsocketStatusService)
savedViewService = TestBed.inject(SavedViewService)
router = TestBed.inject(Router)
activatedRoute = TestBed.inject(ActivatedRoute)
const reloadSpy = jest.spyOn(documentListService, 'reload')
const fileStatusSubject = new Subject<FileStatus>()
jest
- .spyOn(consumerStatusService, 'onDocumentConsumptionFinished')
+ .spyOn(websocketStatusService, 'onDocumentConsumptionFinished')
.mockReturnValue(fileStatusSubject)
fixture.detectChanges()
fileStatusSubject.next(new FileStatus())
expect(reloadSpy).toHaveBeenCalled()
})
+ it('should reload on document deleted', () => {
+ const reloadSpy = jest.spyOn(documentListService, 'reload')
+ const documentDeletedSubject = new Subject<boolean>()
+ jest
+ .spyOn(websocketStatusService, 'onDocumentDeleted')
+ .mockReturnValue(documentDeletedSubject)
+ fixture.detectChanges()
+ documentDeletedSubject.next(true)
+ expect(reloadSpy).toHaveBeenCalled()
+ })
+
it('should show score sort fields on fulltext queries', () => {
documentListService.filterRules = [
{
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'
import { HotKeyService } from 'src/app/services/hot-key.service'
import { OpenDocumentsService } from 'src/app/services/open-documents.service'
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
+import { WebsocketStatusService } from 'src/app/services/websocket-status.service'
import {
filterRulesDiffer,
isFullTextFilterRule,
private router: Router,
private toastService: ToastService,
private modalService: NgbModal,
- private consumerStatusService: ConsumerStatusService,
+ private websocketStatusService: WebsocketStatusService,
public openDocumentsService: OpenDocumentsService,
public settingsService: SettingsService,
private hotKeyService: HotKeyService,
}
ngOnInit(): void {
- this.consumerStatusService
+ this.websocketStatusService
.onDocumentConsumptionFinished()
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe(() => {
this.list.reload()
})
+ this.websocketStatusService.onDocumentDeleted().subscribe(() => {
+ this.list.reload()
+ })
+
this.route.paramMap
.pipe(
filter((params) => params.has('id')), // only on saved view e.g. /view/id
--- /dev/null
+export interface WebsocketDocumentsDeletedMessage {
+ documents: number[]
+}
-export interface WebsocketConsumerStatusMessage {
+export interface WebsocketProgressMessage {
filename?: string
task_id?: string
current_progress?: number
+++ /dev/null
-import {
- HttpEventType,
- HttpResponse,
- provideHttpClient,
- withInterceptorsFromDi,
-} from '@angular/common/http'
-import {
- HttpTestingController,
- provideHttpClientTesting,
-} from '@angular/common/http/testing'
-import { TestBed } from '@angular/core/testing'
-import WS from 'jest-websocket-mock'
-import { environment } from 'src/environments/environment'
-import {
- ConsumerStatusService,
- FILE_STATUS_MESSAGES,
- FileStatusPhase,
-} from './consumer-status.service'
-import { DocumentService } from './rest/document.service'
-import { SettingsService } from './settings.service'
-
-describe('ConsumerStatusService', () => {
- let httpTestingController: HttpTestingController
- let consumerStatusService: ConsumerStatusService
- let documentService: DocumentService
- let settingsService: SettingsService
-
- const server = new WS(
- `${environment.webSocketProtocol}//${environment.webSocketHost}${environment.webSocketBaseUrl}status/`,
- { jsonProtocol: true }
- )
-
- beforeEach(() => {
- TestBed.configureTestingModule({
- imports: [],
- providers: [
- ConsumerStatusService,
- DocumentService,
- SettingsService,
- provideHttpClient(withInterceptorsFromDi()),
- provideHttpClientTesting(),
- ],
- })
-
- httpTestingController = TestBed.inject(HttpTestingController)
- settingsService = TestBed.inject(SettingsService)
- settingsService.currentUser = {
- id: 1,
- username: 'testuser',
- is_superuser: false,
- }
- consumerStatusService = TestBed.inject(ConsumerStatusService)
- documentService = TestBed.inject(DocumentService)
- })
-
- afterEach(() => {
- httpTestingController.verify()
- })
-
- it('should update status on websocket processing progress', () => {
- const task_id = '1234'
- const status = consumerStatusService.newFileUpload('file.pdf')
- expect(status.getProgress()).toEqual(0)
-
- consumerStatusService.connect()
-
- consumerStatusService
- .onDocumentConsumptionFinished()
- .subscribe((filestatus) => {
- expect(filestatus.phase).toEqual(FileStatusPhase.SUCCESS)
- })
-
- consumerStatusService.onDocumentDetected().subscribe((filestatus) => {
- expect(filestatus.phase).toEqual(FileStatusPhase.STARTED)
- })
-
- server.send({
- task_id,
- filename: 'file.pdf',
- current_progress: 50,
- max_progress: 100,
- document_id: 12,
- status: 'WORKING',
- })
-
- expect(status.getProgress()).toBeCloseTo(0.6) // (0.8 * 50/100) + .2
- expect(consumerStatusService.getConsumerStatusNotCompleted()).toEqual([
- status,
- ])
-
- server.send({
- task_id,
- filename: 'file.pdf',
- current_progress: 100,
- max_progress: 100,
- document_id: 12,
- status: 'SUCCESS',
- message: FILE_STATUS_MESSAGES.finished,
- })
-
- expect(status.getProgress()).toEqual(1)
- expect(consumerStatusService.getConsumerStatusNotCompleted()).toHaveLength(
- 0
- )
- expect(consumerStatusService.getConsumerStatusCompleted()).toHaveLength(1)
-
- consumerStatusService.disconnect()
- })
-
- it('should update status on websocket failed progress', () => {
- const task_id = '1234'
- const status = consumerStatusService.newFileUpload('file.pdf')
- status.taskId = task_id
- consumerStatusService.connect()
-
- consumerStatusService
- .onDocumentConsumptionFailed()
- .subscribe((filestatus) => {
- expect(filestatus.phase).toEqual(FileStatusPhase.FAILED)
- })
-
- server.send({
- task_id,
- filename: 'file.pdf',
- current_progress: 50,
- max_progress: 100,
- document_id: 12,
- })
-
- expect(consumerStatusService.getConsumerStatusNotCompleted()).toEqual([
- status,
- ])
-
- server.send({
- task_id,
- filename: 'file.pdf',
- current_progress: 50,
- max_progress: 100,
- document_id: 12,
- status: 'FAILED',
- message: FILE_STATUS_MESSAGES.document_already_exists,
- })
-
- expect(consumerStatusService.getConsumerStatusNotCompleted()).toHaveLength(
- 0
- )
- expect(consumerStatusService.getConsumerStatusCompleted()).toHaveLength(1)
- })
-
- it('should update status on upload progress', () => {
- const task_id = '1234'
- const status = consumerStatusService.newFileUpload('file.pdf')
-
- documentService.uploadDocument({}).subscribe((event) => {
- if (event.type === HttpEventType.Response) {
- status.taskId = event.body['task_id']
- status.message = $localize`Upload complete, waiting...`
- } else if (event.type === HttpEventType.UploadProgress) {
- status.updateProgress(
- FileStatusPhase.UPLOADING,
- event.loaded,
- event.total
- )
- }
- })
-
- const req = httpTestingController.expectOne(
- `${environment.apiBaseUrl}documents/post_document/`
- )
-
- req.event(
- new HttpResponse({
- body: {
- task_id,
- },
- })
- )
-
- req.event({
- type: HttpEventType.UploadProgress,
- loaded: 100,
- total: 300,
- })
-
- expect(
- consumerStatusService.getConsumerStatus(FileStatusPhase.UPLOADING)
- ).toEqual([status])
- expect(consumerStatusService.getConsumerStatus()).toEqual([status])
- expect(consumerStatusService.getConsumerStatusNotCompleted()).toEqual([
- status,
- ])
-
- req.event({
- type: HttpEventType.UploadProgress,
- loaded: 300,
- total: 300,
- })
-
- expect(status.getProgress()).toEqual(0.2) // 0.2 * 300/300
- })
-
- it('should support dismiss completed', () => {
- consumerStatusService.connect()
- server.send({
- task_id: '1234',
- filename: 'file.pdf',
- current_progress: 100,
- max_progress: 100,
- document_id: 12,
- status: 'SUCCESS',
- message: 'finished',
- })
-
- expect(consumerStatusService.getConsumerStatusCompleted()).toHaveLength(1)
- consumerStatusService.dismissCompleted()
- expect(consumerStatusService.getConsumerStatusCompleted()).toHaveLength(0)
- consumerStatusService.disconnect()
- })
-
- it('should support dismiss', () => {
- const task_id = '1234'
- const status = consumerStatusService.newFileUpload('file.pdf')
- status.taskId = task_id
- status.updateProgress(FileStatusPhase.UPLOADING, 50, 100)
-
- const status2 = consumerStatusService.newFileUpload('file2.pdf')
- status2.updateProgress(FileStatusPhase.UPLOADING, 50, 100)
-
- expect(
- consumerStatusService.getConsumerStatus(FileStatusPhase.UPLOADING)
- ).toEqual([status, status2])
- expect(consumerStatusService.getConsumerStatus()).toEqual([status, status2])
- expect(consumerStatusService.getConsumerStatusNotCompleted()).toEqual([
- status,
- status2,
- ])
-
- consumerStatusService.dismiss(status)
- expect(consumerStatusService.getConsumerStatus()).toEqual([status2])
-
- consumerStatusService.dismiss(status2)
- expect(consumerStatusService.getConsumerStatus()).toHaveLength(0)
- })
-
- it('should support fail', () => {
- const task_id = '1234'
- const status = consumerStatusService.newFileUpload('file.pdf')
- status.taskId = task_id
- status.updateProgress(FileStatusPhase.UPLOADING, 50, 100)
- expect(consumerStatusService.getConsumerStatusNotCompleted()).toHaveLength(
- 1
- )
- expect(consumerStatusService.getConsumerStatusCompleted()).toHaveLength(0)
- consumerStatusService.fail(status, 'fail')
- expect(consumerStatusService.getConsumerStatusNotCompleted()).toHaveLength(
- 0
- )
- expect(consumerStatusService.getConsumerStatusCompleted()).toHaveLength(1)
- })
-
- it('should notify of document created on status message without upload', () => {
- let detected = false
- consumerStatusService.onDocumentDetected().subscribe((filestatus) => {
- expect(filestatus.phase).toEqual(FileStatusPhase.STARTED)
- detected = true
- })
-
- consumerStatusService.connect()
- server.send({
- task_id: '1234',
- filename: 'file.pdf',
- current_progress: 0,
- max_progress: 100,
- message: 'new_file',
- status: 'STARTED',
- })
-
- consumerStatusService.disconnect()
- expect(detected).toBeTruthy()
- })
-
- it('should notify of document in progress without upload', () => {
- consumerStatusService.connect()
- server.send({
- task_id: '1234',
- filename: 'file.pdf',
- current_progress: 50,
- max_progress: 100,
- docuement_id: 12,
- status: 'WORKING',
- })
-
- consumerStatusService.disconnect()
- expect(consumerStatusService.getConsumerStatusNotCompleted()).toHaveLength(
- 1
- )
- })
-
- it('should not notify current user if document has different expected owner', () => {
- consumerStatusService.connect()
- server.send({
- task_id: '1234',
- filename: 'file1.pdf',
- current_progress: 50,
- max_progress: 100,
- docuement_id: 12,
- owner_id: 1,
- status: 'WORKING',
- })
-
- server.send({
- task_id: '5678',
- filename: 'file2.pdf',
- current_progress: 50,
- max_progress: 100,
- docuement_id: 13,
- owner_id: 2,
- status: 'WORKING',
- })
-
- consumerStatusService.disconnect()
- expect(consumerStatusService.getConsumerStatusNotCompleted()).toHaveLength(
- 1
- )
- })
-})
} from '@angular/common/http/testing'
import { TestBed } from '@angular/core/testing'
import { environment } from 'src/environments/environment'
+import { UploadDocumentsService } from './upload-documents.service'
import {
- ConsumerStatusService,
FileStatusPhase,
-} from './consumer-status.service'
-import { UploadDocumentsService } from './upload-documents.service'
+ WebsocketStatusService,
+} from './websocket-status.service'
const files = [
{
describe('UploadDocumentsService', () => {
let httpTestingController: HttpTestingController
let uploadDocumentsService: UploadDocumentsService
- let consumerStatusService: ConsumerStatusService
+ let websocketStatusService: WebsocketStatusService
beforeEach(() => {
TestBed.configureTestingModule({
imports: [],
providers: [
UploadDocumentsService,
- ConsumerStatusService,
+ WebsocketStatusService,
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
],
httpTestingController = TestBed.inject(HttpTestingController)
uploadDocumentsService = TestBed.inject(UploadDocumentsService)
- consumerStatusService = TestBed.inject(ConsumerStatusService)
+ websocketStatusService = TestBed.inject(WebsocketStatusService)
})
afterEach(() => {
it('updates progress during upload and failure', () => {
uploadDocumentsService.uploadFiles(fileList)
- expect(consumerStatusService.getConsumerStatusNotCompleted()).toHaveLength(
+ expect(websocketStatusService.getConsumerStatusNotCompleted()).toHaveLength(
2
)
expect(
- consumerStatusService.getConsumerStatus(FileStatusPhase.UPLOADING)
+ websocketStatusService.getConsumerStatus(FileStatusPhase.UPLOADING)
).toHaveLength(0)
const req = httpTestingController.match(
})
expect(
- consumerStatusService.getConsumerStatus(FileStatusPhase.UPLOADING)
+ websocketStatusService.getConsumerStatus(FileStatusPhase.UPLOADING)
).toHaveLength(1)
})
)
expect(
- consumerStatusService.getConsumerStatus(FileStatusPhase.FAILED)
+ websocketStatusService.getConsumerStatus(FileStatusPhase.FAILED)
).toHaveLength(0)
req[0].flush(
)
expect(
- consumerStatusService.getConsumerStatus(FileStatusPhase.FAILED)
+ websocketStatusService.getConsumerStatus(FileStatusPhase.FAILED)
).toHaveLength(1)
uploadDocumentsService.uploadFiles(fileList)
)
expect(
- consumerStatusService.getConsumerStatus(FileStatusPhase.FAILED)
+ websocketStatusService.getConsumerStatus(FileStatusPhase.FAILED)
).toHaveLength(2)
})
import { Injectable } from '@angular/core'
import { FileSystemFileEntry, NgxFileDropEntry } from 'ngx-file-drop'
import { Subscription } from 'rxjs'
+import { DocumentService } from './rest/document.service'
import {
- ConsumerStatusService,
FileStatusPhase,
-} from './consumer-status.service'
-import { DocumentService } from './rest/document.service'
+ WebsocketStatusService,
+} from './websocket-status.service'
@Injectable({
providedIn: 'root',
constructor(
private documentService: DocumentService,
- private consumerStatusService: ConsumerStatusService
+ private websocketStatusService: WebsocketStatusService
) {}
onNgxFileDrop(files: NgxFileDropEntry[]) {
private uploadFile(file: File) {
let formData = new FormData()
formData.append('document', file, file.name)
- let status = this.consumerStatusService.newFileUpload(file.name)
+ let status = this.websocketStatusService.newFileUpload(file.name)
status.message = $localize`Connecting...`
error: (error) => {
switch (error.status) {
case 400: {
- this.consumerStatusService.fail(status, error.error.document)
+ this.websocketStatusService.fail(status, error.error.document)
break
}
default: {
- this.consumerStatusService.fail(
+ this.websocketStatusService.fail(
status,
$localize`HTTP error: ${error.status} ${error.statusText}`
)
--- /dev/null
+import {
+ HttpEventType,
+ HttpResponse,
+ provideHttpClient,
+ withInterceptorsFromDi,
+} from '@angular/common/http'
+import {
+ HttpTestingController,
+ provideHttpClientTesting,
+} from '@angular/common/http/testing'
+import { TestBed } from '@angular/core/testing'
+import WS from 'jest-websocket-mock'
+import { environment } from 'src/environments/environment'
+import { DocumentService } from './rest/document.service'
+import { SettingsService } from './settings.service'
+import {
+ FILE_STATUS_MESSAGES,
+ FileStatusPhase,
+ WebsocketStatusService,
+ WebsocketStatusType,
+} from './websocket-status.service'
+
+describe('ConsumerStatusService', () => {
+ let httpTestingController: HttpTestingController
+ let websocketStatusService: WebsocketStatusService
+ let documentService: DocumentService
+ let settingsService: SettingsService
+
+ const server = new WS(
+ `${environment.webSocketProtocol}//${environment.webSocketHost}${environment.webSocketBaseUrl}status/`,
+ { jsonProtocol: true }
+ )
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [],
+ providers: [
+ WebsocketStatusService,
+ DocumentService,
+ SettingsService,
+ provideHttpClient(withInterceptorsFromDi()),
+ provideHttpClientTesting(),
+ ],
+ })
+
+ httpTestingController = TestBed.inject(HttpTestingController)
+ settingsService = TestBed.inject(SettingsService)
+ settingsService.currentUser = {
+ id: 1,
+ username: 'testuser',
+ is_superuser: false,
+ }
+ websocketStatusService = TestBed.inject(WebsocketStatusService)
+ documentService = TestBed.inject(DocumentService)
+ })
+
+ afterEach(() => {
+ httpTestingController.verify()
+ })
+
+ it('should update status on websocket processing progress', () => {
+ const task_id = '1234'
+ const status = websocketStatusService.newFileUpload('file.pdf')
+ expect(status.getProgress()).toEqual(0)
+
+ websocketStatusService.connect()
+
+ websocketStatusService
+ .onDocumentConsumptionFinished()
+ .subscribe((filestatus) => {
+ expect(filestatus.phase).toEqual(FileStatusPhase.SUCCESS)
+ })
+
+ websocketStatusService.onDocumentDetected().subscribe((filestatus) => {
+ expect(filestatus.phase).toEqual(FileStatusPhase.STARTED)
+ })
+
+ server.send({
+ type: WebsocketStatusType.STATUS_UPDATE,
+ data: {
+ task_id,
+ filename: 'file.pdf',
+ current_progress: 50,
+ max_progress: 100,
+ document_id: 12,
+ status: 'WORKING',
+ },
+ })
+
+ expect(status.getProgress()).toBeCloseTo(0.6) // (0.8 * 50/100) + .2
+ expect(websocketStatusService.getConsumerStatusNotCompleted()).toEqual([
+ status,
+ ])
+
+ server.send({
+ type: WebsocketStatusType.STATUS_UPDATE,
+ data: {
+ task_id,
+ filename: 'file.pdf',
+ current_progress: 100,
+ max_progress: 100,
+ document_id: 12,
+ status: 'SUCCESS',
+ message: FILE_STATUS_MESSAGES.finished,
+ },
+ })
+
+ expect(status.getProgress()).toEqual(1)
+ expect(websocketStatusService.getConsumerStatusNotCompleted()).toHaveLength(
+ 0
+ )
+ expect(websocketStatusService.getConsumerStatusCompleted()).toHaveLength(1)
+
+ websocketStatusService.disconnect()
+ })
+
+ it('should update status on websocket failed progress', () => {
+ const task_id = '1234'
+ const status = websocketStatusService.newFileUpload('file.pdf')
+ status.taskId = task_id
+ websocketStatusService.connect()
+
+ websocketStatusService
+ .onDocumentConsumptionFailed()
+ .subscribe((filestatus) => {
+ expect(filestatus.phase).toEqual(FileStatusPhase.FAILED)
+ })
+
+ server.send({
+ type: WebsocketStatusType.STATUS_UPDATE,
+ data: {
+ task_id,
+ filename: 'file.pdf',
+ current_progress: 50,
+ max_progress: 100,
+ document_id: 12,
+ },
+ })
+
+ expect(websocketStatusService.getConsumerStatusNotCompleted()).toEqual([
+ status,
+ ])
+
+ server.send({
+ type: WebsocketStatusType.STATUS_UPDATE,
+ data: {
+ task_id,
+ filename: 'file.pdf',
+ current_progress: 50,
+ max_progress: 100,
+ document_id: 12,
+ status: 'FAILED',
+ message: FILE_STATUS_MESSAGES.document_already_exists,
+ },
+ })
+
+ expect(websocketStatusService.getConsumerStatusNotCompleted()).toHaveLength(
+ 0
+ )
+ expect(websocketStatusService.getConsumerStatusCompleted()).toHaveLength(1)
+ })
+
+ it('should update status on upload progress', () => {
+ const task_id = '1234'
+ const status = websocketStatusService.newFileUpload('file.pdf')
+
+ documentService.uploadDocument({}).subscribe((event) => {
+ if (event.type === HttpEventType.Response) {
+ status.taskId = event.body['task_id']
+ status.message = $localize`Upload complete, waiting...`
+ } else if (event.type === HttpEventType.UploadProgress) {
+ status.updateProgress(
+ FileStatusPhase.UPLOADING,
+ event.loaded,
+ event.total
+ )
+ }
+ })
+
+ const req = httpTestingController.expectOne(
+ `${environment.apiBaseUrl}documents/post_document/`
+ )
+
+ req.event(
+ new HttpResponse({
+ body: {
+ task_id,
+ },
+ })
+ )
+
+ req.event({
+ type: HttpEventType.UploadProgress,
+ loaded: 100,
+ total: 300,
+ })
+
+ expect(
+ websocketStatusService.getConsumerStatus(FileStatusPhase.UPLOADING)
+ ).toEqual([status])
+ expect(websocketStatusService.getConsumerStatus()).toEqual([status])
+ expect(websocketStatusService.getConsumerStatusNotCompleted()).toEqual([
+ status,
+ ])
+
+ req.event({
+ type: HttpEventType.UploadProgress,
+ loaded: 300,
+ total: 300,
+ })
+
+ expect(status.getProgress()).toEqual(0.2) // 0.2 * 300/300
+ })
+
+ it('should support dismiss completed', () => {
+ websocketStatusService.connect()
+ server.send({
+ type: WebsocketStatusType.STATUS_UPDATE,
+ data: {
+ task_id: '1234',
+ filename: 'file.pdf',
+ current_progress: 100,
+ max_progress: 100,
+ document_id: 12,
+ status: 'SUCCESS',
+ message: 'finished',
+ },
+ })
+
+ expect(websocketStatusService.getConsumerStatusCompleted()).toHaveLength(1)
+ websocketStatusService.dismissCompleted()
+ expect(websocketStatusService.getConsumerStatusCompleted()).toHaveLength(0)
+ websocketStatusService.disconnect()
+ })
+
+ it('should support dismiss', () => {
+ const task_id = '1234'
+ const status = websocketStatusService.newFileUpload('file.pdf')
+ status.taskId = task_id
+ status.updateProgress(FileStatusPhase.UPLOADING, 50, 100)
+
+ const status2 = websocketStatusService.newFileUpload('file2.pdf')
+ status2.updateProgress(FileStatusPhase.UPLOADING, 50, 100)
+
+ expect(
+ websocketStatusService.getConsumerStatus(FileStatusPhase.UPLOADING)
+ ).toEqual([status, status2])
+ expect(websocketStatusService.getConsumerStatus()).toEqual([
+ status,
+ status2,
+ ])
+ expect(websocketStatusService.getConsumerStatusNotCompleted()).toEqual([
+ status,
+ status2,
+ ])
+
+ websocketStatusService.dismiss(status)
+ expect(websocketStatusService.getConsumerStatus()).toEqual([status2])
+
+ websocketStatusService.dismiss(status2)
+ expect(websocketStatusService.getConsumerStatus()).toHaveLength(0)
+ })
+
+ it('should support fail', () => {
+ const task_id = '1234'
+ const status = websocketStatusService.newFileUpload('file.pdf')
+ status.taskId = task_id
+ status.updateProgress(FileStatusPhase.UPLOADING, 50, 100)
+ expect(websocketStatusService.getConsumerStatusNotCompleted()).toHaveLength(
+ 1
+ )
+ expect(websocketStatusService.getConsumerStatusCompleted()).toHaveLength(0)
+ websocketStatusService.fail(status, 'fail')
+ expect(websocketStatusService.getConsumerStatusNotCompleted()).toHaveLength(
+ 0
+ )
+ expect(websocketStatusService.getConsumerStatusCompleted()).toHaveLength(1)
+ })
+
+ it('should notify of document created on status message without upload', () => {
+ let detected = false
+ websocketStatusService.onDocumentDetected().subscribe((filestatus) => {
+ expect(filestatus.phase).toEqual(FileStatusPhase.STARTED)
+ detected = true
+ })
+
+ websocketStatusService.connect()
+ server.send({
+ type: WebsocketStatusType.STATUS_UPDATE,
+ data: {
+ task_id: '1234',
+ filename: 'file.pdf',
+ current_progress: 0,
+ max_progress: 100,
+ message: 'new_file',
+ status: 'STARTED',
+ },
+ })
+
+ websocketStatusService.disconnect()
+ expect(detected).toBeTruthy()
+ })
+
+ it('should notify of document in progress without upload', () => {
+ websocketStatusService.connect()
+ server.send({
+ type: WebsocketStatusType.STATUS_UPDATE,
+ data: {
+ task_id: '1234',
+ filename: 'file.pdf',
+ current_progress: 50,
+ max_progress: 100,
+ docuement_id: 12,
+ status: 'WORKING',
+ },
+ })
+
+ websocketStatusService.disconnect()
+ expect(websocketStatusService.getConsumerStatusNotCompleted()).toHaveLength(
+ 1
+ )
+ })
+
+ it('should not notify current user if document has different expected owner', () => {
+ websocketStatusService.connect()
+ server.send({
+ type: WebsocketStatusType.STATUS_UPDATE,
+ data: {
+ task_id: '1234',
+ filename: 'file1.pdf',
+ current_progress: 50,
+ max_progress: 100,
+ docuement_id: 12,
+ owner_id: 1,
+ status: 'WORKING',
+ },
+ })
+
+ server.send({
+ type: WebsocketStatusType.STATUS_UPDATE,
+ data: {
+ task_id: '5678',
+ filename: 'file2.pdf',
+ current_progress: 50,
+ max_progress: 100,
+ docuement_id: 13,
+ owner_id: 2,
+ status: 'WORKING',
+ },
+ })
+
+ websocketStatusService.disconnect()
+ expect(websocketStatusService.getConsumerStatusNotCompleted()).toHaveLength(
+ 1
+ )
+ })
+
+ it('should trigger deleted subject on document deleted', () => {
+ let deleted = false
+ websocketStatusService.onDocumentDeleted().subscribe(() => {
+ deleted = true
+ })
+
+ websocketStatusService.connect()
+ server.send({
+ type: WebsocketStatusType.DOCUMENTS_DELETED,
+ data: {
+ documents: [1, 2, 3],
+ },
+ })
+
+ websocketStatusService.disconnect()
+ expect(deleted).toBeTruthy()
+ })
+})
import { Injectable } from '@angular/core'
import { Subject } from 'rxjs'
import { environment } from 'src/environments/environment'
-import { WebsocketConsumerStatusMessage } from '../data/websocket-consumer-status-message'
+import { WebsocketDocumentsDeletedMessage } from '../data/websocket-documents-deleted-message'
+import { WebsocketProgressMessage } from '../data/websocket-progress-message'
import { SettingsService } from './settings.service'
+export enum WebsocketStatusType {
+ STATUS_UPDATE = 'status_update',
+ DOCUMENTS_DELETED = 'documents_deleted',
+}
+
// see ProgressStatusOptions in src/documents/plugins/helpers.py
export enum FileStatusPhase {
STARTED = 0,
@Injectable({
providedIn: 'root',
})
-export class ConsumerStatusService {
+export class WebsocketStatusService {
constructor(private settingsService: SettingsService) {}
private statusWebSocket: WebSocket
private documentDetectedSubject = new Subject<FileStatus>()
private documentConsumptionFinishedSubject = new Subject<FileStatus>()
private documentConsumptionFailedSubject = new Subject<FileStatus>()
+ private documentDeletedSubject = new Subject<boolean>()
private get(taskId: string, filename?: string) {
let status =
this.statusWebSocket = new WebSocket(
`${environment.webSocketProtocol}//${environment.webSocketHost}${environment.webSocketBaseUrl}status/`
)
- this.statusWebSocket.onmessage = (ev) => {
- let statusMessage: WebsocketConsumerStatusMessage = JSON.parse(ev['data'])
-
- // fallback if backend didn't restrict message
- if (
- statusMessage.owner_id &&
- statusMessage.owner_id !== this.settingsService.currentUser?.id &&
- !this.settingsService.currentUser?.is_superuser
- ) {
- return
+ this.statusWebSocket.onmessage = (ev: MessageEvent) => {
+ const {
+ type,
+ data: messageData,
+ }: {
+ type: WebsocketStatusType
+ data: WebsocketProgressMessage | WebsocketDocumentsDeletedMessage
+ } = JSON.parse(ev.data)
+
+ switch (type) {
+ case WebsocketStatusType.DOCUMENTS_DELETED:
+ this.documentDeletedSubject.next(true)
+ break
+
+ case WebsocketStatusType.STATUS_UPDATE:
+ this.handleProgressUpdate(messageData as WebsocketProgressMessage)
+ break
}
+ }
+ }
- let statusMessageGet = this.get(
- statusMessage.task_id,
- statusMessage.filename
- )
- let status = statusMessageGet.status
- let created = statusMessageGet.created
+ handleProgressUpdate(messageData: WebsocketProgressMessage) {
+ // fallback if backend didn't restrict message
+ if (
+ messageData.owner_id &&
+ messageData.owner_id !== this.settingsService.currentUser?.id &&
+ !this.settingsService.currentUser?.is_superuser
+ ) {
+ return
+ }
- status.updateProgress(
- FileStatusPhase.WORKING,
- statusMessage.current_progress,
- statusMessage.max_progress
- )
- if (
- statusMessage.message &&
- statusMessage.message in FILE_STATUS_MESSAGES
- ) {
- status.message = FILE_STATUS_MESSAGES[statusMessage.message]
- } else if (statusMessage.message) {
- status.message = statusMessage.message
- }
- status.documentId = statusMessage.document_id
+ let statusMessageGet = this.get(messageData.task_id, messageData.filename)
+ let status = statusMessageGet.status
+ let created = statusMessageGet.created
- if (statusMessage.status in FileStatusPhase) {
- status.phase = FileStatusPhase[statusMessage.status]
- }
+ status.updateProgress(
+ FileStatusPhase.WORKING,
+ messageData.current_progress,
+ messageData.max_progress
+ )
+ if (messageData.message && messageData.message in FILE_STATUS_MESSAGES) {
+ status.message = FILE_STATUS_MESSAGES[messageData.message]
+ } else if (messageData.message) {
+ status.message = messageData.message
+ }
+ status.documentId = messageData.document_id
- switch (status.phase) {
- case FileStatusPhase.STARTED:
- if (created) this.documentDetectedSubject.next(status)
- break
+ if (messageData.status in FileStatusPhase) {
+ status.phase = FileStatusPhase[messageData.status]
+ }
- case FileStatusPhase.SUCCESS:
- this.documentConsumptionFinishedSubject.next(status)
- break
+ switch (status.phase) {
+ case FileStatusPhase.STARTED:
+ if (created) this.documentDetectedSubject.next(status)
+ break
- case FileStatusPhase.FAILED:
- this.documentConsumptionFailedSubject.next(status)
- break
+ case FileStatusPhase.SUCCESS:
+ this.documentConsumptionFinishedSubject.next(status)
+ break
- default:
- break
- }
+ case FileStatusPhase.FAILED:
+ this.documentConsumptionFailedSubject.next(status)
+ break
+
+ default:
+ break
}
}
onDocumentDetected() {
return this.documentDetectedSubject
}
+
+ onDocumentDeleted() {
+ return this.documentDeletedSubject
+ }
}
from documents.models import DocumentType
from documents.models import StoragePath
from documents.permissions import set_permissions_for_object
+from documents.plugins.helpers import DocumentsStatusManager
from documents.tasks import bulk_update_documents
from documents.tasks import consume_file
from documents.tasks import update_document_content_maybe_archive_file
with index.open_index_writer() as writer:
for id in doc_ids:
index.remove_document_by_id(writer, id)
+
+ status_mgr = DocumentsStatusManager()
+ status_mgr.send_documents_deleted(doc_ids)
except Exception as e:
if "Data too long for column" in str(e):
logger.warning(
FAILED = "FAILED"
-class ProgressManager:
+class BaseStatusManager:
"""
Handles sending of progress information via the channel layer, with proper management
of the open/close of the layer to ensure messages go out and everything is cleaned up
"""
- def __init__(self, filename: str, task_id: str | None = None) -> None:
- self.filename = filename
+ def __init__(self) -> None:
self._channel: RedisPubSubChannelLayer | None = None
- self.task_id = task_id
def __enter__(self):
self.open()
async_to_sync(self._channel.flush)
self._channel = None
+ def send(self, payload: dict[str, str | int | None]) -> None:
+ # Ensure the layer is open
+ self.open()
+
+ # Just for IDEs
+ if TYPE_CHECKING:
+ assert self._channel is not None
+
+ # Construct and send the update
+ async_to_sync(self._channel.group_send)("status_updates", payload)
+
+
+class ProgressManager(BaseStatusManager):
+ def __init__(self, filename: str | None = None, task_id: str | None = None) -> None:
+ super().__init__()
+ self.filename = filename
+ self.task_id = task_id
+
def send_progress(
self,
status: ProgressStatusOptions,
max_progress: int,
extra_args: dict[str, str | int | None] | None = None,
) -> None:
- # Ensure the layer is open
- self.open()
-
- # Just for IDEs
- if TYPE_CHECKING:
- assert self._channel is not None
-
payload = {
"type": "status_update",
"data": {
if extra_args is not None:
payload["data"].update(extra_args)
- # Construct and send the update
- async_to_sync(self._channel.group_send)("status_updates", payload)
+ self.send(payload)
+
+
+class DocumentsStatusManager(BaseStatusManager):
+ def send_documents_deleted(self, documents: list[int]) -> None:
+ payload = {
+ "type": "documents_deleted",
+ "data": {
+ "documents": documents,
+ },
+ }
+
+ self.send(payload)
self.close()
else:
if self._is_owner_or_unowned(event["data"]):
- self.send(json.dumps(event["data"]))
+ self.send(json.dumps(event))
+
+ def documents_deleted(self, event):
+ if not self._authenticated():
+ self.close()
+ else:
+ self.send(json.dumps(event))
from django.test import TestCase
from django.test import override_settings
+from documents.plugins.helpers import DocumentsStatusManager
+from documents.plugins.helpers import ProgressManager
+from documents.plugins.helpers import ProgressStatusOptions
from paperless.asgi import application
TEST_CHANNEL_LAYERS = {
self.assertFalse(connected)
await communicator.disconnect()
+ @mock.patch("paperless.consumers.StatusConsumer.close")
+ @mock.patch("paperless.consumers.StatusConsumer._authenticated")
+ async def test_close_on_no_auth(self, _authenticated, mock_close):
+ _authenticated.return_value = True
+
+ communicator = WebsocketCommunicator(application, "/ws/status/")
+ connected, subprotocol = await communicator.connect()
+ self.assertTrue(connected)
+
+ message = {"type": "status_update", "data": {"task_id": "test"}}
+
+ _authenticated.return_value = False
+
+ channel_layer = get_channel_layer()
+ await channel_layer.group_send(
+ "status_updates",
+ message,
+ )
+ await communicator.receive_nothing()
+
+ mock_close.assert_called_once()
+ mock_close.reset_mock()
+
+ message = {"type": "documents_deleted", "data": {"documents": [1, 2, 3]}}
+
+ await channel_layer.group_send(
+ "status_updates",
+ message,
+ )
+ await communicator.receive_nothing()
+
+ mock_close.assert_called_once()
+
@mock.patch("paperless.consumers.StatusConsumer._authenticated")
async def test_auth(self, _authenticated):
_authenticated.return_value = True
await communicator.disconnect()
@mock.patch("paperless.consumers.StatusConsumer._authenticated")
- async def test_receive(self, _authenticated):
+ async def test_receive_status_update(self, _authenticated):
+ _authenticated.return_value = True
+
+ communicator = WebsocketCommunicator(application, "/ws/status/")
+ connected, subprotocol = await communicator.connect()
+ self.assertTrue(connected)
+
+ message = {"type": "status_update", "data": {"task_id": "test"}}
+
+ channel_layer = get_channel_layer()
+ await channel_layer.group_send(
+ "status_updates",
+ message,
+ )
+
+ response = await communicator.receive_json_from()
+
+ self.assertEqual(response, message)
+
+ await communicator.disconnect()
+
+ @mock.patch("paperless.consumers.StatusConsumer._authenticated")
+ async def test_receive_documents_deleted(self, _authenticated):
_authenticated.return_value = True
communicator = WebsocketCommunicator(application, "/ws/status/")
connected, subprotocol = await communicator.connect()
self.assertTrue(connected)
- message = {"task_id": "test"}
+ message = {"type": "documents_deleted", "data": {"documents": [1, 2, 3]}}
channel_layer = get_channel_layer()
await channel_layer.group_send(
"status_updates",
- {"type": "status_update", "data": message},
+ message,
)
response = await communicator.receive_json_from()
self.assertEqual(response, message)
await communicator.disconnect()
+
+ @mock.patch("channels.layers.InMemoryChannelLayer.group_send")
+ def test_manager_send_progress(self, mock_group_send):
+ with ProgressManager(task_id="test") as manager:
+ manager.send_progress(
+ ProgressStatusOptions.STARTED,
+ "Test message",
+ 1,
+ 10,
+ extra_args={
+ "foo": "bar",
+ },
+ )
+
+ message = mock_group_send.call_args[0][1]
+
+ self.assertEqual(
+ message,
+ {
+ "type": "status_update",
+ "data": {
+ "filename": None,
+ "task_id": "test",
+ "current_progress": 1,
+ "max_progress": 10,
+ "status": ProgressStatusOptions.STARTED,
+ "message": "Test message",
+ "foo": "bar",
+ },
+ },
+ )
+
+ @mock.patch("channels.layers.InMemoryChannelLayer.group_send")
+ def test_manager_send_documents_deleted(self, mock_group_send):
+ with DocumentsStatusManager() as manager:
+ manager.send_documents_deleted([1, 2, 3])
+
+ message = mock_group_send.call_args[0][1]
+
+ self.assertEqual(
+ message,
+ {
+ "type": "documents_deleted",
+ "data": {
+ "documents": [1, 2, 3],
+ },
+ },
+ )