diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index f2af44401a..1a0d181ca1 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -46,6 +46,9 @@ import { RestRequestMethod } from './rest-request-method'; import { CreateData, CreateDataImpl } from './base/create-data'; import { RequestParam } from '../cache/models/request-param.model'; import { dataService } from './base/data-service.decorator'; +import { Duplicate } from '../../shared/object-list/duplicate-data/duplicate.model'; +import { SearchDataImpl } from './base/search-data'; +import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; /** * An abstract service for CRUD operations on Items @@ -56,6 +59,7 @@ export abstract class BaseItemDataService extends IdentifiableDataService private createData: CreateData; private patchData: PatchData; private deleteData: DeleteData; + private searchData: SearchDataImpl; protected constructor( protected linkPath, @@ -74,6 +78,7 @@ export abstract class BaseItemDataService extends IdentifiableDataService this.createData = new CreateDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, notificationsService, this.responseMsToLive); this.patchData = new PatchDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, comparator, this.responseMsToLive, this.constructIdEndpoint); this.deleteData = new DeleteDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, notificationsService, this.responseMsToLive, this.constructIdEndpoint); + this.searchData = new SearchDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive); } /** @@ -242,24 +247,18 @@ export abstract class BaseItemDataService extends IdentifiableDataService ); } - public getDuplicatesEndpoint(itemId: string): Observable { - return this.halService.getEndpoint(this.linkPath).pipe( - switchMap((url: string) => this.halService.getEndpoint('duplicates', `${url}/${itemId}`)) - ); - } - - public getDuplicates(itemId: string, searchOptions?: PaginatedSearchOptions): Observable>> { - const hrefObs = this.getDuplicatesEndpoint(itemId).pipe( - map((href) => searchOptions ? searchOptions.toRestUrl(href) : href) - ); - hrefObs.pipe( - take(1) - ).subscribe((href) => { - const request = new GetRequest(this.requestService.generateRequestId(), href); - this.requestService.send(request); - }); - - return this.rdbService.buildList(hrefObs); + public findDuplicates(uuid: string, options?: FindListOptions, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig[]): Observable>> { + const searchParams = [new RequestParam('uuid', uuid)]; + let findListOptions = new FindListOptions(); + if (options) { + findListOptions = Object.assign(new FindListOptions(), options); + } + if (findListOptions.searchParams) { + findListOptions.searchParams = [...findListOptions.searchParams, ...searchParams]; + } else { + findListOptions.searchParams = searchParams; + } + return this.searchData.searchBy('findDuplicates', findListOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow); } /** diff --git a/src/app/core/shared/item.model.ts b/src/app/core/shared/item.model.ts index 5ef670c42b..20fc275ee2 100644 --- a/src/app/core/shared/item.model.ts +++ b/src/app/core/shared/item.model.ts @@ -26,7 +26,6 @@ import { AccessStatusObject } from 'src/app/shared/object-collection/shared/badg import { HandleObject } from './handle-object.model'; import { IDENTIFIERS } from '../../shared/object-list/identifier-data/identifier-data.resource-type'; import { IdentifierData } from '../../shared/object-list/identifier-data/identifier-data.model'; -import { Duplicate } from '../../shared/object-list/duplicate-data/duplicate.model'; /** * Class representing a DSpace Item @@ -80,7 +79,6 @@ export class Item extends DSpaceObject implements ChildHALResource, HandleObject thumbnail: HALLink; accessStatus: HALLink; identifiers: HALLink; - duplicates: HALLink; self: HALLink; }; @@ -133,9 +131,6 @@ export class Item extends DSpaceObject implements ChildHALResource, HandleObject @link(IDENTIFIERS, false, 'identifiers') identifiers?: Observable>; - @link(ITEM, true, 'duplicates') - duplicates?: Observable>>; - /** * Method that returns as which type of object this object should be rendered */ diff --git a/src/app/shared/object-list/duplicate-data/duplicate.model.ts b/src/app/shared/object-list/duplicate-data/duplicate.model.ts index c5cd1705e1..cbcff155e1 100644 --- a/src/app/shared/object-list/duplicate-data/duplicate.model.ts +++ b/src/app/shared/object-list/duplicate-data/duplicate.model.ts @@ -1,7 +1,14 @@ -import { autoserialize } from 'cerialize'; +import {autoserialize, deserialize} from 'cerialize'; import { MetadataMap } from '../../../core/shared/metadata.models'; +import { HALLink} from '../../../core/shared/hal-link.model'; +import { CacheableObject } from '../../../core/cache/cacheable-object.model'; +import { DUPLICATE } from './duplicate.resource-type'; +import { ResourceType } from '../../../core/shared/resource-type'; + +export class Duplicate implements CacheableObject { + + static type = DUPLICATE; -export class Duplicate { /** * The item title */ @@ -23,5 +30,13 @@ export class Duplicate { metadata: MetadataMap; @autoserialize - type: string; + type: ResourceType; + + /** + * The {@link HALLink}s for this Bitstream + */ + @deserialize + _links: { + self: HALLink; + }; } diff --git a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.spec.ts b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.spec.ts index c1778c1792..6c4616ea2e 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.spec.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.spec.ts @@ -15,7 +15,7 @@ import { Item } from '../../../../core/shared/item.model'; import { ClaimedSearchResultListElementComponent } from './claimed-search-result-list-element.component'; import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model'; import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; -import { createSuccessfulRemoteDataObject } from '../../../remote-data.utils'; +import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../remote-data.utils'; import { ClaimedTaskSearchResult } from '../../../object-collection/shared/claimed-task-search-result.model'; import { TruncatableService } from '../../../truncatable/truncatable.service'; import { VarDirective } from '../../../utils/var.directive'; @@ -28,6 +28,8 @@ import { APP_CONFIG } from '../../../../../config/app-config.interface'; import { environment } from '../../../../../environments/environment'; import { ObjectCacheService } from '../../../../core/cache/object-cache.service'; import { Context } from '../../../../core/shared/context.model'; +import { createPaginatedList } from '../../../testing/utils.test'; +import { ItemDataService } from '../../../../core/data/item-data.service'; let component: ClaimedSearchResultListElementComponent; let fixture: ComponentFixture; @@ -35,6 +37,12 @@ let fixture: ComponentFixture; const mockResultObject: ClaimedTaskSearchResult = new ClaimedTaskSearchResult(); mockResultObject.hitHighlights = {}; +const emptyList = createSuccessfulRemoteDataObject(createPaginatedList([])); +const itemDataServiceStub = { + findDuplicates: () => createSuccessfulRemoteDataObject$({}), + findListByHref: () => observableOf(emptyList), +}; + const item = Object.assign(new Item(), { bundles: observableOf({}), metadata: { @@ -83,7 +91,8 @@ describe('ClaimedSearchResultListElementComponent', () => { { provide: LinkService, useValue: linkService }, { provide: DSONameService, useClass: DSONameServiceMock }, { provide: APP_CONFIG, useValue: environment }, - { provide: ObjectCacheService, useValue: objectCacheServiceMock } + { provide: ObjectCacheService, useValue: objectCacheServiceMock }, + { provide: ItemDataService, useValue: itemDataServiceStub }, ], schemas: [NO_ERRORS_SCHEMA] }).overrideComponent(ClaimedSearchResultListElementComponent, { diff --git a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.ts b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.ts index ee675405a6..77d83ffe38 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.ts @@ -23,6 +23,7 @@ import { isNotEmpty, hasValue } from '../../../empty.util'; import { Context } from '../../../../core/shared/context.model'; import { Duplicate } from '../../duplicate-data/duplicate.model'; import { PaginatedList } from '../../../../core/data/paginated-list.model'; +import { ItemDataService } from '../../../../core/data/item-data.service'; @Component({ selector: 'ds-claimed-search-result-list-element', @@ -67,6 +68,7 @@ export class ClaimedSearchResultListElementComponent extends SearchResultListEle protected truncatableService: TruncatableService, public dsoNameService: DSONameService, protected objectCache: ObjectCacheService, + protected itemDataService: ItemDataService, @Inject(APP_CONFIG) protected appConfig: AppConfig ) { super(truncatableService, dsoNameService, appConfig); @@ -97,7 +99,7 @@ export class ClaimedSearchResultListElementComponent extends SearchResultListEle tap((itemRD: RemoteData) => { if (isNotEmpty(itemRD) && itemRD.hasSucceeded) { this.item$.next(itemRD.payload); - this.duplicates$ = itemRD.payload.duplicates.pipe( + this.duplicates$ = this.itemDataService.findDuplicates(itemRD.payload.uuid).pipe( getFirstCompletedRemoteData(), map((remoteData: RemoteData>) => { if (remoteData.hasSucceeded) { diff --git a/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.spec.ts b/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.spec.ts index 9aad4a8b7b..02e375b37d 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.spec.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.spec.ts @@ -15,7 +15,7 @@ import { Item } from '../../../../core/shared/item.model'; import { PoolSearchResultListElementComponent } from './pool-search-result-list-element.component'; import { PoolTask } from '../../../../core/tasks/models/pool-task-object.model'; import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; -import { createSuccessfulRemoteDataObject } from '../../../remote-data.utils'; +import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../remote-data.utils'; import { PoolTaskSearchResult } from '../../../object-collection/shared/pool-task-search-result.model'; import { TruncatableService } from '../../../truncatable/truncatable.service'; import { VarDirective } from '../../../utils/var.directive'; @@ -27,6 +27,8 @@ import { DSONameServiceMock } from '../../../mocks/dso-name.service.mock'; import { APP_CONFIG } from '../../../../../config/app-config.interface'; import { ObjectCacheService } from '../../../../core/cache/object-cache.service'; import { Context } from '../../../../core/shared/context.model'; +import { createPaginatedList } from '../../../testing/utils.test'; +import { ItemDataService } from '../../../../core/data/item-data.service'; let component: PoolSearchResultListElementComponent; let fixture: ComponentFixture; @@ -34,6 +36,12 @@ let fixture: ComponentFixture; const mockResultObject: PoolTaskSearchResult = new PoolTaskSearchResult(); mockResultObject.hitHighlights = {}; +const emptyList = createSuccessfulRemoteDataObject(createPaginatedList([])); +const itemDataServiceStub = { + findDuplicates: () => createSuccessfulRemoteDataObject$({}), + findListByHref: () => observableOf(emptyList), +}; + const item = Object.assign(new Item(), { duplicates: observableOf([]), bundles: observableOf({}), @@ -90,7 +98,8 @@ describe('PoolSearchResultListElementComponent', () => { { provide: LinkService, useValue: linkService }, { provide: DSONameService, useClass: DSONameServiceMock }, { provide: APP_CONFIG, useValue: environmentUseThumbs }, - { provide: ObjectCacheService, useValue: objectCacheServiceMock } + { provide: ObjectCacheService, useValue: objectCacheServiceMock }, + { provide: ItemDataService, useValue: itemDataServiceStub }, ], schemas: [NO_ERRORS_SCHEMA] }).overrideComponent(PoolSearchResultListElementComponent, { diff --git a/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.ts b/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.ts index 5161faee89..e9e8088757 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.ts @@ -24,6 +24,7 @@ import { isNotEmpty, hasValue } from '../../../empty.util'; import { Context } from '../../../../core/shared/context.model'; import { PaginatedList } from '../../../../core/data/paginated-list.model'; import { Duplicate } from '../../duplicate-data/duplicate.model'; +import { ItemDataService } from '../../../../core/data/item-data.service'; /** * This component renders pool task object for the search result in the list view. @@ -77,6 +78,7 @@ export class PoolSearchResultListElementComponent extends SearchResultListElemen protected truncatableService: TruncatableService, public dsoNameService: DSONameService, protected objectCache: ObjectCacheService, + protected itemDataService: ItemDataService, @Inject(APP_CONFIG) protected appConfig: AppConfig ) { super(truncatableService, dsoNameService, appConfig); @@ -88,7 +90,7 @@ export class PoolSearchResultListElementComponent extends SearchResultListElemen ngOnInit() { super.ngOnInit(); this.linkService.resolveLinks(this.dso, followLink('workflowitem', {}, - followLink('item', {}, followLink('bundles'), followLink('duplicates')), + followLink('item', {}, followLink('bundles')), followLink('submitter') ), followLink('action')); @@ -107,7 +109,7 @@ export class PoolSearchResultListElementComponent extends SearchResultListElemen tap((itemRD: RemoteData) => { if (isNotEmpty(itemRD) && itemRD.hasSucceeded) { this.item$.next(itemRD.payload); - this.duplicates$ = itemRD.payload.duplicates.pipe( + this.duplicates$ = this.itemDataService.findDuplicates(itemRD.payload.uuid).pipe( getFirstCompletedRemoteData(), map((remoteData: RemoteData>) => { if (remoteData.hasSucceeded) {