]> git.ipfire.org Git - thirdparty/paperless-ngx.git/commitdiff
Fix: deselect and trigger refresh for deleted documents from bulk operations with...
authorshamoon <4887959+shamoon@users.noreply.github.com>
Fri, 7 Feb 2025 00:47:50 +0000 (16:47 -0800)
committerGitHub <noreply@github.com>
Fri, 7 Feb 2025 00:47:50 +0000 (00:47 +0000)
24 files changed:
src-ui/messages.xlf
src-ui/src/app/app.component.spec.ts
src-ui/src/app/app.component.ts
src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.spec.ts
src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.ts
src-ui/src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.spec.ts
src-ui/src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.ts
src-ui/src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.spec.ts
src-ui/src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts
src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.spec.ts
src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts
src-ui/src/app/components/document-list/document-list.component.spec.ts
src-ui/src/app/components/document-list/document-list.component.ts
src-ui/src/app/data/websocket-documents-deleted-message.ts [new file with mode: 0644]
src-ui/src/app/data/websocket-progress-message.ts [moved from src-ui/src/app/data/websocket-consumer-status-message.ts with 77% similarity]
src-ui/src/app/services/consumer-status.service.spec.ts [deleted file]
src-ui/src/app/services/upload-documents.service.spec.ts
src-ui/src/app/services/upload-documents.service.ts
src-ui/src/app/services/websocket-status.service.spec.ts [new file with mode: 0644]
src-ui/src/app/services/websocket-status.service.ts [moved from src-ui/src/app/services/consumer-status.service.ts with 71% similarity]
src/documents/bulk_edit.py
src/documents/plugins/helpers.py
src/paperless/consumers.py
src/paperless/tests/test_websockets.py

index 65e25d8ba49e4aaee22bbcd6a8ba8be976cbc395..9983e6b55f9bea46603f50ad4288616214443938 100644 (file)
         </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>&quot;<x id="PH" equiv-text="items[0].name"/>&quot;</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>&quot;<x id="PH" equiv-text="items[0].name"/>&quot; and &quot;<x id="PH_1" equiv-text="items[1].name"/>&quot;</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 &apos;modify &quot;tag1&quot; and &quot;tag2&quot;&apos;</note>
       </trans-unit>
         <source><x id="PH" equiv-text="list"/> and &quot;<x id="PH_1" equiv-text="items[items.length - 1].name"/>&quot;</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 &apos;modify &quot;tag1&quot;, &quot;tag2&quot; and &quot;tag3&quot;&apos;</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 &quot;<x id="PH" equiv-text="tag.name"/>&quot; 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 &quot;<x id="PH" equiv-text="tag.name"/>&quot; 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 &quot;<x id="PH" equiv-text="correspondent.name"/>&quot; 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 &quot;<x id="PH" equiv-text="documentType.name"/>&quot; 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 &quot;<x id="PH" equiv-text="storagePath.name"/>&quot; 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 &quot;<x id="PH" equiv-text="customField.name"/>&quot; 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 &quot;<x id="PH" equiv-text="customField.name"/>&quot; 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 &quot;<x id="PH" equiv-text="this.list.activeSavedViewTitle"/>&quot; saved successfully.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
-          <context context-type="linenumber">379</context>
+          <context context-type="linenumber">383</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6837554170707123455" datatype="html">
         <source>View &quot;<x id="PH" equiv-text="savedView.name"/>&quot; created successfully.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
-          <context context-type="linenumber">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>
index 74626f847df17a97ec99fc81dccdb2d08b1c1d1c..bc59f78dc8ccb106621dad9a33262a89e2b394c4 100644 (file)
@@ -18,20 +18,20 @@ import { ToastsComponent } from './components/common/toasts/toasts.component'
 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
@@ -59,7 +59,7 @@ describe('AppComponent', () => {
     }).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)
