Submission from an external source initial commit

This commit is contained in:
Matteo Perelli
2020-06-18 16:16:31 +02:00
parent 8b8d7ba91a
commit fb40b8f031
17 changed files with 383 additions and 3 deletions

View File

@@ -0,0 +1 @@
<ds-submission-import-external></ds-submission-import-external>

View File

@@ -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<ImportExternalPageComponent>;
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();
});
});

View File

@@ -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 {
}

View File

@@ -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 {
}

View File

@@ -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 {
}

View File

@@ -10,6 +10,9 @@
<a class="btn btn-lg btn-primary mt-1 ml-2" [routerLink]="['/submit']" role="button">
<i class="fa fa-plus-circle" aria-hidden="true"></i> {{'mydspace.new-submission' | translate}}
</a>
<a class="btn btn-lg btn-primary mt-1 ml-2" [routerLink]="['/import-external']" role="button">
<i class="fa fa-file-import" aria-hidden="true"></i> {{'mydspace.new-submission-external' | translate}}
</a>
</div>
</div>

View File

@@ -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'

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 } 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<ExternalSource> {
);
}
/**
* 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<RemoteData<PaginatedList<ExternalSource>>>
* The list of the external sources.
*/
getAllExternalSources(options: FindListOptions = {}, ...linksToFollow: Array<FollowLinkConfig<ExternalSource>>): Observable<RemoteData<PaginatedList<ExternalSource>>> {
return this.findAll(options, ...linksToFollow);
}
/**
* Get the entries for an external source
* @param externalSourceId The id of the external source to fetch entries for

View File

@@ -0,0 +1,24 @@
<div class="input-group mb-5">
<input type="text" class="form-control" (keyup.enter)="(searchString === '')?null:search()" [(ngModel)]="searchString" placeholder="{{'submission.import-external.search.placeholder' |translate}}" aria-label="" aria-describedby="">
<div class="input-group-append">
<div class="btn-group" ngbDropdown role="group" aria-label="">
<button class="btn btn-outline-secondary w-fx" title="{{'submission.import-external.search.source.hint' |translate}}" ngbDropdownToggle>{{'submission.import-external.source.' + selectedElement.name | translate}}</button>
<div ngbDropdownMenu class="dropdown-menu scrollable-dropdown-menu w-100"
aria-haspopup="true"
aria-expanded="false"
aria-labelledby="scrollableDropdownMenuButton">
<div class="scrollable-menu"
aria-labelledby="scrollableDropdownMenuButton"
infiniteScroll
[infiniteScrollDistance]="2"
[infiniteScrollThrottle]="50"
(scrolled)="onScroll()"
[scrollWindow]="false">
<button ngbDropdownItem class="dropdown-item text-truncate" title="{{'submission.import-external.source.' + source.name | translate}}" (click)="makeSourceSelection(source)" *ngFor="let source of sourceList">{{'submission.import-external.source.' + source.name | translate}}</button>
<div ngbDropdownItem class="scrollable-dropdown-loading text-center" *ngIf="sourceListloading"><p>{{'submission.import-external.source.loading' | translate}}</p></div>
</div>
</div>
<button type="button" class="btn btn-outline-secondary" [title]="(searchString === '')?('submission.import-external.search.button.hint' | translate):('submission.import-external.search.button' | translate)" [disabled]="searchString === ''" (click)="search()">{{'submission.import-external.search.button' | translate}}</button>
</div>
</div>
</div>

View File

@@ -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;
}

View File

@@ -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<ExternalSourceData> = new EventEmitter<ExternalSourceData>();
/**
* 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<PaginatedList<ExternalSource>>) => {
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<PaginatedList<ExternalSource>>) => {
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});
}
}

View File

@@ -0,0 +1,24 @@
<div class="container">
<div class="row">
<div class="col-md-12">
<h2 id="header" class="pb-2">{{'submission.import-external.title' | translate}}</h2>
<ds-submission-import-external-searchbar
(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>
</div>
<div class="row">
<div class="col-md-12">
<hr>
<a class="btn btn-outline-secondary" [routerLink]="['/mydspace']" role="button">
<i class="fa fa-chevron-left" aria-hidden="true"></i> {{'submission.import-external.back-to-my-dspace' | translate}}
</a>
</div>
</div>
</div>

View File

@@ -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;
}
}

View File

@@ -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,

View File

@@ -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",