diff --git a/src/app/+import-external-page/import-external-page.component.html b/src/app/+import-external-page/import-external-page.component.html new file mode 100644 index 0000000000..5edccd55cb --- /dev/null +++ b/src/app/+import-external-page/import-external-page.component.html @@ -0,0 +1 @@ + diff --git a/src/app/+import-external-page/import-external-page.component.scss b/src/app/+import-external-page/import-external-page.component.scss new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..e673959a32 --- /dev/null +++ b/src/app/+import-external-page/import-external-page.component.spec.ts @@ -0,0 +1,26 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ImportExternalPageComponent } from './import-external-page.component'; + +fdescribe('ImportExternalPageComponent', () => { + let component: ImportExternalPageComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ImportExternalPageComponent ], + schemas: [NO_ERRORS_SCHEMA] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ImportExternalPageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create ImportExternalPageComponent', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/+import-external-page/import-external-page.component.ts b/src/app/+import-external-page/import-external-page.component.ts new file mode 100644 index 0000000000..c205482cb3 --- /dev/null +++ b/src/app/+import-external-page/import-external-page.component.ts @@ -0,0 +1,13 @@ +import { Component, Injector, OnInit } from '@angular/core'; + +/** + * Component representing the external import page of the submission. + */ +@Component({ + selector: 'ds-import-external-page', + templateUrl: './import-external-page.component.html', + styleUrls: ['./import-external-page.component.scss'] +}) +export class ImportExternalPageComponent { + +} diff --git a/src/app/+import-external-page/import-external-page.module.ts b/src/app/+import-external-page/import-external-page.module.ts new file mode 100644 index 0000000000..c81e80615f --- /dev/null +++ b/src/app/+import-external-page/import-external-page.module.ts @@ -0,0 +1,28 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; + +import { SharedModule } from '../shared/shared.module'; +import { CoreModule } from '../core/core.module'; +import { ImportExternalRoutingModule } from './import-external-routing.module'; +import { SubmissionImportExternalComponent } from '../submission/import-external/submission-import-external.component'; +import { SubmissionImportExternalSearchbarComponent } from '../submission/import-external/import-external-searchbar/submission-import-external-searchbar.component'; +import { SubmissionModule } from '../submission/submission.module'; + +@NgModule({ + imports: [ + CommonModule, + SharedModule, + CoreModule.forRoot(), + ImportExternalRoutingModule, + SubmissionModule, + ], + declarations: [ ], + entryComponents: [ ] +}) + +/** + * This module handles all components that are necessary for the submission external import page + */ +export class ImportExternalPageModule { + +} diff --git a/src/app/+import-external-page/import-external-routing.module.ts b/src/app/+import-external-page/import-external-routing.module.ts new file mode 100644 index 0000000000..91cdbf9877 --- /dev/null +++ b/src/app/+import-external-page/import-external-routing.module.ts @@ -0,0 +1,24 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; +import { SubmissionImportExternalComponent } from '../submission/import-external/submission-import-external.component'; + +@NgModule({ + imports: [ + RouterModule.forChild([ + { + canActivate: [ AuthenticatedGuard ], + path: '', + component: SubmissionImportExternalComponent, + pathMatch: 'full', + data: { + title: 'submission.import-external.page.title' + } + } + ]) + ], + providers: [ ] +}) +export class ImportExternalRoutingModule { + +} diff --git a/src/app/+my-dspace-page/my-dspace-new-submission/my-dspace-new-submission.component.html b/src/app/+my-dspace-page/my-dspace-new-submission/my-dspace-new-submission.component.html index 911ba26b31..3adaea7128 100644 --- a/src/app/+my-dspace-page/my-dspace-new-submission/my-dspace-new-submission.component.html +++ b/src/app/+my-dspace-page/my-dspace-new-submission/my-dspace-new-submission.component.html @@ -10,6 +10,9 @@ {{'mydspace.new-submission' | translate}} + + {{'mydspace.new-submission-external' | translate}} + diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 0ba0851e4e..655f0f94fc 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -86,6 +86,7 @@ export function getDSOPath(dso: DSpaceObject): string { { path: 'login', loadChildren: './+login-page/login-page.module#LoginPageModule' }, { path: 'logout', loadChildren: './+logout-page/logout-page.module#LogoutPageModule' }, { path: 'submit', loadChildren: './+submit-page/submit-page.module#SubmitPageModule' }, + { path: 'import-external', loadChildren: './+import-external-page/import-external-page.module#ImportExternalPageModule' }, { path: 'workspaceitems', loadChildren: './+workspaceitems-edit-page/workspaceitems-edit-page.module#WorkspaceitemsEditPageModule' diff --git a/src/app/core/data/external-source.service.ts b/src/app/core/data/external-source.service.ts index 0c1a8d255c..8ad0494f3d 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 } from 'rxjs/operators'; +import { distinctUntilChanged, map, switchMap, take, flatMap } from 'rxjs/operators'; import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model'; import { hasValue, isNotEmptyOperator } from '../../shared/empty.util'; import { configureRequest } from '../shared/operators'; @@ -19,6 +19,7 @@ import { RemoteData } from './remote-data'; import { PaginatedList } from './paginated-list'; import { ExternalSourceEntry } from '../shared/external-source-entry.model'; import { DefaultChangeAnalyzer } from './default-change-analyzer.service'; +import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; /** * A service handling all external source requests @@ -59,6 +60,20 @@ export class ExternalSourceService extends DataService { ); } + /** + * Return the list of external sources. + * + * @param options + * Find list options object. + * @param linksToFollow + * List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved. + * @return Observable>> + * The list of the external sources. + */ + getAllExternalSources(options: FindListOptions = {}, ...linksToFollow: Array>): Observable>> { + return this.findAll(options, ...linksToFollow); + } + /** * Get the entries for an external source * @param externalSourceId The id of the external source to fetch entries for diff --git a/src/app/submission/import-external/import-external-searchbar/submission-import-external-searchbar.component.html b/src/app/submission/import-external/import-external-searchbar/submission-import-external-searchbar.component.html new file mode 100644 index 0000000000..f7a971a381 --- /dev/null +++ b/src/app/submission/import-external/import-external-searchbar/submission-import-external-searchbar.component.html @@ -0,0 +1,24 @@ +
+ +
+
+ + + +
+
+
diff --git a/src/app/submission/import-external/import-external-searchbar/submission-import-external-searchbar.component.scss b/src/app/submission/import-external/import-external-searchbar/submission-import-external-searchbar.component.scss new file mode 100644 index 0000000000..076ceae9b8 --- /dev/null +++ b/src/app/submission/import-external/import-external-searchbar/submission-import-external-searchbar.component.scss @@ -0,0 +1,12 @@ +.input-group-append .btn.btn { + margin-left: -1px; +} + +.input-group-append .dropdown-toggle { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.w-fx { + min-width: 200px; +} 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 new file mode 100644 index 0000000000..e13c8dad3b --- /dev/null +++ b/src/app/submission/import-external/import-external-searchbar/submission-import-external-searchbar.component.ts @@ -0,0 +1,143 @@ +import { Component, OnInit, ChangeDetectorRef, Output, EventEmitter } from '@angular/core'; +import { of as observableOf } from 'rxjs'; +import { ExternalSourceService } from '../../../core/data/external-source.service'; +import { catchError, first, tap } from 'rxjs/operators'; +import { ExternalSource } from '../../../core/shared/external-source.model'; +import { PaginatedList } from '../../../core/data/paginated-list'; +import { RemoteData } from '../../../core/data/remote-data'; +import { PageInfo } from '../../../core/shared/page-info.model'; +import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils'; +import { FindListOptions } from '../../../core/data/request.models'; + +/** + * Interface for the selected external source element. + */ +interface SourceElement { + id: string; + name: string; +} + +/** + * Interface for the external source data to export. + */ +export interface ExternalSourceData { + query: string; + sourceId: string; +} + +/** + * This component builds the searchbar for the submission external import. + */ +@Component({ + selector: 'ds-submission-import-external-searchbar', + styleUrls: ['./submission-import-external-searchbar.component.scss'], + templateUrl: './submission-import-external-searchbar.component.html' +}) +export class SubmissionImportExternalSearchbarComponent implements OnInit { + /** + * The selected external sources. + */ + public selectedElement: SourceElement; + /** + * The list of external sources. + */ + public sourceList: SourceElement[] = []; + /** + * The string used to search items in the external sources. + */ + public searchString: string; + /** + * The external sources loading status. + */ + public sourceListloading = false; + /** + * The external source data to use to perform the search. + */ + @Output() public externalSourceData: EventEmitter = new EventEmitter(); + + /** + * The external sources pagination data. + */ + protected pageInfo: PageInfo; + /** + * The options for REST data retireval. + */ + protected findListOptions: FindListOptions; + + /** + * Initialize the component variables. + * @param {ExternalSourceService} externalService + * @param {ChangeDetectorRef} cdr + */ + constructor( + private externalService: ExternalSourceService, + private cdr: ChangeDetectorRef, + ) { } + + /** + * Component intitialization and retireve first page of external sources. + */ + ngOnInit() { + this.searchString = ''; + this.selectedElement = {id: '', name: 'loading'}; + this.findListOptions = { + elementsPerPage: 5, + currentPage: 0, + }; + this.externalService.getAllExternalSources(this.findListOptions).pipe( + catchError(() => { + const pageInfo = new PageInfo(); + const paginatedList = new PaginatedList(pageInfo, []); + const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList); + return observableOf(paginatedListRD); + }), + first() + ).subscribe((externalSource: RemoteData>) => { + externalSource.payload.page.forEach((element) => { + this.sourceList.push({id: element.id, name: element.name}); + this.selectedElement = this.sourceList[0]; + }); + this.pageInfo = externalSource.payload.pageInfo; + this.cdr.detectChanges(); + }); + } + + /** + * Set the selected external source. + */ + makeSourceSelection(source) { + this.selectedElement = source; + } + + /** + * Load the next pages of external sources. + */ + onScroll() { + if (!this.sourceListloading && this.pageInfo.currentPage <= this.pageInfo.totalPages) { + this.sourceListloading = true; + this.findListOptions.currentPage++; + this.externalService.getAllExternalSources(this.findListOptions).pipe( + catchError(() => { + const pageInfo = new PageInfo(); + const paginatedList = new PaginatedList(pageInfo, []); + const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList); + return observableOf(paginatedListRD); + }), + tap(() => this.sourceListloading = false)) + .subscribe((externalSource: RemoteData>) => { + externalSource.payload.page.forEach((element) => { + this.sourceList.push({id: element.id, name: element.name}); + }) + this.pageInfo = externalSource.payload.pageInfo; + this.cdr.detectChanges(); + }) + } + } + + /** + * Passes the search parameters to the parent component. + */ + public search() { + this.externalSourceData.emit({sourceId: this.selectedElement.id, query: this.searchString}); + } +} diff --git a/src/app/submission/import-external/submission-import-external.component.html b/src/app/submission/import-external/submission-import-external.component.html new file mode 100644 index 0000000000..1d2c7cb7c7 --- /dev/null +++ b/src/app/submission/import-external/submission-import-external.component.html @@ -0,0 +1,24 @@ +
+
+
+ + + +
+
+
+
+ DATA
+ {{(externalSourceData)?externalSourceData.sourceId:''}} : {{(externalSourceData)?externalSourceData.query:''}} +
+
+ +
diff --git a/src/app/submission/import-external/submission-import-external.component.scss b/src/app/submission/import-external/submission-import-external.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/submission/import-external/submission-import-external.component.ts b/src/app/submission/import-external/submission-import-external.component.ts new file mode 100644 index 0000000000..2b53961540 --- /dev/null +++ b/src/app/submission/import-external/submission-import-external.component.ts @@ -0,0 +1,35 @@ +import { Component, OnInit, ChangeDetectorRef } from '@angular/core'; +import { Observable, of as observableOf } from 'rxjs'; +import { ExternalSourceService } from '../../core/data/external-source.service'; +import { ExternalSourceData } from './import-external-searchbar/submission-import-external-searchbar.component'; + +/** + * This component allows to submit a new workspaceitem importing the data from an external source. + */ +@Component({ + selector: 'ds-submission-import-external', + styleUrls: ['./submission-import-external.component.scss'], + templateUrl: './submission-import-external.component.html' +}) +export class SubmissionImportExternalComponent { + /** + * The external source search data. + */ + public externalSourceData: ExternalSourceData; + + /** + * Initialize the component variables. + * @param {ExternalSourceService} externalService + */ + constructor( + private externalService: ExternalSourceService, + private cdr: ChangeDetectorRef, + ) { } + + /** + * Get the data to submit a search. + */ + public getExternalsourceData(event: ExternalSourceData) { + this.externalSourceData = event; + } +} diff --git a/src/app/submission/submission.module.ts b/src/app/submission/submission.module.ts index 806258baa8..f7accaf0ca 100644 --- a/src/app/submission/submission.module.ts +++ b/src/app/submission/submission.module.ts @@ -28,7 +28,8 @@ import { SubmissionSectionUploadFileViewComponent } from './sections/upload/file import { SubmissionSectionUploadAccessConditionsComponent } from './sections/upload/accessConditions/submission-section-upload-access-conditions.component'; import { SubmissionSubmitComponent } from './submit/submission-submit.component'; import { storeModuleConfig } from '../app.reducer'; -import { CoreState } from '../core/core.reducers'; +import { SubmissionImportExternalComponent } from './import-external/submission-import-external.component'; +import { SubmissionImportExternalSearchbarComponent } from './import-external/import-external-searchbar/submission-import-external-searchbar.component'; @NgModule({ imports: [ @@ -55,7 +56,9 @@ import { CoreState } from '../core/core.reducers'; SubmissionUploadFilesComponent, SubmissionSectionUploadFileComponent, SubmissionSectionUploadFileEditComponent, - SubmissionSectionUploadFileViewComponent + SubmissionSectionUploadFileViewComponent, + SubmissionImportExternalComponent, + SubmissionImportExternalSearchbarComponent, ], entryComponents: [ SubmissionSectionUploadComponent, diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 4173fa1cf2..bd0f23d24e 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1767,6 +1767,8 @@ "mydspace.new-submission": "New submission", + "mydspace.new-submission-external": "Import from external source", + "mydspace.results.head": "Your submissions", "mydspace.results.no-abstract": "No Abstract", @@ -2396,6 +2398,32 @@ "submission.general.save-later": "Save for later", + "submission.import-external.page.title": "Import metadata from an external source", + + "submission.import-external.title": "Import metadata from an external source", + + "submission.import-external.back-to-my-dspace": "Back to MyDSpace", + + "submission.import-external.search.placeholder": "Search the external source", + + "submission.import-external.search.button": "Search", + + "submission.import-external.search.button.hint": "Write some words to search", + + "submission.import-external.search.source.hint": "Pick an external source", + + "submission.import-external.source.loading": "Loading ...", + + "submission.import-external.source.sherpaJournal": "SHERPA Journals", + + "submission.import-external.source.sherpaPublisher": "SHERPA Publishers", + + "submission.import-external.source.orcidV2": "ORCID", + + "submission.import-external.source.pubmed": "Pubmed", + + "submission.import-external.source.lcname": "Library of Congress Names", + "submission.sections.describe.relationship-lookup.close": "Close",