Skip to content

Commit f4fe2f7

Browse files
jr-rkclaude
andcommitted
feat(statistics): show scoped top-items report on community and collection statistics pages
Adds the new backend TopItems report to the community and collection statistics pages so each lists (and paginates through) the most-visited items within that scope, mirroring the site-level page. Also clears each statistics table's pagination query params on destroy so a page/rpp selection does not leak (via queryParamsHandling: 'merge') into the next scope's statistics page. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 0f0555e commit f4fe2f7

9 files changed

Lines changed: 61 additions & 5 deletions

File tree

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ describe('CollectionStatisticsPageComponent', () => {
8383
{ provide: DSpaceObjectDataService, useValue: {} },
8484
{ provide: DSONameService, useValue: nameService },
8585
{ provide: AuthService, useValue: authService },
86-
{ provide: PaginationService, useValue: { getCurrentPagination: () => observableOf({ currentPage: 1, pageSize: 10 }) } },
86+
{ provide: PaginationService, useValue: { getCurrentPagination: () => observableOf({ currentPage: 1, pageSize: 10 }), clearPagination: () => undefined } },
8787
],
8888
})
8989
.compileComponents();
@@ -110,6 +110,8 @@ describe('CollectionStatisticsPageComponent', () => {
110110
.toBeTruthy();
111111
expect(de.query(By.css('ds-statistics-table.collection_id-TotalVisitsPerMonth-report')).nativeElement)
112112
.toBeTruthy();
113+
expect(de.query(By.css('ds-statistics-table.collection_id-TopItems-report')).nativeElement)
114+
.toBeTruthy();
113115
expect(de.query(By.css('ds-statistics-table.collection_id-TopCountries-report')).nativeElement)
114116
.toBeTruthy();
115117
expect(de.query(By.css('ds-statistics-table.collection_id-TopCities-report')).nativeElement)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export class CollectionStatisticsPageComponent extends StatisticsPageDirective<C
2626
types: string[] = [
2727
'TotalVisits',
2828
'TotalVisitsPerMonth',
29+
'TopItems',
2930
'TopCountries',
3031
'TopCities',
3132
];

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ describe('CommunityStatisticsPageComponent', () => {
8383
{ provide: DSpaceObjectDataService, useValue: {} },
8484
{ provide: DSONameService, useValue: nameService },
8585
{ provide: AuthService, useValue: authService },
86-
{ provide: PaginationService, useValue: { getCurrentPagination: () => observableOf({ currentPage: 1, pageSize: 10 }) } },
86+
{ provide: PaginationService, useValue: { getCurrentPagination: () => observableOf({ currentPage: 1, pageSize: 10 }), clearPagination: () => undefined } },
8787
],
8888
})
8989
.compileComponents();
@@ -110,6 +110,8 @@ describe('CommunityStatisticsPageComponent', () => {
110110
.toBeTruthy();
111111
expect(de.query(By.css('ds-statistics-table.community_id-TotalVisitsPerMonth-report')).nativeElement)
112112
.toBeTruthy();
113+
expect(de.query(By.css('ds-statistics-table.community_id-TopItems-report')).nativeElement)
114+
.toBeTruthy();
113115
expect(de.query(By.css('ds-statistics-table.community_id-TopCountries-report')).nativeElement)
114116
.toBeTruthy();
115117
expect(de.query(By.css('ds-statistics-table.community_id-TopCities-report')).nativeElement)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export class CommunityStatisticsPageComponent extends StatisticsPageDirective<Co
2626
types: string[] = [
2727
'TotalVisits',
2828
'TotalVisitsPerMonth',
29+
'TopItems',
2930
'TopCountries',
3031
'TopCities',
3132
];

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ describe('ItemStatisticsPageComponent', () => {
8383
{ provide: DSpaceObjectDataService, useValue: {} },
8484
{ provide: DSONameService, useValue: nameService },
8585
{ provide: AuthService, useValue: authService },
86-
{ provide: PaginationService, useValue: { getCurrentPagination: () => observableOf({ currentPage: 1, pageSize: 10 }) } },
86+
{ provide: PaginationService, useValue: { getCurrentPagination: () => observableOf({ currentPage: 1, pageSize: 10 }), clearPagination: () => undefined } },
8787
],
8888
})
8989
.compileComponents();

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ describe('SiteStatisticsPageComponent', () => {
8484
{ provide: DSONameService, useValue: nameService },
8585
{ provide: SiteDataService, useValue: siteService },
8686
{ provide: AuthService, useValue: authService },
87-
{ provide: PaginationService, useValue: { getCurrentPagination: () => observableOf({ currentPage: 1, pageSize: 10 }) } },
87+
{ provide: PaginationService, useValue: { getCurrentPagination: () => observableOf({ currentPage: 1, pageSize: 10 }), clearPagination: () => undefined } },
8888
],
8989
})
9090
.compileComponents();

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ describe('StatisticsTableComponent', () => {
4747

4848
const paginationService = {
4949
getCurrentPagination: (_id: string, _options: PaginationComponentOptions) => currentPagination$.asObservable(),
50+
clearPagination: (_id: string) => undefined,
5051
};
5152

5253
const setPage = (currentPage: number, pageSize = 10) => {
@@ -221,4 +222,42 @@ describe('StatisticsTableComponent', () => {
221222
expect(de.query(By.css(`td.item_${numberOfPoints - 1}-views-data`))).toBeTruthy();
222223
});
223224
});
225+
226+
describe('on destroy', () => {
227+
228+
it('should clear its own pagination params so they do not leak to the next scope', () => {
229+
component.report = Object.assign(new UsageReport(), {
230+
id: 'uuid_TopItems',
231+
points: [],
232+
});
233+
component.ngOnInit();
234+
const spy = spyOn(paginationService, 'clearPagination');
235+
236+
fixture.destroy();
237+
238+
expect(spy).toHaveBeenCalledWith('stats-uuid_TopItems');
239+
});
240+
241+
it('should not throw when destroyed before initialisation', () => {
242+
const uninitialised = TestBed.createComponent(StatisticsTableComponent).componentInstance;
243+
expect(() => uninitialised.ngOnDestroy()).not.toThrow();
244+
});
245+
});
246+
247+
describe('getLabel', () => {
248+
249+
it('should return the point label for a TopItems report without resolving the DSO', (done) => {
250+
// DSpaceObjectDataService is provided as {} in this suite, so any findById lookup would throw — proving
251+
// the TopItems path relies solely on the label the backend already supplied.
252+
component.report = Object.assign(new UsageReport(), {
253+
reportType: 'TopItems',
254+
points: [],
255+
});
256+
257+
component.getLabel({ id: 'item-uuid', label: 'My item', type: 'item', values: [{ views: 3 }] }).subscribe((label) => {
258+
expect(label).toEqual('My item');
259+
done();
260+
});
261+
});
262+
});
224263
});

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
import {
77
Component,
88
Input,
9+
OnDestroy,
910
OnInit,
1011
} from '@angular/core';
1112
import {
@@ -43,7 +44,7 @@ import { PaginationComponentOptions } from '../../shared/pagination/pagination-c
4344
standalone: true,
4445
imports: [NgIf, NgFor, AsyncPipe, TranslateModule, PaginationComponent],
4546
})
46-
export class StatisticsTableComponent implements OnInit {
47+
export class StatisticsTableComponent implements OnInit, OnDestroy {
4748

4849
/**
4950
* The usage report to display a statistics table for
@@ -110,6 +111,14 @@ export class StatisticsTableComponent implements OnInit {
110111
);
111112
}
112113

114+
ngOnDestroy() {
115+
// Drop this table's page/rpp query params so they don't leak (via queryParamsHandling: 'merge') to the next
116+
// scope's statistics page. Guarded in case the component is destroyed before ngOnInit ran.
117+
if (this.paginationOptions) {
118+
this.paginationService.clearPagination(this.paginationOptions.id);
119+
}
120+
}
121+
113122
/**
114123
* Get the row label to display for a statistics point.
115124
* @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
@@ -4814,6 +4814,8 @@
48144814

48154815
"statistics.table.title.TopCities": "Top city views",
48164816

4817+
"statistics.table.title.TopItems": "Top item views",
4818+
48174819
"statistics.table.header.views": "Views",
48184820

48194821
"statistics.table.no-name": "(object name could not be loaded)",

0 commit comments

Comments
 (0)