External source items import code completed

This commit is contained in:
Matteo Perelli
2020-06-22 19:59:10 +02:00
parent 63cff331d2
commit e7ef9dab20
8 changed files with 213 additions and 24 deletions

View File

@@ -2,7 +2,7 @@ import { NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ImportExternalPageComponent } from './import-external-page.component';
fdescribe('ImportExternalPageComponent', () => {
describe('ImportExternalPageComponent', () => {
let component: ImportExternalPageComponent;
let fixture: ComponentFixture<ImportExternalPageComponent>;

View File

@@ -74,4 +74,21 @@ describe('ExternalSourceService', () => {
});
});
});
describe('getAllExternalSources', () => {
it('should call findAll', () => {
spyOn(service, 'findAll');
service.getAllExternalSources();
expect(service.findAll).toHaveBeenCalled();
});
});
describe('getExternalSource', () => {
it('should call findById', () => {
const externalSourceId = 'orcidV2';
spyOn(service, 'findById');
service.getExternalSource(externalSourceId);
expect(service.findById).toHaveBeenCalledWith(externalSourceId);
});
});
});

View File

@@ -11,7 +11,7 @@ import { NotificationsService } from '../../shared/notifications/notifications.s
import { HttpClient } from '@angular/common/http';
import { FindListOptions, GetRequest } from './request.models';
import { Observable } from 'rxjs/internal/Observable';
import { distinctUntilChanged, map, switchMap, take, flatMap } from 'rxjs/operators';
import { distinctUntilChanged, map, switchMap, take, flatMap, catchError } from 'rxjs/operators';
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
import { hasValue, isNotEmptyOperator } from '../../shared/empty.util';
import { configureRequest } from '../shared/operators';
@@ -60,6 +60,20 @@ export class ExternalSourceService extends DataService<ExternalSource> {
);
}
/**
* Return a single external source.
*
* @param id
* The external source id.
* @param linksToFollow
* List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved.
* @return Observable<RemoteData<ExternalSource>>
* The list of the external sources.
*/
getExternalSource(id: string, ...linksToFollow: Array<FollowLinkConfig<ExternalSource>>): Observable<RemoteData<ExternalSource>> {
return this.findById(id, ...linksToFollow);
}
/**
* Return the list of external sources.
*

View File

@@ -41,7 +41,7 @@ export class SubmissionImportExternalSearchbarComponent implements OnInit {
/**
* The init external source value.
*/
@Input() public initExternalSourceValue: string;
@Input() public initExternalSourceData: ExternalSourceData;
/**
* The selected external sources.
*/
@@ -49,7 +49,7 @@ export class SubmissionImportExternalSearchbarComponent implements OnInit {
/**
* The list of external sources.
*/
public sourceList: SourceElement[] = [];
public sourceList: SourceElement[];
/**
* The string used to search items in the external sources.
*/
@@ -87,8 +87,12 @@ export class SubmissionImportExternalSearchbarComponent implements OnInit {
* Component initialization and retrieve first page of external sources.
*/
ngOnInit() {
this.selectedElement = {
id: '',
name: 'loading'
};
this.searchString = '';
this.selectedElement = { id: '', name: 'loading' };
this.sourceList = [];
this.findListOptions = Object.assign({}, new FindListOptions(), {
elementsPerPage: 5,
currentPage: 0,
@@ -102,14 +106,16 @@ export class SubmissionImportExternalSearchbarComponent implements OnInit {
}),
getFirstSucceededRemoteDataPayload()
).subscribe((externalSource: PaginatedList<ExternalSource>) => {
let initEntry;
externalSource.page.forEach((element) => {
this.sourceList.push({ id: element.id, name: element.name });
if (isEmpty(initEntry) || this.initExternalSourceValue === element.id) {
initEntry = { id: element.id, name: element.name }
if (this.initExternalSourceData.sourceId === element.id) {
this.selectedElement = { id: element.id, name: element.name };
this.searchString = this.initExternalSourceData.query;
}
});
this.selectedElement = initEntry;
if (this.selectedElement.id === '') {
this.selectedElement = this.sourceList[0];
}
this.pageInfo = externalSource.pageInfo;
this.cdr.detectChanges();
});

View File

@@ -3,15 +3,36 @@
<div class="col-md-12">
<h2 id="header" class="pb-2">{{'submission.import-external.title' | translate}}</h2>
<ds-submission-import-external-searchbar
[initExternalSourceValue]="''"
[initExternalSourceData]="routeData"
(externalSourceData) = "getExternalsourceData($event)">
</ds-submission-import-external-searchbar>
</div>
</div>
<div class="row">
<div class="col-md-12">
DATA<br>
{{(externalSourceData)?externalSourceData.sourceId:''}} : {{(externalSourceData)?externalSourceData.query:''}}
<div *ngIf="routeData.sourceId !== ''" class="col-md-12">
<ng-container *ngVar="(entriesRD$ | async) as entriesRD">
<h3 *ngIf="entriesRD?.payload?.page?.length !== 0">{{ 'submission.sections.describe.relationship-lookup.selection-tab.title.' + routeData.sourceId | translate}}</h3>
<ds-viewable-collection *ngIf="entriesRD?.hasSucceeded && !(isLoading$ | async) && entriesRD?.payload?.page?.length > 0" @fadeIn
[objects]="entriesRD"
[selectionConfig]="{ repeatable: repeatable, listId: listId }"
[config]="initialPagination"
[hideGear]="true"
[context]="context"
[importable]="true"
[importConfig]="importConfig"
(importObject)="import($event)">
</ds-viewable-collection>
<ds-loading *ngIf="(isLoading$ | async)"
message="{{'loading.search-results' | translate}}"></ds-loading>
<div *ngIf="!(isLoading$ | async) && entriesRD?.payload?.page?.length === 0" id="empty-external-entry-list">
{{ 'search.results.empty' | translate }}
</div>
</ng-container>
</div>
<div *ngIf="routeData.sourceId === ''" class="col-md-12">
<div class="jumbotron">
<p class="lead">{{'submission.import-external.page.hint' | translate}}</p>
</div>
</div>
</div>
<div class="row">

View File

@@ -1,7 +1,22 @@
import { Component, OnInit, ChangeDetectorRef } from '@angular/core';
import { Observable, of as observableOf } from 'rxjs';
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { combineLatest, BehaviorSubject } from 'rxjs';
import { ExternalSourceService } from '../../core/data/external-source.service';
import { ExternalSourceData } from './import-external-searchbar/submission-import-external-searchbar.component';
import { RemoteData } from '../../core/data/remote-data';
import { PaginatedList } from '../../core/data/paginated-list';
import { ExternalSourceEntry } from '../../core/shared/external-source-entry.model';
import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service';
import { switchMap, filter } from 'rxjs/operators';
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
import { Context } from '../../core/shared/context.model';
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
import { RouteService } from '../../core/services/route.service';
import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils';
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
import { SubmissionImportExternalPreviewComponent } from './import-external-preview/submission-import-external-preview.component';
import { fadeIn } from '../../shared/animations/fade';
import { createPaginatedList } from '../../shared/testing/utils.test';
/**
* This component allows to submit a new workspaceitem importing the data from an external source.
@@ -9,27 +24,129 @@ import { ExternalSourceData } from './import-external-searchbar/submission-impor
@Component({
selector: 'ds-submission-import-external',
styleUrls: ['./submission-import-external.component.scss'],
templateUrl: './submission-import-external.component.html'
templateUrl: './submission-import-external.component.html',
animations: [ fadeIn ]
})
export class SubmissionImportExternalComponent {
export class SubmissionImportExternalComponent implements OnInit {
/**
* The external source search data.
* The external source search data from the routing service.
*/
public externalSourceData: ExternalSourceData;
public routeData: ExternalSourceData;
/**
* The displayed list of entries
*/
public entriesRD$: BehaviorSubject<RemoteData<PaginatedList<ExternalSourceEntry>>>;
/**
* TRUE if the REST service is called to retrieve the external source items
*/
public isLoading$: BehaviorSubject<boolean>;
/**
* Configuration to use for the import buttons
*/
public importConfig: { buttonLabel: string };
/**
* Suffix for button label
*/
public label: string;
/**
* The ID of the list to add/remove selected items to/from
*/
public listId: string;
/**
* TRUE if the selection is repeatable
*/
public repeatable: boolean;
/**
* The initial pagination options
*/
public initialPagination = Object.assign(new PaginationComponentOptions(), {
id: 'submission-external-source-relation-list',
pageSize: 5
});
/**
* The context to displaying lists for
*/
public context: Context;
/**
* The modal for the entry preview
*/
modalRef: NgbModalRef;
/**
* Initialize the component variables.
* @param {SearchConfigurationService} searchConfigService
* @param {ExternalSourceService} externalService
* @param {RouteService} routeService
* @param {Router} router
* @param {NgbModal} modalService
*/
constructor(
public searchConfigService: SearchConfigurationService,
private externalService: ExternalSourceService,
private cdr: ChangeDetectorRef,
private routeService: RouteService,
private router: Router,
private modalService: NgbModal,
) { }
/**
* Get the data to submit a search.
* Get the entries for the selected external source and set initial configuration.
*/
public getExternalsourceData(event: ExternalSourceData) {
this.externalSourceData = event;
ngOnInit(): void {
this.label = 'Journal';
this.listId = 'list-submission-external-sources';
this.context = Context.SubmissionModal;
this.repeatable = false;
this.routeData = { sourceId: '', query: '' };
this.importConfig = {
buttonLabel: 'submission.sections.describe.relationship-lookup.external-source.import-button-title.' + this.label
};
this.entriesRD$ = new BehaviorSubject(createSuccessfulRemoteDataObject(createPaginatedList([])));
this.isLoading$ = new BehaviorSubject(false);
combineLatest(
[
this.routeService.getQueryParameterValue('source'),
this.routeService.getQueryParameterValue('query')
]).pipe(
filter(([source, query]) => source && query && source !== '' && query !== ''),
filter(([source, query]) => source !== this.routeData.sourceId || query !== this.routeData.query),
switchMap(([source, query]) => {
this.routeData.sourceId = source;
this.routeData.query = query;
this.isLoading$.next(true);
return this.searchConfigService.paginatedSearchOptions.pipe(
switchMap((searchOptions: PaginatedSearchOptions) => {
return this.externalService.getExternalSourceEntries(this.routeData.sourceId, searchOptions);
})
)
}),
).subscribe((rdData) => {
this.entriesRD$.next(rdData);
this.isLoading$.next(false);
});
}
/**
* Get the data from the searchbar and changes the router data.
*/
public getExternalsourceData(event: ExternalSourceData): void {
this.router.navigate(
[],
{
queryParams: { source: event.sourceId, query: event.query },
replaceUrl: true
}
);
}
/**
* Display an item preview by opening up an import modal window.
* @param entry The entry to import
*/
public import(entry): void {
this.modalRef = this.modalService.open(SubmissionImportExternalPreviewComponent, {
size: 'lg',
});
const modalComp = this.modalRef.componentInstance;
modalComp.externalSourceEntry = entry;
}
}

View File

@@ -30,6 +30,7 @@ import { SubmissionSubmitComponent } from './submit/submission-submit.component'
import { storeModuleConfig } from '../app.reducer';
import { SubmissionImportExternalComponent } from './import-external/submission-import-external.component';
import { SubmissionImportExternalSearchbarComponent } from './import-external/import-external-searchbar/submission-import-external-searchbar.component';
import { SubmissionImportExternalPreviewComponent } from './import-external/import-external-preview/submission-import-external-preview.component';
@NgModule({
imports: [
@@ -59,12 +60,15 @@ import { SubmissionImportExternalSearchbarComponent } from './import-external/im
SubmissionSectionUploadFileViewComponent,
SubmissionImportExternalComponent,
SubmissionImportExternalSearchbarComponent,
SubmissionImportExternalPreviewComponent
],
entryComponents: [
SubmissionSectionUploadComponent,
SubmissionSectionformComponent,
SubmissionSectionLicenseComponent,
SubmissionSectionContainerComponent],
SubmissionSectionContainerComponent,
SubmissionImportExternalPreviewComponent
],
exports: [
SubmissionEditComponent,
SubmissionFormComponent,

View File

@@ -2402,6 +2402,8 @@
"submission.import-external.title": "Import metadata from an external source",
"submission.import-external.page.hint": "Search items using external sources.",
"submission.import-external.back-to-my-dspace": "Back to MyDSpace",
"submission.import-external.search.placeholder": "Search the external source",
@@ -2424,6 +2426,12 @@
"submission.import-external.source.lcname": "Library of Congress Names",
"submission.import-external.preview.title": "Item Preview",
"submission.import-external.preview.subtitle": "The metadata below was imported from an external source. It will be pre-filled when you start the submission.",
"submission.import-external.preview.button.import": "Start submission",
"submission.sections.describe.relationship-lookup.close": "Close",
@@ -2555,6 +2563,8 @@
"submission.sections.describe.relationship-lookup.selection-tab.title.orcidV2": "Search Results",
"submission.sections.describe.relationship-lookup.selection-tab.title.orcidv2": "Search Results",
"submission.sections.describe.relationship-lookup.selection-tab.title.lcname": "Search Results",
"submission.sections.describe.relationship-lookup.name-variant.notification.content": "Would you like to save \"{{ value }}\" as a name variant for this person so you and others can reuse it for future submissions? If you don\'t you can still use it for this submission.",