Guard against stale ws events for version uploads

This commit is contained in:
shamoon
2026-02-12 11:39:34 -08:00
parent 825b241362
commit 56e52f8701
2 changed files with 50 additions and 3 deletions

View File

@@ -1842,6 +1842,43 @@ describe('DocumentDetailComponent', () => {
expect(errorSpy).toHaveBeenCalled()
})
it('should clear and isolate version upload state on document change', () => {
initNormally()
httpTestingController.expectOne(component.previewUrl).flush('preview')
component.versionUploadState = UploadState.Failed
component.versionUploadError = 'boom'
component.docChangeNotifier.next(999)
expect(component.versionUploadState).toBe(UploadState.Idle)
expect(component.versionUploadError).toBeNull()
const uploadSpy = jest.spyOn(documentService, 'uploadVersion')
const versionsSpy = jest.spyOn(documentService, 'getVersions')
const finished$ = new Subject<any>()
const failed$ = new Subject<any>()
jest
.spyOn(websocketStatusService, 'onDocumentConsumptionFinished')
.mockReturnValueOnce(finished$ as any)
jest
.spyOn(websocketStatusService, 'onDocumentConsumptionFailed')
.mockReturnValueOnce(failed$ as any)
uploadSpy.mockReturnValueOnce(of('task-stale'))
component.onVersionFileSelected({
target: createFileInput(
new File(['data'], 'version.pdf', { type: 'application/pdf' })
),
} as any)
expect(component.versionUploadState).toBe(UploadState.Processing)
component.docChangeNotifier.next(1000)
failed$.next({ taskId: 'task-stale', message: 'stale-error' })
expect(component.versionUploadState).toBe(UploadState.Idle)
expect(component.versionUploadError).toBeNull()
expect(versionsSpy).not.toHaveBeenCalled()
})
it('createDisabled should return true if the user does not have permission to add the specified data type', () => {
currentUserCan = false
expect(component.createDisabled(DataType.Correspondent)).toBeTruthy()

View File

@@ -674,6 +674,10 @@ export class DocumentDetailComponent
this.loadDocument(documentId)
})
this.docChangeNotifier
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe(() => this.clearVersionUploadStatus())
this.route.paramMap
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe((paramMap) => {
@@ -1311,6 +1315,7 @@ export class DocumentDetailComponent
onVersionFileSelected(event: Event) {
const input = event.target as HTMLInputElement
if (!input?.files || input.files.length === 0) return
const uploadDocumentId = this.documentId
const file = input.files[0]
// Reset input to allow re-selection of the same file later
input.value = ''
@@ -1318,7 +1323,7 @@ export class DocumentDetailComponent
this.versionUploadState = UploadState.Uploading
this.versionUploadError = null
this.documentsService
.uploadVersion(this.documentId, file, label)
.uploadVersion(uploadDocumentId, file, label)
.pipe(
first(),
tap(() => {
@@ -1351,7 +1356,10 @@ export class DocumentDetailComponent
message: status.message,
}))
)
).pipe(take(1))
).pipe(
takeUntil(merge(this.unsubscribeNotifier, this.docChangeNotifier)),
take(1)
)
}),
switchMap((result) => {
if (result?.state !== 'success') {
@@ -1362,13 +1370,14 @@ export class DocumentDetailComponent
}
return of(null)
}
return this.documentsService.getVersions(this.documentId)
return this.documentsService.getVersions(uploadDocumentId)
}),
takeUntil(this.unsubscribeNotifier),
takeUntil(this.docChangeNotifier)
)
.subscribe({
next: (doc) => {
if (uploadDocumentId !== this.documentId) return
if (doc?.versions) {
this.document.versions = doc.versions
const openDoc = this.openDocumentService.getOpenDocument(
@@ -1385,6 +1394,7 @@ export class DocumentDetailComponent
}
},
error: (error) => {
if (uploadDocumentId !== this.documentId) return
this.versionUploadState = UploadState.Failed
this.versionUploadError = error?.message || $localize`Upload failed.`
this.toastService.showError(