[TLC-674] Refactor duplicates from item link to searchBy

This commit is contained in:
Kim Shepherd
2024-02-01 11:29:16 +13:00
parent 140cb88510
commit db8d47e598
7 changed files with 64 additions and 33 deletions

View File

@@ -46,6 +46,9 @@ import { RestRequestMethod } from './rest-request-method';
import { CreateData, CreateDataImpl } from './base/create-data'; import { CreateData, CreateDataImpl } from './base/create-data';
import { RequestParam } from '../cache/models/request-param.model'; import { RequestParam } from '../cache/models/request-param.model';
import { dataService } from './base/data-service.decorator'; 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 * An abstract service for CRUD operations on Items
@@ -56,6 +59,7 @@ export abstract class BaseItemDataService extends IdentifiableDataService<Item>
private createData: CreateData<Item>; private createData: CreateData<Item>;
private patchData: PatchData<Item>; private patchData: PatchData<Item>;
private deleteData: DeleteData<Item>; private deleteData: DeleteData<Item>;
private searchData: SearchDataImpl<Duplicate>;
protected constructor( protected constructor(
protected linkPath, protected linkPath,
@@ -74,6 +78,7 @@ export abstract class BaseItemDataService extends IdentifiableDataService<Item>
this.createData = new CreateDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, notificationsService, this.responseMsToLive); this.createData = new CreateDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, notificationsService, this.responseMsToLive);
this.patchData = new PatchDataImpl<Item>(this.linkPath, requestService, rdbService, objectCache, halService, comparator, this.responseMsToLive, this.constructIdEndpoint); this.patchData = new PatchDataImpl<Item>(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.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<Item>
); );
} }
public getDuplicatesEndpoint(itemId: string): Observable<string> { public findDuplicates(uuid: string, options?: FindListOptions, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Duplicate>[]): Observable<RemoteData<PaginatedList<Duplicate>>> {
return this.halService.getEndpoint(this.linkPath).pipe( const searchParams = [new RequestParam('uuid', uuid)];
switchMap((url: string) => this.halService.getEndpoint('duplicates', `${url}/${itemId}`)) let findListOptions = new FindListOptions();
); if (options) {
} findListOptions = Object.assign(new FindListOptions(), options);
}
public getDuplicates(itemId: string, searchOptions?: PaginatedSearchOptions): Observable<RemoteData<PaginatedList<Item>>> { if (findListOptions.searchParams) {
const hrefObs = this.getDuplicatesEndpoint(itemId).pipe( findListOptions.searchParams = [...findListOptions.searchParams, ...searchParams];
map((href) => searchOptions ? searchOptions.toRestUrl(href) : href) } else {
); findListOptions.searchParams = searchParams;
hrefObs.pipe( }
take(1) return this.searchData.searchBy('findDuplicates', findListOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
).subscribe((href) => {
const request = new GetRequest(this.requestService.generateRequestId(), href);
this.requestService.send(request);
});
return this.rdbService.buildList<Item>(hrefObs);
} }
/** /**

View File

@@ -26,7 +26,6 @@ import { AccessStatusObject } from 'src/app/shared/object-collection/shared/badg
import { HandleObject } from './handle-object.model'; import { HandleObject } from './handle-object.model';
import { IDENTIFIERS } from '../../shared/object-list/identifier-data/identifier-data.resource-type'; import { IDENTIFIERS } from '../../shared/object-list/identifier-data/identifier-data.resource-type';
import { IdentifierData } from '../../shared/object-list/identifier-data/identifier-data.model'; 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 * Class representing a DSpace Item
@@ -80,7 +79,6 @@ export class Item extends DSpaceObject implements ChildHALResource, HandleObject
thumbnail: HALLink; thumbnail: HALLink;
accessStatus: HALLink; accessStatus: HALLink;
identifiers: HALLink; identifiers: HALLink;
duplicates: HALLink;
self: HALLink; self: HALLink;
}; };
@@ -133,9 +131,6 @@ export class Item extends DSpaceObject implements ChildHALResource, HandleObject
@link(IDENTIFIERS, false, 'identifiers') @link(IDENTIFIERS, false, 'identifiers')
identifiers?: Observable<RemoteData<IdentifierData>>; identifiers?: Observable<RemoteData<IdentifierData>>;
@link(ITEM, true, 'duplicates')
duplicates?: Observable<RemoteData<PaginatedList<Duplicate>>>;
/** /**
* Method that returns as which type of object this object should be rendered * Method that returns as which type of object this object should be rendered
*/ */

View File

@@ -1,7 +1,14 @@
import { autoserialize } from 'cerialize'; import {autoserialize, deserialize} from 'cerialize';
import { MetadataMap } from '../../../core/shared/metadata.models'; 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 * The item title
*/ */
@@ -23,5 +30,13 @@ export class Duplicate {
metadata: MetadataMap; metadata: MetadataMap;
@autoserialize @autoserialize
type: string; type: ResourceType;
/**
* The {@link HALLink}s for this Bitstream
*/
@deserialize
_links: {
self: HALLink;
};
} }

