From b44176b3ea71889215ce15b2cf809ddf265f0e84 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Mon, 19 Feb 2024 08:49:12 +0100 Subject: [PATCH 1/2] 112185: Add support to use next page to atmireshowmore --- src/app-atmire/atmire-app.module.ts | 4 +- .../show-more/atmire-show-more.component.ts | 28 ++++-- .../abstract-listable-object-source.ts | 20 +++- .../atmire-href-listable-object-source.ts | 4 +- .../atmire-search-listable-object-source.ts | 15 ++- .../listable-object-source-factory.service.ts | 8 +- .../shared/search/atmire-search.service.ts | 92 +++++++++++++++++++ src/app/core/core.module.ts | 4 +- src/app/core/shared/search/search.service.ts | 24 ++--- 9 files changed, 166 insertions(+), 33 deletions(-) create mode 100644 src/app-atmire/core/shared/search/atmire-search.service.ts diff --git a/src/app-atmire/atmire-app.module.ts b/src/app-atmire/atmire-app.module.ts index 02d1c1d2f43..1104bdbf759 100644 --- a/src/app-atmire/atmire-app.module.ts +++ b/src/app-atmire/atmire-app.module.ts @@ -18,6 +18,7 @@ import { SectionHeaderMenuItemComponent } from './shared/menu/menu-item/section- import { AtmireMenuService } from './shared/menu/atmire-menu.service'; import { HtmlWithRouterLinksDirective } from './shared/utils/html-with-router-links.directive'; import { AtmirePlaceholderComponent } from './shared/placeholder/atmire-placeholder.component'; +import { AtmireSearchService } from './core/shared/search/atmire-search.service'; const DECLARATIONS = [ SectionHeaderMenuItemComponent, @@ -38,7 +39,8 @@ const PROVIDERS = [ provide: DSOEditMenuResolver, useClass: AtmireDsoEditMenuResolver, }, - AtmireMenuService + AtmireMenuService, + AtmireSearchService, ]; @NgModule({ diff --git a/src/app-atmire/atmire-object-collection/show-more/atmire-show-more.component.ts b/src/app-atmire/atmire-object-collection/show-more/atmire-show-more.component.ts index f517fcdc1a8..2c6d2b84097 100644 --- a/src/app-atmire/atmire-object-collection/show-more/atmire-show-more.component.ts +++ b/src/app-atmire/atmire-object-collection/show-more/atmire-show-more.component.ts @@ -49,6 +49,7 @@ export class AtmireShowMoreComponent extends isLastPage: boolean; currentPage: number; + next: string; results$: BehaviorSubject; constructor( @@ -62,6 +63,7 @@ export class AtmireShowMoreComponent extends * This method will retrieve the next page of search results from the external SearchService#search call. * It'll retrieve the currentPage from the class variables and it'll add the next page to the already existing one. * If the currentPage variable is undefined, we'll set it to 1 and retrieve the first page. + * If the previous RemoteData contained a "next" link, it'll use this link instead to directly retrieve the next page * * Adapted from FileSectionComponent#getNextPage() */ @@ -80,11 +82,18 @@ export class AtmireShowMoreComponent extends this.resultSub.unsubscribe(); } - this.resultSub = - this.source.getList( - {...this.paginationOptions, currentPage: this.currentPage}, + let list$; + if (hasValue(this.next)) { + list$ = this.source.getListByHref(this.next); + } else { + list$ = this.source.getList( + { ...this.paginationOptions, currentPage: this.currentPage }, this.sortOptions, - ).pipe( + ); + } + + this.resultSub = + list$.pipe( getFirstCompletedRemoteData() ).subscribe((resultRD: RemoteData>) => { if (resultRD.errorMessage) { @@ -112,8 +121,14 @@ export class AtmireShowMoreComponent extends this.setError('atmire.object-collection.error.rendering-some'); } - this.isLastPage = this.currentPage === this.paginationOptions.maxSize - || this.currentPage === resultRD.payload.totalPages; + this.next = resultRD.payload.next; + + if (hasValue(this.next)) { + this.isLastPage = false; + } else { + this.isLastPage = this.currentPage >= this.paginationOptions.maxSize + || this.currentPage >= resultRD.payload.totalPages; + } } this.isLoading$.next(false); @@ -122,6 +137,7 @@ export class AtmireShowMoreComponent extends collapse(): void { this.currentPage = undefined; + this.next = undefined; this.showMore(); } diff --git a/src/app-atmire/atmire-object-collection/sources/abstract-listable-object-source.ts b/src/app-atmire/atmire-object-collection/sources/abstract-listable-object-source.ts index 1460df37435..2a1ef257ed6 100644 --- a/src/app-atmire/atmire-object-collection/sources/abstract-listable-object-source.ts +++ b/src/app-atmire/atmire-object-collection/sources/abstract-listable-object-source.ts @@ -16,6 +16,7 @@ import { PaginatedList } from '../../../app/core/data/paginated-list.model'; import { ListableObjectSourceType } from '../listable-object-sources'; import { CacheableObject } from '../../../app/core/cache/cacheable-object.model'; import { ListableCacheableObject } from '../base/listable-cachable-object.model'; +import { HrefOnlyDataService } from '../../../app/core/data/href-only-data.service'; export interface AbstractSourceKwargs { @@ -52,7 +53,8 @@ export abstract class AbstractListableObjectSource[] = []; - protected constructor(model?: AbstractListableObjectSourceModel) { + protected constructor(protected hrefOnlyDataService: HrefOnlyDataService, + model?: AbstractListableObjectSourceModel) { this.useCachedVersionIfAvailable = model.useCachedVersionIfAvailable; this.reRequestOnStale = model.reRequestOnStale; this.linksToFollow = model.linksToFollow; @@ -72,4 +74,20 @@ export abstract class AbstractListableObjectSource, sort: Partial, ): Observable>>; + + /** + * Retrieve a list of objects from a link + * The link is expected to contain all necessary parameters already + * An example usage of this is using the "next" link on paginated lists to retrieve the next page directly + * @param href Full link to the list of objects + */ + public getListByHref(href: string): Observable>> { + return this.hrefOnlyDataService.findListByHref( + href, + undefined, + this.useCachedVersionIfAvailable, + this.reRequestOnStale, + ...this.linksToFollow + ); + } } diff --git a/src/app-atmire/atmire-object-collection/sources/atmire-href-listable-object-source.ts b/src/app-atmire/atmire-object-collection/sources/atmire-href-listable-object-source.ts index 6757b869b3a..dcd8e2fcd5b 100644 --- a/src/app-atmire/atmire-object-collection/sources/atmire-href-listable-object-source.ts +++ b/src/app-atmire/atmire-object-collection/sources/atmire-href-listable-object-source.ts @@ -26,10 +26,10 @@ export class AtmireHrefListableObjectSource, - private hrefOnlyDataService: HrefOnlyDataService, ) { - super(model); + super(hrefOnlyDataService, model); this.href = model.href; } diff --git a/src/app-atmire/atmire-object-collection/sources/atmire-search-listable-object-source.ts b/src/app-atmire/atmire-object-collection/sources/atmire-search-listable-object-source.ts index f437e49f9e0..bc6f2c13555 100644 --- a/src/app-atmire/atmire-object-collection/sources/atmire-search-listable-object-source.ts +++ b/src/app-atmire/atmire-object-collection/sources/atmire-search-listable-object-source.ts @@ -7,19 +7,19 @@ */ import { PaginationComponentOptions } from '../../../app/shared/pagination/pagination-component-options.model'; import { SortOptions } from '../../../app/core/cache/models/sort-options.model'; -import { Observable } from 'rxjs/internal/Observable'; import { RemoteData } from '../../../app/core/data/remote-data'; import { PaginatedList } from '../../../app/core/data/paginated-list.model'; import { PaginatedSearchOptions } from '../../../app/shared/search/models/paginated-search-options.model'; -import { SearchService } from '../../../app/core/shared/search/search.service'; import { SearchOptions } from '../../../app/shared/search/models/search-options.model'; import { AtmireSearchListableObjectSourceModel } from '../listable-object-sources'; import { AbstractListableObjectSource, AbstractListableObjectSourceModel } from './abstract-listable-object-source'; import { map } from 'rxjs/operators'; import { DSpaceObject } from '../../../app/core/shared/dspace-object.model'; import { ListableObject } from '../../../app/shared/object-collection/shared/listable-object.model'; -import { BehaviorSubject } from 'rxjs'; +import { BehaviorSubject, Observable, of as observableOf } from 'rxjs'; import { environment } from '../../../environments/environment'; +import { AtmireSearchService } from '../../core/shared/search/atmire-search.service'; +import { HrefOnlyDataService } from '../../../app/core/data/href-only-data.service'; /** * Retrieves objects via {@link SearchService}. @@ -31,10 +31,11 @@ export class AtmireSearchListableObjectSource extends Ab private readonly href$: BehaviorSubject = new BehaviorSubject('search'); constructor( + protected hrefOnlyDataService: HrefOnlyDataService, model: AtmireSearchListableObjectSourceModel, - private searchService: SearchService, + protected searchService: AtmireSearchService, ) { - super(model as AbstractListableObjectSourceModel); + super(hrefOnlyDataService, model as AbstractListableObjectSourceModel); this.searchOptions = model.searchOptions; this.displayAsSearchResults = model.displayAsSearchResults; @@ -72,6 +73,10 @@ export class AtmireSearchListableObjectSource extends Ab ); } + getListByHref(href: string): Observable>> { + return this.searchService.searchByHref(observableOf(href)); + } + public get params() { return { href: this.href$.getValue(), diff --git a/src/app-atmire/atmire-object-collection/sources/listable-object-source-factory.service.ts b/src/app-atmire/atmire-object-collection/sources/listable-object-source-factory.service.ts index 7eccf7ae0af..846f9836546 100644 --- a/src/app-atmire/atmire-object-collection/sources/listable-object-source-factory.service.ts +++ b/src/app-atmire/atmire-object-collection/sources/listable-object-source-factory.service.ts @@ -6,8 +6,8 @@ * https://www.atmire.com/software-license/ */ import { Injectable } from '@angular/core'; +import { AtmireSearchService } from '../../core/shared/search/atmire-search.service'; import { HrefOnlyDataService } from '../../../app/core/data/href-only-data.service'; -import { SearchService } from '../../../app/core/shared/search/search.service'; import { ListableObjectSource, ListableObjectSourceModel, ListableObjectSourceType } from '../listable-object-sources'; import { AtmireHrefListableObjectSource } from './atmire-href-listable-object-source'; import { AtmireSearchListableObjectSource } from './atmire-search-listable-object-source'; @@ -20,7 +20,7 @@ import { AtmireSearchListableObjectSource } from './atmire-search-listable-objec export class ListableObjectSourceFactoryService { constructor( private hrefOnlyDataService: HrefOnlyDataService, - private searchService: SearchService, + private searchService: AtmireSearchService, ) { } @@ -31,10 +31,10 @@ export class ListableObjectSourceFactoryService { getSource(model: ListableObjectSourceModel): ListableObjectSource { switch (model.source) { case ListableObjectSourceType.HREF: { - return new AtmireHrefListableObjectSource(model, this.hrefOnlyDataService); + return new AtmireHrefListableObjectSource(this.hrefOnlyDataService, model); } case ListableObjectSourceType.SEARCH: { - return new AtmireSearchListableObjectSource(model, this.searchService); + return new AtmireSearchListableObjectSource(this.hrefOnlyDataService, model, this.searchService); } } } diff --git a/src/app-atmire/core/shared/search/atmire-search.service.ts b/src/app-atmire/core/shared/search/atmire-search.service.ts new file mode 100644 index 00000000000..cca992a8146 --- /dev/null +++ b/src/app-atmire/core/shared/search/atmire-search.service.ts @@ -0,0 +1,92 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE_ATMIRE and NOTICE_ATMIRE files at the root of the source + * tree and available online at + * + * https://www.atmire.com/software-license/ + */ +import { SearchService } from '../../../../app/core/shared/search/search.service'; +import { Injectable } from '@angular/core'; +import { RouteService } from '../../../../app/core/services/route.service'; +import { RequestService } from '../../../../app/core/data/request.service'; +import { RemoteDataBuildService } from '../../../../app/core/cache/builders/remote-data-build.service'; +import { LinkService } from '../../../../app/core/cache/builders/link.service'; +import { HALEndpointService } from '../../../../app/core/shared/hal-endpoint.service'; +import { CommunityDataService } from '../../../../app/core/data/community-data.service'; +import { DSpaceObjectDataService } from '../../../../app/core/data/dspace-object-data.service'; +import { PaginationService } from '../../../../app/core/pagination/pagination.service'; +import { SearchConfigurationService } from '../../../../app/core/shared/search/search-configuration.service'; +import { PaginatedSearchOptions } from '../../../../app/shared/search/models/paginated-search-options.model'; +import { Observable } from 'rxjs'; +import { map, switchMap, take } from 'rxjs/operators'; +import { hasValue, isNotEmpty } from '../../../../app/shared/empty.util'; +import { Angulartics2 } from 'angulartics2'; +import { DSpaceObject } from '../../../../app/core/shared/dspace-object.model'; +import { FollowLinkConfig } from '../../../../app/shared/utils/follow-link-config.model'; +import { RemoteData } from '../../../../app/core/data/remote-data'; +import { SearchObjects } from '../../../../app/shared/search/models/search-objects.model'; +import { URLCombiner } from '../../../../app/core/url-combiner/url-combiner'; +import { GenericConstructor } from '../../../../app/core/shared/generic-constructor'; +import { ResponseParsingService } from '../../../../app/core/data/parsing.service'; + +@Injectable() +export class AtmireSearchService extends SearchService { + constructor( + protected routeService: RouteService, + protected requestService: RequestService, + protected rdb: RemoteDataBuildService, + protected linkService: LinkService, + protected halService: HALEndpointService, + protected communityService: CommunityDataService, + protected dspaceObjectService: DSpaceObjectDataService, + protected paginationService: PaginationService, + protected searchConfigurationService: SearchConfigurationService, + protected angulartics2: Angulartics2, + ) { + super(routeService, requestService, rdb, halService, dspaceObjectService, paginationService, searchConfigurationService, angulartics2); + } + + + /** + * Overridden method to split up into searchByHref() to allow for searches with just a raw href + */ + search(searchOptions?: PaginatedSearchOptions, responseMsToLive?: number, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig[]): Observable>> { + return this.searchByHref(this.getEndpoint(searchOptions), responseMsToLive, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow); + } + + /** + * Search by href + */ + searchByHref(href$: Observable, responseMsToLive?: number, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig[]): Observable>> { + href$.pipe( + take(1), + map((href: string) => { + const args = this.searchDataService.addEmbedParams(href, [], ...linksToFollow); + if (isNotEmpty(args)) { + return new URLCombiner(href, `?${args.join('&')}`).toString(); + } else { + return href; + } + }) + ).subscribe((url: string) => { + const request = new this.request(this.requestService.generateRequestId(), url); + + const getResponseParserFn: () => GenericConstructor = () => { + return this.parser; + }; + + Object.assign(request, { + responseMsToLive: hasValue(responseMsToLive) ? responseMsToLive : request.responseMsToLive, + getResponseParser: getResponseParserFn, + }); + + this.requestService.send(request, useCachedVersionIfAvailable); + }); + + const sqr$ = href$.pipe( + switchMap((href: string) => this.rdb.buildFromHref>(href)) + ); + + return this.directlyAttachIndexableObjects(sqr$, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow); + } +} diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index c9cfbf5df29..7da5e0bd5ba 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -184,6 +184,7 @@ import { NonHierarchicalBrowseDefinition } from './shared/non-hierarchical-brows import { BulkAccessConditionOptions } from './config/models/bulk-access-condition-options.model'; import { AtmireObjectUpdatesService } from '../../app-atmire/core/data/object-updates/atmire-object-updates.service'; import { AtmireMenuService } from '../../app-atmire/shared/menu/atmire-menu.service'; +import { AtmireSearchService } from '../../app-atmire/core/shared/search/atmire-search.service'; /** * When not in production, endpoint responses can be mocked for testing purposes @@ -257,7 +258,7 @@ const PROVIDERS = [ ObjectUpdatesService, { provide: MenuService, useClass: AtmireMenuService }, { provide: ObjectUpdatesService, useClass: AtmireObjectUpdatesService }, - SearchService, + { provide: SearchService, useClass: AtmireSearchService }, RelationshipDataService, MyDSpaceGuard, RoleService, @@ -268,7 +269,6 @@ const PROVIDERS = [ EntityTypeDataService, ContentSourceResponseParsingService, ItemTemplateDataService, - SearchService, SidebarService, SearchFilterService, SearchFilterService, diff --git a/src/app/core/shared/search/search.service.ts b/src/app/core/shared/search/search.service.ts index a88a8b0d16b..bd89cb6e728 100644 --- a/src/app/core/shared/search/search.service.ts +++ b/src/app/core/shared/search/search.service.ts @@ -65,37 +65,37 @@ export class SearchService implements OnDestroy { /** * Endpoint link path for retrieving general search results */ - private searchLinkPath = 'discover/search/objects'; + protected searchLinkPath = 'discover/search/objects'; /** * The ResponseParsingService constructor name */ - private parser: GenericConstructor = SearchResponseParsingService; + protected parser: GenericConstructor = SearchResponseParsingService; /** * The RestRequest constructor name */ - private request: GenericConstructor = GetRequest; + protected request: GenericConstructor = GetRequest; /** * Subscription to unsubscribe from */ - private sub; + protected sub; /** * Instance of SearchDataService to forward data service methods to */ - private searchDataService: SearchDataService; + protected searchDataService: SearchDataService; constructor( - private routeService: RouteService, + protected routeService: RouteService, protected requestService: RequestService, - private rdb: RemoteDataBuildService, - private halService: HALEndpointService, - private dspaceObjectService: DSpaceObjectDataService, - private paginationService: PaginationService, - private searchConfigurationService: SearchConfigurationService, - private angulartics2: Angulartics2, + protected rdb: RemoteDataBuildService, + protected halService: HALEndpointService, + protected dspaceObjectService: DSpaceObjectDataService, + protected paginationService: PaginationService, + protected searchConfigurationService: SearchConfigurationService, + protected angulartics2: Angulartics2, ) { this.searchDataService = new SearchDataService(); } From b65345d88f08f7ea0179820f2757d37642d6331c Mon Sep 17 00:00:00 2001 From: lotte Date: Mon, 26 Feb 2024 17:54:18 +0100 Subject: [PATCH 2/2] 112507: Solved problem with missing embeds --- .../sources/atmire-search-listable-object-source.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/app-atmire/atmire-object-collection/sources/atmire-search-listable-object-source.ts b/src/app-atmire/atmire-object-collection/sources/atmire-search-listable-object-source.ts index bc6f2c13555..d48c6340861 100644 --- a/src/app-atmire/atmire-object-collection/sources/atmire-search-listable-object-source.ts +++ b/src/app-atmire/atmire-object-collection/sources/atmire-search-listable-object-source.ts @@ -74,7 +74,11 @@ export class AtmireSearchListableObjectSource extends Ab } getListByHref(href: string): Observable>> { - return this.searchService.searchByHref(observableOf(href)); + return this.searchService.searchByHref(observableOf(href), + undefined, + this.useCachedVersionIfAvailable, + this.reRequestOnStale, + ...this.linksToFollow); } public get params() {