From 1477ba6392e06c7a285fe62305c890827ff6f3b1 Mon Sep 17 00:00:00 2001 From: milanmajchrak Date: Fri, 7 Aug 2026 12:24:30 +0200 Subject: [PATCH 1/2] Stop comparing embed params and encoding as if they were differences The frontend strips embed params from its own url before comparing it to the self link the API returns, but not from the self link itself, so it reported a mismatch it had created. The API also percent decodes values it has no reason to escape - ':' and '/' are legal in a query component - which looked like another difference. Both sides are now brought to the same form before the comparison: embed params stripped, values percent decoded. A difference in an actual value still warns. Only the warning is affected. The condition that rewrites _links.self is unchanged, so the url a response is cached under stays the same. Adds the first spec this service has had, 17 cases covering both what is now tolerated and what still has to warn. Co-Authored-By: Claude Opus 5 (1M context) --- ...pace-rest-response-parsing.service.spec.ts | 235 ++++++++++++++++++ .../dspace-rest-response-parsing.service.ts | 49 +++- 2 files changed, 281 insertions(+), 3 deletions(-) create mode 100644 src/app/core/data/dspace-rest-response-parsing.service.spec.ts diff --git a/src/app/core/data/dspace-rest-response-parsing.service.spec.ts b/src/app/core/data/dspace-rest-response-parsing.service.spec.ts new file mode 100644 index 00000000000..726501a4383 --- /dev/null +++ b/src/app/core/data/dspace-rest-response-parsing.service.spec.ts @@ -0,0 +1,235 @@ +import { ObjectCacheService } from '../cache/object-cache.service'; +import { RawRestResponse } from '../dspace-rest/raw-rest-response.model'; +import { DspaceRestResponseParsingService } from './dspace-rest-response-parsing.service'; +import { + GetRequest, + PostRequest, +} from './request.models'; +import { RestRequest } from './rest-request.model'; + +/** + * Exposes the protected {@link DspaceRestResponseParsingService#ensureSelfLink} so it can be + * tested in isolation. + */ +class TestParsingService extends DspaceRestResponseParsingService { + public callEnsureSelfLink(request: RestRequest, response: RawRestResponse): RawRestResponse { + return this.ensureSelfLink(request, response); + } +} + +describe('DspaceRestResponseParsingService', () => { + let service: TestParsingService; + let objectCache: ObjectCacheService; + + const MISMATCH = jasmine.stringMatching(/These don't match/); + const NO_SELF_LINK = jasmine.stringMatching(/doesn't have a self link/); + + const requestFor = (href: string): RestRequest => + new GetRequest('c4f0b1b7-3ffa-4b1a-9f5f-8bd6b1c4de71', href); + + const responseWithSelfLink = (href: string, page?: any): RawRestResponse => ({ + payload: { + _links: { + self: { href }, + }, + ...(page ? { page } : {}), + }, + statusCode: 200, + statusText: 'OK', + }); + + beforeEach(() => { + objectCache = jasmine.createSpyObj('objectCache', ['add', 'remove']); + service = new TestParsingService(objectCache); + spyOn(console, 'warn'); + }); + + describe('ensureSelfLink', () => { + + describe('differences the REST API is expected to introduce', () => { + + it('should not warn when the self link matches the requested url', () => { + const href = 'https://rest.api/core/bundles/9d18168a/bitstreams?page=0&size=5'; + const response = service.callEnsureSelfLink(requestFor(href), responseWithSelfLink(href)); + + expect(console.warn).not.toHaveBeenCalled(); + expect(response.payload._links.self.href).toBe(href); + }); + + it('should not warn when the self link only echoes the embed params of the request', () => { + // https://github.com/dataquest-dev/dspace-customers/issues/862 + const href = 'https://rest.api/core/bundles/9d18168a/bitstreams?page=0&embed=accessStatus&size=5'; + const response = service.callEnsureSelfLink(requestFor(href), responseWithSelfLink(href)); + + expect(console.warn).not.toHaveBeenCalled(); + // the self link is still normalized, because that's the url the response is cached under + expect(response.payload._links.self.href).toBe('https://rest.api/core/bundles/9d18168a/bitstreams?page=0&size=5'); + }); + + it('should not warn when the self link echoes embed params and the request has no other params', () => { + // observed in the browser against a DSpace 9.1 backend + const href = 'https://rest.api/core/items/eba1c085/bundles?embed=primaryBitstream&embed=bitstreams/format&embed.size=bitstreams=5'; + const response = service.callEnsureSelfLink(requestFor(href), responseWithSelfLink(href)); + + expect(console.warn).not.toHaveBeenCalled(); + expect(response.payload._links.self.href).toBe('https://rest.api/core/items/eba1c085/bundles'); + }); + + it('should not warn when the self link only percent decoded a param value', () => { + // https://github.com/dataquest-dev/dspace-customers/issues/862 + const request = requestFor('https://rest.api/statistics/usagereports/search/object?page=-1&size=10&uri=https%3A%2F%2Frest.api%2Fcore%2Fsites%2F8f842a80'); + const response = service.callEnsureSelfLink(request, responseWithSelfLink( + 'https://rest.api/statistics/usagereports/search/object?page=-1&size=10&uri=https://rest.api/core/sites/8f842a80')); + + expect(console.warn).not.toHaveBeenCalled(); + expect(response.payload._links.self.href) + .toBe('https://rest.api/statistics/usagereports/search/object?page=-1&size=10&uri=https%3A%2F%2Frest.api%2Fcore%2Fsites%2F8f842a80'); + }); + + it('should not warn or normalize when params are only in a different order', () => { + const request = requestFor('https://rest.api/core/items/eba1c085/bundles?page=0&size=5'); + const response = service.callEnsureSelfLink(request, + responseWithSelfLink('https://rest.api/core/items/eba1c085/bundles?size=5&page=0')); + + expect(console.warn).not.toHaveBeenCalled(); + // the urls hold the same params, so nothing is rewritten here + expect(response.payload._links.self.href).toBe('https://rest.api/core/items/eba1c085/bundles?size=5&page=0'); + }); + + }); + + describe('differences that point at a problem with the endpoint', () => { + + it('should warn when the REST API reduced the requested page size', () => { + // callers are expected to stay within MAX_PAGE_SIZE, so a reduced size means a caller asked + // for a page the API was never going to serve + const request = requestFor('https://rest.api/core/items/eba1c085/bundles?size=9999'); + service.callEnsureSelfLink(request, responseWithSelfLink( + 'https://rest.api/core/items/eba1c085/bundles?size=1000', + { number: 0, size: 1000, totalPages: 1, totalElements: 2 })); + + expect(console.warn).toHaveBeenCalledTimes(1); + expect(console.warn).toHaveBeenCalledWith(MISMATCH); + }); + + it('should warn when the returned page size is larger than the requested one', () => { + const request = requestFor('https://rest.api/core/items/eba1c085/bundles?size=5'); + service.callEnsureSelfLink(request, responseWithSelfLink( + 'https://rest.api/core/items/eba1c085/bundles?size=50', + { number: 0, size: 50, totalPages: 1, totalElements: 2 })); + + expect(console.warn).toHaveBeenCalledTimes(1); + expect(console.warn).toHaveBeenCalledWith(MISMATCH); + }); + + it('should still warn when a param value differs beyond its encoding', () => { + const request = requestFor('https://rest.api/core/items/eba1c085/bundles?uri=https%3A%2F%2Frest.api%2Fcore%2Fsites%2Faaa'); + service.callEnsureSelfLink(request, + responseWithSelfLink('https://rest.api/core/items/eba1c085/bundles?uri=https://rest.api/core/sites/bbb')); + + expect(console.warn).toHaveBeenCalledTimes(1); + expect(console.warn).toHaveBeenCalledWith(MISMATCH); + }); + + it('should report the normalized request url and the raw self link in the warning', () => { + const request = requestFor('https://rest.api/core/items/eba1c085/bundles?page=0&embed=primaryBitstream&size=5'); + service.callEnsureSelfLink(request, + responseWithSelfLink('https://rest.api/core/items/eba1c085/bundles?page=3&embed=primaryBitstream&size=5')); + + expect(console.warn).toHaveBeenCalledWith( + 'The response for \'https://rest.api/core/items/eba1c085/bundles?page=0&size=5\' has the self link ' + + '\'https://rest.api/core/items/eba1c085/bundles?page=3&embed=primaryBitstream&size=5\'. ' + + 'These don\'t match. This could mean there\'s an issue with the REST endpoint'); + }); + + it('should warn when a non-embed param differs', () => { + const request = requestFor('https://rest.api/core/items/eba1c085/bundles?page=0&embed=primaryBitstream&size=5'); + service.callEnsureSelfLink(request, + responseWithSelfLink('https://rest.api/core/items/eba1c085/bundles?page=3&embed=primaryBitstream&size=5')); + + expect(console.warn).toHaveBeenCalledTimes(1); + expect(console.warn).toHaveBeenCalledWith(MISMATCH); + }); + + it('should warn when the self link has a param the request did not have', () => { + const request = requestFor('https://rest.api/core/items/eba1c085/bundles?size=5'); + service.callEnsureSelfLink(request, + responseWithSelfLink('https://rest.api/core/items/eba1c085/bundles?size=5&sort=name,ASC')); + + expect(console.warn).toHaveBeenCalledTimes(1); + expect(console.warn).toHaveBeenCalledWith(MISMATCH); + }); + + it('should warn and fill in the requested url when the response has no self link', () => { + const request = requestFor('https://rest.api/core/items/eba1c085/bundles?embed=primaryBitstream&size=5'); + const response = service.callEnsureSelfLink(request, { + payload: { _links: {} }, + statusCode: 200, + statusText: 'OK', + }); + + expect(console.warn).toHaveBeenCalledTimes(1); + expect(console.warn).toHaveBeenCalledWith(NO_SELF_LINK); + expect(response.payload._links.self.href).toBe('https://rest.api/core/items/eba1c085/bundles?size=5'); + }); + + }); + + describe('normalization of the self link', () => { + + it('should normalize the self link when it differs, so it matches the cache key', () => { + const request = requestFor('https://rest.api/core/items/eba1c085/bundles?page=0&embed=primaryBitstream&size=5'); + const response = service.callEnsureSelfLink(request, + responseWithSelfLink('https://rest.api/core/items/eba1c085/bundles?page=3&embed=primaryBitstream&size=5')); + + expect(response.payload._links.self.href).toBe('https://rest.api/core/items/eba1c085/bundles?page=0&size=5'); + }); + + it('should keep the other links when it normalizes the self link', () => { + const request = requestFor('https://rest.api/core/items/eba1c085/bundles?page=0&size=5'); + const response = service.callEnsureSelfLink(request, { + payload: { + _links: { + self: { href: 'https://rest.api/core/items/eba1c085/bundles?page=3&size=5' }, + primaryBitstream: { href: 'https://rest.api/core/bitstreams/6a5f' }, + }, + }, + statusCode: 200, + statusText: 'OK', + }); + + expect(response.payload._links.self.href).toBe('https://rest.api/core/items/eba1c085/bundles?page=0&size=5'); + expect(response.payload._links.primaryBitstream.href).toBe('https://rest.api/core/bitstreams/6a5f'); + }); + + it('should not touch a self link on a different host', () => { + const request = requestFor('https://rest.api/core/items/eba1c085/bundles?size=5'); + const response = service.callEnsureSelfLink(request, + responseWithSelfLink('https://other.api/core/items/eba1c085/bundles?size=5')); + + expect(console.warn).not.toHaveBeenCalled(); + expect(response.payload._links.self.href).toBe('https://other.api/core/items/eba1c085/bundles?size=5'); + }); + + it('should not touch a self link that points at a different path', () => { + const request = requestFor('https://rest.api/core/items/eba1c085/bundles?size=5'); + const response = service.callEnsureSelfLink(request, + responseWithSelfLink('https://rest.api/core/items/eba1c085?size=5')); + + expect(console.warn).not.toHaveBeenCalled(); + expect(response.payload._links.self.href).toBe('https://rest.api/core/items/eba1c085?size=5'); + }); + + it('should leave non-GET requests alone', () => { + const request = new PostRequest('c4f0b1b7-3ffa-4b1a-9f5f-8bd6b1c4de71', 'https://rest.api/core/items/eba1c085/bundles?size=5'); + const response = service.callEnsureSelfLink(request, + responseWithSelfLink('https://rest.api/core/items/eba1c085/bundles?size=1000')); + + expect(console.warn).not.toHaveBeenCalled(); + expect(response.payload._links.self.href).toBe('https://rest.api/core/items/eba1c085/bundles?size=1000'); + }); + + }); + + }); +}); diff --git a/src/app/core/data/dspace-rest-response-parsing.service.ts b/src/app/core/data/dspace-rest-response-parsing.service.ts index 1cd286427f3..b6be712314d 100644 --- a/src/app/core/data/dspace-rest-response-parsing.service.ts +++ b/src/app/core/data/dspace-rest-response-parsing.service.ts @@ -65,6 +65,45 @@ const splitUrlInParts = (url: string): string[] => { .reduce((combined, current) => [...combined, ...current]); }; +/** + * Return true if two lists of url parts don't hold the same parts, ignoring their order + */ +const urlPartsDiffer = (expected: string[], actual: string[]): boolean => { + return expected.some((part: string) => !actual.includes(part)) + || actual.some((part: string) => !expected.includes(part)); +}; + +/** + * Percent decode each url part, so `uri=http%3A%2F%2Fx` and `uri=http://x` compare equal. Parts are + * decoded one by one, after the url was split, so a decoded `&` can't merge two params. + */ +const decodeUrlParts = (parts: string[]): string[] => { + return parts.map((part: string) => { + try { + return decodeURIComponent(part); + } catch (e) { + return part; + } + }); +}; + +/** + * Return true if the self link differs from the requested url in a way that isn't just a different + * way of writing the same request. Takes the requested url already split, since the caller has it. + * + * Both sides are brought to the same form first: `embed`/`embed.size` params are stripped, because + * the frontend treats them as not part of a resource's identity and indexes without them, and both + * are percent decoded. Anything still differing is a real difference between what was asked for and + * what came back, including a page size the API reduced — callers are expected to stay within + * `MAX_PAGE_SIZE` rather than have that reported difference filtered out here. + */ +const isUnexpectedSelfLink = (requestedUrlParts: string[], selfLink: string): boolean => { + return urlPartsDiffer( + decodeUrlParts(requestedUrlParts), + decodeUrlParts(splitUrlInParts(getUrlWithoutEmbedParams(selfLink))), + ); +}; + @Injectable({ providedIn: 'root' }) export class DspaceRestResponseParsingService implements ResponseParsingService { protected serializerConstructor: GenericConstructor> = DSpaceSerializer; @@ -167,10 +206,14 @@ export class DspaceRestResponseParsingService implements ResponseParsingService }); } else { + const selfLink = response.payload._links.self.href; const expected = splitUrlInParts(urlWithoutEmbedParams); - const actual = splitUrlInParts(response.payload._links.self.href); - if (expected[0] === actual[0] && (expected.some((e) => !actual.includes(e)) || actual.some((e) => !expected.includes(e)))) { - console.warn(`The response for '${urlWithoutEmbedParams}' has the self link '${response.payload._links.self.href}'. These don't match. This could mean there's an issue with the REST endpoint`); + const actual = splitUrlInParts(selfLink); + if (expected[0] === actual[0] && urlPartsDiffer(expected, actual)) { + // the self link is normalized either way, only the warning is filtered + if (isUnexpectedSelfLink(expected, selfLink)) { + console.warn(`The response for '${urlWithoutEmbedParams}' has the self link '${selfLink}'. These don't match. This could mean there's an issue with the REST endpoint`); + } response.payload._links = Object.assign({}, response.payload._links, { self: { href: urlWithoutEmbedParams, From d28ff3d39b1c96c4849169dc53c287d99d64db04 Mon Sep 17 00:00:00 2001 From: milanmajchrak Date: Fri, 7 Aug 2026 12:24:30 +0200 Subject: [PATCH 2/2] Ask the REST API for page sizes it can actually serve Spring Data REST caps a page at spring.data.rest.max-page-size, which DSpace leaves at its default of 1000 - it is set nowhere in DSpace/DSpace nor in our fork. So asking for 9999 or 10000 returns the same 1000 rows it would have returned anyway, while making the request claim something the API never honours, and leaving the self link legitimately different from the requested url. Adds MAX_PAGE_SIZE and uses it wherever a caller needs "everything": bundle-data.service.ts 9999 browse.service.ts 9999 relationship-type-data.service.ts 9999 registry.service.ts 10000 item-bitstreams.service.ts 9999 filtered-items.component.ts 4 x 10000 Same rows returned in every case; only the number in the request changes. browse-by-geospatial-data.component.ts is left alone on purpose: its 99999 is a Discovery facet limit, not a Spring Data REST page size, so it is not capped at 1000 and lowering it would drop points off the map. Co-Authored-By: Claude Opus 5 (1M context) --- .../filtered-items/filtered-items.component.ts | 9 +++++---- src/app/core/browse/browse.service.ts | 3 ++- src/app/core/data/bundle-data.service.ts | 7 +++++-- src/app/core/data/find-list-options.model.ts | 10 ++++++++++ src/app/core/data/relationship-type-data.service.ts | 3 ++- src/app/core/registry/registry.service.ts | 7 +++++-- .../item-bitstreams/item-bitstreams.service.ts | 3 ++- 7 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/app/admin/admin-reports/filtered-items/filtered-items.component.ts b/src/app/admin/admin-reports/filtered-items/filtered-items.component.ts index c43522b8b1b..8a48f87f779 100644 --- a/src/app/admin/admin-reports/filtered-items/filtered-items.component.ts +++ b/src/app/admin/admin-reports/filtered-items/filtered-items.component.ts @@ -42,6 +42,7 @@ import { isEmpty } from 'src/app/shared/empty.util'; import { ThemedLoadingComponent } from 'src/app/shared/loading/themed-loading.component'; import { environment } from 'src/environments/environment'; +import { MAX_PAGE_SIZE } from '../../../core/data/find-list-options.model'; import { BtnDisabledDirective } from '../../../shared/btn-disabled.directive'; import { FiltersComponent } from '../filters-section/filters-section.component'; import { FilteredItemsExportCsvComponent } from './filtered-items-export-csv/filtered-items-export-csv.component'; @@ -135,7 +136,7 @@ export class FilteredItemsComponent implements OnInit { const wholeRepo$ = this.translateService.stream('admin.reports.items.wholeRepo'); this.collections.push(OptionVO.collectionLoc('', wholeRepo$)); - this.communityService.findAll({ elementsPerPage: 10000, currentPage: 1 }).pipe( + this.communityService.findAll({ elementsPerPage: MAX_PAGE_SIZE, currentPage: 1 }).pipe( getFirstSucceededRemoteListPayload(), ).subscribe( (communitiesRest: Community[]) => { @@ -143,7 +144,7 @@ export class FilteredItemsComponent implements OnInit { const commVO = OptionVO.collection(community.uuid, community.name, true); this.collections.push(commVO); - this.collectionService.findByParent(community.uuid, { elementsPerPage: 10000, currentPage: 1 }).pipe( + this.collectionService.findByParent(community.uuid, { elementsPerPage: MAX_PAGE_SIZE, currentPage: 1 }).pipe( getFirstSucceededRemoteListPayload(), ).subscribe( (collectionsRest: Collection[]) => { @@ -207,12 +208,12 @@ export class FilteredItemsComponent implements OnInit { this.metadataFieldsWithAny = []; const anyField$ = this.translateService.stream('admin.reports.items.anyField'); this.metadataFieldsWithAny.push(OptionVO.itemLoc('*', anyField$)); - this.metadataSchemaService.findAll({ elementsPerPage: 10000, currentPage: 1 }).pipe( + this.metadataSchemaService.findAll({ elementsPerPage: MAX_PAGE_SIZE, currentPage: 1 }).pipe( getFirstSucceededRemoteListPayload(), ).subscribe( (schemasRest: MetadataSchema[]) => { schemasRest.forEach(schema => { - this.metadataFieldService.findBySchema(schema, { elementsPerPage: 10000, currentPage: 1 }).pipe( + this.metadataFieldService.findBySchema(schema, { elementsPerPage: MAX_PAGE_SIZE, currentPage: 1 }).pipe( getFirstSucceededRemoteListPayload(), ).subscribe( (fieldsRest: MetadataField[]) => { diff --git a/src/app/core/browse/browse.service.ts b/src/app/core/browse/browse.service.ts index 5fe06a700e5..913cec5f1fe 100644 --- a/src/app/core/browse/browse.service.ts +++ b/src/app/core/browse/browse.service.ts @@ -18,6 +18,7 @@ import { FollowLinkConfig, } from '../../shared/utils/follow-link-config.model'; import { SortDirection } from '../cache/models/sort-options.model'; +import { MAX_PAGE_SIZE } from '../data/find-list-options.model'; import { HrefOnlyDataService } from '../data/href-only-data.service'; import { PaginatedList } from '../data/paginated-list.model'; import { RemoteData } from '../data/remote-data'; @@ -81,7 +82,7 @@ export class BrowseService { */ getBrowseDefinitions(): Observable>> { // TODO properly support pagination - return this.browseDefinitionDataService.findAll({ elementsPerPage: 9999 }).pipe( + return this.browseDefinitionDataService.findAll({ elementsPerPage: MAX_PAGE_SIZE }).pipe( getFirstSucceededRemoteData(), ); } diff --git a/src/app/core/data/bundle-data.service.ts b/src/app/core/data/bundle-data.service.ts index 79f877fadd7..d5a801b7b85 100644 --- a/src/app/core/data/bundle-data.service.ts +++ b/src/app/core/data/bundle-data.service.ts @@ -22,7 +22,10 @@ import { PatchDataImpl, } from './base/patch-data'; import { DSOChangeAnalyzer } from './dso-change-analyzer.service'; -import { FindListOptions } from './find-list-options.model'; +import { + FindListOptions, + MAX_PAGE_SIZE, +} from './find-list-options.model'; import { PaginatedList } from './paginated-list.model'; import { RemoteData } from './remote-data'; import { GetRequest } from './request.models'; @@ -84,7 +87,7 @@ export class BundleDataService extends IdentifiableDataService implement findByItemAndName(item: Item, bundleName: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, options?: FindListOptions, ...linksToFollow: FollowLinkConfig[]): Observable> { //Since we filter by bundleName where the pagination options are not indicated we need to load all the possible bundles. // This is a workaround, in substitution of the previously recursive call with expand - const paginationOptions = options ?? { elementsPerPage: 9999 }; + const paginationOptions = options ?? { elementsPerPage: MAX_PAGE_SIZE }; return this.findAllByItem(item, paginationOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow).pipe( map((rd: RemoteData>) => { if (hasValue(rd.payload) && hasValue(rd.payload.page)) { diff --git a/src/app/core/data/find-list-options.model.ts b/src/app/core/data/find-list-options.model.ts index 78fe26fcab9..97e708061ae 100644 --- a/src/app/core/data/find-list-options.model.ts +++ b/src/app/core/data/find-list-options.model.ts @@ -1,6 +1,16 @@ import { RequestParam } from '../cache/models/request-param.model'; import { SortOptions } from '../cache/models/sort-options.model'; +/** + * The largest page the REST API will serve. Asking for more is not an error: the API silently + * reduces the size to this maximum, so a bigger number returns the exact same page while making the + * request claim something the API never honours. + * + * The limit is Spring Data REST's `spring.data.rest.max-page-size`, which DSpace leaves at its + * default. Use this instead of an arbitrary large number when a caller needs "everything". + */ +export const MAX_PAGE_SIZE = 1000; + /** * The options for a find list request */ diff --git a/src/app/core/data/relationship-type-data.service.ts b/src/app/core/data/relationship-type-data.service.ts index 43c018bbce2..16447ef1e69 100644 --- a/src/app/core/data/relationship-type-data.service.ts +++ b/src/app/core/data/relationship-type-data.service.ts @@ -29,6 +29,7 @@ import { import { BaseDataService } from './base/base-data.service'; import { FindAllDataImpl } from './base/find-all-data'; import { SearchDataImpl } from './base/search-data'; +import { MAX_PAGE_SIZE } from './find-list-options.model'; import { PaginatedList } from './paginated-list.model'; import { RemoteData } from './remote-data'; import { RequestService } from './request.service'; @@ -75,7 +76,7 @@ export class RelationshipTypeDataService extends BaseDataService { // Retrieve all relationship types from the server in a single page - return this.findAllData.findAll({ currentPage: 1, elementsPerPage: 9999 }, true, true, followLink('leftType'), followLink('rightType')) + return this.findAllData.findAll({ currentPage: 1, elementsPerPage: MAX_PAGE_SIZE }, true, true, followLink('leftType'), followLink('rightType')) .pipe( getFirstSucceededRemoteData(), // Emit each type in the page array separately diff --git a/src/app/core/registry/registry.service.ts b/src/app/core/registry/registry.service.ts index d4a272570bf..597948db92b 100644 --- a/src/app/core/registry/registry.service.ts +++ b/src/app/core/registry/registry.service.ts @@ -37,7 +37,10 @@ import { import { NotificationsService } from '../../shared/notifications/notifications.service'; import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; import { RequestParam } from '../cache/models/request-param.model'; -import { FindListOptions } from '../data/find-list-options.model'; +import { + FindListOptions, + MAX_PAGE_SIZE, +} from '../data/find-list-options.model'; import { MetadataFieldDataService } from '../data/metadata-field-data.service'; import { MetadataSchemaDataService } from '../data/metadata-schema-data.service'; import { PaginatedList } from '../data/paginated-list.model'; @@ -94,7 +97,7 @@ export class RegistryService { public getMetadataSchemaByPrefix(prefix: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig[]): Observable> { // Temporary options to get ALL metadataschemas until there's a rest api endpoint for fetching a specific schema const options: FindListOptions = Object.assign(new FindListOptions(), { - elementsPerPage: 10000, + elementsPerPage: MAX_PAGE_SIZE, }); return this.getMetadataSchemas(options).pipe( getFirstSucceededRemoteDataPayload(), diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.ts index 2221e037fb8..5cb25c6537f 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.service.ts @@ -17,6 +17,7 @@ import { getBitstreamDownloadRoute } from '../../../app-routing-paths'; import { DSONameService } from '../../../core/breadcrumbs/dso-name.service'; import { BitstreamDataService } from '../../../core/data/bitstream-data.service'; import { BundleDataService } from '../../../core/data/bundle-data.service'; +import { MAX_PAGE_SIZE } from '../../../core/data/find-list-options.model'; import { FieldChangeType } from '../../../core/data/object-updates/field-change-type.model'; import { FieldUpdate } from '../../../core/data/object-updates/field-update.model'; import { FieldUpdates } from '../../../core/data/object-updates/field-updates.model'; @@ -360,7 +361,7 @@ export class ItemBitstreamsService { return Object.assign(new PaginationComponentOptions(), { id: 'bundles-pagination-options', currentPage: 1, - pageSize: 9999, + pageSize: MAX_PAGE_SIZE, }); }