Skip to content

Commit 6c3ad31

Browse files
milanmajchrakclaude
andcommitted
UoE/Add pagination to statistics tables (DSpace#726)
The statistics tables (e.g. the repository-wide "Total visits" report) rendered every point in a single, ungoverned table and, combined with the backend cap of 10 items, users could only ever see the first 10 datasets. Add client-side pagination to the shared StatisticsTableComponent so that all reports paginate their points and every dataset can be browsed page by page. - Render only the current page of points (default 10 per page). - Show an ngb-pagination control when there are more points than fit on a page. - Add the `statistics.table.pagination.label` i18n key for accessibility. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 700d7c5 commit 6c3ad31

4 files changed

Lines changed: 110 additions & 2 deletions

File tree

src/app/statistics-page/statistics-table/statistics-table.component.html

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ <h2 class="m-1">
1818
</th>
1919
</tr>
2020

21-
<tr *ngFor="let point of report.points"
21+
<tr *ngFor="let point of paginatedPoints"
2222
class="{{point.id}}-data">
2323
<th scope="row" data-test="statistics-label">
2424
{{ getLabel(point) | async }}
@@ -33,4 +33,18 @@ <h2 class="m-1">
3333

3434
</table>
3535

36+
<div *ngIf="showPagination"
37+
class="d-flex justify-content-center">
38+
<ngb-pagination [collectionSize]="report.points.length"
39+
[page]="currentPage"
40+
[pageSize]="pageSize"
41+
[maxSize]="5"
42+
[rotate]="true"
43+
[ellipses]="true"
44+
[boundaryLinks]="true"
45+
(pageChange)="onPageChange($event)"
46+
[attr.aria-label]="'statistics.table.pagination.label' | translate">
47+
</ngb-pagination>
48+
</div>
49+
3650
</div>

src/app/statistics-page/statistics-table/statistics-table.component.spec.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,5 +96,62 @@ describe('StatisticsTableComponent', () => {
9696
expect(de.query(By.css('td.item_2-downloads-data')).nativeElement.innerText)
9797
.toEqual('8');
9898
});
99+
100+
it('should not display a pagination control when all points fit on a single page', () => {
101+
expect(de.query(By.css('ngb-pagination'))).toBeNull();
102+
});
103+
});
104+
105+
describe('when the report has more points than the page size', () => {
106+
107+
const numberOfPoints = 25;
108+
109+
beforeEach(() => {
110+
const points = [];
111+
for (let i = 0; i < numberOfPoints; i++) {
112+
points.push({
113+
id: `item_${i}`,
114+
label: `item_${i}`,
115+
values: {
116+
views: i,
117+
},
118+
});
119+
}
120+
component.report = Object.assign(new UsageReport(), { points });
121+
component.ngOnInit();
122+
fixture.detectChanges();
123+
});
124+
125+
it('should only render the first page of points', () => {
126+
expect(de.queryAll(By.css('[data-test="statistics-label"]')).length)
127+
.toEqual(component.pageSize);
128+
expect(de.query(By.css('td.item_0-views-data'))).toBeTruthy();
129+
expect(de.query(By.css('td.item_10-views-data'))).toBeNull();
130+
});
131+
132+
it('should display a pagination control', () => {
133+
expect(de.query(By.css('ngb-pagination'))).toBeTruthy();
134+
});
135+
136+
it('should render the next page of points when the page changes', () => {
137+
component.onPageChange(2);
138+
fixture.detectChanges();
139+
140+
expect(de.query(By.css('td.item_0-views-data'))).toBeNull();
141+
expect(de.query(By.css('td.item_10-views-data'))).toBeTruthy();
142+
expect(de.queryAll(By.css('[data-test="statistics-label"]')).length)
143+
.toEqual(component.pageSize);
144+
});
145+
146+
it('should render the remaining points on the last page', () => {
147+
const lastPage = Math.ceil(numberOfPoints / component.pageSize);
148+
component.onPageChange(lastPage);
149+
fixture.detectChanges();
150+
151+
const remaining = numberOfPoints - (lastPage - 1) * component.pageSize;
152+
expect(de.queryAll(By.css('[data-test="statistics-label"]')).length)
153+
.toEqual(remaining);
154+
expect(de.query(By.css(`td.item_${numberOfPoints - 1}-views-data`))).toBeTruthy();
155+
});
99156
});
100157
});

src/app/statistics-page/statistics-table/statistics-table.component.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
Input,
99
OnInit,
1010
} from '@angular/core';
11+
import { NgbPaginationModule } from '@ng-bootstrap/ng-bootstrap';
1112
import {
1213
TranslateModule,
1314
TranslateService,
@@ -38,7 +39,7 @@ import { isEmpty } from '../../shared/empty.util';
3839
templateUrl: './statistics-table.component.html',
3940
styleUrls: ['./statistics-table.component.scss'],
4041
standalone: true,
41-
imports: [NgIf, NgFor, AsyncPipe, TranslateModule],
42+
imports: [NgIf, NgFor, AsyncPipe, TranslateModule, NgbPaginationModule],
4243
})
4344
export class StatisticsTableComponent implements OnInit {
4445

@@ -48,6 +49,17 @@ export class StatisticsTableComponent implements OnInit {
4849
@Input()
4950
report: UsageReport;
5051

52+
/**
53+
* The number of points (e.g. datasets, countries, cities) to show per page.
54+
*/
55+
@Input()
56+
pageSize = 10;
57+
58+
/**
59+
* The currently displayed page (1-based, as expected by ngb-pagination).
60+
*/
61+
currentPage = 1;
62+
5163
/**
5264
* Boolean indicating whether the usage report has data
5365
*/
@@ -73,6 +85,29 @@ export class StatisticsTableComponent implements OnInit {
7385
}
7486
}
7587

88+
/**
89+
* The points to render for the currently selected page.
90+
*/
91+
get paginatedPoints(): Point[] {
92+
const start = (this.currentPage - 1) * this.pageSize;
93+
return this.report.points.slice(start, start + this.pageSize);
94+
}
95+
96+
/**
97+
* Whether a pagination control is needed, i.e. there are more points than fit on a single page.
98+
*/
99+
get showPagination(): boolean {
100+
return this.report.points.length > this.pageSize;
101+
}
102+
103+
/**
104+
* Switch to the given page.
105+
* @param page the 1-based page number to display
106+
*/
107+
onPageChange(page: number): void {
108+
this.currentPage = page;
109+
}
110+
76111
/**
77112
* Get the row label to display for a statistics point.
78113
* @param point the statistics point to get the label for

src/assets/i18n/en.json5

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4794,6 +4794,8 @@
47944794

47954795
"statistics.table.no-data": "No data available",
47964796

4797+
"statistics.table.pagination.label": "Statistics table pagination",
4798+
47974799
"statistics.table.title.TotalVisits": "Total visits",
47984800

47994801
"statistics.table.title.TotalVisitsPerMonth": "Total visits per month",

0 commit comments

Comments
 (0)