Use different channels, refactor because now its not just consumer

This commit is contained in:
shamoon 2025-02-02 22:02:31 -08:00
parent 37368e64bd
commit 6eb8479871
22 changed files with 710 additions and 638 deletions

View File

@ -9233,122 +9233,6 @@
<context context-type="linenumber">11</context> <context context-type="linenumber">11</context>
</context-group> </context-group>
</trans-unit> </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"> <trans-unit id="5523607037798226031" datatype="html">
<source>You have unsaved changes to the document</source> <source>You have unsaved changes to the document</source>
<context-group purpose="location"> <context-group purpose="location">
@ -9664,6 +9548,122 @@
<context context-type="linenumber">70</context> <context context-type="linenumber">70</context>
</context-group> </context-group>
</trans-unit> </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> </body>
</file> </file>
</xliff> </xliff>

View File

@ -18,20 +18,20 @@ import { ToastsComponent } from './components/common/toasts/toasts.component'
import { FileDropComponent } from './components/file-drop/file-drop.component' import { FileDropComponent } from './components/file-drop/file-drop.component'
import { DirtySavedViewGuard } from './guards/dirty-saved-view.guard' import { DirtySavedViewGuard } from './guards/dirty-saved-view.guard'
import { PermissionsGuard } from './guards/permissions.guard' import { PermissionsGuard } from './guards/permissions.guard'
import {
ConsumerStatusService,
FileStatus,
} from './services/consumer-status.service'
import { HotKeyService } from './services/hot-key.service' import { HotKeyService } from './services/hot-key.service'
import { PermissionsService } from './services/permissions.service' import { PermissionsService } from './services/permissions.service'
import { SettingsService } from './services/settings.service' import { SettingsService } from './services/settings.service'
import { Toast, ToastService } from './services/toast.service' import { Toast, ToastService } from './services/toast.service'
import {
FileStatus,
WebsocketStatusService,
} from './services/websocket-status.service'
describe('AppComponent', () => { describe('AppComponent', () => {
let component: AppComponent let component: AppComponent
let fixture: ComponentFixture<AppComponent> let fixture: ComponentFixture<AppComponent>
let tourService: TourService let tourService: TourService
let consumerStatusService: ConsumerStatusService let websocketStatusService: WebsocketStatusService
let permissionsService: PermissionsService let permissionsService: PermissionsService
let toastService: ToastService let toastService: ToastService
let router: Router let router: Router
@ -59,7 +59,7 @@ describe('AppComponent', () => {
}).compileComponents() }).compileComponents()
tourService = TestBed.inject(TourService) tourService = TestBed.inject(TourService)
consumerStatusService = TestBed.inject(ConsumerStatusService) websocketStatusService = TestBed.inject(WebsocketStatusService)
permissionsService = TestBed.inject(PermissionsService) permissionsService = TestBed.inject(PermissionsService)
settingsService = TestBed.inject(SettingsService) settingsService = TestBed.inject(SettingsService)
toastService = TestBed.inject(ToastService) toastService = TestBed.inject(ToastService)
@ -90,7 +90,7 @@ describe('AppComponent', () => {
const toastSpy = jest.spyOn(toastService, 'show') const toastSpy = jest.spyOn(toastService, 'show')
const fileStatusSubject = new Subject<FileStatus>() const fileStatusSubject = new Subject<FileStatus>()
jest jest
.spyOn(consumerStatusService, 'onDocumentConsumptionFinished') .spyOn(websocketStatusService, 'onDocumentConsumptionFinished')
.mockReturnValue(fileStatusSubject) .mockReturnValue(fileStatusSubject)
component.ngOnInit() component.ngOnInit()
const status = new FileStatus() const status = new FileStatus()
@ -109,7 +109,7 @@ describe('AppComponent', () => {
const toastSpy = jest.spyOn(toastService, 'show') const toastSpy = jest.spyOn(toastService, 'show')
const fileStatusSubject = new Subject<FileStatus>() const fileStatusSubject = new Subject<FileStatus>()
jest jest
.spyOn(consumerStatusService, 'onDocumentConsumptionFinished') .spyOn(websocketStatusService, 'onDocumentConsumptionFinished')
.mockReturnValue(fileStatusSubject) .mockReturnValue(fileStatusSubject)
component.ngOnInit() component.ngOnInit()
fileStatusSubject.next(new FileStatus()) fileStatusSubject.next(new FileStatus())
@ -122,7 +122,7 @@ describe('AppComponent', () => {
const toastSpy = jest.spyOn(toastService, 'show') const toastSpy = jest.spyOn(toastService, 'show')
const fileStatusSubject = new Subject<FileStatus>() const fileStatusSubject = new Subject<FileStatus>()
jest jest
.spyOn(consumerStatusService, 'onDocumentDetected') .spyOn(websocketStatusService, 'onDocumentDetected')
.mockReturnValue(fileStatusSubject) .mockReturnValue(fileStatusSubject)
component.ngOnInit() component.ngOnInit()
fileStatusSubject.next(new FileStatus()) fileStatusSubject.next(new FileStatus())
@ -136,7 +136,7 @@ describe('AppComponent', () => {
const toastSpy = jest.spyOn(toastService, 'show') const toastSpy = jest.spyOn(toastService, 'show')
const fileStatusSubject = new Subject<FileStatus>() const fileStatusSubject = new Subject<FileStatus>()
jest jest
.spyOn(consumerStatusService, 'onDocumentDetected') .spyOn(websocketStatusService, 'onDocumentDetected')
.mockReturnValue(fileStatusSubject) .mockReturnValue(fileStatusSubject)
component.ngOnInit() component.ngOnInit()
fileStatusSubject.next(new FileStatus()) fileStatusSubject.next(new FileStatus())
@ -148,7 +148,7 @@ describe('AppComponent', () => {
const toastSpy = jest.spyOn(toastService, 'showError') const toastSpy = jest.spyOn(toastService, 'showError')
const fileStatusSubject = new Subject<FileStatus>() const fileStatusSubject = new Subject<FileStatus>()
jest jest
.spyOn(consumerStatusService, 'onDocumentConsumptionFailed') .spyOn(websocketStatusService, 'onDocumentConsumptionFailed')
.mockReturnValue(fileStatusSubject) .mockReturnValue(fileStatusSubject)
component.ngOnInit() component.ngOnInit()
fileStatusSubject.next(new FileStatus()) fileStatusSubject.next(new FileStatus())

View File

@ -6,7 +6,6 @@ import { ToastsComponent } from './components/common/toasts/toasts.component'
import { FileDropComponent } from './components/file-drop/file-drop.component' import { FileDropComponent } from './components/file-drop/file-drop.component'
import { SETTINGS_KEYS } from './data/ui-settings' import { SETTINGS_KEYS } from './data/ui-settings'
import { ComponentRouterService } from './services/component-router.service' import { ComponentRouterService } from './services/component-router.service'
import { ConsumerStatusService } from './services/consumer-status.service'
import { HotKeyService } from './services/hot-key.service' import { HotKeyService } from './services/hot-key.service'
import { import {
PermissionAction, PermissionAction,
@ -16,6 +15,7 @@ import {
import { SettingsService } from './services/settings.service' import { SettingsService } from './services/settings.service'
import { TasksService } from './services/tasks.service' import { TasksService } from './services/tasks.service'
import { ToastService } from './services/toast.service' import { ToastService } from './services/toast.service'
import { WebsocketStatusService } from './services/websocket-status.service'
@Component({ @Component({
selector: 'pngx-root', selector: 'pngx-root',
@ -35,7 +35,7 @@ export class AppComponent implements OnInit, OnDestroy {
constructor( constructor(
private settings: SettingsService, private settings: SettingsService,
private consumerStatusService: ConsumerStatusService, private websocketStatusService: WebsocketStatusService,
private toastService: ToastService, private toastService: ToastService,
private router: Router, private router: Router,
private tasksService: TasksService, private tasksService: TasksService,
@ -51,7 +51,7 @@ export class AppComponent implements OnInit, OnDestroy {
} }
ngOnDestroy(): void { ngOnDestroy(): void {
this.consumerStatusService.disconnect() this.websocketStatusService.disconnect()
if (this.successSubscription) { if (this.successSubscription) {
this.successSubscription.unsubscribe() this.successSubscription.unsubscribe()
} }
@ -76,9 +76,9 @@ export class AppComponent implements OnInit, OnDestroy {
} }
ngOnInit(): void { ngOnInit(): void {
this.consumerStatusService.connect() this.websocketStatusService.connect()
this.successSubscription = this.consumerStatusService this.successSubscription = this.websocketStatusService
.onDocumentConsumptionFinished() .onDocumentConsumptionFinished()
.subscribe((status) => { .subscribe((status) => {
this.tasksService.reload() this.tasksService.reload()
@ -108,7 +108,7 @@ export class AppComponent implements OnInit, OnDestroy {
} }
}) })
this.failedSubscription = this.consumerStatusService this.failedSubscription = this.websocketStatusService
.onDocumentConsumptionFailed() .onDocumentConsumptionFailed()
.subscribe((status) => { .subscribe((status) => {
this.tasksService.reload() this.tasksService.reload()
@ -121,7 +121,7 @@ export class AppComponent implements OnInit, OnDestroy {
} }
}) })
this.newDocumentSubscription = this.consumerStatusService this.newDocumentSubscription = this.websocketStatusService
.onDocumentDetected() .onDocumentDetected()
.subscribe((status) => { .subscribe((status) => {
this.tasksService.reload() this.tasksService.reload()

View File

@ -33,14 +33,14 @@ import { PermissionsGuard } from 'src/app/guards/permissions.guard'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe' import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe' import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
import { SafeUrlPipe } from 'src/app/pipes/safeurl.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 { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { PermissionsService } from 'src/app/services/permissions.service' import { PermissionsService } from 'src/app/services/permissions.service'
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service' import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
import { DocumentService } from 'src/app/services/rest/document.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 { WidgetFrameComponent } from '../widget-frame/widget-frame.component'
import { SavedViewWidgetComponent } from './saved-view-widget.component' import { SavedViewWidgetComponent } from './saved-view-widget.component'
@ -112,7 +112,7 @@ describe('SavedViewWidgetComponent', () => {
let component: SavedViewWidgetComponent let component: SavedViewWidgetComponent
let fixture: ComponentFixture<SavedViewWidgetComponent> let fixture: ComponentFixture<SavedViewWidgetComponent>
let documentService: DocumentService let documentService: DocumentService
let consumerStatusService: ConsumerStatusService let websocketStatusService: WebsocketStatusService
let documentListViewService: DocumentListViewService let documentListViewService: DocumentListViewService
let router: Router let router: Router
@ -176,7 +176,7 @@ describe('SavedViewWidgetComponent', () => {
}).compileComponents() }).compileComponents()
documentService = TestBed.inject(DocumentService) documentService = TestBed.inject(DocumentService)
consumerStatusService = TestBed.inject(ConsumerStatusService) websocketStatusService = TestBed.inject(WebsocketStatusService)
documentListViewService = TestBed.inject(DocumentListViewService) documentListViewService = TestBed.inject(DocumentListViewService)
router = TestBed.inject(Router) router = TestBed.inject(Router)
fixture = TestBed.createComponent(SavedViewWidgetComponent) fixture = TestBed.createComponent(SavedViewWidgetComponent)
@ -235,7 +235,7 @@ describe('SavedViewWidgetComponent', () => {
it('should reload on document consumption finished', () => { it('should reload on document consumption finished', () => {
const fileStatusSubject = new Subject<FileStatus>() const fileStatusSubject = new Subject<FileStatus>()
jest jest
.spyOn(consumerStatusService, 'onDocumentConsumptionFinished') .spyOn(websocketStatusService, 'onDocumentConsumptionFinished')
.mockReturnValue(fileStatusSubject) .mockReturnValue(fileStatusSubject)
const reloadSpy = jest.spyOn(component, 'reload') const reloadSpy = jest.spyOn(component, 'reload')
component.ngOnInit() component.ngOnInit()

View 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 { DocumentTypeNamePipe } from 'src/app/pipes/document-type-name.pipe'
import { StoragePathNamePipe } from 'src/app/pipes/storage-path-name.pipe' import { StoragePathNamePipe } from 'src/app/pipes/storage-path-name.pipe'
import { UsernamePipe } from 'src/app/pipes/username.pipe' import { UsernamePipe } from 'src/app/pipes/username.pipe'
import { ConsumerStatusService } from 'src/app/services/consumer-status.service'
import { DocumentListViewService } from 'src/app/services/document-list-view.service' import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { OpenDocumentsService } from 'src/app/services/open-documents.service' import { OpenDocumentsService } from 'src/app/services/open-documents.service'
import { import {
@ -53,6 +52,7 @@ import {
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service' import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
import { DocumentService } from 'src/app/services/rest/document.service' import { DocumentService } from 'src/app/services/rest/document.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { WebsocketStatusService } from 'src/app/services/websocket-status.service'
import { WidgetFrameComponent } from '../widget-frame/widget-frame.component' import { WidgetFrameComponent } from '../widget-frame/widget-frame.component'
@Component({ @Component({
@ -94,7 +94,7 @@ export class SavedViewWidgetComponent
private documentService: DocumentService, private documentService: DocumentService,
private router: Router, private router: Router,
private list: DocumentListViewService, private list: DocumentListViewService,
private consumerStatusService: ConsumerStatusService, private websocketStatusService: WebsocketStatusService,
public openDocumentsService: OpenDocumentsService, public openDocumentsService: OpenDocumentsService,
public documentListViewService: DocumentListViewService, public documentListViewService: DocumentListViewService,
public permissionsService: PermissionsService, public permissionsService: PermissionsService,
@ -124,7 +124,7 @@ export class SavedViewWidgetComponent
ngOnInit(): void { ngOnInit(): void {
this.reload() this.reload()
this.displayMode = this.savedView.display_mode ?? DisplayMode.TABLE this.displayMode = this.savedView.display_mode ?? DisplayMode.TABLE
this.consumerStatusService this.websocketStatusService
.onDocumentConsumptionFinished() .onDocumentConsumptionFinished()
.pipe(takeUntil(this.unsubscribeNotifier)) .pipe(takeUntil(this.unsubscribeNotifier))
.subscribe(() => { .subscribe(() => {

View File

@ -12,9 +12,9 @@ import { routes } from 'src/app/app-routing.module'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { PermissionsGuard } from 'src/app/guards/permissions.guard' import { PermissionsGuard } from 'src/app/guards/permissions.guard'
import { import {
ConsumerStatusService,
FileStatus, FileStatus,
} from 'src/app/services/consumer-status.service' WebsocketStatusService,
} from 'src/app/services/websocket-status.service'
import { environment } from 'src/environments/environment' import { environment } from 'src/environments/environment'
import { WidgetFrameComponent } from '../widget-frame/widget-frame.component' import { WidgetFrameComponent } from '../widget-frame/widget-frame.component'
import { StatisticsWidgetComponent } from './statistics-widget.component' import { StatisticsWidgetComponent } from './statistics-widget.component'
@ -23,7 +23,7 @@ describe('StatisticsWidgetComponent', () => {
let component: StatisticsWidgetComponent let component: StatisticsWidgetComponent
let fixture: ComponentFixture<StatisticsWidgetComponent> let fixture: ComponentFixture<StatisticsWidgetComponent>
let httpTestingController: HttpTestingController let httpTestingController: HttpTestingController
let consumerStatusService: ConsumerStatusService let websocketStatusService: WebsocketStatusService
const fileStatusSubject = new Subject<FileStatus>() const fileStatusSubject = new Subject<FileStatus>()
beforeEach(async () => { beforeEach(async () => {
@ -44,9 +44,9 @@ describe('StatisticsWidgetComponent', () => {
}).compileComponents() }).compileComponents()
fixture = TestBed.createComponent(StatisticsWidgetComponent) fixture = TestBed.createComponent(StatisticsWidgetComponent)
consumerStatusService = TestBed.inject(ConsumerStatusService) websocketStatusService = TestBed.inject(WebsocketStatusService)
jest jest
.spyOn(consumerStatusService, 'onDocumentConsumptionFinished') .spyOn(websocketStatusService, 'onDocumentConsumptionFinished')
.mockReturnValue(fileStatusSubject) .mockReturnValue(fileStatusSubject)
component = fixture.componentInstance component = fixture.componentInstance

View File

@ -8,8 +8,8 @@ import { first, Subject, Subscription, takeUntil } from 'rxjs'
import { ComponentWithPermissions } from 'src/app/components/with-permissions/with-permissions.component' import { ComponentWithPermissions } from 'src/app/components/with-permissions/with-permissions.component'
import { FILTER_HAS_TAGS_ANY } from 'src/app/data/filter-rule-type' import { FILTER_HAS_TAGS_ANY } from 'src/app/data/filter-rule-type'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' 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 { 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 { environment } from 'src/environments/environment'
import { WidgetFrameComponent } from '../widget-frame/widget-frame.component' import { WidgetFrameComponent } from '../widget-frame/widget-frame.component'
@ -51,7 +51,7 @@ export class StatisticsWidgetComponent
constructor( constructor(
private http: HttpClient, private http: HttpClient,
private consumerStatusService: ConsumerStatusService, private websocketConnectionService: WebsocketStatusService,
private documentListViewService: DocumentListViewService private documentListViewService: DocumentListViewService
) { ) {
super() super()
@ -109,7 +109,7 @@ export class StatisticsWidgetComponent
ngOnInit(): void { ngOnInit(): void {
this.reload() this.reload()
this.subscription = this.consumerStatusService this.subscription = this.websocketConnectionService
.onDocumentConsumptionFinished() .onDocumentConsumptionFinished()
.subscribe(() => { .subscribe(() => {
this.reload() this.reload()

View File

@ -12,13 +12,13 @@ import { NgbAlert, NgbCollapse } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { routes } from 'src/app/app-routing.module' import { routes } from 'src/app/app-routing.module'
import { PermissionsGuard } from 'src/app/guards/permissions.guard' import { PermissionsGuard } from 'src/app/guards/permissions.guard'
import {
ConsumerStatusService,
FileStatus,
FileStatusPhase,
} from 'src/app/services/consumer-status.service'
import { PermissionsService } from 'src/app/services/permissions.service' import { PermissionsService } from 'src/app/services/permissions.service'
import { UploadDocumentsService } from 'src/app/services/upload-documents.service' import { UploadDocumentsService } from 'src/app/services/upload-documents.service'
import {
FileStatus,
FileStatusPhase,
WebsocketStatusService,
} from 'src/app/services/websocket-status.service'
import { UploadFileWidgetComponent } from './upload-file-widget.component' import { UploadFileWidgetComponent } from './upload-file-widget.component'
const FAILED_STATUSES = [new FileStatus()] const FAILED_STATUSES = [new FileStatus()]
@ -42,7 +42,7 @@ const DEFAULT_STATUSES = [
describe('UploadFileWidgetComponent', () => { describe('UploadFileWidgetComponent', () => {
let component: UploadFileWidgetComponent let component: UploadFileWidgetComponent
let fixture: ComponentFixture<UploadFileWidgetComponent> let fixture: ComponentFixture<UploadFileWidgetComponent>
let consumerStatusService: ConsumerStatusService let websocketStatusService: WebsocketStatusService
let uploadDocumentsService: UploadDocumentsService let uploadDocumentsService: UploadDocumentsService
beforeEach(async () => { beforeEach(async () => {
@ -65,7 +65,7 @@ describe('UploadFileWidgetComponent', () => {
], ],
}).compileComponents() }).compileComponents()
consumerStatusService = TestBed.inject(ConsumerStatusService) websocketStatusService = TestBed.inject(WebsocketStatusService)
uploadDocumentsService = TestBed.inject(UploadDocumentsService) uploadDocumentsService = TestBed.inject(UploadDocumentsService)
fixture = TestBed.createComponent(UploadFileWidgetComponent) fixture = TestBed.createComponent(UploadFileWidgetComponent)
component = fixture.componentInstance component = fixture.componentInstance
@ -91,14 +91,14 @@ describe('UploadFileWidgetComponent', () => {
}) })
it('should generate stats summary', () => { it('should generate stats summary', () => {
mockConsumerStatuses(consumerStatusService) mockConsumerStatuses(websocketStatusService)
expect(component.getStatusSummary()).toEqual( expect(component.getStatusSummary()).toEqual(
'Processing: 6, Failed: 1, Added: 4' 'Processing: 6, Failed: 1, Added: 4'
) )
}) })
it('should report an upload progress summary', () => { it('should report an upload progress summary', () => {
mockConsumerStatuses(consumerStatusService) mockConsumerStatuses(websocketStatusService)
expect(component.getTotalUploadProgress()).toEqual(0.75) expect(component.getTotalUploadProgress()).toEqual(0.75)
}) })
@ -117,7 +117,7 @@ describe('UploadFileWidgetComponent', () => {
}) })
it('should enforce a maximum number of alerts', () => { it('should enforce a maximum number of alerts', () => {
mockConsumerStatuses(consumerStatusService) mockConsumerStatuses(websocketStatusService)
fixture.detectChanges() fixture.detectChanges()
// 5 total, 1 hidden // 5 total, 1 hidden
expect(fixture.debugElement.queryAll(By.directive(NgbAlert))).toHaveLength( expect(fixture.debugElement.queryAll(By.directive(NgbAlert))).toHaveLength(
@ -131,19 +131,19 @@ describe('UploadFileWidgetComponent', () => {
}) })
it('should allow dismissing an alert', () => { it('should allow dismissing an alert', () => {
const dismissSpy = jest.spyOn(consumerStatusService, 'dismiss') const dismissSpy = jest.spyOn(websocketStatusService, 'dismiss')
component.dismiss(new FileStatus()) component.dismiss(new FileStatus())
expect(dismissSpy).toHaveBeenCalled() expect(dismissSpy).toHaveBeenCalled()
}) })
it('should allow dismissing completed alerts', fakeAsync(() => { it('should allow dismissing completed alerts', fakeAsync(() => {
mockConsumerStatuses(consumerStatusService) mockConsumerStatuses(websocketStatusService)
component.alertsExpanded = true component.alertsExpanded = true
fixture.detectChanges() fixture.detectChanges()
jest jest
.spyOn(component, 'getStatusCompleted') .spyOn(component, 'getStatusCompleted')
.mockImplementation(() => SUCCESS_STATUSES) .mockImplementation(() => SUCCESS_STATUSES)
const dismissSpy = jest.spyOn(consumerStatusService, 'dismiss') const dismissSpy = jest.spyOn(websocketStatusService, 'dismiss')
component.dismissCompleted() component.dismissCompleted()
tick(1000) tick(1000)
fixture.detectChanges() fixture.detectChanges()

View 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 { ComponentWithPermissions } from 'src/app/components/with-permissions/with-permissions.component'
import { SETTINGS_KEYS } from 'src/app/data/ui-settings' import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import {
ConsumerStatusService,
FileStatus,
FileStatusPhase,
} from 'src/app/services/consumer-status.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { UploadDocumentsService } from 'src/app/services/upload-documents.service' import { UploadDocumentsService } from 'src/app/services/upload-documents.service'
import {
FileStatus,
FileStatusPhase,
WebsocketStatusService,
} from 'src/app/services/websocket-status.service'
import { WidgetFrameComponent } from '../widget-frame/widget-frame.component' import { WidgetFrameComponent } from '../widget-frame/widget-frame.component'
const MAX_ALERTS = 5 const MAX_ALERTS = 5
@ -46,7 +46,7 @@ export class UploadFileWidgetComponent extends ComponentWithPermissions {
@ViewChildren(NgbAlert) alerts: QueryList<NgbAlert> @ViewChildren(NgbAlert) alerts: QueryList<NgbAlert>
constructor( constructor(
private consumerStatusService: ConsumerStatusService, private websocketStatusService: WebsocketStatusService,
private uploadDocumentsService: UploadDocumentsService, private uploadDocumentsService: UploadDocumentsService,
public settingsService: SettingsService public settingsService: SettingsService
) { ) {
@ -54,13 +54,13 @@ export class UploadFileWidgetComponent extends ComponentWithPermissions {
} }
getStatus() { getStatus() {
return this.consumerStatusService.getConsumerStatus().slice(0, MAX_ALERTS) return this.websocketStatusService.getConsumerStatus().slice(0, MAX_ALERTS)
} }
getStatusSummary() { getStatusSummary() {
let strings = [] let strings = []
let countUploadingAndProcessing = let countUploadingAndProcessing =
this.consumerStatusService.getConsumerStatusNotCompleted().length this.websocketStatusService.getConsumerStatusNotCompleted().length
let countFailed = this.getStatusFailed().length let countFailed = this.getStatusFailed().length
let countSuccess = this.getStatusSuccess().length let countSuccess = this.getStatusSuccess().length
if (countUploadingAndProcessing > 0) { if (countUploadingAndProcessing > 0) {
@ -78,27 +78,30 @@ export class UploadFileWidgetComponent extends ComponentWithPermissions {
} }
getStatusHidden() { getStatusHidden() {
if (this.consumerStatusService.getConsumerStatus().length < MAX_ALERTS) if (this.websocketStatusService.getConsumerStatus().length < MAX_ALERTS)
return [] return []
else return this.consumerStatusService.getConsumerStatus().slice(MAX_ALERTS) else
return this.websocketStatusService.getConsumerStatus().slice(MAX_ALERTS)
} }
getStatusUploading() { getStatusUploading() {
return this.consumerStatusService.getConsumerStatus( return this.websocketStatusService.getConsumerStatus(
FileStatusPhase.UPLOADING FileStatusPhase.UPLOADING
) )
} }
getStatusFailed() { getStatusFailed() {
return this.consumerStatusService.getConsumerStatus(FileStatusPhase.FAILED) return this.websocketStatusService.getConsumerStatus(FileStatusPhase.FAILED)
} }
getStatusSuccess() { getStatusSuccess() {
return this.consumerStatusService.getConsumerStatus(FileStatusPhase.SUCCESS) return this.websocketStatusService.getConsumerStatus(
FileStatusPhase.SUCCESS
)
} }
getStatusCompleted() { getStatusCompleted() {
return this.consumerStatusService.getConsumerStatusCompleted() return this.websocketStatusService.getConsumerStatusCompleted()
} }
getTotalUploadProgress() { getTotalUploadProgress() {
@ -134,12 +137,12 @@ export class UploadFileWidgetComponent extends ComponentWithPermissions {
} }
dismiss(status: FileStatus) { dismiss(status: FileStatus) {
this.consumerStatusService.dismiss(status) this.websocketStatusService.dismiss(status)
} }
dismissCompleted() { dismissCompleted() {
this.getStatusCompleted().forEach((status) => this.getStatusCompleted().forEach((status) =>
this.consumerStatusService.dismiss(status) this.websocketStatusService.dismiss(status)
) )
} }

View File

@ -38,16 +38,16 @@ import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
import { FilterPipe } from 'src/app/pipes/filter.pipe' import { FilterPipe } from 'src/app/pipes/filter.pipe'
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe' import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
import { UsernamePipe } from 'src/app/pipes/username.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 { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { PermissionsService } from 'src/app/services/permissions.service' import { PermissionsService } from 'src/app/services/permissions.service'
import { DocumentService } from 'src/app/services/rest/document.service' import { DocumentService } from 'src/app/services/rest/document.service'
import { SavedViewService } from 'src/app/services/rest/saved-view.service' import { SavedViewService } from 'src/app/services/rest/saved-view.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.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 { DocumentCardLargeComponent } from './document-card-large/document-card-large.component'
import { DocumentCardSmallComponent } from './document-card-small/document-card-small.component' import { DocumentCardSmallComponent } from './document-card-small/document-card-small.component'
import { DocumentListComponent } from './document-list.component' import { DocumentListComponent } from './document-list.component'
@ -81,7 +81,7 @@ describe('DocumentListComponent', () => {
let fixture: ComponentFixture<DocumentListComponent> let fixture: ComponentFixture<DocumentListComponent>
let documentListService: DocumentListViewService let documentListService: DocumentListViewService
let documentService: DocumentService let documentService: DocumentService
let consumerStatusService: ConsumerStatusService let websocketStatusService: WebsocketStatusService
let savedViewService: SavedViewService let savedViewService: SavedViewService
let router: Router let router: Router
let activatedRoute: ActivatedRoute let activatedRoute: ActivatedRoute
@ -112,7 +112,7 @@ describe('DocumentListComponent', () => {
documentListService = TestBed.inject(DocumentListViewService) documentListService = TestBed.inject(DocumentListViewService)
documentService = TestBed.inject(DocumentService) documentService = TestBed.inject(DocumentService)
consumerStatusService = TestBed.inject(ConsumerStatusService) websocketStatusService = TestBed.inject(WebsocketStatusService)
savedViewService = TestBed.inject(SavedViewService) savedViewService = TestBed.inject(SavedViewService)
router = TestBed.inject(Router) router = TestBed.inject(Router)
activatedRoute = TestBed.inject(ActivatedRoute) activatedRoute = TestBed.inject(ActivatedRoute)
@ -128,7 +128,7 @@ describe('DocumentListComponent', () => {
const reloadSpy = jest.spyOn(documentListService, 'reload') const reloadSpy = jest.spyOn(documentListService, 'reload')
const fileStatusSubject = new Subject<FileStatus>() const fileStatusSubject = new Subject<FileStatus>()
jest jest
.spyOn(consumerStatusService, 'onDocumentConsumptionFinished') .spyOn(websocketStatusService, 'onDocumentConsumptionFinished')
.mockReturnValue(fileStatusSubject) .mockReturnValue(fileStatusSubject)
fixture.detectChanges() fixture.detectChanges()
fileStatusSubject.next(new FileStatus()) fileStatusSubject.next(new FileStatus())
@ -139,7 +139,7 @@ describe('DocumentListComponent', () => {
const reloadSpy = jest.spyOn(documentListService, 'reload') const reloadSpy = jest.spyOn(documentListService, 'reload')
const documentDeletedSubject = new Subject<boolean>() const documentDeletedSubject = new Subject<boolean>()
jest jest
.spyOn(consumerStatusService, 'onDocumentDeleted') .spyOn(websocketStatusService, 'onDocumentDeleted')
.mockReturnValue(documentDeletedSubject) .mockReturnValue(documentDeletedSubject)
fixture.detectChanges() fixture.detectChanges()
documentDeletedSubject.next(true) documentDeletedSubject.next(true)

View 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 { DocumentTypeNamePipe } from 'src/app/pipes/document-type-name.pipe'
import { StoragePathNamePipe } from 'src/app/pipes/storage-path-name.pipe' import { StoragePathNamePipe } from 'src/app/pipes/storage-path-name.pipe'
import { UsernamePipe } from 'src/app/pipes/username.pipe' import { UsernamePipe } from 'src/app/pipes/username.pipe'
import { ConsumerStatusService } from 'src/app/services/consumer-status.service'
import { DocumentListViewService } from 'src/app/services/document-list-view.service' import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { HotKeyService } from 'src/app/services/hot-key.service' import { HotKeyService } from 'src/app/services/hot-key.service'
import { OpenDocumentsService } from 'src/app/services/open-documents.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 { SavedViewService } from 'src/app/services/rest/saved-view.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service' import { ToastService } from 'src/app/services/toast.service'
import { WebsocketStatusService } from 'src/app/services/websocket-status.service'
import { import {
filterRulesDiffer, filterRulesDiffer,
isFullTextFilterRule, isFullTextFilterRule,
@ -113,7 +113,7 @@ export class DocumentListComponent
private router: Router, private router: Router,
private toastService: ToastService, private toastService: ToastService,
private modalService: NgbModal, private modalService: NgbModal,
private consumerStatusService: ConsumerStatusService, private websocketStatusService: WebsocketStatusService,
public openDocumentsService: OpenDocumentsService, public openDocumentsService: OpenDocumentsService,
public settingsService: SettingsService, public settingsService: SettingsService,
private hotKeyService: HotKeyService, private hotKeyService: HotKeyService,
@ -234,14 +234,14 @@ export class DocumentListComponent
} }
ngOnInit(): void { ngOnInit(): void {
this.consumerStatusService this.websocketStatusService
.onDocumentConsumptionFinished() .onDocumentConsumptionFinished()
.pipe(takeUntil(this.unsubscribeNotifier)) .pipe(takeUntil(this.unsubscribeNotifier))
.subscribe(() => { .subscribe(() => {
this.list.reload() this.list.reload()
}) })
this.consumerStatusService.onDocumentDeleted().subscribe(() => { this.websocketStatusService.onDocumentDeleted().subscribe(() => {
this.list.reload() this.list.reload()
}) })

View File

@ -0,0 +1,3 @@
export interface WebsocketDocumentsDeletedMessage {
documents: number[]
}

View File

@ -1,4 +1,4 @@
export interface WebsocketConsumerStatusMessage { export interface WebsocketProgressMessage {
filename?: string filename?: string
task_id?: string task_id?: string
current_progress?: number current_progress?: number

View File

@ -1,343 +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
)
})
it('should trigger deleted subject on document deleted', () => {
let deleted = false
consumerStatusService.onDocumentDeleted().subscribe(() => {
deleted = true
})
consumerStatusService.connect()
server.send({
current_progress: 1,
max_progress: 1,
status: 'DELETED',
})
consumerStatusService.disconnect()
expect(deleted).toBeTruthy()
})
})

View File

@ -9,11 +9,11 @@ import {
} from '@angular/common/http/testing' } from '@angular/common/http/testing'
import { TestBed } from '@angular/core/testing' import { TestBed } from '@angular/core/testing'
import { environment } from 'src/environments/environment' import { environment } from 'src/environments/environment'
import {
ConsumerStatusService,
FileStatusPhase,
} from './consumer-status.service'
import { UploadDocumentsService } from './upload-documents.service' import { UploadDocumentsService } from './upload-documents.service'
import {
FileStatusPhase,
WebsocketStatusService,
} from './websocket-status.service'
const files = [ const files = [
{ {
@ -45,14 +45,14 @@ const fileList = {
describe('UploadDocumentsService', () => { describe('UploadDocumentsService', () => {
let httpTestingController: HttpTestingController let httpTestingController: HttpTestingController
let uploadDocumentsService: UploadDocumentsService let uploadDocumentsService: UploadDocumentsService
let consumerStatusService: ConsumerStatusService let websocketStatusService: WebsocketStatusService
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [], imports: [],
providers: [ providers: [
UploadDocumentsService, UploadDocumentsService,
ConsumerStatusService, WebsocketStatusService,
provideHttpClient(withInterceptorsFromDi()), provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(), provideHttpClientTesting(),
], ],
@ -60,7 +60,7 @@ describe('UploadDocumentsService', () => {
httpTestingController = TestBed.inject(HttpTestingController) httpTestingController = TestBed.inject(HttpTestingController)
uploadDocumentsService = TestBed.inject(UploadDocumentsService) uploadDocumentsService = TestBed.inject(UploadDocumentsService)
consumerStatusService = TestBed.inject(ConsumerStatusService) websocketStatusService = TestBed.inject(WebsocketStatusService)
}) })
afterEach(() => { afterEach(() => {
@ -80,11 +80,11 @@ describe('UploadDocumentsService', () => {
it('updates progress during upload and failure', () => { it('updates progress during upload and failure', () => {
uploadDocumentsService.uploadFiles(fileList) uploadDocumentsService.uploadFiles(fileList)
expect(consumerStatusService.getConsumerStatusNotCompleted()).toHaveLength( expect(websocketStatusService.getConsumerStatusNotCompleted()).toHaveLength(
2 2
) )
expect( expect(
consumerStatusService.getConsumerStatus(FileStatusPhase.UPLOADING) websocketStatusService.getConsumerStatus(FileStatusPhase.UPLOADING)
).toHaveLength(0) ).toHaveLength(0)
const req = httpTestingController.match( const req = httpTestingController.match(
@ -98,7 +98,7 @@ describe('UploadDocumentsService', () => {
}) })
expect( expect(
consumerStatusService.getConsumerStatus(FileStatusPhase.UPLOADING) websocketStatusService.getConsumerStatus(FileStatusPhase.UPLOADING)
).toHaveLength(1) ).toHaveLength(1)
}) })
@ -110,7 +110,7 @@ describe('UploadDocumentsService', () => {
) )
expect( expect(
consumerStatusService.getConsumerStatus(FileStatusPhase.FAILED) websocketStatusService.getConsumerStatus(FileStatusPhase.FAILED)
).toHaveLength(0) ).toHaveLength(0)
req[0].flush( req[0].flush(
@ -122,7 +122,7 @@ describe('UploadDocumentsService', () => {
) )
expect( expect(
consumerStatusService.getConsumerStatus(FileStatusPhase.FAILED) websocketStatusService.getConsumerStatus(FileStatusPhase.FAILED)
).toHaveLength(1) ).toHaveLength(1)
uploadDocumentsService.uploadFiles(fileList) uploadDocumentsService.uploadFiles(fileList)
@ -140,7 +140,7 @@ describe('UploadDocumentsService', () => {
) )
expect( expect(
consumerStatusService.getConsumerStatus(FileStatusPhase.FAILED) websocketStatusService.getConsumerStatus(FileStatusPhase.FAILED)
).toHaveLength(2) ).toHaveLength(2)
}) })

View File

@ -2,11 +2,11 @@ import { HttpEventType } from '@angular/common/http'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { FileSystemFileEntry, NgxFileDropEntry } from 'ngx-file-drop' import { FileSystemFileEntry, NgxFileDropEntry } from 'ngx-file-drop'
import { Subscription } from 'rxjs' import { Subscription } from 'rxjs'
import {
ConsumerStatusService,
FileStatusPhase,
} from './consumer-status.service'
import { DocumentService } from './rest/document.service' import { DocumentService } from './rest/document.service'
import {
FileStatusPhase,
WebsocketStatusService,
} from './websocket-status.service'
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
@ -16,7 +16,7 @@ export class UploadDocumentsService {
constructor( constructor(
private documentService: DocumentService, private documentService: DocumentService,
private consumerStatusService: ConsumerStatusService private websocketStatusService: WebsocketStatusService
) {} ) {}
onNgxFileDrop(files: NgxFileDropEntry[]) { onNgxFileDrop(files: NgxFileDropEntry[]) {
@ -37,7 +37,7 @@ export class UploadDocumentsService {
private uploadFile(file: File) { private uploadFile(file: File) {
let formData = new FormData() let formData = new FormData()
formData.append('document', file, file.name) formData.append('document', file, file.name)
let status = this.consumerStatusService.newFileUpload(file.name) let status = this.websocketStatusService.newFileUpload(file.name)
status.message = $localize`Connecting...` status.message = $localize`Connecting...`
@ -61,11 +61,11 @@ export class UploadDocumentsService {
error: (error) => { error: (error) => {
switch (error.status) { switch (error.status) {
case 400: { case 400: {
this.consumerStatusService.fail(status, error.error.document) this.websocketStatusService.fail(status, error.error.document)
break break
} }
default: { default: {
this.consumerStatusService.fail( this.websocketStatusService.fail(
status, status,
$localize`HTTP error: ${error.status} ${error.statusText}` $localize`HTTP error: ${error.status} ${error.statusText}`
) )

View File

@ -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()
})
})

View File

@ -1,9 +1,15 @@
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { Subject } from 'rxjs' import { Subject } from 'rxjs'
import { environment } from 'src/environments/environment' 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' import { SettingsService } from './settings.service'
export enum WebsocketStatusType {
STATUS_UPDATE = 'status_update',
DOCUMENTS_DELETED = 'documents_deleted',
}
// see ProgressStatusOptions in src/documents/plugins/helpers.py // see ProgressStatusOptions in src/documents/plugins/helpers.py
export enum FileStatusPhase { export enum FileStatusPhase {
STARTED = 0, STARTED = 0,
@ -85,7 +91,7 @@ export class FileStatus {
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class ConsumerStatusService { export class WebsocketStatusService {
constructor(private settingsService: SettingsService) {} constructor(private settingsService: SettingsService) {}
private statusWebSocket: WebSocket private statusWebSocket: WebSocket
@ -146,61 +152,22 @@ export class ConsumerStatusService {
this.statusWebSocket = new WebSocket( this.statusWebSocket = new WebSocket(
`${environment.webSocketProtocol}//${environment.webSocketHost}${environment.webSocketBaseUrl}status/` `${environment.webSocketProtocol}//${environment.webSocketHost}${environment.webSocketBaseUrl}status/`
) )
this.statusWebSocket.onmessage = (ev) => { this.statusWebSocket.onmessage = (ev: MessageEvent) => {
let statusMessage: WebsocketConsumerStatusMessage = JSON.parse(ev['data']) const {
type,
data: messageData,
}: {
type: WebsocketStatusType
data: WebsocketProgressMessage | WebsocketDocumentsDeletedMessage
} = JSON.parse(ev.data)
// special case for documents deletion switch (type) {
if (statusMessage.status === 'DELETED') { case WebsocketStatusType.STATUS_UPDATE:
this.documentDeletedSubject.next(true) this.handleProgressUpdate(messageData as WebsocketProgressMessage)
return
}
// fallback if backend didn't restrict message
if (
statusMessage.owner_id &&
statusMessage.owner_id !== this.settingsService.currentUser?.id &&
!this.settingsService.currentUser?.is_superuser
) {
return
}
let statusMessageGet = this.get(
statusMessage.task_id,
statusMessage.filename
)
let status = statusMessageGet.status
let created = statusMessageGet.created
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
if (statusMessage.status in FileStatusPhase) {
status.phase = FileStatusPhase[statusMessage.status]
}
switch (status.phase) {
case FileStatusPhase.STARTED:
if (created) this.documentDetectedSubject.next(status)
break break
case FileStatusPhase.SUCCESS: case WebsocketStatusType.DOCUMENTS_DELETED:
this.documentConsumptionFinishedSubject.next(status) this.documentDeletedSubject.next(true)
break
case FileStatusPhase.FAILED:
this.documentConsumptionFailedSubject.next(status)
break break
default: default:
@ -209,6 +176,54 @@ export class ConsumerStatusService {
} }
} }
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
}
let statusMessageGet = this.get(messageData.task_id, messageData.filename)
let status = statusMessageGet.status
let created = statusMessageGet.created
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
if (messageData.status in FileStatusPhase) {
status.phase = FileStatusPhase[messageData.status]
}
switch (status.phase) {
case FileStatusPhase.STARTED:
if (created) this.documentDetectedSubject.next(status)
break
case FileStatusPhase.SUCCESS:
this.documentConsumptionFinishedSubject.next(status)
break
case FileStatusPhase.FAILED:
this.documentConsumptionFailedSubject.next(status)
break
default:
break
}
}
fail(status: FileStatus, message: string) { fail(status: FileStatus, message: string) {
status.message = message status.message = message
status.phase = FileStatusPhase.FAILED status.phase = FileStatusPhase.FAILED

View File

@ -24,8 +24,7 @@ from documents.models import Document
from documents.models import DocumentType from documents.models import DocumentType
from documents.models import StoragePath from documents.models import StoragePath
from documents.permissions import set_permissions_for_object from documents.permissions import set_permissions_for_object
from documents.plugins.helpers import ProgressManager from documents.plugins.helpers import DocumentsStatusManager
from documents.plugins.helpers import ProgressStatusOptions
from documents.tasks import bulk_update_documents from documents.tasks import bulk_update_documents
from documents.tasks import consume_file from documents.tasks import consume_file
from documents.tasks import update_document_content_maybe_archive_file from documents.tasks import update_document_content_maybe_archive_file
@ -222,13 +221,8 @@ def delete(doc_ids: list[int]) -> Literal["OK"]:
for id in doc_ids: for id in doc_ids:
index.remove_document_by_id(writer, id) index.remove_document_by_id(writer, id)
status_mgr = ProgressManager() status_mgr = DocumentsStatusManager()
status_mgr.send_progress( status_mgr.send_documents_deleted(doc_ids)
status=ProgressStatusOptions.DELETED,
message="Documents deleted",
current_progress=1,
max_progress=1,
)
except Exception as e: except Exception as e:
if "Data too long for column" in str(e): if "Data too long for column" in str(e):
logger.warning( logger.warning(

View File

@ -13,19 +13,16 @@ class ProgressStatusOptions(str, enum.Enum):
WORKING = "WORKING" WORKING = "WORKING"
SUCCESS = "SUCCESS" SUCCESS = "SUCCESS"
FAILED = "FAILED" FAILED = "FAILED"
DELETED = "DELETED"
class ProgressManager: class BaseStatusManager:
""" """
Handles sending of progress information via the channel layer, with proper management 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 of the open/close of the layer to ensure messages go out and everything is cleaned up
""" """
def __init__(self, filename: str | None = None, task_id: str | None = None) -> None: def __init__(self) -> None:
self.filename = filename
self._channel: RedisPubSubChannelLayer | None = None self._channel: RedisPubSubChannelLayer | None = None
self.task_id = task_id
def __enter__(self): def __enter__(self):
self.open() self.open()
@ -50,6 +47,24 @@ class ProgressManager:
async_to_sync(self._channel.flush) async_to_sync(self._channel.flush)
self._channel = None 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( def send_progress(
self, self,
status: ProgressStatusOptions, status: ProgressStatusOptions,
@ -58,13 +73,6 @@ class ProgressManager:
max_progress: int, max_progress: int,
extra_args: dict[str, str | int | None] | None = None, extra_args: dict[str, str | int | None] | None = None,
) -> None: ) -> None:
# Ensure the layer is open
self.open()
# Just for IDEs
if TYPE_CHECKING:
assert self._channel is not None
payload = { payload = {
"type": "status_update", "type": "status_update",
"data": { "data": {
@ -79,5 +87,16 @@ class ProgressManager:
if extra_args is not None: if extra_args is not None:
payload["data"].update(extra_args) payload["data"].update(extra_args)
# Construct and send the update self.send(payload)
async_to_sync(self._channel.group_send)("status_updates", payload)
class DocumentsStatusManager(BaseStatusManager):
def send_documents_deleted(self, documents: list[int]) -> None:
payload = {
"type": "documents_deleted",
"data": {
"documents": documents,
},
}
self.send(payload)

View File

@ -41,4 +41,10 @@ class StatusConsumer(WebsocketConsumer):
self.close() self.close()
else: else:
if self._is_owner_or_unowned(event["data"]): 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))

View File

@ -40,12 +40,12 @@ class TestWebSockets(TestCase):
connected, subprotocol = await communicator.connect() connected, subprotocol = await communicator.connect()
self.assertTrue(connected) self.assertTrue(connected)
message = {"task_id": "test"} message = {"type": "status_update", "data": {"task_id": "test"}}
channel_layer = get_channel_layer() channel_layer = get_channel_layer()
await channel_layer.group_send( await channel_layer.group_send(
"status_updates", "status_updates",
{"type": "status_update", "data": message}, message,
) )
response = await communicator.receive_json_from() response = await communicator.receive_json_from()