mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
[TLC-674] Duplicate detection comp, template, i18n
Duplicate data is accessed in the submission section, pooled tasks list and claimed tasks list.
This commit is contained in:
@@ -242,6 +242,26 @@ export abstract class BaseItemDataService extends IdentifiableDataService<Item>
|
||||
);
|
||||
}
|
||||
|
||||
public getDuplicatesEndpoint(itemId: string): Observable<string> {
|
||||
return this.halService.getEndpoint(this.linkPath).pipe(
|
||||
switchMap((url: string) => this.halService.getEndpoint('duplicates', `${url}/${itemId}`))
|
||||
);
|
||||
}
|
||||
|
||||
public getDuplicates(itemId: string, searchOptions?: PaginatedSearchOptions): Observable<RemoteData<PaginatedList<Item>>> {
|
||||
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<Item>(hrefObs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the endpoint to move the item
|
||||
* @param itemId
|
||||
|
@@ -26,6 +26,7 @@ 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
|
||||
@@ -79,6 +80,7 @@ export class Item extends DSpaceObject implements ChildHALResource, HandleObject
|
||||
thumbnail: HALLink;
|
||||
accessStatus: HALLink;
|
||||
identifiers: HALLink;
|
||||
duplicates: HALLink;
|
||||
self: HALLink;
|
||||
};
|
||||
|
||||
@@ -131,6 +133,9 @@ export class Item extends DSpaceObject implements ChildHALResource, HandleObject
|
||||
@link(IDENTIFIERS, false, 'identifiers')
|
||||
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
|
||||
*/
|
||||
|
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
* Object model for the data returned by the REST API to present minted identifiers in a submission section
|
||||
*/
|
||||
import { Duplicate } from '../../../shared/object-list/duplicate-data/duplicate.model';
|
||||
|
||||
export interface WorkspaceitemSectionDuplicatesObject {
|
||||
potentialDuplicates?: Duplicate[]
|
||||
}
|
24
src/app/shared/object-list/duplicate-data/duplicate.model.ts
Normal file
24
src/app/shared/object-list/duplicate-data/duplicate.model.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import {autoserialize} from "cerialize";
|
||||
import {MetadataMap} from "../../../core/shared/metadata.models";
|
||||
|
||||
export class Duplicate {
|
||||
/**
|
||||
* The item title
|
||||
*/
|
||||
@autoserialize
|
||||
title: string;
|
||||
@autoserialize
|
||||
uuid: string;
|
||||
@autoserialize
|
||||
workflowItemId: bigint;
|
||||
@autoserialize
|
||||
workspaceItemId: bigint;
|
||||
@autoserialize
|
||||
owningCollection: string;
|
||||
|
||||
/**
|
||||
* Metadata for the bitstream (e.g. dc.description)
|
||||
*/
|
||||
@autoserialize
|
||||
metadata: MetadataMap;
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
import { ResourceType } from 'src/app/core/shared/resource-type';
|
||||
|
||||
/**
|
||||
* The resource type for Access Status
|
||||
*
|
||||
* Needs to be in a separate file to prevent circular
|
||||
* dependencies in webpack.
|
||||
*/
|
||||
export const DUPLICATE = new ResourceType('duplicate');
|
@@ -21,6 +21,7 @@ import { Item } from '../../../../core/shared/item.model';
|
||||
import { mergeMap, tap } from 'rxjs/operators';
|
||||
import { isNotEmpty, hasValue } from '../../../empty.util';
|
||||
import { Context } from '../../../../core/shared/context.model';
|
||||
import {Duplicate} from "../../duplicate-data/duplicate.model";
|
||||
|
||||
@Component({
|
||||
selector: 'ds-claimed-search-result-list-element',
|
||||
@@ -50,6 +51,11 @@ export class ClaimedSearchResultListElementComponent extends SearchResultListEle
|
||||
*/
|
||||
public workflowitem$: BehaviorSubject<WorkflowItem> = new BehaviorSubject<WorkflowItem>(null);
|
||||
|
||||
/**
|
||||
* The potential duplicates of this item
|
||||
*/
|
||||
public duplicates$: Observable<Duplicate[]>;
|
||||
|
||||
/**
|
||||
* Display thumbnails if required by configuration
|
||||
*/
|
||||
|
@@ -4,6 +4,14 @@
|
||||
[showSubmitter]="showSubmitter"
|
||||
[badgeContext]="badgeContext"
|
||||
[workflowItem]="workflowitem$.value"></ds-themed-item-list-preview>
|
||||
<ng-container *ngVar="(duplicates$|async).length as duplicateCount">
|
||||
<div [ngClass]="'col-md-12'" *ngIf="duplicateCount > 0">
|
||||
<div class="d-flex">
|
||||
{{ duplicateCount }} {{ 'submission.workflow.tasks.duplicates' | translate }}
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<div class="row">
|
||||
<div [ngClass]="showThumbnails ? 'offset-3 offset-md-2 pl-3' : ''">
|
||||
<ds-pool-task-actions id="actions"
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
|
||||
|
||||
import { BehaviorSubject, EMPTY, Observable } from 'rxjs';
|
||||
import { mergeMap, tap } from 'rxjs/operators';
|
||||
import {map, mergeMap, tap} from 'rxjs/operators';
|
||||
|
||||
import { ViewMode } from '../../../../core/shared/view-mode.model';
|
||||
import { RemoteData } from '../../../../core/data/remote-data';
|
||||
@@ -22,6 +22,8 @@ import { getFirstCompletedRemoteData } from '../../../../core/shared/operators';
|
||||
import { Item } from '../../../../core/shared/item.model';
|
||||
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";
|
||||
|
||||
/**
|
||||
* This component renders pool task object for the search result in the list view.
|
||||
@@ -55,6 +57,11 @@ export class PoolSearchResultListElementComponent extends SearchResultListElemen
|
||||
*/
|
||||
public workflowitem$: BehaviorSubject<WorkflowItem> = new BehaviorSubject<WorkflowItem>(null);
|
||||
|
||||
/**
|
||||
* The potential duplicates of this workflow item
|
||||
*/
|
||||
public duplicates$: Observable<Duplicate[]>;
|
||||
|
||||
/**
|
||||
* The index of this list element
|
||||
*/
|
||||
@@ -81,7 +88,7 @@ export class PoolSearchResultListElementComponent extends SearchResultListElemen
|
||||
ngOnInit() {
|
||||
super.ngOnInit();
|
||||
this.linkService.resolveLinks(this.dso, followLink('workflowitem', {},
|
||||
followLink('item', {}, followLink('bundles')),
|
||||
followLink('item', {}, followLink('bundles'), followLink('duplicates')),
|
||||
followLink('submitter')
|
||||
), followLink('action'));
|
||||
|
||||
@@ -100,6 +107,19 @@ export class PoolSearchResultListElementComponent extends SearchResultListElemen
|
||||
tap((itemRD: RemoteData<Item>) => {
|
||||
if (isNotEmpty(itemRD) && itemRD.hasSucceeded) {
|
||||
this.item$.next(itemRD.payload);
|
||||
console.dir(itemRD.payload);
|
||||
this.duplicates$ = itemRD.payload.duplicates.pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
map((remoteData: RemoteData<PaginatedList<Duplicate>>) => {
|
||||
console.dir(remoteData);
|
||||
if (remoteData.hasSucceeded) {
|
||||
if (remoteData.payload.page) {
|
||||
console.dir(remoteData.payload.page);
|
||||
return remoteData.payload.page;
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
})
|
||||
).subscribe();
|
||||
|
@@ -0,0 +1,20 @@
|
||||
<!--
|
||||
Template for the detect duplicates submission section component
|
||||
@author Kim Shepherd
|
||||
-->
|
||||
<div class="text-sm-left" *ngVar="(this.data$ | async) as data">
|
||||
<ng-container *ngIf="data.potentialDuplicates.length == 0">
|
||||
<p>{{ 'submission.sections.duplicates.none' }}</p>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="data.potentialDuplicates.length > 0">
|
||||
<p>{{ 'submission.sections.duplicates.detected' | translate }}</p>
|
||||
<div *ngFor="let dupe of data.potentialDuplicates" class="ds-duplicate">
|
||||
<a target="_blank" [href]="'/items/'+dupe.uuid">{{dupe.title}}</a>
|
||||
<div *ngFor="let metadatum of Metadata.toViewModelList(dupe.metadata)">
|
||||
{{('item.preview.' + metadatum.key) | translate}} {{metadatum.value}}
|
||||
</div>
|
||||
<p *ngIf="dupe.workspaceItemId">{{ 'submission.sections.duplicates.in-workspace' | translate }}</p>
|
||||
<p *ngIf="dupe.workflowItemId">{{ 'submission.sections.duplicates.in-workflow' | translate }}</p>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
@@ -0,0 +1,127 @@
|
||||
import {ChangeDetectionStrategy, Component, Inject } from '@angular/core';
|
||||
|
||||
import { Observable, of as observableOf, Subscription } from 'rxjs';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { SectionsType } from '../sections-type';
|
||||
import { SectionModelComponent } from '../models/section.model';
|
||||
import { renderSectionFor } from '../sections-decorator';
|
||||
import { SectionDataObject } from '../models/section-data.model';
|
||||
import { SubmissionService } from '../../submission.service';
|
||||
import { AlertType } from '../../../shared/alert/alert-type';
|
||||
import { SectionsService } from '../sections.service';
|
||||
import {map} from "rxjs/operators";
|
||||
import {ItemDataService} from "../../../core/data/item-data.service";
|
||||
import {
|
||||
WorkspaceitemSectionDuplicatesObject
|
||||
} from "../../../core/submission/models/workspaceitem-section-duplicates.model";
|
||||
import {Metadata} from "../../../core/shared/metadata.utils";
|
||||
|
||||
/**
|
||||
* Detect duplicates step
|
||||
*
|
||||
* @author Kim Shepherd
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-submission-section-duplicates',
|
||||
templateUrl: './section-duplicates.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.Default
|
||||
})
|
||||
|
||||
@renderSectionFor(SectionsType.Duplicates)
|
||||
export class SubmissionSectionDuplicatesComponent extends SectionModelComponent {
|
||||
/**
|
||||
* The Alert categories.
|
||||
* @type {AlertType}
|
||||
*/
|
||||
public AlertTypeEnum = AlertType;
|
||||
|
||||
/**
|
||||
* Variable to track if the section is loading.
|
||||
* @type {boolean}
|
||||
*/
|
||||
public isLoading = true;
|
||||
|
||||
/**
|
||||
* Array to track all subscriptions and unsubscribe them onDestroy
|
||||
* @type {Array}
|
||||
*/
|
||||
protected subs: Subscription[] = [];
|
||||
|
||||
/**
|
||||
* Section data observable
|
||||
*/
|
||||
public data$: Observable<WorkspaceitemSectionDuplicatesObject>;
|
||||
|
||||
/**
|
||||
* Initialize instance variables.
|
||||
*
|
||||
* @param {TranslateService} translate
|
||||
* @param {SectionsService} sectionService
|
||||
* @param {SubmissionService} submissionService
|
||||
* @param itemDataService
|
||||
* @param nameService
|
||||
* @param {string} injectedCollectionId
|
||||
* @param {SectionDataObject} injectedSectionData
|
||||
* @param {string} injectedSubmissionId
|
||||
*/
|
||||
constructor(protected translate: TranslateService,
|
||||
protected sectionService: SectionsService,
|
||||
protected submissionService: SubmissionService,
|
||||
private itemDataService: ItemDataService,
|
||||
// private nameService: DSONameService,
|
||||
@Inject('collectionIdProvider') public injectedCollectionId: string,
|
||||
@Inject('sectionDataProvider') public injectedSectionData: SectionDataObject,
|
||||
@Inject('submissionIdProvider') public injectedSubmissionId: string) {
|
||||
super(injectedCollectionId, injectedSectionData, injectedSubmissionId);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
super.ngOnInit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all instance variables and retrieve configuration.
|
||||
*/
|
||||
onSectionInit() {
|
||||
this.isLoading = false;
|
||||
this.data$ = this.getDuplicateData().pipe(
|
||||
map((data: WorkspaceitemSectionDuplicatesObject) => {
|
||||
console.dir(data);
|
||||
return data;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if identifier section has read-only visibility
|
||||
*/
|
||||
isReadOnly(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe from all subscriptions, if needed.
|
||||
*/
|
||||
onSectionDestroy(): void {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get section status. Because this simple component never requires human interaction, this is basically
|
||||
* always going to be the opposite of "is this section still loading". This is not the place for API response
|
||||
* error checking but determining whether the step can 'proceed'.
|
||||
*
|
||||
* @return Observable<boolean>
|
||||
* the section status
|
||||
*/
|
||||
public getSectionStatus(): Observable<boolean> {
|
||||
return observableOf(!this.isLoading);
|
||||
}
|
||||
|
||||
public getDuplicateData(): Observable<WorkspaceitemSectionDuplicatesObject> {
|
||||
return this.sectionService.getSectionData(this.submissionId, this.sectionData.id, this.sectionData.sectionType) as
|
||||
Observable<WorkspaceitemSectionDuplicatesObject>;
|
||||
}
|
||||
|
||||
protected readonly Metadata = Metadata;
|
||||
}
|
@@ -10,4 +10,5 @@ export enum SectionsType {
|
||||
Identifiers = 'identifiers',
|
||||
Collection = 'collection',
|
||||
CoarNotify = 'coarnotify'
|
||||
Duplicates = 'duplicates'
|
||||
}
|
||||
|
@@ -72,6 +72,7 @@ import {
|
||||
CoarNotifyConfigDataService
|
||||
} from './sections/section-coar-notify/coar-notify-config-data.service';
|
||||
import { LdnServicesService } from '../admin/admin-ldn-services/ldn-services-data/ldn-services-data.service';
|
||||
import { SubmissionSectionDuplicatesComponent } from './sections/duplicates/section-duplicates.component';
|
||||
|
||||
const ENTRY_COMPONENTS = [
|
||||
// put only entry components that use custom decorator
|
||||
@@ -81,7 +82,8 @@ const ENTRY_COMPONENTS = [
|
||||
SubmissionSectionCcLicensesComponent,
|
||||
SubmissionSectionAccessesComponent,
|
||||
SubmissionSectionSherpaPoliciesComponent,
|
||||
SubmissionSectionCoarNotifyComponent
|
||||
SubmissionSectionCoarNotifyComponent,
|
||||
SubmissionSectionDuplicatesComponent
|
||||
];
|
||||
|
||||
const DECLARATIONS = [
|
||||
|
@@ -2829,6 +2829,8 @@
|
||||
|
||||
"item.preview.organization.legalName": "Legal Name",
|
||||
|
||||
"item.preview.dspace.entity.type": "Entity Type:",
|
||||
|
||||
"item.select.confirm": "Confirm selected",
|
||||
|
||||
"item.select.empty": "No items to show",
|
||||
@@ -5111,7 +5113,7 @@
|
||||
|
||||
"submission.sections.submit.progressbar.describe.steptwo": "Describe",
|
||||
|
||||
"submission.sections.submit.progressbar.detect-duplicate": "Potential duplicates",
|
||||
"submission.sections.submit.progressbar.duplicates": "Potential duplicates",
|
||||
|
||||
"submission.sections.submit.progressbar.identifiers": "Identifiers",
|
||||
|
||||
@@ -5243,6 +5245,14 @@
|
||||
|
||||
"submission.sections.accesses.form.until-placeholder": "Until",
|
||||
|
||||
"submission.sections.duplicates.none": "No duplicates were detected.",
|
||||
|
||||
"submission.sections.duplicates.detected": "Potential duplicates were detected. Please review the list below.",
|
||||
|
||||
"submission.sections.duplicates.in-workspace": "This item is in workspace",
|
||||
|
||||
"submission.sections.duplicates.in-workflow": "This item is in workflow",
|
||||
|
||||
"submission.sections.license.granted-label": "I confirm the license above",
|
||||
|
||||
"submission.sections.license.required": "You must accept the license",
|
||||
@@ -5369,6 +5379,8 @@
|
||||
|
||||
"submission.workflow.tasks.pool.show-detail": "Show detail",
|
||||
|
||||
"submission.workflow.tasks.duplicates": "potential duplicates were detected for this item. Claim and edit this item to see details.",
|
||||
|
||||
"submission.workspace.generic.view": "View",
|
||||
|
||||
"submission.workspace.generic.view-help": "Select this option to view the item's metadata.",
|
||||
|
Reference in New Issue
Block a user