mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Merge branch 'w2p-72699_Hard-redirect-after-log-in' into Hard-redirect-after-log-in
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
<ds-submission-import-external></ds-submission-import-external>
|
@@ -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';
|
||||||
|
|
||||||
|
describe('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();
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,13 @@
|
|||||||
|
import { Component } 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 {
|
||||||
|
|
||||||
|
}
|
29
src/app/+import-external-page/import-external-page.module.ts
Normal file
29
src/app/+import-external-page/import-external-page.module.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
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 { SubmissionModule } from '../submission/submission.module';
|
||||||
|
import { ImportExternalPageComponent } from './import-external-page.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
SharedModule,
|
||||||
|
CoreModule.forRoot(),
|
||||||
|
ImportExternalRoutingModule,
|
||||||
|
SubmissionModule,
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
ImportExternalPageComponent
|
||||||
|
],
|
||||||
|
entryComponents: [ ]
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This module handles all components that are necessary for the submission external import page
|
||||||
|
*/
|
||||||
|
export class ImportExternalPageModule {
|
||||||
|
|
||||||
|
}
|
@@ -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 {
|
||||||
|
|
||||||
|
}
|
@@ -8,9 +8,14 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="add">
|
<div class="add">
|
||||||
<button class="btn btn-lg btn-primary mt-1 ml-2" (click)="openDialog()" role="button">
|
<button class="btn btn-lg btn-primary mt-1 ml-2" (click)="openDialog()" role="button" title="{{'mydspace.new-submission' | translate}}">
|
||||||
<i class="fa fa-plus-circle" aria-hidden="true"></i> {{'mydspace.new-submission' | translate}}
|
<i class="fa fa-plus-circle" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="add">
|
||||||
|
<a class="btn btn-lg btn-primary mt-1 ml-2" [routerLink]="['/import-external']" role="button" title="{{'mydspace.new-submission-external' | translate}}">
|
||||||
|
<i class="fa fa-file-import" aria-hidden="true"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@@ -22,6 +22,8 @@ import { getMockScrollToService } from '../../shared/mocks/scroll-to-service.moc
|
|||||||
import { UploaderService } from '../../shared/uploader/uploader.service';
|
import { UploaderService } from '../../shared/uploader/uploader.service';
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { HostWindowService } from '../../shared/host-window.service';
|
||||||
|
import { HostWindowServiceStub } from '../../shared/testing/host-window-service.stub';
|
||||||
import { UploaderComponent } from 'src/app/shared/uploader/uploader.component';
|
import { UploaderComponent } from 'src/app/shared/uploader/uploader.component';
|
||||||
|
|
||||||
describe('MyDSpaceNewSubmissionComponent test', () => {
|
describe('MyDSpaceNewSubmissionComponent test', () => {
|
||||||
@@ -73,7 +75,8 @@ describe('MyDSpaceNewSubmissionComponent test', () => {
|
|||||||
{ provide: NgbModal, useValue: modalService },
|
{ provide: NgbModal, useValue: modalService },
|
||||||
ChangeDetectorRef,
|
ChangeDetectorRef,
|
||||||
MyDSpaceNewSubmissionComponent,
|
MyDSpaceNewSubmissionComponent,
|
||||||
UploaderService
|
UploaderService,
|
||||||
|
{ provide: HostWindowService, useValue: new HostWindowServiceStub(800) },
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
|
@@ -3,6 +3,8 @@ import { ChangeDetectorRef, Component, EventEmitter, OnDestroy, OnInit, Output,
|
|||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { first } from 'rxjs/operators';
|
import { first } from 'rxjs/operators';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
|
||||||
import { AuthService } from '../../core/auth/auth.service';
|
import { AuthService } from '../../core/auth/auth.service';
|
||||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
@@ -12,11 +14,10 @@ import { HALEndpointService } from '../../core/shared/hal-endpoint.service';
|
|||||||
import { NotificationType } from '../../shared/notifications/models/notification-type';
|
import { NotificationType } from '../../shared/notifications/models/notification-type';
|
||||||
import { hasValue } from '../../shared/empty.util';
|
import { hasValue } from '../../shared/empty.util';
|
||||||
import { SearchResult } from '../../shared/search/search-result.model';
|
import { SearchResult } from '../../shared/search/search-result.model';
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
import { CreateItemParentSelectorComponent } from '../../shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component';
|
||||||
import { CreateItemParentSelectorComponent } from 'src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component';
|
|
||||||
import { CollectionSelectorComponent } from '../collection-selector/collection-selector.component';
|
import { CollectionSelectorComponent } from '../collection-selector/collection-selector.component';
|
||||||
import { UploaderComponent } from 'src/app/shared/uploader/uploader.component';
|
import { UploaderComponent } from '../../shared/uploader/uploader.component';
|
||||||
import { UploaderError } from 'src/app/shared/uploader/uploader-error.model';
|
import { UploaderError } from '../../shared/uploader/uploader-error.model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component represents the whole mydspace page header
|
* This component represents the whole mydspace page header
|
||||||
@@ -56,6 +57,8 @@ export class MyDSpaceNewSubmissionComponent implements OnDestroy, OnInit {
|
|||||||
* @param {NotificationsService} notificationsService
|
* @param {NotificationsService} notificationsService
|
||||||
* @param {Store<SubmissionState>} store
|
* @param {Store<SubmissionState>} store
|
||||||
* @param {TranslateService} translate
|
* @param {TranslateService} translate
|
||||||
|
* @param {Router} router
|
||||||
|
* @param {NgbModal} modalService
|
||||||
*/
|
*/
|
||||||
constructor(private authService: AuthService,
|
constructor(private authService: AuthService,
|
||||||
private changeDetectorRef: ChangeDetectorRef,
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
|
@@ -48,6 +48,7 @@ import { ReloadGuard } from './core/reload/reload.guard';
|
|||||||
{ path: 'login', loadChildren: './+login-page/login-page.module#LoginPageModule' },
|
{ path: 'login', loadChildren: './+login-page/login-page.module#LoginPageModule' },
|
||||||
{ path: 'logout', loadChildren: './+logout-page/logout-page.module#LogoutPageModule' },
|
{ path: 'logout', loadChildren: './+logout-page/logout-page.module#LogoutPageModule' },
|
||||||
{ path: 'submit', loadChildren: './+submit-page/submit-page.module#SubmitPageModule' },
|
{ path: 'submit', loadChildren: './+submit-page/submit-page.module#SubmitPageModule' },
|
||||||
|
{ path: 'import-external', loadChildren: './+import-external-page/import-external-page.module#ImportExternalPageModule' },
|
||||||
{
|
{
|
||||||
path: 'workspaceitems',
|
path: 'workspaceitems',
|
||||||
loadChildren: './+workspaceitems-edit-page/workspaceitems-edit-page.module#WorkspaceitemsEditPageModule'
|
loadChildren: './+workspaceitems-edit-page/workspaceitems-edit-page.module#WorkspaceitemsEditPageModule'
|
||||||
|
@@ -19,6 +19,9 @@ export class AuthBlockingGuard implements CanActivate {
|
|||||||
constructor(private store: Store<AppState>) {
|
constructor(private store: Store<AppState>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True when the authentication isn't blocking everything
|
||||||
|
*/
|
||||||
canActivate(): Observable<boolean> {
|
canActivate(): Observable<boolean> {
|
||||||
return this.store.pipe(select(isAuthenticationBlocking)).pipe(
|
return this.store.pipe(select(isAuthenticationBlocking)).pipe(
|
||||||
map((isBlocking: boolean) => isBlocking === false),
|
map((isBlocking: boolean) => isBlocking === false),
|
||||||
|
@@ -19,6 +19,7 @@ import { RemoteData } from './remote-data';
|
|||||||
import { PaginatedList } from './paginated-list';
|
import { PaginatedList } from './paginated-list';
|
||||||
import { ExternalSourceEntry } from '../shared/external-source-entry.model';
|
import { ExternalSourceEntry } from '../shared/external-source-entry.model';
|
||||||
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
|
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
|
||||||
|
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A service handling all external source requests
|
* A service handling all external source requests
|
||||||
|
@@ -24,7 +24,7 @@ interface CollectionListEntryItem {
|
|||||||
/**
|
/**
|
||||||
* An interface to represent an entry in the collection list
|
* An interface to represent an entry in the collection list
|
||||||
*/
|
*/
|
||||||
interface CollectionListEntry {
|
export interface CollectionListEntry {
|
||||||
communities: CollectionListEntryItem[],
|
communities: CollectionListEntryItem[],
|
||||||
collection: CollectionListEntryItem
|
collection: CollectionListEntryItem
|
||||||
}
|
}
|
||||||
|
59
src/app/shared/mocks/external-source.service.mock.ts
Normal file
59
src/app/shared/mocks/external-source.service.mock.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { ExternalSourceService } from '../../core/data/external-source.service';
|
||||||
|
import { ExternalSource } from '../../core/shared/external-source.model';
|
||||||
|
import { ResourceType } from '../../core/shared/resource-type';
|
||||||
|
|
||||||
|
export const externalSourceOrcid: ExternalSource = {
|
||||||
|
type: new ResourceType('externalsource'),
|
||||||
|
id: 'orcid',
|
||||||
|
name: 'orcid',
|
||||||
|
hierarchical: false,
|
||||||
|
_links: {
|
||||||
|
entries: {
|
||||||
|
href: 'https://dspace7.4science.cloud/server/api/integration/externalsources/orcid/entries'
|
||||||
|
},
|
||||||
|
self: {
|
||||||
|
href: 'https://dspace7.4science.cloud/server/api/integration/externalsources/orcid'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const externalSourceCiencia: ExternalSource = {
|
||||||
|
type: new ResourceType('externalsource'),
|
||||||
|
id: 'ciencia',
|
||||||
|
name: 'ciencia',
|
||||||
|
hierarchical: false,
|
||||||
|
_links: {
|
||||||
|
entries: {
|
||||||
|
href: 'https://dspace7.4science.cloud/server/api/integration/externalsources/ciencia/entries'
|
||||||
|
},
|
||||||
|
self: {
|
||||||
|
href: 'https://dspace7.4science.cloud/server/api/integration/externalsources/ciencia'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const externalSourceMyStaffDb: ExternalSource = {
|
||||||
|
type: new ResourceType('externalsource'),
|
||||||
|
id: 'my_staff_db',
|
||||||
|
name: 'my_staff_db',
|
||||||
|
hierarchical: false,
|
||||||
|
_links: {
|
||||||
|
entries: {
|
||||||
|
href: 'https://dspace7.4science.cloud/server/api/integration/externalsources/my_staff_db/entries'
|
||||||
|
},
|
||||||
|
self: {
|
||||||
|
href: 'https://dspace7.4science.cloud/server/api/integration/externalsources/my_staff_db'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock for [[ExternalSourceService]]
|
||||||
|
*/
|
||||||
|
export function getMockExternalSourceService():
|
||||||
|
ExternalSourceService {
|
||||||
|
return jasmine.createSpyObj('ExternalSourceService', {
|
||||||
|
findAll: jasmine.createSpy('findAll'),
|
||||||
|
getExternalSourceEntries: jasmine.createSpy('getExternalSourceEntries'),
|
||||||
|
});
|
||||||
|
}
|
@@ -478,6 +478,7 @@ const ENTRY_COMPONENTS = [
|
|||||||
ClaimedTaskActionsRejectComponent,
|
ClaimedTaskActionsRejectComponent,
|
||||||
ClaimedTaskActionsReturnToPoolComponent,
|
ClaimedTaskActionsReturnToPoolComponent,
|
||||||
ClaimedTaskActionsEditMetadataComponent,
|
ClaimedTaskActionsEditMetadataComponent,
|
||||||
|
CollectionDropdownComponent,
|
||||||
FileDownloadLinkComponent,
|
FileDownloadLinkComponent,
|
||||||
CurationFormComponent,
|
CurationFormComponent,
|
||||||
ExportMetadataSelectorComponent,
|
ExportMetadataSelectorComponent,
|
||||||
|
@@ -2,6 +2,7 @@ export class SubmissionServiceStub {
|
|||||||
|
|
||||||
changeSubmissionCollection = jasmine.createSpy('changeSubmissionCollection');
|
changeSubmissionCollection = jasmine.createSpy('changeSubmissionCollection');
|
||||||
createSubmission = jasmine.createSpy('createSubmission');
|
createSubmission = jasmine.createSpy('createSubmission');
|
||||||
|
createSubmissionFromExternalSource = jasmine.createSpy('createSubmissionFromExternalSource');
|
||||||
depositSubmission = jasmine.createSpy('depositSubmission');
|
depositSubmission = jasmine.createSpy('depositSubmission');
|
||||||
discardSubmission = jasmine.createSpy('discardSubmission');
|
discardSubmission = jasmine.createSpy('discardSubmission');
|
||||||
dispatchInit = jasmine.createSpy('dispatchInit');
|
dispatchInit = jasmine.createSpy('dispatchInit');
|
||||||
|
@@ -0,0 +1,11 @@
|
|||||||
|
<div>
|
||||||
|
<div class="modal-header">{{'dso-selector.create.collection.head' | translate}}
|
||||||
|
<button type="button" class="close" (click)="closeCollectionModal()" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<ds-collection-dropdown (selectionChange)="selectObject($event)">
|
||||||
|
</ds-collection-dropdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -0,0 +1,3 @@
|
|||||||
|
.close:focus {
|
||||||
|
outline: none !important;
|
||||||
|
}
|
@@ -0,0 +1,89 @@
|
|||||||
|
import { Component, NO_ERRORS_SCHEMA, EventEmitter } from '@angular/core';
|
||||||
|
import { async, TestBed, ComponentFixture, inject } from '@angular/core/testing';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { createTestComponent } from '../../../shared/testing/utils.test';
|
||||||
|
import { SubmissionImportExternalCollectionComponent } from './submission-import-external-collection.component';
|
||||||
|
import { CollectionListEntry } from '../../../shared/collection-dropdown/collection-dropdown.component';
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
|
||||||
|
describe('SubmissionImportExternalCollectionComponent test suite', () => {
|
||||||
|
let comp: SubmissionImportExternalCollectionComponent;
|
||||||
|
let compAsAny: any;
|
||||||
|
let fixture: ComponentFixture<SubmissionImportExternalCollectionComponent>;
|
||||||
|
|
||||||
|
beforeEach(async (() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
TranslateModule.forRoot(),
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
SubmissionImportExternalCollectionComponent,
|
||||||
|
TestComponent,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
NgbActiveModal,
|
||||||
|
SubmissionImportExternalCollectionComponent
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents().then();
|
||||||
|
}));
|
||||||
|
|
||||||
|
// First test to check the correct component creation
|
||||||
|
describe('', () => {
|
||||||
|
let testComp: TestComponent;
|
||||||
|
let testFixture: ComponentFixture<TestComponent>;
|
||||||
|
|
||||||
|
// synchronous beforeEach
|
||||||
|
beforeEach(() => {
|
||||||
|
const html = `
|
||||||
|
<ds-submission-import-external-collection></ds-submission-import-external-collection>`;
|
||||||
|
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
||||||
|
testComp = testFixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
testFixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create SubmissionImportExternalCollectionComponent', inject([SubmissionImportExternalCollectionComponent], (app: SubmissionImportExternalCollectionComponent) => {
|
||||||
|
expect(app).toBeDefined();
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(SubmissionImportExternalCollectionComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
compAsAny = comp;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
fixture.destroy();
|
||||||
|
comp = null;
|
||||||
|
compAsAny = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('The variable \'selectedEvent\' should be assigned', () => {
|
||||||
|
const event = new EventEmitter<CollectionListEntry>();
|
||||||
|
comp.selectObject(event);
|
||||||
|
|
||||||
|
expect(comp.selectedEvent).toEqual(event);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('The variable \'selectedEvent\' should be assigned', () => {
|
||||||
|
spyOn(compAsAny.activeModal, 'dismiss');
|
||||||
|
comp.closeCollectionModal();
|
||||||
|
|
||||||
|
expect(compAsAny.activeModal.dismiss).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// declare a test component
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-test-cmp',
|
||||||
|
template: ``
|
||||||
|
})
|
||||||
|
class TestComponent {
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,40 @@
|
|||||||
|
import { Component, Output, EventEmitter } from '@angular/core';
|
||||||
|
import { CollectionListEntry } from '../../../shared/collection-dropdown/collection-dropdown.component';
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap component for 'ds-collection-dropdown'.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-submission-import-external-collection',
|
||||||
|
styleUrls: ['./submission-import-external-collection.component.scss'],
|
||||||
|
templateUrl: './submission-import-external-collection.component.html'
|
||||||
|
})
|
||||||
|
export class SubmissionImportExternalCollectionComponent {
|
||||||
|
/**
|
||||||
|
* The event passed by 'ds-collection-dropdown'.
|
||||||
|
*/
|
||||||
|
@Output() public selectedEvent = new EventEmitter<CollectionListEntry>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the component variables.
|
||||||
|
* @param {NgbActiveModal} activeModal
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
private activeModal: NgbActiveModal
|
||||||
|
) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method populates the 'selectedEvent' variable.
|
||||||
|
*/
|
||||||
|
public selectObject(event): void {
|
||||||
|
this.selectedEvent.emit(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method closes the modal.
|
||||||
|
*/
|
||||||
|
public closeCollectionModal(): void {
|
||||||
|
this.activeModal.dismiss(false);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,39 @@
|
|||||||
|
<div class="modal-header">
|
||||||
|
<h2>{{'submission.import-external.preview.title' | translate}}</h2>
|
||||||
|
<button type="button" class="close"
|
||||||
|
(click)="closeMetadataModal()" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<p>{{'submission.import-external.preview.subtitle' | translate}}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<hr>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div *ngFor="let metadata of metadataList" class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<strong>{{'item.preview.' + metadata.key | translate}}</strong>
|
||||||
|
<p>{{metadata.value.value}}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<hr>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12 text-right">
|
||||||
|
<button class="btn btn-success" (click)="import()" role="button">
|
||||||
|
<i class="fa fa-file-import" aria-hidden="true"></i> {{'submission.import-external.preview.button.import' | translate}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -0,0 +1,3 @@
|
|||||||
|
.close:focus {
|
||||||
|
outline: none !important;
|
||||||
|
}
|
@@ -0,0 +1,165 @@
|
|||||||
|
import { Component, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
|
import { of as observableOf } from 'rxjs/internal/observable/of';
|
||||||
|
import { getTestScheduler } from 'jasmine-marbles';
|
||||||
|
import { SubmissionImportExternalPreviewComponent } from './submission-import-external-preview.component';
|
||||||
|
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||||
|
import { RouterStub } from '../../../shared/testing/router.stub';
|
||||||
|
import { SubmissionService } from '../../submission.service';
|
||||||
|
import { createTestComponent } from '../../../shared/testing/utils.test';
|
||||||
|
import { SubmissionServiceStub } from '../../../shared/testing/submission-service.stub';
|
||||||
|
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
|
||||||
|
import { ExternalSourceEntry } from '../../../core/shared/external-source-entry.model';
|
||||||
|
import { Metadata } from '../../../core/shared/metadata.utils';
|
||||||
|
import { SubmissionImportExternalCollectionComponent } from '../import-external-collection/submission-import-external-collection.component';
|
||||||
|
import { CollectionListEntry } from '../../../shared/collection-dropdown/collection-dropdown.component';
|
||||||
|
|
||||||
|
describe('SubmissionImportExternalPreviewComponent test suite', () => {
|
||||||
|
let comp: SubmissionImportExternalPreviewComponent;
|
||||||
|
let compAsAny: any;
|
||||||
|
let fixture: ComponentFixture<SubmissionImportExternalPreviewComponent>;
|
||||||
|
let submissionServiceStub: SubmissionServiceStub;
|
||||||
|
let scheduler: TestScheduler;
|
||||||
|
const ngbActiveModal = jasmine.createSpyObj('modal', ['close', 'dismiss']);
|
||||||
|
const ngbModal = jasmine.createSpyObj('modal', ['open']);
|
||||||
|
const externalEntry = Object.assign(new ExternalSourceEntry(), {
|
||||||
|
id: '0001-0001-0001-0001',
|
||||||
|
display: 'John Doe',
|
||||||
|
value: 'John, Doe',
|
||||||
|
metadata: {
|
||||||
|
'dc.identifier.uri': [
|
||||||
|
{
|
||||||
|
value: 'https://orcid.org/0001-0001-0001-0001'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
_links: { self: { href: 'http://test-rest.com/server/api/integration/externalSources/orcidV2/entryValues/0000-0003-4851-8004' } }
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
scheduler = getTestScheduler();
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
TranslateModule.forRoot()
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
SubmissionImportExternalPreviewComponent,
|
||||||
|
TestComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: Router, useValue: new RouterStub() },
|
||||||
|
{ provide: SubmissionService, useValue: new SubmissionServiceStub() },
|
||||||
|
{ provide: NotificationsService, useValue: new NotificationsServiceStub() },
|
||||||
|
{ provide: NgbModal, useValue: ngbModal },
|
||||||
|
{ provide: NgbActiveModal, useValue: ngbActiveModal },
|
||||||
|
SubmissionImportExternalPreviewComponent
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents().then();
|
||||||
|
}));
|
||||||
|
|
||||||
|
// First test to check the correct component creation
|
||||||
|
describe('', () => {
|
||||||
|
let testComp: TestComponent;
|
||||||
|
let testFixture: ComponentFixture<TestComponent>;
|
||||||
|
|
||||||
|
// synchronous beforeEach
|
||||||
|
beforeEach(() => {
|
||||||
|
const html = `
|
||||||
|
<ds-submission-import-external-preview></ds-submission-import-external-preview>`;
|
||||||
|
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
||||||
|
testComp = testFixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
testFixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create SubmissionImportExternalPreviewComponent', inject([SubmissionImportExternalPreviewComponent], (app: SubmissionImportExternalPreviewComponent) => {
|
||||||
|
expect(app).toBeDefined();
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(SubmissionImportExternalPreviewComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
compAsAny = comp;
|
||||||
|
submissionServiceStub = TestBed.get(SubmissionService);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
fixture.destroy();
|
||||||
|
comp = null;
|
||||||
|
compAsAny = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should init component properly', () => {
|
||||||
|
comp.externalSourceEntry = externalEntry;
|
||||||
|
const expected = [
|
||||||
|
{ key: 'dc.identifier.uri', value: Metadata.first(comp.externalSourceEntry.metadata, 'dc.identifier.uri') }
|
||||||
|
];
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(comp.metadataList).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should close the modal calling \'activeModal.dismiss\'', () => {
|
||||||
|
comp.modalRef = jasmine.createSpyObj('modal', ['close', 'dismiss']);
|
||||||
|
comp.closeMetadataModal();
|
||||||
|
|
||||||
|
expect(compAsAny.activeModal.dismiss).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should start the import process opening a modal', (done) => {
|
||||||
|
const emittedEvent: CollectionListEntry = {
|
||||||
|
communities: [
|
||||||
|
{
|
||||||
|
id: 'dummy',
|
||||||
|
uuid: 'dummy',
|
||||||
|
name: 'dummy',
|
||||||
|
}
|
||||||
|
],
|
||||||
|
collection: {
|
||||||
|
id: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88',
|
||||||
|
uuid: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88',
|
||||||
|
name: 'Collection 1',
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const submissionObjects = [
|
||||||
|
{ id: 'jk11k13o-9v4z-632i-sr88-wq071n0h1d47' }
|
||||||
|
];
|
||||||
|
comp.externalSourceEntry = externalEntry;
|
||||||
|
ngbModal.open.and.returnValue({
|
||||||
|
componentInstance: { selectedEvent: observableOf(emittedEvent) },
|
||||||
|
close: () => {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
spyOn(comp, 'closeMetadataModal');
|
||||||
|
submissionServiceStub.createSubmissionFromExternalSource.and.returnValue(observableOf(submissionObjects));
|
||||||
|
spyOn(compAsAny.router, 'navigateByUrl');
|
||||||
|
scheduler.schedule(() => comp.import());
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(compAsAny.modalService.open).toHaveBeenCalledWith(SubmissionImportExternalCollectionComponent, { size: 'lg' });
|
||||||
|
expect(comp.closeMetadataModal).toHaveBeenCalled();
|
||||||
|
expect(compAsAny.submissionService.createSubmissionFromExternalSource).toHaveBeenCalledWith(externalEntry._links.self.href, emittedEvent.collection.id);
|
||||||
|
expect(compAsAny.router.navigateByUrl).toHaveBeenCalledWith('/workspaceitems/' + submissionObjects[0].id + '/edit');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// declare a test component
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-test-cmp',
|
||||||
|
template: ``
|
||||||
|
})
|
||||||
|
class TestComponent {
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,100 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { NgbActiveModal, NgbModalRef, NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { ExternalSourceEntry } from '../../../core/shared/external-source-entry.model';
|
||||||
|
import { MetadataValue } from '../../../core/shared/metadata.models';
|
||||||
|
import { Metadata } from '../../../core/shared/metadata.utils';
|
||||||
|
import { CollectionListEntry } from '../../../shared/collection-dropdown/collection-dropdown.component';
|
||||||
|
import { mergeMap } from 'rxjs/operators';
|
||||||
|
import { SubmissionService } from '../../submission.service';
|
||||||
|
import { SubmissionObject } from '../../../core/submission/models/submission-object.model';
|
||||||
|
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||||
|
import { SubmissionImportExternalCollectionComponent } from '../import-external-collection/submission-import-external-collection.component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component display a preview of an external source item.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-submission-import-external-preview',
|
||||||
|
styleUrls: ['./submission-import-external-preview.component.scss'],
|
||||||
|
templateUrl: './submission-import-external-preview.component.html'
|
||||||
|
})
|
||||||
|
export class SubmissionImportExternalPreviewComponent implements OnInit {
|
||||||
|
/**
|
||||||
|
* The external source entry
|
||||||
|
*/
|
||||||
|
public externalSourceEntry: ExternalSourceEntry;
|
||||||
|
/**
|
||||||
|
* The entry metadata list
|
||||||
|
*/
|
||||||
|
public metadataList: Array<{ key: string, value: MetadataValue }>;
|
||||||
|
/**
|
||||||
|
* The modal for the entry preview
|
||||||
|
*/
|
||||||
|
modalRef: NgbModalRef;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the component variables.
|
||||||
|
* @param {NgbActiveModal} activeModal
|
||||||
|
* @param {SubmissionService} submissionService
|
||||||
|
* @param {NgbModal} modalService
|
||||||
|
* @param {Router} router
|
||||||
|
* @param {NotificationsService} notificationService
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
private activeModal: NgbActiveModal,
|
||||||
|
private submissionService: SubmissionService,
|
||||||
|
private modalService: NgbModal,
|
||||||
|
private router: Router,
|
||||||
|
private notificationService: NotificationsService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metadata initialization for HTML display.
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.metadataList = [];
|
||||||
|
const metadataKeys = Object.keys(this.externalSourceEntry.metadata);
|
||||||
|
metadataKeys.forEach((key) => {
|
||||||
|
this.metadataList.push({
|
||||||
|
key: key,
|
||||||
|
value: Metadata.first(this.externalSourceEntry.metadata, key)
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the modal.
|
||||||
|
*/
|
||||||
|
public closeMetadataModal(): void {
|
||||||
|
this.activeModal.dismiss(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the import of an entry by opening up a collection choice modal window.
|
||||||
|
*/
|
||||||
|
public import(): void {
|
||||||
|
this.modalRef = this.modalService.open(SubmissionImportExternalCollectionComponent, {
|
||||||
|
size: 'lg',
|
||||||
|
});
|
||||||
|
this.closeMetadataModal();
|
||||||
|
|
||||||
|
this.modalRef.componentInstance.selectedEvent.pipe(
|
||||||
|
mergeMap((collectionListEntry: CollectionListEntry) => {
|
||||||
|
return this.submissionService.createSubmissionFromExternalSource(this.externalSourceEntry._links.self.href, collectionListEntry.collection.id);
|
||||||
|
})
|
||||||
|
).subscribe((submissionObjects: SubmissionObject[]) => {
|
||||||
|
let isValid = false
|
||||||
|
if (submissionObjects.length === 1) {
|
||||||
|
if (submissionObjects[0] !== null) {
|
||||||
|
isValid = true;
|
||||||
|
this.router.navigateByUrl('/workspaceitems/' + submissionObjects[0].id + '/edit');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isValid) {
|
||||||
|
this.notificationService.error('submission.import-external.preview.error.import.title', 'submission.import-external.preview.error.import.body');
|
||||||
|
}
|
||||||
|
this.modalRef.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,25 @@
|
|||||||
|
<div *ngIf="(isXsOrSm$ | async)" class="input-group mb-2">
|
||||||
|
<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>
|
||||||
|
<div class="input-group mb-5">
|
||||||
|
<input *ngIf="!(isXsOrSm$ | async)" type="text" class="form-control" (keyup.enter)="(searchString === '')?null:search()" [(ngModel)]="searchString" placeholder="{{'submission.import-external.search.placeholder' |translate}}" aria-label="" aria-describedby="">
|
||||||
|
<div [ngClass]="{'input-group-append': !(isXsOrSm$ | async)}" 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-primary" [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>
|
@@ -0,0 +1,27 @@
|
|||||||
|
.input-group-append:last-child > .btn:not(:last-child):not(.dropdown-toggle) {
|
||||||
|
margin-left: -1px;
|
||||||
|
border-radius: 0 0.25rem 0.25rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group-append .dropdown-toggle {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-fx {
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollable-menu {
|
||||||
|
height: auto;
|
||||||
|
max-height: $dropdown-menu-max-height;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollable-dropdown-loading {
|
||||||
|
background-color: map-get($theme-colors, primary);
|
||||||
|
color: white;
|
||||||
|
height: $spacer * 2 !important;
|
||||||
|
line-height: $spacer * 2;
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
@@ -0,0 +1,158 @@
|
|||||||
|
import { Component, NO_ERRORS_SCHEMA, ChangeDetectorRef } from '@angular/core';
|
||||||
|
import { async, TestBed, ComponentFixture, inject, fakeAsync, tick } from '@angular/core/testing';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { SubmissionImportExternalSearchbarComponent, SourceElement } from './submission-import-external-searchbar.component';
|
||||||
|
import { ExternalSourceService } from '../../../core/data/external-source.service';
|
||||||
|
import { createTestComponent } from '../../../shared/testing/utils.test';
|
||||||
|
import { getMockExternalSourceService, externalSourceOrcid, externalSourceCiencia, externalSourceMyStaffDb } from '../../../shared/mocks/external-source.service.mock';
|
||||||
|
import { PageInfo } from '../../../core/shared/page-info.model';
|
||||||
|
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||||
|
import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils';
|
||||||
|
import { ExternalSource } from '../../../core/shared/external-source.model';
|
||||||
|
import { FindListOptions } from '../../../core/data/request.models';
|
||||||
|
import { HostWindowService } from '../../../shared/host-window.service';
|
||||||
|
import { HostWindowServiceStub } from '../../../shared/testing/host-window-service.stub';
|
||||||
|
import { getTestScheduler } from 'jasmine-marbles';
|
||||||
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
|
|
||||||
|
describe('SubmissionImportExternalSearchbarComponent test suite', () => {
|
||||||
|
let comp: SubmissionImportExternalSearchbarComponent;
|
||||||
|
let compAsAny: any;
|
||||||
|
let fixture: ComponentFixture<SubmissionImportExternalSearchbarComponent>;
|
||||||
|
let scheduler: TestScheduler;
|
||||||
|
|
||||||
|
beforeEach(async (() => {
|
||||||
|
scheduler = getTestScheduler();
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
TranslateModule.forRoot(),
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
SubmissionImportExternalSearchbarComponent,
|
||||||
|
TestComponent,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: ExternalSourceService, useClass: getMockExternalSourceService },
|
||||||
|
ChangeDetectorRef,
|
||||||
|
{ provide: HostWindowService, useValue: new HostWindowServiceStub(800) },
|
||||||
|
SubmissionImportExternalSearchbarComponent
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents().then();
|
||||||
|
}));
|
||||||
|
|
||||||
|
// First test to check the correct component creation
|
||||||
|
describe('', () => {
|
||||||
|
let testComp: TestComponent;
|
||||||
|
let testFixture: ComponentFixture<TestComponent>;
|
||||||
|
|
||||||
|
// synchronous beforeEach
|
||||||
|
beforeEach(() => {
|
||||||
|
const html = `
|
||||||
|
<ds-submission-import-external-searchbar></ds-submission-import-external-searchbar>`;
|
||||||
|
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
||||||
|
testComp = testFixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
testFixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create SubmissionImportExternalSearchbarComponent', inject([SubmissionImportExternalSearchbarComponent], (app: SubmissionImportExternalSearchbarComponent) => {
|
||||||
|
expect(app).toBeDefined();
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
let sourceList: SourceElement[];
|
||||||
|
let paginatedList: PaginatedList<ExternalSource>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(SubmissionImportExternalSearchbarComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
compAsAny = comp;
|
||||||
|
const pageInfo = new PageInfo();
|
||||||
|
paginatedList = new PaginatedList(pageInfo, [externalSourceOrcid, externalSourceCiencia, externalSourceMyStaffDb]);
|
||||||
|
const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList);
|
||||||
|
compAsAny.externalService.findAll.and.returnValue(observableOf(paginatedListRD));
|
||||||
|
sourceList = [
|
||||||
|
{id: 'orcid', name: 'orcid'},
|
||||||
|
{id: 'ciencia', name: 'ciencia'},
|
||||||
|
{id: 'my_staff_db', name: 'my_staff_db'},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
fixture.destroy();
|
||||||
|
comp = null;
|
||||||
|
compAsAny = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should init component properly (without initExternalSourceData)', () => {
|
||||||
|
comp.initExternalSourceData = { sourceId: '', query: '' };
|
||||||
|
scheduler.schedule(() => fixture.detectChanges());
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(comp.selectedElement).toEqual(sourceList[0]);
|
||||||
|
expect(compAsAny.pageInfo).toEqual(paginatedList.pageInfo);
|
||||||
|
expect(comp.sourceList).toEqual(sourceList);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should init component properly (with initExternalSourceData populated)', () => {
|
||||||
|
comp.initExternalSourceData = { query: 'dummy', sourceId: 'ciencia' };
|
||||||
|
scheduler.schedule(() => fixture.detectChanges());
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(comp.selectedElement).toEqual(sourceList[1]);
|
||||||
|
expect(compAsAny.pageInfo).toEqual(paginatedList.pageInfo);
|
||||||
|
expect(comp.sourceList).toEqual(sourceList);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Variable \'selectedElement\' should be assigned', () => {
|
||||||
|
const selectedElement = {id: 'orcid', name: 'orcid'};
|
||||||
|
comp.makeSourceSelection(selectedElement);
|
||||||
|
expect(comp.selectedElement).toEqual(selectedElement);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should load additional external sources', () => {
|
||||||
|
comp.sourceListLoading = false;
|
||||||
|
compAsAny.pageInfo = new PageInfo({
|
||||||
|
elementsPerPage: 3,
|
||||||
|
totalElements: 6,
|
||||||
|
totalPages: 2,
|
||||||
|
currentPage: 0
|
||||||
|
});
|
||||||
|
compAsAny.findListOptions = Object.assign({}, new FindListOptions(), {
|
||||||
|
elementsPerPage: 3,
|
||||||
|
currentPage: 0,
|
||||||
|
});
|
||||||
|
comp.sourceList = sourceList;
|
||||||
|
const expected = sourceList.concat(sourceList);
|
||||||
|
|
||||||
|
scheduler.schedule(() => comp.onScroll());
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(comp.sourceList).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('The \'search\' method should call \'emit\'', () => {
|
||||||
|
comp.selectedElement = { id: 'orcidV2', name: 'orcidV2' };
|
||||||
|
comp.searchString = 'dummy';
|
||||||
|
const expected = { sourceId: comp.selectedElement.id, query: comp.searchString };
|
||||||
|
spyOn(comp.externalSourceData, 'emit');
|
||||||
|
comp.search();
|
||||||
|
|
||||||
|
expect(comp.externalSourceData.emit).toHaveBeenCalledWith(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// declare a test component
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-test-cmp',
|
||||||
|
template: ``
|
||||||
|
})
|
||||||
|
class TestComponent {
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,172 @@
|
|||||||
|
import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||||
|
|
||||||
|
import { of as observableOf, Observable } from 'rxjs';
|
||||||
|
import { catchError, tap } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { ExternalSourceService } from '../../../core/data/external-source.service';
|
||||||
|
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';
|
||||||
|
import { getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators';
|
||||||
|
import { HostWindowService } from '../../../shared/host-window.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for the selected external source element.
|
||||||
|
*/
|
||||||
|
export 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 init external source value.
|
||||||
|
*/
|
||||||
|
@Input() public initExternalSourceData: ExternalSourceData;
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
/**
|
||||||
|
* Emits true if were on a small screen
|
||||||
|
*/
|
||||||
|
public isXsOrSm$: Observable<boolean>;
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* @param {HostWindowService} windowService
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
private externalService: ExternalSourceService,
|
||||||
|
private cdr: ChangeDetectorRef,
|
||||||
|
protected windowService: HostWindowService
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component initialization and retrieve first page of external sources.
|
||||||
|
*/
|
||||||
|
ngOnInit() {
|
||||||
|
this.selectedElement = {
|
||||||
|
id: '',
|
||||||
|
name: 'loading'
|
||||||
|
};
|
||||||
|
this.searchString = '';
|
||||||
|
this.sourceList = [];
|
||||||
|
this.findListOptions = Object.assign({}, new FindListOptions(), {
|
||||||
|
elementsPerPage: 5,
|
||||||
|
currentPage: 0,
|
||||||
|
});
|
||||||
|
this.externalService.findAll(this.findListOptions).pipe(
|
||||||
|
catchError(() => {
|
||||||
|
const pageInfo = new PageInfo();
|
||||||
|
const paginatedList = new PaginatedList(pageInfo, []);
|
||||||
|
const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList);
|
||||||
|
return observableOf(paginatedListRD);
|
||||||
|
}),
|
||||||
|
getFirstSucceededRemoteDataPayload()
|
||||||
|
).subscribe((externalSource: PaginatedList<ExternalSource>) => {
|
||||||
|
externalSource.page.forEach((element) => {
|
||||||
|
this.sourceList.push({ 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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (this.selectedElement.id === '') {
|
||||||
|
this.selectedElement = this.sourceList[0];
|
||||||
|
}
|
||||||
|
this.pageInfo = externalSource.pageInfo;
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
});
|
||||||
|
this.isXsOrSm$ = this.windowService.isXsOrSm();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the selected external source.
|
||||||
|
*/
|
||||||
|
public makeSourceSelection(source): void {
|
||||||
|
this.selectedElement = source;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the next pages of external sources.
|
||||||
|
*/
|
||||||
|
public onScroll(): void {
|
||||||
|
if (!this.sourceListLoading && this.pageInfo.currentPage <= this.pageInfo.totalPages) {
|
||||||
|
this.sourceListLoading = true;
|
||||||
|
this.findListOptions = Object.assign({}, new FindListOptions(), {
|
||||||
|
elementsPerPage: 5,
|
||||||
|
currentPage: this.findListOptions.currentPage + 1,
|
||||||
|
});
|
||||||
|
this.externalService.findAll(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(): void {
|
||||||
|
this.externalSourceData.emit({ sourceId: this.selectedElement.id, query: this.searchString });
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,46 @@
|
|||||||
|
<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
|
||||||
|
[initExternalSourceData]="routeData"
|
||||||
|
(externalSourceData) = "getExternalsourceData($event)">
|
||||||
|
</ds-submission-import-external-searchbar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<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">
|
||||||
|
<ds-alert [type]="'alert-info'">
|
||||||
|
<p class="lead mb-0">{{'submission.import-external.page.hint' | translate}}</p>
|
||||||
|
</ds-alert>
|
||||||
|
</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>
|
@@ -0,0 +1,158 @@
|
|||||||
|
import { Component, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { async, TestBed, ComponentFixture, inject } from '@angular/core/testing';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { of as observableOf, of } from 'rxjs/internal/observable/of';
|
||||||
|
import { SubmissionImportExternalComponent } from './submission-import-external.component';
|
||||||
|
import { ExternalSourceService } from '../../core/data/external-source.service';
|
||||||
|
import { getMockExternalSourceService } from '../../shared/mocks/external-source.service.mock';
|
||||||
|
import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service';
|
||||||
|
import { RouteService } from '../../core/services/route.service';
|
||||||
|
import { createTestComponent, createPaginatedList } from '../../shared/testing/utils.test';
|
||||||
|
import { RouterStub } from '../../shared/testing/router.stub';
|
||||||
|
import { VarDirective } from '../../shared/utils/var.directive';
|
||||||
|
import { routeServiceStub } from '../../shared/testing/route-service.stub';
|
||||||
|
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
|
||||||
|
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||||
|
import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils';
|
||||||
|
import { ExternalSourceEntry } from '../../core/shared/external-source-entry.model';
|
||||||
|
import { SubmissionImportExternalPreviewComponent } from './import-external-preview/submission-import-external-preview.component';
|
||||||
|
|
||||||
|
describe('SubmissionImportExternalComponent test suite', () => {
|
||||||
|
let comp: SubmissionImportExternalComponent;
|
||||||
|
let compAsAny: any;
|
||||||
|
let fixture: ComponentFixture<SubmissionImportExternalComponent>;
|
||||||
|
const ngbModal = jasmine.createSpyObj('modal', ['open']);
|
||||||
|
const mockSearchOptions = of(new PaginatedSearchOptions({
|
||||||
|
pagination: Object.assign(new PaginationComponentOptions(), {
|
||||||
|
pageSize: 10,
|
||||||
|
currentPage: 0
|
||||||
|
})
|
||||||
|
}));
|
||||||
|
const searchConfigServiceStub = {
|
||||||
|
paginatedSearchOptions: mockSearchOptions
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(async (() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
TranslateModule.forRoot()
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
SubmissionImportExternalComponent,
|
||||||
|
TestComponent,
|
||||||
|
VarDirective
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: ExternalSourceService, useClass: getMockExternalSourceService },
|
||||||
|
{ provide: SearchConfigurationService, useValue: searchConfigServiceStub },
|
||||||
|
{ provide: RouteService, useValue: routeServiceStub },
|
||||||
|
{ provide: Router, useValue: new RouterStub() },
|
||||||
|
{ provide: NgbModal, useValue: ngbModal },
|
||||||
|
SubmissionImportExternalComponent
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents().then();
|
||||||
|
}));
|
||||||
|
|
||||||
|
// First test to check the correct component creation
|
||||||
|
describe('', () => {
|
||||||
|
let testComp: TestComponent;
|
||||||
|
let testFixture: ComponentFixture<TestComponent>;
|
||||||
|
|
||||||
|
// synchronous beforeEach
|
||||||
|
beforeEach(() => {
|
||||||
|
const html = `
|
||||||
|
<ds-submission-import-external></ds-submission-import-external>`;
|
||||||
|
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
||||||
|
testComp = testFixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
testFixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create SubmissionImportExternalComponent', inject([SubmissionImportExternalComponent], (app: SubmissionImportExternalComponent) => {
|
||||||
|
expect(app).toBeDefined();
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(SubmissionImportExternalComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
compAsAny = comp;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
fixture.destroy();
|
||||||
|
comp = null;
|
||||||
|
compAsAny = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should init component properly (without route data)', () => {
|
||||||
|
const expectedEntries = createSuccessfulRemoteDataObject(createPaginatedList([]));
|
||||||
|
spyOn(compAsAny.routeService, 'getQueryParameterValue').and.returnValue(observableOf(''));
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(comp.routeData).toEqual({ sourceId: '', query: '' });
|
||||||
|
expect(comp.isLoading$.value).toBe(false);
|
||||||
|
expect(comp.entriesRD$.value).toEqual(expectedEntries);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should init component properly (with route data)', () => {
|
||||||
|
const expectedEntries = createSuccessfulRemoteDataObject(createPaginatedList([]));
|
||||||
|
const searchOptions = new PaginatedSearchOptions({
|
||||||
|
pagination: Object.assign(new PaginationComponentOptions(), {
|
||||||
|
pageSize: 10,
|
||||||
|
currentPage: 0
|
||||||
|
})
|
||||||
|
});
|
||||||
|
spyOn(compAsAny.routeService, 'getQueryParameterValue').and.returnValue(observableOf('dummy'));
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(comp.routeData).toEqual({ sourceId: 'dummy', query: 'dummy' });
|
||||||
|
expect(comp.isLoading$.value).toBe(true);
|
||||||
|
expect(comp.entriesRD$.value).toEqual(expectedEntries);
|
||||||
|
expect(compAsAny.externalService.getExternalSourceEntries).toHaveBeenCalledWith('dummy', searchOptions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should call \'router.navigate\'', () => {
|
||||||
|
const event = { sourceId: 'orcidV2', query: 'dummy' };
|
||||||
|
comp.getExternalsourceData(event);
|
||||||
|
|
||||||
|
expect(compAsAny.router.navigate).toHaveBeenCalledWith([], { queryParams: { source: event.sourceId, query: event.query }, replaceUrl: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Entry should be passed to the component loaded inside the modal', () => {
|
||||||
|
const entry = Object.assign(new ExternalSourceEntry(), {
|
||||||
|
id: '0001-0001-0001-0001',
|
||||||
|
display: 'John Doe',
|
||||||
|
value: 'John, Doe',
|
||||||
|
metadata: {
|
||||||
|
'dc.identifier.uri': [
|
||||||
|
{
|
||||||
|
value: 'https://orcid.org/0001-0001-0001-0001'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ngbModal.open.and.returnValue({componentInstance: { externalSourceEntry: null}});
|
||||||
|
comp.import(entry);
|
||||||
|
|
||||||
|
expect(compAsAny.modalService.open).toHaveBeenCalledWith(SubmissionImportExternalPreviewComponent, { size: 'lg' });
|
||||||
|
expect(comp.modalRef.componentInstance.externalSourceEntry).toEqual(entry);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// declare a test component
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-test-cmp',
|
||||||
|
template: ``
|
||||||
|
})
|
||||||
|
class TestComponent {
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,153 @@
|
|||||||
|
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, take } 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 { PageInfo } from '../../core/shared/page-info.model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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',
|
||||||
|
animations: [ fadeIn ]
|
||||||
|
})
|
||||||
|
export class SubmissionImportExternalComponent implements OnInit {
|
||||||
|
/**
|
||||||
|
* The external source search data from the routing service.
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
public 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 routeService: RouteService,
|
||||||
|
private router: Router,
|
||||||
|
private modalService: NgbModal,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the entries for the selected external source and set initial configuration.
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.label = 'Journal';
|
||||||
|
this.listId = 'list-submission-external-sources';
|
||||||
|
this.context = Context.EntitySearchModalWithNameVariants;
|
||||||
|
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(new PaginatedList(new PageInfo(), [])));
|
||||||
|
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);
|
||||||
|
}),
|
||||||
|
take(1)
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
).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;
|
||||||
|
}
|
||||||
|
}
|
@@ -28,6 +28,10 @@ import { SubmissionSectionUploadFileViewComponent } from './sections/upload/file
|
|||||||
import { SubmissionSectionUploadAccessConditionsComponent } from './sections/upload/accessConditions/submission-section-upload-access-conditions.component';
|
import { SubmissionSectionUploadAccessConditionsComponent } from './sections/upload/accessConditions/submission-section-upload-access-conditions.component';
|
||||||
import { SubmissionSubmitComponent } from './submit/submission-submit.component';
|
import { SubmissionSubmitComponent } from './submit/submission-submit.component';
|
||||||
import { storeModuleConfig } from '../app.reducer';
|
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';
|
||||||
|
import { SubmissionImportExternalCollectionComponent } from './import-external/import-external-collection/submission-import-external-collection.component';
|
||||||
import { SubmissionSectionCcLicensesComponent } from './sections/cc-license/submission-section-cc-licenses.component';
|
import { SubmissionSectionCcLicensesComponent } from './sections/cc-license/submission-section-cc-licenses.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@@ -56,19 +60,26 @@ import { SubmissionSectionCcLicensesComponent } from './sections/cc-license/subm
|
|||||||
SubmissionSectionContainerComponent,
|
SubmissionSectionContainerComponent,
|
||||||
SubmissionSectionUploadFileComponent,
|
SubmissionSectionUploadFileComponent,
|
||||||
SubmissionSectionUploadFileEditComponent,
|
SubmissionSectionUploadFileEditComponent,
|
||||||
SubmissionSectionUploadFileViewComponent
|
SubmissionSectionUploadFileViewComponent,
|
||||||
|
SubmissionImportExternalComponent,
|
||||||
|
SubmissionImportExternalSearchbarComponent,
|
||||||
|
SubmissionImportExternalPreviewComponent,
|
||||||
|
SubmissionImportExternalCollectionComponent
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
SubmissionSectionUploadComponent,
|
SubmissionSectionUploadComponent,
|
||||||
SubmissionSectionformComponent,
|
SubmissionSectionformComponent,
|
||||||
SubmissionSectionLicenseComponent,
|
SubmissionSectionLicenseComponent,
|
||||||
SubmissionSectionContainerComponent,
|
SubmissionSectionContainerComponent,
|
||||||
SubmissionSectionCcLicensesComponent,
|
SubmissionImportExternalPreviewComponent,
|
||||||
|
SubmissionImportExternalCollectionComponent,
|
||||||
|
SubmissionSectionCcLicensesComponent
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
SubmissionEditComponent,
|
SubmissionEditComponent,
|
||||||
SubmissionFormComponent,
|
SubmissionFormComponent,
|
||||||
SubmissionSubmitComponent
|
SubmissionSubmitComponent,
|
||||||
|
SubmissionImportExternalComponent
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
SectionUploadService,
|
SectionUploadService,
|
||||||
|
@@ -411,6 +411,19 @@ describe('SubmissionService test suite', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('createSubmissionFromExternalSource', () => {
|
||||||
|
it('should deposit submission', () => {
|
||||||
|
const options: HttpOptions = Object.create({});
|
||||||
|
let headers = new HttpHeaders();
|
||||||
|
headers = headers.append('Content-Type', 'text/uri-list');
|
||||||
|
options.headers = headers;
|
||||||
|
|
||||||
|
service.createSubmissionFromExternalSource(selfUrl, collectionId);
|
||||||
|
|
||||||
|
expect((service as any).restService.postToEndpoint).toHaveBeenCalledWith('workspaceitems', selfUrl, null, options, collectionId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('depositSubmission', () => {
|
describe('depositSubmission', () => {
|
||||||
it('should deposit submission', () => {
|
it('should deposit submission', () => {
|
||||||
const options: HttpOptions = Object.create({});
|
const options: HttpOptions = Object.create({});
|
||||||
|
@@ -114,6 +114,24 @@ export class SubmissionService {
|
|||||||
catchError(() => observableOf({} as SubmissionObject)))
|
catchError(() => observableOf({} as SubmissionObject)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a REST call to deposit a workspaceitem and return response
|
||||||
|
*
|
||||||
|
* @param selfUrl
|
||||||
|
* The workspaceitem self url
|
||||||
|
* @param collectionId
|
||||||
|
* Optional collection id
|
||||||
|
* @return Observable<SubmissionObject>
|
||||||
|
* observable of SubmissionObject
|
||||||
|
*/
|
||||||
|
createSubmissionFromExternalSource(selfUrl: string, collectionId?: string): Observable<SubmissionObject[]> {
|
||||||
|
const options: HttpOptions = Object.create({});
|
||||||
|
let headers = new HttpHeaders();
|
||||||
|
headers = headers.append('Content-Type', 'text/uri-list');
|
||||||
|
options.headers = headers;
|
||||||
|
return this.restService.postToEndpoint(this.workspaceLinkPath, selfUrl, null, options, collectionId) as Observable<SubmissionObject[]>;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform a REST call to deposit a workspaceitem and return response
|
* Perform a REST call to deposit a workspaceitem and return response
|
||||||
*
|
*
|
||||||
|
@@ -1606,6 +1606,28 @@
|
|||||||
|
|
||||||
"item.page.filesection.license.bundle" : "License bundle",
|
"item.page.filesection.license.bundle" : "License bundle",
|
||||||
|
|
||||||
|
"item.preview.dc.identifier.uri": "Identifier:",
|
||||||
|
|
||||||
|
"item.preview.dc.contributor.author": "Authors:",
|
||||||
|
|
||||||
|
"item.preview.dc.date.issued": "Published date:",
|
||||||
|
|
||||||
|
"item.preview.dc.description.abstract": "Abstract:",
|
||||||
|
|
||||||
|
"item.preview.dc.identifier.other": "Other identifier:",
|
||||||
|
|
||||||
|
"item.preview.dc.language.iso": "Language:",
|
||||||
|
|
||||||
|
"item.preview.dc.subject": "Subjects:",
|
||||||
|
|
||||||
|
"item.preview.dc.title": "Title:",
|
||||||
|
|
||||||
|
"item.preview.person.familyName": "Surname:",
|
||||||
|
|
||||||
|
"item.preview.person.givenName": "Name:",
|
||||||
|
|
||||||
|
"item.preview.person.identifier.orcid": "ORCID:",
|
||||||
|
|
||||||
|
|
||||||
"item.select.confirm": "Confirm selected",
|
"item.select.confirm": "Confirm selected",
|
||||||
|
|
||||||
@@ -1960,6 +1982,10 @@
|
|||||||
|
|
||||||
"mydspace.new-submission": "New submission",
|
"mydspace.new-submission": "New submission",
|
||||||
|
|
||||||
|
"mydspace.new-submission-external": "Import metadata from external source",
|
||||||
|
|
||||||
|
"mydspace.new-submission-external-short": "Import metadata",
|
||||||
|
|
||||||
"mydspace.results.head": "Your submissions",
|
"mydspace.results.head": "Your submissions",
|
||||||
|
|
||||||
"mydspace.results.no-abstract": "No Abstract",
|
"mydspace.results.no-abstract": "No Abstract",
|
||||||
@@ -2748,6 +2774,43 @@
|
|||||||
"submission.general.save-later": "Save for later",
|
"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.page.hint": "Enter a query above to find items from the web to import in to DSpace.",
|
||||||
|
|
||||||
|
"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.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.import-external.preview.error.import.title": "Submission error",
|
||||||
|
|
||||||
|
"submission.import-external.preview.error.import.body": "An error occurs during the external source entry import process.",
|
||||||
|
|
||||||
"submission.sections.describe.relationship-lookup.close": "Close",
|
"submission.sections.describe.relationship-lookup.close": "Close",
|
||||||
|
|
||||||
@@ -2889,8 +2952,12 @@
|
|||||||
|
|
||||||
"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.orcidv2": "Search Results",
|
||||||
|
|
||||||
"submission.sections.describe.relationship-lookup.selection-tab.title.lcname": "Search Results",
|
"submission.sections.describe.relationship-lookup.selection-tab.title.lcname": "Search Results",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.selection-tab.title.pubmed": "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.",
|
"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.",
|
||||||
|
|
||||||
"submission.sections.describe.relationship-lookup.name-variant.notification.confirm": "Save a new name variant",
|
"submission.sections.describe.relationship-lookup.name-variant.notification.confirm": "Save a new name variant",
|
||||||
|
Reference in New Issue
Block a user