@@ -90,7 +90,7 @@ describe('AppComponent', () => {
     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()
@@ -109,7 +109,7 @@ describe('AppComponent', () => {
     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())
@@ -122,7 +122,7 @@ describe('AppComponent', () => {
     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())
@@ -136,7 +136,7 @@ describe('AppComponent', () => {
     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())
@@ -148,7 +148,7 @@ describe('AppComponent', () => {
     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())
index c89f5d4c2a284c04d5b2acb44f7ca16da0e0ec6b..a6c4702b726ec1d941c8e6a8bd969374e83d9297 100644 (file)
@@ -6,7 +6,6 @@ import { ToastsComponent } from './components/common/toasts/toasts.component'
 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,
@@ -16,6 +15,7 @@ import {
 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',
@@ -35,7 +35,7 @@ export class AppComponent implements OnInit, OnDestroy {
 
   constructor(
     private settings: SettingsService,
-    private consumerStatusService: ConsumerStatusService,
+    private websocketStatusService: WebsocketStatusService,
     private toastService: ToastService,
     private router: Router,
     private tasksService: TasksService,
@@ -51,7 +51,7 @@ export class AppComponent implements OnInit, OnDestroy {
   }
 
   ngOnDestroy(): void {
-    this.consumerStatusService.disconnect()
+    this.websocketStatusService.disconnect()
     if (this.successSubscription) {
       this.successSubscription.unsubscribe()
     }
@@ -76,9 +76,9 @@ export class AppComponent implements OnInit, OnDestroy {
   }
 
   ngOnInit(): void {
-    this.consumerStatusService.connect()
+    this.websocketStatusService.connect()
 
-    this.successSubscription = this.consumerStatusService
+    this.successSubscription = this.websocketStatusService
       .onDocumentConsumptionFinished()
       .subscribe((status) => {
         this.tasksService.reload()
@@ -108,7 +108,7 @@ export class AppComponent implements OnInit, OnDestroy {
         }
       })
 
-    this.failedSubscription = this.consumerStatusService
+    this.failedSubscription = this.websocketStatusService
       .onDocumentConsumptionFailed()
       .subscribe((status) => {
         this.tasksService.reload()
@@ -121,7 +121,7 @@ export class AppComponent implements OnInit, OnDestroy {
         }
       })
 
-    this.newDocumentSubscription = this.consumerStatusService
+    this.newDocumentSubscription = this.websocketStatusService
       .onDocumentDetected()
       .subscribe((status) => {
         this.tasksService.reload()
index 5f66c68d6306d7d96e42100afccfcf88ec839950..621a9049198505536937fdcd0797f9445a8d4eda 100644 (file)
@@ -33,14 +33,14 @@ import { PermissionsGuard } from 'src/app/guards/permissions.guard'
 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'
 
@@ -112,7 +112,7 @@ describe('SavedViewWidgetComponent', () => {
   let component: SavedViewWidgetComponent
   let fixture: ComponentFixture<SavedViewWidgetComponent>
   let documentService: DocumentService
-  let consumerStatusService: ConsumerStatusService
+  let websocketStatusService: WebsocketStatusService
   let documentListViewService: DocumentListViewService
   let router: Router
 
@@ -176,7 +176,7 @@ describe('SavedViewWidgetComponent', () => {
     }).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)
@@ -235,7 +235,7 @@ describe('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()
index 7f6c5755b85865adbc9d48bf00f27f76c8977b67..32bf7a004a14a0e8c7d18682cb6180955dd73a7c 100644 (file)
@@ -42,7 +42,6 @@ import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
 import { DocumentTypeNamePipe } from 'src/app/pipes/document-type-name.pipe'
 import { StoragePathNamePipe } from 'src/app/pipes/storage-path-name.pipe'
 import { UsernamePipe } from 'src/app/pipes/username.pipe'
-import { ConsumerStatusService } from 'src/app/services/consumer-status.service'
 import { DocumentListViewService } from 'src/app/services/document-list-view.service'
 import { OpenDocumentsService } from 'src/app/services/open-documents.service'
 import {
@@ -53,6 +52,7 @@ 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({
@@ -94,7 +94,7 @@ export class SavedViewWidgetComponent
     private documentService: DocumentService,
     private router: Router,
     private list: DocumentListViewService,
-    private consumerStatusService: ConsumerStatusService,
+    private websocketStatusService: WebsocketStatusService,
     public openDocumentsService: OpenDocumentsService,
     public documentListViewService: DocumentListViewService,
     public permissionsService: PermissionsService,
@@ -124,7 +124,7 @@ export class SavedViewWidgetComponent
   ngOnInit(): void {
     this.reload()
     this.displayMode = this.savedView.display_mode ?? DisplayMode.TABLE
-    this.consumerStatusService
+    this.websocketStatusService
       .onDocumentConsumptionFinished()
       .pipe(takeUntil(this.unsubscribeNotifier))
       .subscribe(() => {
index da0c2c08335c737e174fa08cf53c10e53c52e6d9..48ca50a1011c7c1eaf0295ff73663bf1e961f85d 100644 (file)
@@ -12,9 +12,9 @@ import { routes } from 'src/app/app-routing.module'
 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'
@@ -23,7 +23,7 @@ describe('StatisticsWidgetComponent', () => {
   let component: StatisticsWidgetComponent
   let fixture: ComponentFixture<StatisticsWidgetComponent>
   let httpTestingController: HttpTestingController
-  let consumerStatusService: ConsumerStatusService
+  let websocketStatusService: WebsocketStatusService
   const fileStatusSubject = new Subject<FileStatus>()
 
   beforeEach(async () => {
@@ -44,9 +44,9 @@ describe('StatisticsWidgetComponent', () => {
     }).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
 
index f54852429fb0b215ee5d16bf1ce90dc07ad70b72..0669a366614cd040e40cace924687ae7a50535e8 100644 (file)
@@ -8,8 +8,8 @@ import { first, Subject, Subscription, takeUntil } from 'rxjs'
 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'
 
@@ -51,7 +51,7 @@ export class StatisticsWidgetComponent
 
   constructor(
     private http: HttpClient,
-    private consumerStatusService: ConsumerStatusService,
+    private websocketConnectionService: WebsocketStatusService,
     private documentListViewService: DocumentListViewService
   ) {
     super()
@@ -109,7 +109,7 @@ export class StatisticsWidgetComponent
 
   ngOnInit(): void {
     this.reload()
-    this.subscription = this.consumerStatusService
+    this.subscription = this.websocketConnectionService
       .onDocumentConsumptionFinished()
       .subscribe(() => {
         this.reload()
index cc1591966a9c0161d1f11bba34d5455827982a46..45ac9217a39602a760f7cea72f5474644c9c0195 100644 (file)
@@ -12,13 +12,13 @@ import { NgbAlert, NgbCollapse } from '@ng-bootstrap/ng-bootstrap'
 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()]
@@ -42,7 +42,7 @@ const DEFAULT_STATUSES = [
 describe('UploadFileWidgetComponent', () => {
   let component: UploadFileWidgetComponent
   let fixture: ComponentFixture<UploadFileWidgetComponent>
-  let consumerStatusService: ConsumerStatusService
+  let websocketStatusService: WebsocketStatusService
   let uploadDocumentsService: UploadDocumentsService
 
   beforeEach(async () => {
@@ -65,7 +65,7 @@ describe('UploadFileWidgetComponent', () => {
       ],
     }).compileComponents()
 
-    consumerStatusService = TestBed.inject(ConsumerStatusService)
+    websocketStatusService = TestBed.inject(WebsocketStatusService)
     uploadDocumentsService = TestBed.inject(UploadDocumentsService)
     fixture = TestBed.createComponent(UploadFileWidgetComponent)
     component = fixture.componentInstance
@@ -91,14 +91,14 @@ describe('UploadFileWidgetComponent', () => {
   })
 
   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)
   })
 
@@ -117,7 +117,7 @@ describe('UploadFileWidgetComponent', () => {
   })
 
   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(
@@ -131,19 +131,19 @@ describe('UploadFileWidgetComponent', () => {
   })
 
   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()
index f237ab7aa80b5bf42a933603d57f4a616fe8f561..f60cdce60c261d6c2d766830acff87e168d48f0a 100644 (file)
@@ -12,13 +12,13 @@ import { TourNgBootstrapModule } from 'ngx-ui-tour-ng-bootstrap'
 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
@@ -46,7 +46,7 @@ export class UploadFileWidgetComponent extends ComponentWithPermissions {
   @ViewChildren(NgbAlert) alerts: QueryList<NgbAlert>
 
   constructor(
-    private consumerStatusService: ConsumerStatusService,
+    private websocketStatusService: WebsocketStatusService,
     private uploadDocumentsService: UploadDocumentsService,
     public settingsService: SettingsService
   ) {
@@ -54,13 +54,13 @@ export class UploadFileWidgetComponent extends ComponentWithPermissions {
   }
 
   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) {
@@ -78,27 +78,30 @@ export class UploadFileWidgetComponent extends ComponentWithPermissions {
   }
 
   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() {
@@ -134,12 +137,12 @@ export class UploadFileWidgetComponent extends ComponentWithPermissions {
   }
 
   dismiss(status: FileStatus) {
-    this.consumerStatusService.dismiss(status)
+    this.websocketStatusService.dismiss(status)
   }
 
   dismissCompleted() {
     this.getStatusCompleted().forEach((status) =>
-      this.consumerStatusService.dismiss(status)
+      this.websocketStatusService.dismiss(status)
     )
   }
 
index 21b8f41755c04089beee33dac77f9e868320dfe4..aa4a07d1283256c3af81ed08064021e35619058f 100644 (file)
@@ -1039,6 +1039,7 @@ describe('BulkEditorComponent', () => {
     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', () => {
index 5750c4b2ff67c8d6cad7bf5707747573f0a514bc..9864761fabf3bde73bb49603269692ff422b0384 100644 (file)
@@ -268,6 +268,9 @@ export class BulkEditorComponent
       .pipe(first())
       .subscribe({
         next: () => {
+          if (args['delete_originals']) {
+            this.list.selected.clear()
+          }
           this.list.reload()
           this.list.reduceSelectionToFilter()
           this.list.selected.forEach((id) => {
index 805a65846764a7f338dbaf9bb5179a3289ae26b8..13a938f5926bd3d8bbb24c724963705489560b2a 100644 (file)
@@ -38,16 +38,16 @@ import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
 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'
@@ -81,7 +81,7 @@ describe('DocumentListComponent', () => {
   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
@@ -112,7 +112,7 @@ describe('DocumentListComponent', () => {
 
     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)
@@ -128,13 +128,24 @@ describe('DocumentListComponent', () => {
     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 = [
       {
index b845a524a0f5fbf3e32efada37d21bc1e47e0f51..e1f71edbcfe32f493adb379dbd842eec76c9db25 100644 (file)
@@ -43,7 +43,6 @@ import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
 import { DocumentTypeNamePipe } from 'src/app/pipes/document-type-name.pipe'
 import { StoragePathNamePipe } from 'src/app/pipes/storage-path-name.pipe'
 import { UsernamePipe } from 'src/app/pipes/username.pipe'
-import { ConsumerStatusService } from 'src/app/services/consumer-status.service'
 import { DocumentListViewService } from 'src/app/services/document-list-view.service'
 import { HotKeyService } from 'src/app/services/hot-key.service'
 import { OpenDocumentsService } from 'src/app/services/open-documents.service'
@@ -51,6 +50,7 @@ import { PermissionsService } from 'src/app/services/permissions.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,
@@ -113,7 +113,7 @@ export class DocumentListComponent
     private router: Router,
     private toastService: ToastService,
     private modalService: NgbModal,
-    private consumerStatusService: ConsumerStatusService,
+    private websocketStatusService: WebsocketStatusService,
     public openDocumentsService: OpenDocumentsService,
     public settingsService: SettingsService,
     private hotKeyService: HotKeyService,
@@ -234,13 +234,17 @@ export class DocumentListComponent
   }
 
   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
diff --git a/src-ui/src/app/data/websocket-documents-deleted-message.ts b/src-ui/src/app/data/websocket-documents-deleted-message.ts
new file mode 100644 (file)
index 0000000..11ded37
--- /dev/null
@@ -0,0 +1,3 @@
+export interface WebsocketDocumentsDeletedMessage {
+  documents: number[]
+}
similarity index 77%
rename from src-ui/src/app/data/websocket-consumer-status-message.ts
rename to src-ui/src/app/data/websocket-progress-message.ts
index d1ac590b1f02ff24e201ec4f83e73d6f617dd774..c8e37e232bca27bb11543397f7458c6d8f9dfa8f 100644 (file)
@@ -1,4 +1,4 @@
-export interface WebsocketConsumerStatusMessage {
+export interface WebsocketProgressMessage {
   filename?: string
   task_id?: string
   current_progress?: number
diff --git a/src-ui/src/app/services/consumer-status.service.spec.ts b/src-ui/src/app/services/consumer-status.service.spec.ts
deleted file mode 100644 (file)
index b699f87..0000000
+++ /dev/null
@@ -1,326 +0,0 @@
-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
-    )
-  })
-})
index cf081230699bcf4088c99a9d079e2b1ab187145e..28fb5b2e077f24b3248fe6122823bcd26b69dc4b 100644 (file)
@@ -9,11 +9,11 @@ import {
 } 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 = [
   {
@@ -45,14 +45,14 @@ const fileList = {
 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(),
       ],
@@ -60,7 +60,7 @@ describe('UploadDocumentsService', () => {
 
     httpTestingController = TestBed.inject(HttpTestingController)
     uploadDocumentsService = TestBed.inject(UploadDocumentsService)
-    consumerStatusService = TestBed.inject(ConsumerStatusService)
+    websocketStatusService = TestBed.inject(WebsocketStatusService)
   })
 
   afterEach(() => {
@@ -80,11 +80,11 @@ describe('UploadDocumentsService', () => {
   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(
@@ -98,7 +98,7 @@ describe('UploadDocumentsService', () => {
     })
 
     expect(
-      consumerStatusService.getConsumerStatus(FileStatusPhase.UPLOADING)
+      websocketStatusService.getConsumerStatus(FileStatusPhase.UPLOADING)
     ).toHaveLength(1)
   })
 
@@ -110,7 +110,7 @@ describe('UploadDocumentsService', () => {
     )
 
     expect(
-      consumerStatusService.getConsumerStatus(FileStatusPhase.FAILED)
+      websocketStatusService.getConsumerStatus(FileStatusPhase.FAILED)
     ).toHaveLength(0)
 
     req[0].flush(
@@ -122,7 +122,7 @@ describe('UploadDocumentsService', () => {
     )
 
     expect(
-      consumerStatusService.getConsumerStatus(FileStatusPhase.FAILED)
+      websocketStatusService.getConsumerStatus(FileStatusPhase.FAILED)
     ).toHaveLength(1)
 
     uploadDocumentsService.uploadFiles(fileList)
@@ -140,7 +140,7 @@ describe('UploadDocumentsService', () => {
     )
 
     expect(
-      consumerStatusService.getConsumerStatus(FileStatusPhase.FAILED)
+      websocketStatusService.getConsumerStatus(FileStatusPhase.FAILED)
     ).toHaveLength(2)
   })
 
index 8a5e42b473f55d0a3b6436b22b50def029031e4b..602e6d8ae1f87188468edc7b9219fc12054220f6 100644 (file)
@@ -2,11 +2,11 @@ import { HttpEventType } from '@angular/common/http'
 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',
@@ -16,7 +16,7 @@ export class UploadDocumentsService {
 
   constructor(
     private documentService: DocumentService,
-    private consumerStatusService: ConsumerStatusService
+    private websocketStatusService: WebsocketStatusService
   ) {}
 
   onNgxFileDrop(files: NgxFileDropEntry[]) {
@@ -37,7 +37,7 @@ export class UploadDocumentsService {
   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...`
 
@@ -61,11 +61,11 @@ export class UploadDocumentsService {
         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}`
               )
diff --git a/src-ui/src/app/services/websocket-status.service.spec.ts b/src-ui/src/app/services/websocket-status.service.spec.ts
new file mode 100644 (file)
index 0000000..d3bf71f
--- /dev/null
@@ -0,0 +1,375 @@
+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()
+  })
+})
similarity index 71%
rename from src-ui/src/app/services/consumer-status.service.ts
rename to src-ui/src/app/services/websocket-status.service.ts
index 40641ff81b685aad7dbf9f3e4b99493c49b843af..13f82412f35356590d49f6e2db0b53b99977193b 100644 (file)
@@ -1,9 +1,15 @@
 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,
@@ -85,7 +91,7 @@ export class FileStatus {
 @Injectable({
   providedIn: 'root',
 })
-export class ConsumerStatusService {
+export class WebsocketStatusService {
   constructor(private settingsService: SettingsService) {}
 
   private statusWebSocket: WebSocket
@@ -95,6 +101,7 @@ export class ConsumerStatusService {
   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 =
@@ -145,60 +152,72 @@ export class ConsumerStatusService {
     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
     }
   }
 
@@ -250,4 +269,8 @@ export class ConsumerStatusService {
   onDocumentDetected() {
     return this.documentDetectedSubject
   }
+
+  onDocumentDeleted() {
+    return this.documentDeletedSubject
+  }
 }
index f0522eddccb1f07ff8c68f986f07301dfd4abc1e..0aadcc2950ca6fd1e889cb3848e1f46db6fb31a4 100644 (file)
@@ -24,6 +24,7 @@ from documents.models import Document
 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
@@ -219,6 +220,9 @@ def delete(doc_ids: list[int]) -> Literal["OK"]:
         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(
index 20380b852ff1fb515d6e3caaad4184ec1ee4375b..3315ec60ee8411e77091745633b7e60b4e135f66 100644 (file)
@@ -15,16 +15,14 @@ class ProgressStatusOptions(str, enum.Enum):
     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()
@@ -49,6 +47,24 @@ class ProgressManager:
             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,
@@ -57,13 +73,6 @@ class ProgressManager:
         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": {
@@ -78,5 +87,16 @@ class ProgressManager:
         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)
index cf1a3b548277de4f5069eb702220806b5f5db6c7..c72b58aa7fc27e57921f34e54cd3530ed905fffe 100644 (file)
@@ -41,4 +41,10 @@ class StatusConsumer(WebsocketConsumer):
             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))
index bf838821a5544dc143ce06476a4d7d2183e962f7..5ba909d1c5586bad6388d6e3bfe5695abfa2b55c 100644 (file)
@@ -5,6 +5,9 @@ from channels.testing import WebsocketCommunicator
 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 = {
@@ -22,6 +25,39 @@ class TestWebSockets(TestCase):
         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
@@ -33,19 +69,41 @@ class TestWebSockets(TestCase):
         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()
@@ -53,3 +111,51 @@ class TestWebSockets(TestCase):
         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],
+                },
+            },
+        )