From c5239f9f74c73bb1446fbb9adaf5f8cf2a05ed98 Mon Sep 17 00:00:00 2001
From: shamoon <4887959+shamoon@users.noreply.github.com>
Date: Thu, 5 Nov 2026 19:06:10 -0800
Subject: [PATCH] Fixhancement: rework saved view widget queue processing to
expected (#14699)
* activateSavedViewWithDocuments, move most of loadSavedView to a helper
* Consolidate the page size thing
* Pass widget's documents to list service fpr next / previous
* Tests for the page size + widget click
* Fix close from a dashboard widget list
---
src-ui/src/app/app-routing.module.ts | 2 +-
.../saved-view-widget.component.html | 10 +++--
.../saved-view-widget.component.spec.ts | 36 ++++++++++++++++-
.../saved-view-widget.component.ts | 21 +++++++++-
.../document-detail.component.spec.ts | 14 +++++++
.../document-detail.component.ts | 7 +++-
.../document-list-view.service.spec.ts | 40 +++++++++++++++++++
.../services/document-list-view.service.ts | 33 +++++++++++++--
8 files changed, 150 insertions(+), 13 deletions(-)
diff --git a/src-ui/src/app/app-routing.module.ts b/src-ui/src/app/app-routing.module.ts
index 57ca258769..12a4463879 100644
--- a/src-ui/src/app/app-routing.module.ts
+++ b/src-ui/src/app/app-routing.module.ts
@@ -37,7 +37,7 @@ export const routes: Routes = [
path: 'dashboard',
component: DashboardComponent,
data: {
- componentName: 'AppFrameComponent',
+ componentName: 'DashboardComponent',
},
},
{
diff --git a/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html b/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html
index fb8f8c95ba..823e4f2aff 100644
--- a/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html
+++ b/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html
@@ -62,13 +62,13 @@
} @else {
@switch (field) {
@case (DisplayField.ADDED) {
- {{doc.added | customDate}}
+ {{doc.added | customDate}}
}
@case (DisplayField.CREATED) {
- {{doc.created | customDate}}
+ {{doc.created | customDate}}
}
@case (DisplayField.TITLE) {
- {{doc.title | documentTitle}}
+ {{doc.title | documentTitle}}
}
@case (DisplayField.CORRESPONDENT) {
@if (doc.correspondent) {
@@ -106,7 +106,7 @@
}
@case (DisplayField.NOTES) {
@if (doc.notes.length) {
-
+
{{doc.notes.length}}
@@ -150,6 +150,7 @@
{
it('should navigate to document', () => {
const routerSpy = jest.spyOn(router, 'navigate')
+ const activateSpy = jest.spyOn(
+ documentListViewService,
+ 'activateSavedViewWithDocuments'
+ )
component.openDocumentDetail(documentResults[0])
+ expect(activateSpy).toHaveBeenCalled()
expect(routerSpy).toHaveBeenCalledWith(['documents', documentResults[0].id])
})
+ it('should hand its documents to the list service when opening a document', () => {
+ jest.spyOn(documentService, 'listFiltered').mockReturnValue(
+ of({
+ all: [2, 3],
+ count: 25,
+ results: documentResults,
+ })
+ )
+ jest.spyOn(router, 'navigateByUrl').mockResolvedValue(true)
+ const activateSpy = jest
+ .spyOn(documentListViewService, 'activateSavedViewWithDocuments')
+ .mockImplementation(() => {})
+ component.ngOnInit()
+ fixture.detectChanges()
+ fixture.debugElement
+ .query(By.css('a[title="Open document"]'))
+ .nativeElement.click()
+ expect(activateSpy).toHaveBeenCalledWith(savedView, documentResults, 25, 20)
+ })
+
+ it('should fall back to the default dashboard page size', () => {
+ component.savedView = { ...savedView, page_size: undefined }
+ expect(component.pageSize).toEqual(DEFAULT_DASHBOARD_VIEW_PAGE_SIZE)
+ })
+
it('should navigate via quickfilter on click tag', () => {
const qfSpy = jest.spyOn(documentListViewService, 'quickFilter')
component.clickTag(11, new MouseEvent('click'))
diff --git a/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.ts b/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.ts
index 09988c8992..5daf99fb22 100644
--- a/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.ts
+++ b/src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.ts
@@ -132,7 +132,7 @@ export class SavedViewWidgetComponent
ngOnInit(): void {
this.placeholderRows = Array.from(
{
- length: this.savedView?.page_size ?? DEFAULT_DASHBOARD_VIEW_PAGE_SIZE,
+ length: this.pageSize,
},
(_, index) => index
)
@@ -187,7 +187,7 @@ export class SavedViewWidgetComponent
this.documentService
.listFiltered(
1,
- this.savedView?.page_size ?? DEFAULT_DASHBOARD_VIEW_PAGE_SIZE,
+ this.pageSize,
this.savedView.sort_field,
this.savedView.sort_reverse,
this.savedView.filter_rules,
@@ -292,7 +292,24 @@ export class SavedViewWidgetComponent
])
}
+ get pageSize(): number {
+ return this.savedView?.page_size ?? DEFAULT_DASHBOARD_VIEW_PAGE_SIZE
+ }
+
+ /**
+ * Pass the widget's documents to the list service to enable next / previous.
+ */
+ activateViewForNavigation() {
+ this.documentListViewService.activateSavedViewWithDocuments(
+ this.savedView,
+ this.documents(),
+ this.count(),
+ this.pageSize
+ )
+ }
+
openDocumentDetail(document: Document) {
+ this.activateViewForNavigation()
this.router.navigate(['documents', document.id])
}
diff --git a/src-ui/src/app/components/document-detail/document-detail.component.spec.ts b/src-ui/src/app/components/document-detail/document-detail.component.spec.ts
index f5a94bfd04..26a42336a2 100644
--- a/src-ui/src/app/components/document-detail/document-detail.component.spec.ts
+++ b/src-ui/src/app/components/document-detail/document-detail.component.spec.ts
@@ -940,6 +940,20 @@ describe('DocumentDetailComponent', () => {
expect(navigateSpy).toHaveBeenCalledWith(['view', 77])
})
+ it('should close back to the dashboard even if a saved view is active', () => {
+ initNormally()
+ jest
+ .spyOn(componentRouterService, 'getComponentURLBefore')
+ .mockReturnValue('dashboard')
+ jest
+ .spyOn(documentListViewService, 'activeSavedViewId', 'get')
+ .mockReturnValue(77)
+ const navigateSpy = jest.spyOn(router, 'navigate')
+ component.close()
+ expect(navigateSpy).toHaveBeenCalledWith(['dashboard'])
+ expect(navigateSpy).not.toHaveBeenCalledWith(['view', 77])
+ })
+
it('should not close if e.g. user-cancelled', () => {
initNormally()
jest.spyOn(openDocumentsService, 'closeDocument').mockReturnValue(of(false))
diff --git a/src-ui/src/app/components/document-detail/document-detail.component.ts b/src-ui/src/app/components/document-detail/document-detail.component.ts
index b47937eef9..561fc6b681 100644
--- a/src-ui/src/app/components/document-detail/document-detail.component.ts
+++ b/src-ui/src/app/components/document-detail/document-detail.component.ts
@@ -1454,7 +1454,12 @@ export class DocumentDetailComponent
.subscribe((closed) => {
if (!closed) return
if (closedCallback) closedCallback()
- if (this.documentListViewService.activeSavedViewId) {
+ if (
+ this.componentRouterService.getComponentURLBefore() === 'dashboard'
+ ) {
+ // e.g. opened from a dashboard widget
+ this.router.navigate(['dashboard'])
+ } else if (this.documentListViewService.activeSavedViewId) {
this.router.navigate([
'view',
this.documentListViewService.activeSavedViewId,
diff --git a/src-ui/src/app/services/document-list-view.service.spec.ts b/src-ui/src/app/services/document-list-view.service.spec.ts
index 772c851e06..d5ef7e7b4b 100644
--- a/src-ui/src/app/services/document-list-view.service.spec.ts
+++ b/src-ui/src/app/services/document-list-view.service.spec.ts
@@ -386,6 +386,46 @@ describe('DocumentListViewService', () => {
documentListViewService.activateSavedView(null)
})
+ it('should support activating saved view with pre-fetched documents', () => {
+ const routerSpy = jest.spyOn(router, 'navigate')
+ routerSpy.mockClear()
+ documentListViewService.activateSavedViewWithDocuments(
+ view,
+ documents.slice(0, 2),
+ documents.length,
+ 2
+ )
+ // no navigation, no reload
+ expect(routerSpy).not.toHaveBeenCalled()
+ httpTestingController.expectNone(
+ (req) => req.url === `${environment.apiBaseUrl}documents/`
+ )
+ expect(documentListViewService.activeSavedViewId).toEqual(view.id)
+ expect(documentListViewService.activeSavedViewTitle).toEqual(view.name)
+ expect(documentListViewService.documents).toEqual(documents.slice(0, 2))
+ expect(documentListViewService.collectionSize).toEqual(documents.length)
+ expect(documentListViewService.currentPage).toEqual(1)
+ expect(documentListViewService.pageSize).toEqual(2)
+ expect(documentListViewService.hasNext(documents[0].id)).toBeTruthy()
+
+ let nextDocId: number
+ documentListViewService
+ .getNext(documents[1].id)
+ .subscribe((docId) => (nextDocId = docId))
+ // crossing the page uses the view's sort + filters and the given page size
+ // (the currentPage setter reloads too, so the first request is cancelled)
+ const reqs = httpTestingController.match(
+ `${environment.apiBaseUrl}documents/?page=2&page_size=2&ordering=-added&truncate_content=true&include_selection_data=true&tags__id__all=${tags__id__all}`
+ )
+ expect(reqs.length).toBeGreaterThan(0)
+ reqs[reqs.length - 1].flush({
+ count: documents.length,
+ results: documents.slice(2, 4),
+ })
+ expect(nextDocId).toEqual(documents[2].id)
+ documentListViewService.activateSavedView(null)
+ })
+
it('should support navigating next / previous', () => {
documentListViewService.setFilterRules([])
let req = httpTestingController.expectOne(
diff --git a/src-ui/src/app/services/document-list-view.service.ts b/src-ui/src/app/services/document-list-view.service.ts
index 429cde2c09..5a3a60c5b0 100644
--- a/src-ui/src/app/services/document-list-view.service.ts
+++ b/src-ui/src/app/services/document-list-view.service.ts
@@ -270,11 +270,28 @@ export class DocumentListViewService {
this.currentPage = viewState.currentPage
}
- loadSavedView(view: SavedView, closeCurrentView: boolean = false) {
- if (closeCurrentView) {
- this._activeSavedViewId = null
- }
+ /**
+ * Activate a saved view using documents that were already fetched, e.g. by a
+ * dashboard widget. Does not navigate or reload.
+ */
+ activateSavedViewWithDocuments(
+ view: SavedView,
+ documents: Document[],
+ collectionSize: number,
+ pageSize: number
+ ) {
+ this.cancelPending()
+ this.rangeSelectionAnchorIndex = this.lastRangeSelectionToIndex = null
+ this._activeSavedViewId = view.id
+ this.applySavedViewConfig(view)
+ this.activeListViewState.pageSize = pageSize // match the page size the documents were fetched with
+ this.activeListViewState.currentPage = 1
+ this.activeListViewState.documents = documents
+ this.activeListViewState.collectionSize = collectionSize
+ this.markChanged()
+ }
+ private applySavedViewConfig(view: SavedView) {
this.activeListViewState.filterRules = cloneFilterRules(view.filter_rules)
this.activeListViewState.sortField = view.sort_field
this.activeListViewState.sortReverse = view.sort_reverse
@@ -284,6 +301,14 @@ export class DocumentListViewService {
this.activeListViewState.displayMode = view.display_mode
this.activeListViewState.pageSize = view.page_size
this.activeListViewState.displayFields = view.display_fields
+ }
+
+ loadSavedView(view: SavedView, closeCurrentView: boolean = false) {
+ if (closeCurrentView) {
+ this._activeSavedViewId = null
+ }
+
+ this.applySavedViewConfig(view)
this.reduceSelectionToFilter()
this.markChanged()