View File

@@ -15,7 +15,7 @@ import { Item } from '../../../../core/shared/item.model';
import { ClaimedSearchResultListElementComponent } from './claimed-search-result-list-element.component'; import { ClaimedSearchResultListElementComponent } from './claimed-search-result-list-element.component';
import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model'; import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model';
import { WorkflowItem } from '../../../../core/submission/models/workflowitem.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 { ClaimedTaskSearchResult } from '../../../object-collection/shared/claimed-task-search-result.model';
import { TruncatableService } from '../../../truncatable/truncatable.service'; import { TruncatableService } from '../../../truncatable/truncatable.service';
import { VarDirective } from '../../../utils/var.directive'; import { VarDirective } from '../../../utils/var.directive';
@@ -28,6 +28,8 @@ import { APP_CONFIG } from '../../../../../config/app-config.interface';
import { environment } from '../../../../../environments/environment'; import { environment } from '../../../../../environments/environment';
import { ObjectCacheService } from '../../../../core/cache/object-cache.service'; import { ObjectCacheService } from '../../../../core/cache/object-cache.service';
import { Context } from '../../../../core/shared/context.model'; 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 component: ClaimedSearchResultListElementComponent;
let fixture: ComponentFixture<ClaimedSearchResultListElementComponent>; let fixture: ComponentFixture<ClaimedSearchResultListElementComponent>;
@@ -35,6 +37,12 @@ let fixture: ComponentFixture<ClaimedSearchResultListElementComponent>;
const mockResultObject: ClaimedTaskSearchResult = new ClaimedTaskSearchResult(); const mockResultObject: ClaimedTaskSearchResult = new ClaimedTaskSearchResult();
mockResultObject.hitHighlights = {}; mockResultObject.hitHighlights = {};
const emptyList = createSuccessfulRemoteDataObject(createPaginatedList([]));
const itemDataServiceStub = {
findDuplicates: () => createSuccessfulRemoteDataObject$({}),
findListByHref: () => observableOf(emptyList),
};
const item = Object.assign(new Item(), { const item = Object.assign(new Item(), {
bundles: observableOf({}), bundles: observableOf({}),
metadata: { metadata: {
@@ -83,7 +91,8 @@ describe('ClaimedSearchResultListElementComponent', () => {
{ provide: LinkService, useValue: linkService }, { provide: LinkService, useValue: linkService },
{ provide: DSONameService, useClass: DSONameServiceMock }, { provide: DSONameService, useClass: DSONameServiceMock },
{ provide: APP_CONFIG, useValue: environment }, { provide: APP_CONFIG, useValue: environment },
{ provide: ObjectCacheService, useValue: objectCacheServiceMock } { provide: ObjectCacheService, useValue: objectCacheServiceMock },
{ provide: ItemDataService, useValue: itemDataServiceStub },
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(ClaimedSearchResultListElementComponent, { }).overrideComponent(ClaimedSearchResultListElementComponent, {

View File

@@ -23,6 +23,7 @@ import { isNotEmpty, hasValue } from '../../../empty.util';
import { Context } from '../../../../core/shared/context.model'; import { Context } from '../../../../core/shared/context.model';
import { Duplicate } from '../../duplicate-data/duplicate.model'; import { Duplicate } from '../../duplicate-data/duplicate.model';
import { PaginatedList } from '../../../../core/data/paginated-list.model'; import { PaginatedList } from '../../../../core/data/paginated-list.model';
import { ItemDataService } from '../../../../core/data/item-data.service';
@Component({ @Component({
selector: 'ds-claimed-search-result-list-element', selector: 'ds-claimed-search-result-list-element',
@@ -67,6 +68,7 @@ export class ClaimedSearchResultListElementComponent extends SearchResultListEle
protected truncatableService: TruncatableService, protected truncatableService: TruncatableService,
public dsoNameService: DSONameService, public dsoNameService: DSONameService,
protected objectCache: ObjectCacheService, protected objectCache: ObjectCacheService,
protected itemDataService: ItemDataService,
@Inject(APP_CONFIG) protected appConfig: AppConfig @Inject(APP_CONFIG) protected appConfig: AppConfig
) { ) {
super(truncatableService, dsoNameService, appConfig); super(truncatableService, dsoNameService, appConfig);
@@ -97,7 +99,7 @@ export class ClaimedSearchResultListElementComponent extends SearchResultListEle
tap((itemRD: RemoteData<Item>) => { tap((itemRD: RemoteData<Item>) => {
if (isNotEmpty(itemRD) && itemRD.hasSucceeded) { if (isNotEmpty(itemRD) && itemRD.hasSucceeded) {
this.item$.next(itemRD.payload); this.item$.next(itemRD.payload);
this.duplicates$ = itemRD.payload.duplicates.pipe( this.duplicates$ = this.itemDataService.findDuplicates(itemRD.payload.uuid).pipe(
getFirstCompletedRemoteData(), getFirstCompletedRemoteData(),
map((remoteData: RemoteData<PaginatedList<Duplicate>>) => { map((remoteData: RemoteData<PaginatedList<Duplicate>>) => {
if (remoteData.hasSucceeded) { if (remoteData.hasSucceeded) {

View File

@@ -15,7 +15,7 @@ import { Item } from '../../../../core/shared/item.model';
import { PoolSearchResultListElementComponent } from './pool-search-result-list-element.component'; import { PoolSearchResultListElementComponent } from './pool-search-result-list-element.component';
import { PoolTask } from '../../../../core/tasks/models/pool-task-object.model'; import { PoolTask } from '../../../../core/tasks/models/pool-task-object.model';
import { WorkflowItem } from '../../../../core/submission/models/workflowitem.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 { PoolTaskSearchResult } from '../../../object-collection/shared/pool-task-search-result.model';
import { TruncatableService } from '../../../truncatable/truncatable.service'; import { TruncatableService } from '../../../truncatable/truncatable.service';
import { VarDirective } from '../../../utils/var.directive'; 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 { APP_CONFIG } from '../../../../../config/app-config.interface';
import { ObjectCacheService } from '../../../../core/cache/object-cache.service'; import { ObjectCacheService } from '../../../../core/cache/object-cache.service';
import { Context } from '../../../../core/shared/context.model'; 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 component: PoolSearchResultListElementComponent;
let fixture: ComponentFixture<PoolSearchResultListElementComponent>; let fixture: ComponentFixture<PoolSearchResultListElementComponent>;
@@ -34,6 +36,12 @@ let fixture: ComponentFixture<PoolSearchResultListElementComponent>;
const mockResultObject: PoolTaskSearchResult = new PoolTaskSearchResult(); const mockResultObject: PoolTaskSearchResult = new PoolTaskSearchResult();
mockResultObject.hitHighlights = {}; mockResultObject.hitHighlights = {};
const emptyList = createSuccessfulRemoteDataObject(createPaginatedList([]));
const itemDataServiceStub = {
findDuplicates: () => createSuccessfulRemoteDataObject$({}),
findListByHref: () => observableOf(emptyList),
};
const item = Object.assign(new Item(), { const item = Object.assign(new Item(), {
duplicates: observableOf([]), duplicates: observableOf([]),
bundles: observableOf({}), bundles: observableOf({}),
@@ -90,7 +98,8 @@ describe('PoolSearchResultListElementComponent', () => {
{ provide: LinkService, useValue: linkService }, { provide: LinkService, useValue: linkService },
{ provide: DSONameService, useClass: DSONameServiceMock }, { provide: DSONameService, useClass: DSONameServiceMock },
{ provide: APP_CONFIG, useValue: environmentUseThumbs }, { provide: APP_CONFIG, useValue: environmentUseThumbs },
{ provide: ObjectCacheService, useValue: objectCacheServiceMock } { provide: ObjectCacheService, useValue: objectCacheServiceMock },
{ provide: ItemDataService, useValue: itemDataServiceStub },
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(PoolSearchResultListElementComponent, { }).overrideComponent(PoolSearchResultListElementComponent, {

View File

@@ -24,6 +24,7 @@ import { isNotEmpty, hasValue } from '../../../empty.util';
import { Context } from '../../../../core/shared/context.model'; import { Context } from '../../../../core/shared/context.model';
import { PaginatedList } from '../../../../core/data/paginated-list.model'; import { PaginatedList } from '../../../../core/data/paginated-list.model';
import { Duplicate } from '../../duplicate-data/duplicate.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. * 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, protected truncatableService: TruncatableService,
public dsoNameService: DSONameService, public dsoNameService: DSONameService,
protected objectCache: ObjectCacheService, protected objectCache: ObjectCacheService,
protected itemDataService: ItemDataService,
@Inject(APP_CONFIG) protected appConfig: AppConfig @Inject(APP_CONFIG) protected appConfig: AppConfig
) { ) {
super(truncatableService, dsoNameService, appConfig); super(truncatableService, dsoNameService, appConfig);
@@ -88,7 +90,7 @@ export class PoolSearchResultListElementComponent extends SearchResultListElemen
ngOnInit() { ngOnInit() {
super.ngOnInit(); super.ngOnInit();
this.linkService.resolveLinks(this.dso, followLink('workflowitem', {}, this.linkService.resolveLinks(this.dso, followLink('workflowitem', {},
followLink('item', {}, followLink('bundles'), followLink('duplicates')), followLink('item', {}, followLink('bundles')),
followLink('submitter') followLink('submitter')
), followLink('action')); ), followLink('action'));
@@ -107,7 +109,7 @@ export class PoolSearchResultListElementComponent extends SearchResultListElemen
tap((itemRD: RemoteData<Item>) => { tap((itemRD: RemoteData<Item>) => {
if (isNotEmpty(itemRD) && itemRD.hasSucceeded) { if (isNotEmpty(itemRD) && itemRD.hasSucceeded) {
this.item$.next(itemRD.payload); this.item$.next(itemRD.payload);
this.duplicates$ = itemRD.payload.duplicates.pipe( this.duplicates$ = this.itemDataService.findDuplicates(itemRD.payload.uuid).pipe(
getFirstCompletedRemoteData(), getFirstCompletedRemoteData(),
map((remoteData: RemoteData<PaginatedList<Duplicate>>) => { map((remoteData: RemoteData<PaginatedList<Duplicate>>) => {
if (remoteData.hasSucceeded) { if (remoteData.hasSucceeded) {