diff --git a/src-ui/src/app/components/manage/saved-views/saved-views.component.html b/src-ui/src/app/components/manage/saved-views/saved-views.component.html index 6b7e7cb71..ff49a323c 100644 --- a/src-ui/src/app/components/manage/saved-views/saved-views.component.html +++ b/src-ui/src/app/components/manage/saved-views/saved-views.component.html @@ -7,7 +7,7 @@
- - +
+ + + @if (savedViews()?.length > pageSize) { + + } +
diff --git a/src-ui/src/app/components/manage/saved-views/saved-views.component.spec.ts b/src-ui/src/app/components/manage/saved-views/saved-views.component.spec.ts index 735e5abf4..fbcdbe8c3 100644 --- a/src-ui/src/app/components/manage/saved-views/saved-views.component.spec.ts +++ b/src-ui/src/app/components/manage/saved-views/saved-views.component.spec.ts @@ -4,6 +4,7 @@ import { provideHttpClientTesting } from '@angular/common/http/testing' import { signal } from '@angular/core' import { ComponentFixture, TestBed } from '@angular/core/testing' import { FormsModule, ReactiveFormsModule } from '@angular/forms' +import { By } from '@angular/platform-browser' import { NgbModal, NgbModule } from '@ng-bootstrap/ng-bootstrap' import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' import { Subject, of, throwError } from 'rxjs' @@ -222,6 +223,44 @@ describe('SavedViewsComponent', () => { ).toEqual(view.show_on_dashboard) }) + it('should page saved views, clamp the page if views are removed', () => { + const manyViews = Array.from({ length: 30 }, (_, i) => ({ + id: i + 1, + name: `view${i + 1}`, + })) as SavedView[] + const listSpy = jest.spyOn(savedViewService, 'list').mockReturnValue( + of({ + all: manyViews.map((v) => v.id), + count: manyViews.length, + results: manyViews.concat([]), + }) + ) + component.ngOnInit() + fixture.detectChanges() + expect(listSpy).toHaveBeenCalledWith(1, 100000, null, false, { + full_perms: true, + }) + expect(component.pagedSavedViews()).toHaveLength(25) + expect(fixture.debugElement.query(By.css('ngb-pagination'))).not.toBeNull() + // all views have controls, not just the current page + expect( + Object.keys(component.savedViewsForm.get('savedViews').value) + ).toHaveLength(30) + + component.page.set(2) + expect(component.pagedSavedViews()).toHaveLength(5) + + listSpy.mockReturnValue( + of({ + all: manyViews.slice(0, 25).map((v) => v.id), + count: 25, + results: manyViews.slice(0, 25), + }) + ) + component.ngOnInit() + expect(component.page()).toEqual(1) + }) + it('should support editing permissions', () => { const confirmClicked = new Subject() const modalRef = { diff --git a/src-ui/src/app/components/manage/saved-views/saved-views.component.ts b/src-ui/src/app/components/manage/saved-views/saved-views.component.ts index 21942b9c1..3813e666f 100644 --- a/src-ui/src/app/components/manage/saved-views/saved-views.component.ts +++ b/src-ui/src/app/components/manage/saved-views/saved-views.component.ts @@ -1,12 +1,19 @@ import { AsyncPipe } from '@angular/common' -import { Component, OnDestroy, OnInit, inject, signal } from '@angular/core' +import { + Component, + OnDestroy, + OnInit, + computed, + inject, + signal, +} from '@angular/core' import { FormControl, FormGroup, FormsModule, ReactiveFormsModule, } from '@angular/forms' -import { NgbModal } from '@ng-bootstrap/ng-bootstrap' +import { NgbModal, NgbPaginationModule } from '@ng-bootstrap/ng-bootstrap' import { dirtyCheck } from '@ngneat/dirty-check-forms' import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' import { BehaviorSubject, Observable, of, switchMap, takeUntil } from 'rxjs' @@ -42,6 +49,7 @@ import { LoadingComponentWithPermissions } from '../../loading-component/loading FormsModule, ReactiveFormsModule, AsyncPipe, + NgbPaginationModule, NgxBootstrapIconsModule, ], }) @@ -58,6 +66,14 @@ export class SavedViewsComponent DisplayMode = DisplayMode readonly savedViews = signal(undefined) + readonly page = signal(1) + public readonly pageSize = 25 + // All views are loaded at init, so paging is only for display + readonly pagedSavedViews = computed(() => { + const start = (this.page() - 1) * this.pageSize + return this.savedViews()?.slice(start, start + this.pageSize) + }) + private savedViewsGroup = new FormGroup({}) public savedViewsForm: FormGroup = new FormGroup({ savedViews: this.savedViewsGroup, @@ -84,9 +100,11 @@ export class SavedViewsComponent private reloadViews(): void { this.loading.set(true) this.savedViewService - .list(null, null, null, false, { full_perms: true }) + .list(1, 100000, null, false, { full_perms: true }) .subscribe((r) => { this.savedViews.set(r.results) + const pageCount = Math.ceil(r.results.length / this.pageSize) + this.page.update((page) => Math.min(page, Math.max(1, pageCount))) this.initialize() }) }