diff --git a/src/app/+import-external-page/import-external-page.component.spec.ts b/src/app/+import-external-page/import-external-page.component.spec.ts index e673959a32..5a2b7c5f8e 100644 --- a/src/app/+import-external-page/import-external-page.component.spec.ts +++ b/src/app/+import-external-page/import-external-page.component.spec.ts @@ -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; diff --git a/src/app/core/data/external-source.service.spec.ts b/src/app/core/data/external-source.service.spec.ts index 7c7d676bc7..2fc4589bed 100644 --- a/src/app/core/data/external-source.service.spec.ts +++ b/src/app/core/data/external-source.service.spec.ts @@ -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); + }); + }); }); diff --git a/src/app/core/data/external-source.service.ts b/src/app/core/data/external-source.service.ts index 8ad0494f3d..eda0440b4a 100644 --- a/src/app/core/data/external-source.service.ts +++ b/src/app/core/data/external-source.service.ts @@ -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 { ); } + /** + * 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> + * The list of the external sources. + */ + getExternalSource(id: string, ...linksToFollow: Array>): Observable> { + return this.findById(id, ...linksToFollow); + } + /** * Return the list of external sources. * diff --git a/src/app/submission/import-external/import-external-searchbar/submission-import-external-searchbar.component.ts b/src/app/submission/import-external/import-external-searchbar/submission-import-external-searchbar.component.ts index 039d11de30..cd2f24eb2c 100644 --- a/src/app/submission/import-external/import-external-searchbar/submission-import-external-searchbar.component.ts +++ b/src/app/submission/import-external/import-external-searchbar/submission-import-external-searchbar.component.ts @@ -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) => { - 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(); }); diff --git a/src/app/submission/import-external/submission-import-external.component.html b/src/app/submission/import-external/submission-import-external.component.html index 43e1c8ba71..2aac8557aa 100644 --- a/src/app/submission/import-external/submission-import-external.component.html +++ b/src/app/submission/import-external/submission-import-external.component.html @@ -3,15 +3,36 @@
-
- DATA
- {{(externalSourceData)?externalSourceData.sourceId:''}} : {{(externalSourceData)?externalSourceData.query:''}} +
+ +

{{ 'submission.sections.describe.relationship-lookup.selection-tab.title.' + routeData.sourceId | translate}}

+ + + +
+ {{ 'search.results.empty' | translate }} +
+
+
+
+
+

{{'submission.import-external.page.hint' | translate}}

+
diff --git a/src/app/submission/import-external/submission-import-external.component.ts b/src/app/submission/import-external/submission-import-external.component.ts index 2b53961540..47ae781baa 100644 --- a/src/app/submission/import-external/submission-import-external.component.ts +++ b/src/app/submission/import-external/submission-import-external.component.ts @@ -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>>; + /** + * TRUE if the REST service is called to retrieve the external source items + */ + public isLoading$: BehaviorSubject; + /** + * 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; } } diff --git a/src/app/submission/submission.module.ts b/src/app/submission/submission.module.ts index f7accaf0ca..7ff8b98d9e 100644 --- a/src/app/submission/submission.module.ts +++ b/src/app/submission/submission.module.ts @@ -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, diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index bd0f23d24e..3e2994142d 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -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.",