mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
Merge pull request #540 from atmire/Convert-external-source-to-entity
Convert external source to entity
This commit is contained in:
@@ -1623,8 +1623,67 @@
|
|||||||
"submission.general.save-later": "Save for later",
|
"submission.general.save-later": "Save for later",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"submission.sections.describe.relationship-lookup.close": "Close",
|
"submission.sections.describe.relationship-lookup.close": "Close",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.external-source.added": "Successfully added local entry to the selection",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.external-source.import-button-title.Author": "Import remote author",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.external-source.import-button-title.Journal": "Import remote journal",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.external-source.import-button-title.Journal Issue": "Import remote journal issue",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.external-source.import-button-title.Journal Volume": "Import remote journal volume",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.external-source.import-modal.Author.title": "Import Remote Author",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.external-source.import-modal.Author.added.local-entity": "Successfully added local author to the selection",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.external-source.import-modal.Author.added.new-entity": "Successfully imported and added external author to the selection",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.external-source.import-modal.authority": "Authority",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.external-source.import-modal.authority.new": "Import as a new local authority entry",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.external-source.import-modal.cancel": "Cancel",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.external-source.import-modal.collection": "Select a collection to import new entries to",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.external-source.import-modal.entities": "Entities",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.external-source.import-modal.entities.new": "Import as a new local entity",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.external-source.import-modal.head.lcname": "Importing from LC Name",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.external-source.import-modal.head.orcidV2": "Importing from ORCID",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.external-source.import-modal.head.sherpaJournal": "Importing from Sherpa Journal",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.external-source.import-modal.head.sherpaPublisher": "Importing from Sherpa Publisher",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.external-source.import-modal.import": "Import",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.external-source.import-modal.Journal.title": "Import Remote Journal",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.external-source.import-modal.Journal.added.local-entity": "Successfully added local journal to the selection",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.external-source.import-modal.Journal.added.new-entity": "Successfully imported and added external journal to the selection",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.external-source.import-modal.Journal Issue.title": "Import Remote Journal Issue",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.external-source.import-modal.Journal Issue.added.local-entity": "Successfully added local journal issue to the selection",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.external-source.import-modal.Journal Issue.added.new-entity": "Successfully imported and added external journal issue to the selection",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.external-source.import-modal.Journal Volume.title": "Import Remote Journal Volume",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.external-source.import-modal.Journal Volume.added.local-entity": "Successfully added local journal volume to the selection",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.external-source.import-modal.Journal Volume.added.new-entity": "Successfully imported and added external journal volume to the selection",
|
||||||
|
|
||||||
|
"submission.sections.describe.relationship-lookup.external-source.import-modal.select": "Select a local match:",
|
||||||
|
|
||||||
"submission.sections.describe.relationship-lookup.search-tab.deselect-all": "Deselect all",
|
"submission.sections.describe.relationship-lookup.search-tab.deselect-all": "Deselect all",
|
||||||
|
|
||||||
"submission.sections.describe.relationship-lookup.search-tab.deselect-page": "Deselect page",
|
"submission.sections.describe.relationship-lookup.search-tab.deselect-page": "Deselect page",
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { filter, map, take } from 'rxjs/operators';
|
import { delay, filter, map, take } from 'rxjs/operators';
|
||||||
import { AfterViewInit, ChangeDetectionStrategy, Component, HostListener, Inject, OnInit, ViewEncapsulation } from '@angular/core';
|
import { AfterViewInit, ChangeDetectionStrategy, Component, HostListener, Inject, OnInit, ViewEncapsulation } from '@angular/core';
|
||||||
import { NavigationCancel, NavigationEnd, NavigationStart, Router } from '@angular/router';
|
import { NavigationCancel, NavigationEnd, NavigationStart, Router } from '@angular/router';
|
||||||
|
|
||||||
@@ -125,8 +125,11 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewInit() {
|
ngAfterViewInit() {
|
||||||
this.router.events
|
this.router.events.pipe(
|
||||||
.subscribe((event) => {
|
// This fixes an ExpressionChangedAfterItHasBeenCheckedError from being thrown while loading the component
|
||||||
|
// More information on this bug-fix: https://blog.angular-university.io/angular-debugging/
|
||||||
|
delay(0)
|
||||||
|
).subscribe((event) => {
|
||||||
if (event instanceof NavigationStart) {
|
if (event instanceof NavigationStart) {
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
} else if (
|
} else if (
|
||||||
|
@@ -28,6 +28,12 @@ export class NormalizedExternalSourceEntry extends NormalizedObject<ExternalSour
|
|||||||
@autoserialize
|
@autoserialize
|
||||||
value: string;
|
value: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ID of the external source this entry originates from
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
externalSource: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Metadata of the entry
|
* Metadata of the entry
|
||||||
*/
|
*/
|
||||||
|
@@ -15,6 +15,7 @@ import { NormalizedObjectBuildService } from '../cache/builders/normalized-objec
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { RequestEntry } from './request.reducer';
|
import { RequestEntry } from './request.reducer';
|
||||||
import { getMockRequestService } from '../../shared/mocks/mock-request.service';
|
import { getMockRequestService } from '../../shared/mocks/mock-request.service';
|
||||||
|
import { ExternalSourceEntry } from '../shared/external-source-entry.model';
|
||||||
|
|
||||||
describe('ItemDataService', () => {
|
describe('ItemDataService', () => {
|
||||||
let scheduler: TestScheduler;
|
let scheduler: TestScheduler;
|
||||||
@@ -194,4 +195,24 @@ describe('ItemDataService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('importExternalSourceEntry', () => {
|
||||||
|
let result;
|
||||||
|
|
||||||
|
const externalSourceEntry = Object.assign(new ExternalSourceEntry(), {
|
||||||
|
display: 'John, Doe',
|
||||||
|
value: 'John, Doe',
|
||||||
|
self: 'http://test-rest.com/server/api/integration/externalSources/orcidV2/entryValues/0000-0003-4851-8004'
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
service = initTestService();
|
||||||
|
spyOn(requestService, 'configure');
|
||||||
|
result = service.importExternalSourceEntry(externalSourceEntry, 'collection-id');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should configure a POST request', () => {
|
||||||
|
result.subscribe(() => expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(PostRequest)));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -37,6 +37,7 @@ import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
|||||||
import { Collection } from '../shared/collection.model';
|
import { Collection } from '../shared/collection.model';
|
||||||
import { RemoteData } from './remote-data';
|
import { RemoteData } from './remote-data';
|
||||||
import { PaginatedList } from './paginated-list';
|
import { PaginatedList } from './paginated-list';
|
||||||
|
import { ExternalSourceEntry } from '../shared/external-source-entry.model';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ItemDataService extends DataService<Item> {
|
export class ItemDataService extends DataService<Item> {
|
||||||
@@ -248,6 +249,40 @@ export class ItemDataService extends DataService<Item> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import an external source entry into a collection
|
||||||
|
* @param externalSourceEntry
|
||||||
|
* @param collectionId
|
||||||
|
*/
|
||||||
|
public importExternalSourceEntry(externalSourceEntry: ExternalSourceEntry, collectionId: string): Observable<RemoteData<Item>> {
|
||||||
|
const options: HttpOptions = Object.create({});
|
||||||
|
let headers = new HttpHeaders();
|
||||||
|
headers = headers.append('Content-Type', 'text/uri-list');
|
||||||
|
options.headers = headers;
|
||||||
|
|
||||||
|
const requestId = this.requestService.generateRequestId();
|
||||||
|
const href$ = this.halService.getEndpoint(this.linkPath).pipe(map((href) => `${href}?owningCollection=${collectionId}`));
|
||||||
|
|
||||||
|
href$.pipe(
|
||||||
|
find((href: string) => hasValue(href)),
|
||||||
|
map((href: string) => {
|
||||||
|
const request = new PostRequest(requestId, href, externalSourceEntry.self, options);
|
||||||
|
this.requestService.configure(request);
|
||||||
|
})
|
||||||
|
).subscribe();
|
||||||
|
|
||||||
|
return this.requestService.getByUUID(requestId).pipe(
|
||||||
|
find((request: RequestEntry) => request.completed),
|
||||||
|
getResponseFromEntry(),
|
||||||
|
map((response: any) => {
|
||||||
|
if (isNotEmpty(response.resourceSelfLinks)) {
|
||||||
|
return response.resourceSelfLinks[0];
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
switchMap((selfLink: string) => this.findByHref(selfLink))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the endpoint for an item's bitstreams
|
* Get the endpoint for an item's bitstreams
|
||||||
* @param itemId
|
* @param itemId
|
||||||
|
@@ -10,11 +10,14 @@ import { SearchResult } from '../../shared/search/search-result.model';
|
|||||||
import { Item } from '../shared/item.model';
|
import { Item } from '../shared/item.model';
|
||||||
import { skip, take } from 'rxjs/operators';
|
import { skip, take } from 'rxjs/operators';
|
||||||
import { ExternalSource } from '../shared/external-source.model';
|
import { ExternalSource } from '../shared/external-source.model';
|
||||||
|
import { RequestService } from './request.service';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
|
||||||
describe('LookupRelationService', () => {
|
describe('LookupRelationService', () => {
|
||||||
let service: LookupRelationService;
|
let service: LookupRelationService;
|
||||||
let externalSourceService: ExternalSourceService;
|
let externalSourceService: ExternalSourceService;
|
||||||
let searchService: SearchService;
|
let searchService: SearchService;
|
||||||
|
let requestService: RequestService;
|
||||||
|
|
||||||
const totalExternal = 8;
|
const totalExternal = 8;
|
||||||
const optionsWithQuery = new PaginatedSearchOptions({ query: 'test-query' });
|
const optionsWithQuery = new PaginatedSearchOptions({ query: 'test-query' });
|
||||||
@@ -35,15 +38,18 @@ describe('LookupRelationService', () => {
|
|||||||
name: 'orcidV2',
|
name: 'orcidV2',
|
||||||
hierarchical: false
|
hierarchical: false
|
||||||
});
|
});
|
||||||
|
const searchServiceEndpoint = 'http://test-rest.com/server/api/core/search';
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
externalSourceService = jasmine.createSpyObj('externalSourceService', {
|
externalSourceService = jasmine.createSpyObj('externalSourceService', {
|
||||||
getExternalSourceEntries: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo({ elementsPerPage: 1, totalElements: totalExternal, totalPages: totalExternal, currentPage: 1 }), [{}]))
|
getExternalSourceEntries: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo({ elementsPerPage: 1, totalElements: totalExternal, totalPages: totalExternal, currentPage: 1 }), [{}]))
|
||||||
});
|
});
|
||||||
searchService = jasmine.createSpyObj('searchService', {
|
searchService = jasmine.createSpyObj('searchService', {
|
||||||
search: createSuccessfulRemoteDataObject$(createPaginatedList(localResults))
|
search: createSuccessfulRemoteDataObject$(createPaginatedList(localResults)),
|
||||||
|
getEndpoint: observableOf(searchServiceEndpoint)
|
||||||
});
|
});
|
||||||
service = new LookupRelationService(externalSourceService, searchService);
|
requestService = jasmine.createSpyObj('requestService', ['removeByHrefSubstring']);
|
||||||
|
service = new LookupRelationService(externalSourceService, searchService, requestService);
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -113,4 +119,14 @@ describe('LookupRelationService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('removeLocalResultsCache', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
service.removeLocalResultsCache();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call requestService\'s removeByHrefSubstring with the search endpoint', () => {
|
||||||
|
expect(requestService.removeByHrefSubstring).toHaveBeenCalledWith(searchServiceEndpoint);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -15,6 +15,7 @@ import { getAllSucceededRemoteData, getRemoteDataPayload } from '../shared/opera
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { ExternalSource } from '../shared/external-source.model';
|
import { ExternalSource } from '../shared/external-source.model';
|
||||||
import { ExternalSourceEntry } from '../shared/external-source-entry.model';
|
import { ExternalSourceEntry } from '../shared/external-source-entry.model';
|
||||||
|
import { RequestService } from './request.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A service for retrieving local and external entries information during a relation lookup
|
* A service for retrieving local and external entries information during a relation lookup
|
||||||
@@ -35,7 +36,8 @@ export class LookupRelationService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
constructor(protected externalSourceService: ExternalSourceService,
|
constructor(protected externalSourceService: ExternalSourceService,
|
||||||
protected searchService: SearchService) {
|
protected searchService: SearchService,
|
||||||
|
protected requestService: RequestService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -91,4 +93,11 @@ export class LookupRelationService {
|
|||||||
startWith(0)
|
startWith(0)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove cached requests from local results
|
||||||
|
*/
|
||||||
|
removeLocalResultsCache() {
|
||||||
|
this.searchService.getEndpoint().subscribe((href) => this.requestService.removeByHrefSubstring(href));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -24,6 +24,11 @@ export class ExternalSourceEntry extends ListableObject {
|
|||||||
*/
|
*/
|
||||||
value: string;
|
value: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ID of the external source this entry originates from
|
||||||
|
*/
|
||||||
|
externalSource: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Metadata of the entry
|
* Metadata of the entry
|
||||||
*/
|
*/
|
||||||
|
@@ -1,2 +1,4 @@
|
|||||||
|
<div class="d-inline-block">
|
||||||
<div>{{object.display}}</div>
|
<div>{{object.display}}</div>
|
||||||
<div *ngIf="uri"><a target="_blank" [href]="uri.value">{{uri.value}}</a></div>
|
<div *ngIf="uri"><a target="_blank" [href]="uri.value">{{uri.value}}</a></div>
|
||||||
|
</div>
|
||||||
|
@@ -3,7 +3,7 @@ import { ExternalSourceEntry } from '../../../../../core/shared/external-source-
|
|||||||
import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
|
import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
|
||||||
import { ViewMode } from '../../../../../core/shared/view-mode.model';
|
import { ViewMode } from '../../../../../core/shared/view-mode.model';
|
||||||
import { Context } from '../../../../../core/shared/context.model';
|
import { Context } from '../../../../../core/shared/context.model';
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, Inject, OnInit } from '@angular/core';
|
||||||
import { Metadata } from '../../../../../core/shared/metadata.utils';
|
import { Metadata } from '../../../../../core/shared/metadata.utils';
|
||||||
import { MetadataValue } from '../../../../../core/shared/metadata.models';
|
import { MetadataValue } from '../../../../../core/shared/metadata.models';
|
||||||
|
|
||||||
|
@@ -97,6 +97,7 @@ import { PaginatedList } from '../../../../core/data/paginated-list';
|
|||||||
import { ItemSearchResult } from '../../../object-collection/shared/item-search-result.model';
|
import { ItemSearchResult } from '../../../object-collection/shared/item-search-result.model';
|
||||||
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
|
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
|
||||||
import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
|
import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
|
||||||
|
import { Collection } from '../../../../core/shared/collection.model';
|
||||||
|
|
||||||
export function dsDynamicFormControlMapFn(model: DynamicFormControlModel): Type<DynamicFormControl> | null {
|
export function dsDynamicFormControlMapFn(model: DynamicFormControlModel): Type<DynamicFormControl> | null {
|
||||||
switch (model.type) {
|
switch (model.type) {
|
||||||
@@ -185,6 +186,7 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
|
|||||||
hasRelationLookup: boolean;
|
hasRelationLookup: boolean;
|
||||||
modalRef: NgbModalRef;
|
modalRef: NgbModalRef;
|
||||||
item: Item;
|
item: Item;
|
||||||
|
collection: Collection;
|
||||||
listId: string;
|
listId: string;
|
||||||
searchConfig: string;
|
searchConfig: string;
|
||||||
|
|
||||||
@@ -236,19 +238,18 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
|
|||||||
if (this.hasRelationLookup) {
|
if (this.hasRelationLookup) {
|
||||||
|
|
||||||
this.listId = 'list-' + this.model.relationship.relationshipType;
|
this.listId = 'list-' + this.model.relationship.relationshipType;
|
||||||
const item$ = this.submissionObjectService
|
|
||||||
|
const submissionObject$ = this.submissionObjectService
|
||||||
.findById(this.model.submissionId).pipe(
|
.findById(this.model.submissionId).pipe(
|
||||||
getAllSucceededRemoteData(),
|
|
||||||
getRemoteDataPayload(),
|
|
||||||
switchMap((submissionObject: SubmissionObject) => (submissionObject.item as Observable<RemoteData<Item>>)
|
|
||||||
.pipe(
|
|
||||||
getAllSucceededRemoteData(),
|
getAllSucceededRemoteData(),
|
||||||
getRemoteDataPayload()
|
getRemoteDataPayload()
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const item$ = submissionObject$.pipe(switchMap((submissionObject: SubmissionObject) => (submissionObject.item as Observable<RemoteData<Item>>).pipe(getAllSucceededRemoteData(), getRemoteDataPayload())));
|
||||||
|
const collection$ = submissionObject$.pipe(switchMap((submissionObject: SubmissionObject) => (submissionObject.collection as Observable<RemoteData<Collection>>).pipe(getAllSucceededRemoteData(), getRemoteDataPayload())));
|
||||||
|
|
||||||
this.subs.push(item$.subscribe((item) => this.item = item));
|
this.subs.push(item$.subscribe((item) => this.item = item));
|
||||||
|
this.subs.push(collection$.subscribe((collection) => this.collection = collection));
|
||||||
this.reorderables$ = item$.pipe(
|
this.reorderables$ = item$.pipe(
|
||||||
switchMap((item) => this.relationService.getItemRelationshipsByLabel(item, this.model.relationship.relationshipType)
|
switchMap((item) => this.relationService.getItemRelationshipsByLabel(item, this.model.relationship.relationshipType)
|
||||||
.pipe(
|
.pipe(
|
||||||
@@ -343,6 +344,7 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
|
|||||||
modalComp.label = this.model.label;
|
modalComp.label = this.model.label;
|
||||||
modalComp.metadataFields = this.model.metadataFields;
|
modalComp.metadataFields = this.model.metadataFields;
|
||||||
modalComp.item = this.item;
|
modalComp.item = this.item;
|
||||||
|
modalComp.collection = this.collection;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -25,12 +25,14 @@
|
|||||||
[title]="'submission.sections.describe.relationship-lookup.search-tab.tab-title.' + source.id | translate : {count: (totalExternal$ | async)[idx]}">
|
[title]="'submission.sections.describe.relationship-lookup.search-tab.tab-title.' + source.id | translate : {count: (totalExternal$ | async)[idx]}">
|
||||||
<ng-template ngbTabContent>
|
<ng-template ngbTabContent>
|
||||||
<ds-dynamic-lookup-relation-external-source-tab
|
<ds-dynamic-lookup-relation-external-source-tab
|
||||||
|
[label]="label"
|
||||||
[listId]="listId"
|
[listId]="listId"
|
||||||
[repeatable]="repeatable"
|
[item]="item"
|
||||||
|
[collection]="collection"
|
||||||
|
[relationship]="relationshipOptions"
|
||||||
[context]="context"
|
[context]="context"
|
||||||
[externalSource]="source"
|
[externalSource]="source"
|
||||||
(selectObject)="select($event)"
|
(importedObject)="imported($event)"
|
||||||
(deselectObject)="deselect($event)"
|
|
||||||
class="d-block pt-3">
|
class="d-block pt-3">
|
||||||
</ds-dynamic-lookup-relation-external-source-tab>
|
</ds-dynamic-lookup-relation-external-source-tab>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
@@ -1,3 +1,11 @@
|
|||||||
.modal-footer {
|
.modal-footer {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Render child-modals slightly smaller than this modal to avoid complete overlap */
|
||||||
|
:host {
|
||||||
|
::ng-deep .modal-content {
|
||||||
|
width: 90%;
|
||||||
|
margin: 5%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -66,6 +66,11 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy
|
|||||||
*/
|
*/
|
||||||
item;
|
item;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The collection we're submitting an item to
|
||||||
|
*/
|
||||||
|
collection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is the selection repeatable?
|
* Is the selection repeatable?
|
||||||
*/
|
*/
|
||||||
@@ -233,6 +238,15 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an external object has been imported, resets the total values and adds the object to the selected list
|
||||||
|
* @param object
|
||||||
|
*/
|
||||||
|
imported(object) {
|
||||||
|
this.setTotals();
|
||||||
|
this.select(object);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate and set the total entries available for each tab
|
* Calculate and set the total entries available for each tab
|
||||||
*/
|
*/
|
||||||
|
@@ -10,13 +10,13 @@
|
|||||||
<ng-container *ngVar="(entriesRD$ | async) as entriesRD">
|
<ng-container *ngVar="(entriesRD$ | async) as entriesRD">
|
||||||
<ds-viewable-collection *ngIf="entriesRD?.hasSucceeded && !entriesRD?.isLoading && entriesRD?.payload?.page?.length > 0" @fadeIn
|
<ds-viewable-collection *ngIf="entriesRD?.hasSucceeded && !entriesRD?.isLoading && entriesRD?.payload?.page?.length > 0" @fadeIn
|
||||||
[objects]="entriesRD"
|
[objects]="entriesRD"
|
||||||
[selectable]="true"
|
|
||||||
[selectionConfig]="{ repeatable: repeatable, listId: listId }"
|
[selectionConfig]="{ repeatable: repeatable, listId: listId }"
|
||||||
[config]="initialPagination"
|
[config]="initialPagination"
|
||||||
[hideGear]="true"
|
[hideGear]="true"
|
||||||
[context]="context"
|
[context]="context"
|
||||||
(deselectObject)="deselectObject.emit($event)"
|
[importable]="true"
|
||||||
(selectObject)="selectObject.emit($event)">
|
[importConfig]="importConfig"
|
||||||
|
(importObject)="import($event)">
|
||||||
</ds-viewable-collection>
|
</ds-viewable-collection>
|
||||||
<ds-loading *ngIf="!entriesRD || !entriesRD?.payload || entriesRD?.isLoading"
|
<ds-loading *ngIf="!entriesRD || !entriesRD?.payload || entriesRD?.isLoading"
|
||||||
message="{{'loading.search-results' | translate}}"></ds-loading>
|
message="{{'loading.search-results' | translate}}"></ds-loading>
|
||||||
|
@@ -3,7 +3,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
|||||||
import { VarDirective } from '../../../../../utils/var.directive';
|
import { VarDirective } from '../../../../../utils/var.directive';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import { EventEmitter, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { PaginatedSearchOptions } from '../../../../../search/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../../../../../search/paginated-search-options.model';
|
||||||
import { SearchConfigurationService } from '../../../../../../core/shared/search/search-configuration.service';
|
import { SearchConfigurationService } from '../../../../../../core/shared/search/search-configuration.service';
|
||||||
import { of as observableOf } from 'rxjs/internal/observable/of';
|
import { of as observableOf } from 'rxjs/internal/observable/of';
|
||||||
@@ -18,12 +18,20 @@ import { ExternalSource } from '../../../../../../core/shared/external-source.mo
|
|||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { ExternalSourceEntry } from '../../../../../../core/shared/external-source-entry.model';
|
import { ExternalSourceEntry } from '../../../../../../core/shared/external-source-entry.model';
|
||||||
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { SelectableListService } from '../../../../../object-list/selectable-list/selectable-list.service';
|
||||||
|
import { Item } from '../../../../../../core/shared/item.model';
|
||||||
|
import { Collection } from '../../../../../../core/shared/collection.model';
|
||||||
|
import { RelationshipOptions } from '../../../models/relationship-options.model';
|
||||||
|
import { ExternalSourceEntryImportModalComponent } from './external-source-entry-import-modal/external-source-entry-import-modal.component';
|
||||||
|
|
||||||
describe('DsDynamicLookupRelationExternalSourceTabComponent', () => {
|
describe('DsDynamicLookupRelationExternalSourceTabComponent', () => {
|
||||||
let component: DsDynamicLookupRelationExternalSourceTabComponent;
|
let component: DsDynamicLookupRelationExternalSourceTabComponent;
|
||||||
let fixture: ComponentFixture<DsDynamicLookupRelationExternalSourceTabComponent>;
|
let fixture: ComponentFixture<DsDynamicLookupRelationExternalSourceTabComponent>;
|
||||||
let pSearchOptions;
|
let pSearchOptions;
|
||||||
let externalSourceService;
|
let externalSourceService;
|
||||||
|
let selectableListService;
|
||||||
|
let modalService;
|
||||||
|
|
||||||
const externalSource = {
|
const externalSource = {
|
||||||
id: 'orcidV2',
|
id: 'orcidV2',
|
||||||
@@ -68,6 +76,10 @@ describe('DsDynamicLookupRelationExternalSourceTabComponent', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
] as ExternalSourceEntry[];
|
] as ExternalSourceEntry[];
|
||||||
|
const item = Object.assign(new Item(), { id: 'submission-item' });
|
||||||
|
const collection = Object.assign(new Collection(), { id: 'submission-collection' });
|
||||||
|
const relationship = Object.assign(new RelationshipOptions(), { relationshipType: 'isAuthorOfPublication' });
|
||||||
|
const label = 'Author';
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
pSearchOptions = new PaginatedSearchOptions({
|
pSearchOptions = new PaginatedSearchOptions({
|
||||||
@@ -76,20 +88,22 @@ describe('DsDynamicLookupRelationExternalSourceTabComponent', () => {
|
|||||||
externalSourceService = jasmine.createSpyObj('externalSourceService', {
|
externalSourceService = jasmine.createSpyObj('externalSourceService', {
|
||||||
getExternalSourceEntries: createSuccessfulRemoteDataObject$(createPaginatedList(externalEntries))
|
getExternalSourceEntries: createSuccessfulRemoteDataObject$(createPaginatedList(externalEntries))
|
||||||
});
|
});
|
||||||
|
selectableListService = jasmine.createSpyObj('selectableListService', ['selectSingle']);
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
init();
|
init();
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [DsDynamicLookupRelationExternalSourceTabComponent, VarDirective],
|
declarations: [DsDynamicLookupRelationExternalSourceTabComponent, VarDirective],
|
||||||
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), BrowserAnimationsModule],
|
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), NgbModule.forRoot(), BrowserAnimationsModule],
|
||||||
providers: [
|
providers: [
|
||||||
{
|
{
|
||||||
provide: SearchConfigurationService, useValue: {
|
provide: SearchConfigurationService, useValue: {
|
||||||
paginatedSearchOptions: observableOf(pSearchOptions)
|
paginatedSearchOptions: observableOf(pSearchOptions)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ provide: ExternalSourceService, useValue: externalSourceService }
|
{ provide: ExternalSourceService, useValue: externalSourceService },
|
||||||
|
{ provide: SelectableListService, useValue: selectableListService }
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
@@ -99,13 +113,18 @@ describe('DsDynamicLookupRelationExternalSourceTabComponent', () => {
|
|||||||
fixture = TestBed.createComponent(DsDynamicLookupRelationExternalSourceTabComponent);
|
fixture = TestBed.createComponent(DsDynamicLookupRelationExternalSourceTabComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
component.externalSource = externalSource;
|
component.externalSource = externalSource;
|
||||||
|
component.item = item;
|
||||||
|
component.collection = collection;
|
||||||
|
component.relationship = relationship;
|
||||||
|
component.label = label;
|
||||||
|
modalService = (component as any).modalService;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when the external entries finished loading successfully', () => {
|
describe('when the external entries finished loading successfully', () => {
|
||||||
it('should display a ds-viewable-collection component', () => {
|
it('should display a ds-viewable-collection component', () => {
|
||||||
const collection = fixture.debugElement.query(By.css('ds-viewable-collection'));
|
const viewableCollection = fixture.debugElement.query(By.css('ds-viewable-collection'));
|
||||||
expect(collection).toBeDefined();
|
expect(viewableCollection).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -116,8 +135,8 @@ describe('DsDynamicLookupRelationExternalSourceTabComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not display a ds-viewable-collection component', () => {
|
it('should not display a ds-viewable-collection component', () => {
|
||||||
const collection = fixture.debugElement.query(By.css('ds-viewable-collection'));
|
const viewableCollection = fixture.debugElement.query(By.css('ds-viewable-collection'));
|
||||||
expect(collection).toBeNull();
|
expect(viewableCollection).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display a ds-loading component', () => {
|
it('should display a ds-loading component', () => {
|
||||||
@@ -133,8 +152,8 @@ describe('DsDynamicLookupRelationExternalSourceTabComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not display a ds-viewable-collection component', () => {
|
it('should not display a ds-viewable-collection component', () => {
|
||||||
const collection = fixture.debugElement.query(By.css('ds-viewable-collection'));
|
const viewableCollection = fixture.debugElement.query(By.css('ds-viewable-collection'));
|
||||||
expect(collection).toBeNull();
|
expect(viewableCollection).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display a ds-error component', () => {
|
it('should display a ds-error component', () => {
|
||||||
@@ -150,8 +169,8 @@ describe('DsDynamicLookupRelationExternalSourceTabComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not display a ds-viewable-collection component', () => {
|
it('should not display a ds-viewable-collection component', () => {
|
||||||
const collection = fixture.debugElement.query(By.css('ds-viewable-collection'));
|
const viewableCollection = fixture.debugElement.query(By.css('ds-viewable-collection'));
|
||||||
expect(collection).toBeNull();
|
expect(viewableCollection).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display a message the list is empty', () => {
|
it('should display a message the list is empty', () => {
|
||||||
@@ -159,4 +178,15 @@ describe('DsDynamicLookupRelationExternalSourceTabComponent', () => {
|
|||||||
expect(empty).not.toBeNull();
|
expect(empty).not.toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('import', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(modalService, 'open').and.returnValue(Object.assign({ componentInstance: Object.assign({ importedObject: new EventEmitter<any>() }) }));
|
||||||
|
component.import(externalEntries[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should open a new ExternalSourceEntryImportModalComponent', () => {
|
||||||
|
expect(modalService.open).toHaveBeenCalledWith(ExternalSourceEntryImportModalComponent, jasmine.any(Object))
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
|
||||||
import { SEARCH_CONFIG_SERVICE } from '../../../../../../+my-dspace-page/my-dspace-page.component';
|
import { SEARCH_CONFIG_SERVICE } from '../../../../../../+my-dspace-page/my-dspace-page.component';
|
||||||
import { SearchConfigurationService } from '../../../../../../core/shared/search/search-configuration.service';
|
import { SearchConfigurationService } from '../../../../../../core/shared/search/search-configuration.service';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
@@ -14,6 +14,14 @@ import { Context } from '../../../../../../core/shared/context.model';
|
|||||||
import { ListableObject } from '../../../../../object-collection/shared/listable-object.model';
|
import { ListableObject } from '../../../../../object-collection/shared/listable-object.model';
|
||||||
import { fadeIn, fadeInOut } from '../../../../../animations/fade';
|
import { fadeIn, fadeInOut } from '../../../../../animations/fade';
|
||||||
import { PaginationComponentOptions } from '../../../../../pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../../../../pagination/pagination-component-options.model';
|
||||||
|
import { RelationshipOptions } from '../../../models/relationship-options.model';
|
||||||
|
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { ExternalSourceEntryImportModalComponent } from './external-source-entry-import-modal/external-source-entry-import-modal.component';
|
||||||
|
import { Subscription } from 'rxjs/internal/Subscription';
|
||||||
|
import { hasValue } from '../../../../../empty.util';
|
||||||
|
import { SelectableListService } from '../../../../../object-list/selectable-list/selectable-list.service';
|
||||||
|
import { Item } from '../../../../../../core/shared/item.model';
|
||||||
|
import { Collection } from '../../../../../../core/shared/collection.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-dynamic-lookup-relation-external-source-tab',
|
selector: 'ds-dynamic-lookup-relation-external-source-tab',
|
||||||
@@ -31,11 +39,12 @@ import { PaginationComponentOptions } from '../../../../../pagination/pagination
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
/**
|
/**
|
||||||
* The tab displaying a list of importable entries for an external source
|
* Component rendering the tab content of an external source during submission lookup
|
||||||
|
* Shows a list of entries matching the current search query with the option to import them into the repository
|
||||||
*/
|
*/
|
||||||
export class DsDynamicLookupRelationExternalSourceTabComponent implements OnInit {
|
export class DsDynamicLookupRelationExternalSourceTabComponent implements OnInit, OnDestroy {
|
||||||
/**
|
/**
|
||||||
* The label to use to display i18n messages (describing the type of relationship)
|
* The label to use for all messages (added to the end of relevant i18n keys)
|
||||||
*/
|
*/
|
||||||
@Input() label: string;
|
@Input() label: string;
|
||||||
|
|
||||||
@@ -45,27 +54,32 @@ export class DsDynamicLookupRelationExternalSourceTabComponent implements OnInit
|
|||||||
@Input() listId: string;
|
@Input() listId: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is the selection repeatable?
|
* The item in submission
|
||||||
*/
|
*/
|
||||||
@Input() repeatable: boolean;
|
@Input() item: Item;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The context to display lists
|
* The collection the user is submitting an item into
|
||||||
|
*/
|
||||||
|
@Input() collection: Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The relationship-options for the current lookup
|
||||||
|
*/
|
||||||
|
@Input() relationship: RelationshipOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The context to displaying lists for
|
||||||
*/
|
*/
|
||||||
@Input() context: Context;
|
@Input() context: Context;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send an event to deselect an object from the list
|
* Emit an event when an object has been imported (or selected from similar local entries)
|
||||||
*/
|
*/
|
||||||
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
@Output() importedObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send an event to select an object from the list
|
* The initial pagination options
|
||||||
*/
|
|
||||||
@Output() selectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The initial pagination to start with
|
|
||||||
*/
|
*/
|
||||||
initialPagination = Object.assign(new PaginationComponentOptions(), {
|
initialPagination = Object.assign(new PaginationComponentOptions(), {
|
||||||
id: 'submission-external-source-relation-list',
|
id: 'submission-external-source-relation-list',
|
||||||
@@ -82,15 +96,68 @@ export class DsDynamicLookupRelationExternalSourceTabComponent implements OnInit
|
|||||||
*/
|
*/
|
||||||
entriesRD$: Observable<RemoteData<PaginatedList<ExternalSourceEntry>>>;
|
entriesRD$: Observable<RemoteData<PaginatedList<ExternalSourceEntry>>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Config to use for the import buttons
|
||||||
|
*/
|
||||||
|
importConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The modal for importing the entry
|
||||||
|
*/
|
||||||
|
modalRef: NgbModalRef;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscription to the modal's importedObject event-emitter
|
||||||
|
*/
|
||||||
|
importObjectSub: Subscription;
|
||||||
|
|
||||||
constructor(private router: Router,
|
constructor(private router: Router,
|
||||||
public searchConfigService: SearchConfigurationService,
|
public searchConfigService: SearchConfigurationService,
|
||||||
private externalSourceService: ExternalSourceService) {
|
private externalSourceService: ExternalSourceService,
|
||||||
|
private modalService: NgbModal,
|
||||||
|
private selectableListService: SelectableListService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the entries for the selected external source
|
||||||
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.entriesRD$ = this.searchConfigService.paginatedSearchOptions.pipe(
|
this.entriesRD$ = this.searchConfigService.paginatedSearchOptions.pipe(
|
||||||
switchMap((searchOptions: PaginatedSearchOptions) =>
|
switchMap((searchOptions: PaginatedSearchOptions) =>
|
||||||
this.externalSourceService.getExternalSourceEntries(this.externalSource.id, searchOptions).pipe(startWith(undefined)))
|
this.externalSourceService.getExternalSourceEntries(this.externalSource.id, searchOptions).pipe(startWith(undefined)))
|
||||||
)
|
);
|
||||||
|
this.importConfig = {
|
||||||
|
buttonLabel: 'submission.sections.describe.relationship-lookup.external-source.import-button-title.' + this.label
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the import of an entry by opening up an import modal window
|
||||||
|
* @param entry The entry to import
|
||||||
|
*/
|
||||||
|
import(entry) {
|
||||||
|
this.modalRef = this.modalService.open(ExternalSourceEntryImportModalComponent, {
|
||||||
|
size: 'lg',
|
||||||
|
container: 'ds-dynamic-lookup-relation-modal'
|
||||||
|
});
|
||||||
|
const modalComp = this.modalRef.componentInstance;
|
||||||
|
modalComp.externalSourceEntry = entry;
|
||||||
|
modalComp.item = this.item;
|
||||||
|
modalComp.collection = this.collection;
|
||||||
|
modalComp.relationship = this.relationship;
|
||||||
|
modalComp.label = this.label;
|
||||||
|
this.importObjectSub = modalComp.importedObject.subscribe((object) => {
|
||||||
|
this.selectableListService.selectSingle(this.listId, object);
|
||||||
|
this.importedObject.emit(object);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsubscribe from open subscriptions
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
if (hasValue(this.importObjectSub)) {
|
||||||
|
this.importObjectSub.unsubscribe();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,61 @@
|
|||||||
|
<div class="modal-header">
|
||||||
|
<h4 class="modal-title" id="modal-title">{{ (labelPrefix + label + '.title') | translate }}</h4>
|
||||||
|
<button type="button" class="close" aria-label="Close button" aria-describedby="modal-title"
|
||||||
|
(click)="modal.dismiss()">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<h4>{{ (labelPrefix + 'head.' + externalSourceEntry.externalSource | translate) }}</h4>
|
||||||
|
<div id="external-source-entry-information" class="mb-3">
|
||||||
|
<div><span>{{externalSourceEntry.display}}</span></div>
|
||||||
|
<div *ngIf="uri"><a href="{{uri.value}}">{{uri.value}}</a></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4>{{ (labelPrefix + 'select' | translate) }}</h4>
|
||||||
|
|
||||||
|
<div id="external-source-entry-entities" class="mb-3">
|
||||||
|
<h5 class="font-weight-bold">{{ (labelPrefix + 'entities' | translate) }}</h5>
|
||||||
|
|
||||||
|
<div id="external-source-entry-collection" class="mb-3">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="collection">{{ (labelPrefix + 'collection' | translate) }}</label>
|
||||||
|
<input type="text" class="form-control" id="collection" placeholder="Enter collection ID" [(ngModel)]="collectionId">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ds-search-results *ngIf="(localEntitiesRD$ | async)?.payload?.page?.length > 0"
|
||||||
|
[searchResults]="(localEntitiesRD$ | async)"
|
||||||
|
[sortConfig]="this.lookupRelationService.searchConfig?.sort"
|
||||||
|
[searchConfig]="this.lookupRelationService.searchConfig"
|
||||||
|
[selectable]="true"
|
||||||
|
[disableHeader]="true"
|
||||||
|
[hidePaginationDetail]="true"
|
||||||
|
[selectionConfig]="{ repeatable: false, listId: entityListId }"
|
||||||
|
[linkType]="linkTypes.ExternalLink"
|
||||||
|
[context]="context"
|
||||||
|
(deselectObject)="deselectEntity()"
|
||||||
|
(selectObject)="selectEntity($event)">
|
||||||
|
</ds-search-results>
|
||||||
|
<div class="ml-4">
|
||||||
|
<input class="form-check-input" type="radio" name="new-entity" id="new-entity" value="new-entity" (click)="selectNewEntity()" [checked]="selectedImportType === importType.NewEntity" />
|
||||||
|
<label class="form-check-label" for="new-entity">{{ (labelPrefix + 'entities.new' | translate) }}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="external-source-entry-authority" *ngIf="authorityEnabled">
|
||||||
|
<h5 class="font-weight-bold">{{ (labelPrefix + 'authority' | translate) }}</h5>
|
||||||
|
|
||||||
|
<div class="ml-4">
|
||||||
|
<input class="form-check-input" type="radio" name="new-authority" id="new-authority" value="new-authority" (click)="selectNewAuthority()" [checked]="selectedImportType === importType.NewAuthority" />
|
||||||
|
<label class="form-check-label" for="new-authority">{{ (labelPrefix + 'authority.new' | translate) }}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<div>
|
||||||
|
<button type="button" class="btn btn-outline-secondary" (click)="close()">{{ (labelPrefix + 'cancel' | translate) }}</button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button type="button" class="btn btn-primary" [disabled]="selectedImportType === importType.None" (click)="import()">{{ (labelPrefix + 'import' | translate) }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -0,0 +1,3 @@
|
|||||||
|
.modal-footer {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
@@ -0,0 +1,194 @@
|
|||||||
|
import { ExternalSourceEntryImportModalComponent, ImportType } from './external-source-entry-import-modal.component';
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { LookupRelationService } from '../../../../../../../core/data/lookup-relation.service';
|
||||||
|
import { ExternalSourceEntry } from '../../../../../../../core/shared/external-source-entry.model';
|
||||||
|
import { Item } from '../../../../../../../core/shared/item.model';
|
||||||
|
import { ItemSearchResult } from '../../../../../../object-collection/shared/item-search-result.model';
|
||||||
|
import { createPaginatedList, createSuccessfulRemoteDataObject$ } from '../../../../../../testing/utils';
|
||||||
|
import { Collection } from '../../../../../../../core/shared/collection.model';
|
||||||
|
import { RelationshipOptions } from '../../../../models/relationship-options.model';
|
||||||
|
import { SelectableListService } from '../../../../../../object-list/selectable-list/selectable-list.service';
|
||||||
|
import { ItemDataService } from '../../../../../../../core/data/item-data.service';
|
||||||
|
import { NotificationsService } from '../../../../../../notifications/notifications.service';
|
||||||
|
|
||||||
|
describe('DsDynamicLookupRelationExternalSourceTabComponent', () => {
|
||||||
|
let component: ExternalSourceEntryImportModalComponent;
|
||||||
|
let fixture: ComponentFixture<ExternalSourceEntryImportModalComponent>;
|
||||||
|
let lookupRelationService: LookupRelationService;
|
||||||
|
let selectService: SelectableListService;
|
||||||
|
let itemService: ItemDataService;
|
||||||
|
let notificationsService: NotificationsService;
|
||||||
|
let modalStub: NgbActiveModal;
|
||||||
|
|
||||||
|
const uri = 'https://orcid.org/0001-0001-0001-0001';
|
||||||
|
const entry = Object.assign(new ExternalSourceEntry(), {
|
||||||
|
id: '0001-0001-0001-0001',
|
||||||
|
display: 'John Doe',
|
||||||
|
value: 'John, Doe',
|
||||||
|
metadata: {
|
||||||
|
'dc.identifier.uri': [
|
||||||
|
{
|
||||||
|
value: uri
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const label = 'Author';
|
||||||
|
const relationship = Object.assign(new RelationshipOptions(), { relationshipType: 'isAuthorOfPublication' });
|
||||||
|
const submissionCollection = Object.assign(new Collection(), { uuid: '9398affe-a977-4992-9a1d-6f00908a259f' });
|
||||||
|
const submissionItem = Object.assign(new Item(), { uuid: '26224069-5f99-412a-9e9b-7912a7e35cb1' });
|
||||||
|
const item1 = Object.assign(new Item(), { uuid: 'e1c51c69-896d-42dc-8221-1d5f2ad5516e' });
|
||||||
|
const item2 = Object.assign(new Item(), { uuid: 'c8279647-1acc-41ae-b036-951d5f65649b' });
|
||||||
|
const item3 = Object.assign(new Item(), { uuid: 'c3bcbff5-ec0c-4831-8e4c-94b9c933ccac' });
|
||||||
|
const searchResult1 = Object.assign(new ItemSearchResult(), { indexableObject: item1 });
|
||||||
|
const searchResult2 = Object.assign(new ItemSearchResult(), { indexableObject: item2 });
|
||||||
|
const searchResult3 = Object.assign(new ItemSearchResult(), { indexableObject: item3 });
|
||||||
|
const importedItem = Object.assign(new Item(), { uuid: '5d0098fc-344a-4067-a57d-457092b72e82' });
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
lookupRelationService = jasmine.createSpyObj('lookupRelationService', {
|
||||||
|
getLocalResults: createSuccessfulRemoteDataObject$(createPaginatedList([searchResult1, searchResult2, searchResult3])),
|
||||||
|
removeLocalResultsCache: {}
|
||||||
|
});
|
||||||
|
selectService = jasmine.createSpyObj('selectService', ['deselectAll']);
|
||||||
|
notificationsService = jasmine.createSpyObj('notificationsService', ['success']);
|
||||||
|
itemService = jasmine.createSpyObj('itemService', {
|
||||||
|
importExternalSourceEntry: createSuccessfulRemoteDataObject$(importedItem)
|
||||||
|
});
|
||||||
|
modalStub = jasmine.createSpyObj('modal', ['close']);
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
init();
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ExternalSourceEntryImportModalComponent],
|
||||||
|
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), NgbModule.forRoot()],
|
||||||
|
providers: [
|
||||||
|
{ provide: LookupRelationService, useValue: lookupRelationService },
|
||||||
|
{ provide: SelectableListService, useValue: selectService },
|
||||||
|
{ provide: NotificationsService, useValue: notificationsService },
|
||||||
|
{ provide: ItemDataService, useValue: itemService },
|
||||||
|
{ provide: NgbActiveModal, useValue: modalStub }
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ExternalSourceEntryImportModalComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
component.externalSourceEntry = entry;
|
||||||
|
component.label = label;
|
||||||
|
component.relationship = relationship;
|
||||||
|
component.collection = submissionCollection;
|
||||||
|
component.item = submissionItem;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('close', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
component.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should close the modal', () => {
|
||||||
|
expect(modalStub.close).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('selectEntity', () => {
|
||||||
|
const entity = Object.assign(new Item(), { uuid: 'd8698de5-5b05-4ea4-9d02-da73803a50f9' });
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
component.selectEntity(entity);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set selected entity', () => {
|
||||||
|
expect(component.selectedEntity).toBe(entity);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the import type to local entity', () => {
|
||||||
|
expect(component.selectedImportType).toEqual(ImportType.LocalEntity);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('deselectEntity', () => {
|
||||||
|
const entity = Object.assign(new Item(), { uuid: 'd8698de5-5b05-4ea4-9d02-da73803a50f9' });
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
component.selectedImportType = ImportType.LocalEntity;
|
||||||
|
component.selectedEntity = entity;
|
||||||
|
component.deselectEntity();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove the selected entity', () => {
|
||||||
|
expect(component.selectedEntity).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the import type to none', () => {
|
||||||
|
expect(component.selectedImportType).toEqual(ImportType.None);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('selectNewEntity', () => {
|
||||||
|
describe('when current import type is set to new entity', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
component.selectedImportType = ImportType.NewEntity;
|
||||||
|
component.selectNewEntity();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the import type to none', () => {
|
||||||
|
expect(component.selectedImportType).toEqual(ImportType.None);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when current import type is not set to new entity', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
component.selectedImportType = ImportType.None;
|
||||||
|
component.selectNewEntity();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the import type to new entity', () => {
|
||||||
|
expect(component.selectedImportType).toEqual(ImportType.NewEntity);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should deselect the entity and authority list', () => {
|
||||||
|
expect(selectService.deselectAll).toHaveBeenCalledWith(component.entityListId);
|
||||||
|
expect(selectService.deselectAll).toHaveBeenCalledWith(component.authorityListId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('selectNewAuthority', () => {
|
||||||
|
describe('when current import type is set to new authority', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
component.selectedImportType = ImportType.NewAuthority;
|
||||||
|
component.selectNewAuthority();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the import type to none', () => {
|
||||||
|
expect(component.selectedImportType).toEqual(ImportType.None);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when current import type is not set to new authority', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
component.selectedImportType = ImportType.None;
|
||||||
|
component.selectNewAuthority();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set the import type to new authority', () => {
|
||||||
|
expect(component.selectedImportType).toEqual(ImportType.NewAuthority);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should deselect the entity and authority list', () => {
|
||||||
|
expect(selectService.deselectAll).toHaveBeenCalledWith(component.entityListId);
|
||||||
|
expect(selectService.deselectAll).toHaveBeenCalledWith(component.authorityListId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,311 @@
|
|||||||
|
import { Component, EventEmitter, OnInit } from '@angular/core';
|
||||||
|
import { NgbActiveModal } 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 { Observable } from 'rxjs/internal/Observable';
|
||||||
|
import { RemoteData } from '../../../../../../../core/data/remote-data';
|
||||||
|
import { PaginatedList } from '../../../../../../../core/data/paginated-list';
|
||||||
|
import { SearchResult } from '../../../../../../search/search-result.model';
|
||||||
|
import { Item } from '../../../../../../../core/shared/item.model';
|
||||||
|
import { RelationshipOptions } from '../../../../models/relationship-options.model';
|
||||||
|
import { LookupRelationService } from '../../../../../../../core/data/lookup-relation.service';
|
||||||
|
import { PaginatedSearchOptions } from '../../../../../../search/paginated-search-options.model';
|
||||||
|
import { CollectionElementLinkType } from '../../../../../../object-collection/collection-element-link.type';
|
||||||
|
import { Context } from '../../../../../../../core/shared/context.model';
|
||||||
|
import { SelectableListService } from '../../../../../../object-list/selectable-list/selectable-list.service';
|
||||||
|
import { ListableObject } from '../../../../../../object-collection/shared/listable-object.model';
|
||||||
|
import { Collection } from '../../../../../../../core/shared/collection.model';
|
||||||
|
import { ItemDataService } from '../../../../../../../core/data/item-data.service';
|
||||||
|
import { PaginationComponentOptions } from '../../../../../../pagination/pagination-component-options.model';
|
||||||
|
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../../../../../core/shared/operators';
|
||||||
|
import { take } from 'rxjs/operators';
|
||||||
|
import { ItemSearchResult } from '../../../../../../object-collection/shared/item-search-result.model';
|
||||||
|
import { NotificationsService } from '../../../../../../notifications/notifications.service';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The possible types of import for the external entry
|
||||||
|
*/
|
||||||
|
export enum ImportType {
|
||||||
|
None = 'None',
|
||||||
|
LocalEntity = 'LocalEntity',
|
||||||
|
LocalAuthority = 'LocalAuthority',
|
||||||
|
NewEntity = 'NewEntity',
|
||||||
|
NewAuthority = 'NewAuthority'
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-external-source-entry-import-modal',
|
||||||
|
styleUrls: ['./external-source-entry-import-modal.component.scss'],
|
||||||
|
templateUrl: './external-source-entry-import-modal.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component to display a modal window for importing an external source entry
|
||||||
|
* Shows information about the selected entry and a selectable list of local entities and authorities with similar names
|
||||||
|
* and the ability to add one of those results to the selection instead of the external entry.
|
||||||
|
* The other option is to import the external entry as a new entity or authority into the repository.
|
||||||
|
*/
|
||||||
|
export class ExternalSourceEntryImportModalComponent implements OnInit {
|
||||||
|
/**
|
||||||
|
* The prefix for every i18n key within this modal
|
||||||
|
*/
|
||||||
|
labelPrefix = 'submission.sections.describe.relationship-lookup.external-source.import-modal.';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The label to use for all messages (added to the end of relevant i18n keys)
|
||||||
|
*/
|
||||||
|
label: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The external source entry
|
||||||
|
*/
|
||||||
|
externalSourceEntry: ExternalSourceEntry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The item in submission
|
||||||
|
*/
|
||||||
|
item: Item;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The collection the user is submitting in
|
||||||
|
*/
|
||||||
|
collection: Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ID of the collection to import entries to
|
||||||
|
*/
|
||||||
|
collectionId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current relationship-options used for filtering results
|
||||||
|
*/
|
||||||
|
relationship: RelationshipOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The metadata value for the entry's uri
|
||||||
|
*/
|
||||||
|
uri: MetadataValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Local entities with a similar name
|
||||||
|
*/
|
||||||
|
localEntitiesRD$: Observable<RemoteData<PaginatedList<SearchResult<Item>>>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search options to use for fetching similar results
|
||||||
|
*/
|
||||||
|
searchOptions: PaginatedSearchOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of link to render in listable elements
|
||||||
|
*/
|
||||||
|
linkTypes = CollectionElementLinkType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The context we're currently in (submission)
|
||||||
|
*/
|
||||||
|
context = Context.SubmissionModal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List ID for selecting local entities
|
||||||
|
*/
|
||||||
|
entityListId = 'external-source-import-entity';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List ID for selecting local authorities
|
||||||
|
*/
|
||||||
|
authorityListId = 'external-source-import-authority';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ImportType enum
|
||||||
|
*/
|
||||||
|
importType = ImportType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of import the user currently has selected
|
||||||
|
*/
|
||||||
|
selectedImportType = ImportType.None;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The selected local entity
|
||||||
|
*/
|
||||||
|
selectedEntity: ListableObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The selected local authority
|
||||||
|
*/
|
||||||
|
selectedAuthority: ListableObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object has been imported, send it to the parent component
|
||||||
|
*/
|
||||||
|
importedObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should it display the ability to import the entry as an authority?
|
||||||
|
*/
|
||||||
|
authorityEnabled = false;
|
||||||
|
|
||||||
|
constructor(public modal: NgbActiveModal,
|
||||||
|
public lookupRelationService: LookupRelationService,
|
||||||
|
private selectService: SelectableListService,
|
||||||
|
private itemService: ItemDataService,
|
||||||
|
private notificationsService: NotificationsService,
|
||||||
|
private translateService: TranslateService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.uri = Metadata.first(this.externalSourceEntry.metadata, 'dc.identifier.uri');
|
||||||
|
const pagination = Object.assign(new PaginationComponentOptions(), { id: 'external-entry-import', pageSize: 5 });
|
||||||
|
this.searchOptions = Object.assign(new PaginatedSearchOptions({ query: this.externalSourceEntry.value, pagination: pagination }));
|
||||||
|
this.localEntitiesRD$ = this.lookupRelationService.getLocalResults(this.relationship, this.searchOptions);
|
||||||
|
this.collectionId = this.collection.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the window
|
||||||
|
*/
|
||||||
|
close() {
|
||||||
|
this.modal.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform the import of the external entry
|
||||||
|
*/
|
||||||
|
import() {
|
||||||
|
switch (this.selectedImportType) {
|
||||||
|
case ImportType.LocalEntity : {
|
||||||
|
this.importLocalEntity();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ImportType.NewEntity : {
|
||||||
|
this.importNewEntity();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ImportType.LocalAuthority : {
|
||||||
|
this.importLocalAuthority();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ImportType.NewAuthority : {
|
||||||
|
this.importNewAuthority();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.selectedImportType = ImportType.None;
|
||||||
|
this.deselectAllLists();
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import the selected local entity
|
||||||
|
*/
|
||||||
|
importLocalEntity() {
|
||||||
|
if (this.selectedEntity !== undefined) {
|
||||||
|
this.notificationsService.success(this.translateService.get(this.labelPrefix + this.label + '.added.local-entity'));
|
||||||
|
this.importedObject.emit(this.selectedEntity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create and import a new entity from the external entry
|
||||||
|
*/
|
||||||
|
importNewEntity() {
|
||||||
|
this.itemService.importExternalSourceEntry(this.externalSourceEntry, this.collectionId).pipe(
|
||||||
|
getSucceededRemoteData(),
|
||||||
|
getRemoteDataPayload(),
|
||||||
|
take(1)
|
||||||
|
).subscribe((item: Item) => {
|
||||||
|
this.lookupRelationService.removeLocalResultsCache();
|
||||||
|
const searchResult = Object.assign(new ItemSearchResult(), {
|
||||||
|
indexableObject: item
|
||||||
|
});
|
||||||
|
this.notificationsService.success(this.translateService.get(this.labelPrefix + this.label + '.added.new-entity'));
|
||||||
|
this.importedObject.emit(searchResult);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import the selected local authority
|
||||||
|
*/
|
||||||
|
importLocalAuthority() {
|
||||||
|
// TODO: Implement ability to import local authorities
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create and import a new authority from the external entry
|
||||||
|
*/
|
||||||
|
importNewAuthority() {
|
||||||
|
// TODO: Implement ability to import new authorities
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deselected a local entity
|
||||||
|
*/
|
||||||
|
deselectEntity() {
|
||||||
|
this.selectedEntity = undefined;
|
||||||
|
if (this.selectedImportType === ImportType.LocalEntity) {
|
||||||
|
this.selectedImportType = ImportType.None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selected a local entity
|
||||||
|
* @param entity
|
||||||
|
*/
|
||||||
|
selectEntity(entity) {
|
||||||
|
this.selectedEntity = entity;
|
||||||
|
this.selectedImportType = ImportType.LocalEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selected/deselected the new entity option
|
||||||
|
*/
|
||||||
|
selectNewEntity() {
|
||||||
|
if (this.selectedImportType === ImportType.NewEntity) {
|
||||||
|
this.selectedImportType = ImportType.None;
|
||||||
|
} else {
|
||||||
|
this.selectedImportType = ImportType.NewEntity;
|
||||||
|
this.deselectAllLists();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deselected a local authority
|
||||||
|
*/
|
||||||
|
deselectAuthority() {
|
||||||
|
this.selectedAuthority = undefined;
|
||||||
|
if (this.selectedImportType === ImportType.LocalAuthority) {
|
||||||
|
this.selectedImportType = ImportType.None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selected a local authority
|
||||||
|
* @param authority
|
||||||
|
*/
|
||||||
|
selectAuthority(authority) {
|
||||||
|
this.selectedAuthority = authority;
|
||||||
|
this.selectedImportType = ImportType.LocalAuthority;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selected/deselected the new authority option
|
||||||
|
*/
|
||||||
|
selectNewAuthority() {
|
||||||
|
if (this.selectedImportType === ImportType.NewAuthority) {
|
||||||
|
this.selectedImportType = ImportType.None;
|
||||||
|
} else {
|
||||||
|
this.selectedImportType = ImportType.NewAuthority;
|
||||||
|
this.deselectAllLists();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deselect every element from both entity and authority lists
|
||||||
|
*/
|
||||||
|
deselectAllLists() {
|
||||||
|
this.selectService.deselectAll(this.entityListId);
|
||||||
|
this.selectService.deselectAll(this.authorityListId);
|
||||||
|
}
|
||||||
|
}
|
@@ -5,6 +5,7 @@
|
|||||||
[hideGear]="hideGear"
|
[hideGear]="hideGear"
|
||||||
[linkType]="linkType"
|
[linkType]="linkType"
|
||||||
[context]="context"
|
[context]="context"
|
||||||
|
[hidePaginationDetail]="hidePaginationDetail"
|
||||||
(paginationChange)="onPaginationChange($event)"
|
(paginationChange)="onPaginationChange($event)"
|
||||||
(pageChange)="onPageChange($event)"
|
(pageChange)="onPageChange($event)"
|
||||||
(pageSizeChange)="onPageSizeChange($event)"
|
(pageSizeChange)="onPageSizeChange($event)"
|
||||||
@@ -14,6 +15,9 @@
|
|||||||
(sortFieldChange)="onSortFieldChange($event)"
|
(sortFieldChange)="onSortFieldChange($event)"
|
||||||
[selectable]="selectable"
|
[selectable]="selectable"
|
||||||
[selectionConfig]="selectionConfig"
|
[selectionConfig]="selectionConfig"
|
||||||
|
[importable]="importable"
|
||||||
|
[importConfig]="importConfig"
|
||||||
|
(importObject)="importObject.emit($event)"
|
||||||
*ngIf="(currentMode$ | async) === viewModeEnum.ListElement">
|
*ngIf="(currentMode$ | async) === viewModeEnum.ListElement">
|
||||||
</ds-object-list>
|
</ds-object-list>
|
||||||
|
|
||||||
@@ -23,6 +27,7 @@
|
|||||||
[hideGear]="hideGear"
|
[hideGear]="hideGear"
|
||||||
[linkType]="linkType"
|
[linkType]="linkType"
|
||||||
[context]="context"
|
[context]="context"
|
||||||
|
[hidePaginationDetail]="hidePaginationDetail"
|
||||||
(paginationChange)="onPaginationChange($event)"
|
(paginationChange)="onPaginationChange($event)"
|
||||||
(pageChange)="onPageChange($event)"
|
(pageChange)="onPageChange($event)"
|
||||||
(pageSizeChange)="onPageSizeChange($event)"
|
(pageSizeChange)="onPageSizeChange($event)"
|
||||||
@@ -37,6 +42,7 @@
|
|||||||
[hideGear]="hideGear"
|
[hideGear]="hideGear"
|
||||||
[linkType]="linkType"
|
[linkType]="linkType"
|
||||||
[context]="context"
|
[context]="context"
|
||||||
|
[hidePaginationDetail]="hidePaginationDetail"
|
||||||
*ngIf="(currentMode$ | async) === viewModeEnum.DetailedListElement">
|
*ngIf="(currentMode$ | async) === viewModeEnum.DetailedListElement">
|
||||||
</ds-object-detail>
|
</ds-object-detail>
|
||||||
|
|
||||||
|
@@ -53,6 +53,21 @@ export class ObjectCollectionComponent implements OnInit {
|
|||||||
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
||||||
@Output() selectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
@Output() selectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not to add an import button to the object elements
|
||||||
|
*/
|
||||||
|
@Input() importable = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The config to use for the import button
|
||||||
|
*/
|
||||||
|
@Input() importConfig: { buttonLabel: string };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an import event to the parent component
|
||||||
|
*/
|
||||||
|
@Output() importObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The link type of the rendered list elements
|
* The link type of the rendered list elements
|
||||||
*/
|
*/
|
||||||
@@ -63,6 +78,11 @@ export class ObjectCollectionComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
@Input() context: Context;
|
@Input() context: Context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Option for hiding the pagination detail
|
||||||
|
*/
|
||||||
|
@Input() hidePaginationDetail = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the page info of the list
|
* the page info of the list
|
||||||
*/
|
*/
|
||||||
|
@@ -0,0 +1,7 @@
|
|||||||
|
<div class="d-inline-block mr-2">
|
||||||
|
<button (click)="importObject.emit(object)"
|
||||||
|
class="btn btn-outline-primary btn-sm float-left"
|
||||||
|
title="{{importConfig?.buttonLabel | translate}}">
|
||||||
|
<i class="fas fa-cloud-download-alt fa-fw"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
@@ -0,0 +1,26 @@
|
|||||||
|
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||||
|
import { ListableObject } from '../listable-object.model';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-importable-list-item-control',
|
||||||
|
templateUrl: './importable-list-item-control.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component adding an import button to a list item
|
||||||
|
*/
|
||||||
|
export class ImportableListItemControlComponent {
|
||||||
|
/**
|
||||||
|
* The item or metadata to determine the component for
|
||||||
|
*/
|
||||||
|
@Input() object: ListableObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extra configuration for the import button
|
||||||
|
*/
|
||||||
|
@Input() importConfig: { buttonLabel: string };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Output the object to import
|
||||||
|
*/
|
||||||
|
@Output() importObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
||||||
|
}
|
@@ -89,7 +89,7 @@ export class ObjectDetailComponent {
|
|||||||
/**
|
/**
|
||||||
* Option for hiding the pagination detail
|
* Option for hiding the pagination detail
|
||||||
*/
|
*/
|
||||||
public hidePaginationDetail = true;
|
@Input() hidePaginationDetail = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An event fired when the page is changed.
|
* An event fired when the page is changed.
|
||||||
|
@@ -5,6 +5,7 @@
|
|||||||
[sortOptions]="sortConfig"
|
[sortOptions]="sortConfig"
|
||||||
[hideGear]="hideGear"
|
[hideGear]="hideGear"
|
||||||
[hidePagerWhenSinglePage]="hidePagerWhenSinglePage"
|
[hidePagerWhenSinglePage]="hidePagerWhenSinglePage"
|
||||||
|
[hidePaginationDetail]="hidePaginationDetail"
|
||||||
(pageChange)="onPageChange($event)"
|
(pageChange)="onPageChange($event)"
|
||||||
(pageSizeChange)="onPageSizeChange($event)"
|
(pageSizeChange)="onPageSizeChange($event)"
|
||||||
(sortDirectionChange)="onSortDirectionChange($event)"
|
(sortDirectionChange)="onSortDirectionChange($event)"
|
||||||
|
@@ -69,6 +69,11 @@ export class ObjectGridComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
@Input() context: Context;
|
@Input() context: Context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Option for hiding the pagination detail
|
||||||
|
*/
|
||||||
|
@Input() hidePaginationDetail = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Behavior subject to output the current listable objects
|
* Behavior subject to output the current listable objects
|
||||||
*/
|
*/
|
||||||
|
@@ -5,6 +5,7 @@
|
|||||||
[sortOptions]="sortConfig"
|
[sortOptions]="sortConfig"
|
||||||
[hideGear]="hideGear"
|
[hideGear]="hideGear"
|
||||||
[hidePagerWhenSinglePage]="hidePagerWhenSinglePage"
|
[hidePagerWhenSinglePage]="hidePagerWhenSinglePage"
|
||||||
|
[hidePaginationDetail]="hidePaginationDetail"
|
||||||
(pageChange)="onPageChange($event)"
|
(pageChange)="onPageChange($event)"
|
||||||
(pageSizeChange)="onPageSizeChange($event)"
|
(pageSizeChange)="onPageSizeChange($event)"
|
||||||
(sortDirectionChange)="onSortDirectionChange($event)"
|
(sortDirectionChange)="onSortDirectionChange($event)"
|
||||||
@@ -19,6 +20,11 @@
|
|||||||
(deselectObject)="deselectObject.emit($event)"
|
(deselectObject)="deselectObject.emit($event)"
|
||||||
(selectObject)="selectObject.emit($event)"></ds-selectable-list-item-control>
|
(selectObject)="selectObject.emit($event)"></ds-selectable-list-item-control>
|
||||||
</span>
|
</span>
|
||||||
|
<span *ngIf="importable">
|
||||||
|
<ds-importable-list-item-control [object]="object"
|
||||||
|
[importConfig]="importConfig"
|
||||||
|
(importObject)="importObject.emit($event)"></ds-importable-list-item-control>
|
||||||
|
</span>
|
||||||
<ds-listable-object-component-loader [object]="object" [viewMode]="viewMode" [index]="i" [context]="context" [linkType]="linkType"
|
<ds-listable-object-component-loader [object]="object" [viewMode]="viewMode" [index]="i" [context]="context" [linkType]="linkType"
|
||||||
[listID]="selectionConfig?.listId"></ds-listable-object-component-loader>
|
[listID]="selectionConfig?.listId"></ds-listable-object-component-loader>
|
||||||
</li>
|
</li>
|
||||||
|
@@ -61,6 +61,21 @@ export class ObjectListComponent {
|
|||||||
*/
|
*/
|
||||||
@Input() context: Context;
|
@Input() context: Context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Option for hiding the pagination detail
|
||||||
|
*/
|
||||||
|
@Input() hidePaginationDetail = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not to add an import button to the object
|
||||||
|
*/
|
||||||
|
@Input() importable = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Config used for the import button
|
||||||
|
*/
|
||||||
|
@Input() importConfig: { importLabel: string };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current listable objects
|
* The current listable objects
|
||||||
*/
|
*/
|
||||||
@@ -119,6 +134,12 @@ export class ObjectListComponent {
|
|||||||
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
||||||
|
|
||||||
@Output() selectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
@Output() selectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an import event to the parent component
|
||||||
|
*/
|
||||||
|
@Output() importObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An event fired when the sort field is changed.
|
* An event fired when the sort field is changed.
|
||||||
* Event's payload equals to the newly selected sort field.
|
* Event's payload equals to the newly selected sort field.
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<div *ngIf="currentPageState == undefined || currentPageState == currentPage">
|
<div *ngIf="currentPageState == undefined || currentPageState == currentPage">
|
||||||
<div class="pagination-masked clearfix top">
|
<div *ngIf="(!hidePaginationDetail && collectionSize > 0) || !hideGear" class="pagination-masked clearfix top">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div *ngIf="!hidePaginationDetail && collectionSize > 0" class="col-auto pagination-info">
|
<div *ngIf="!hidePaginationDetail && collectionSize > 0" class="col-auto pagination-info">
|
||||||
<span class="align-middle hidden-xs-down">{{ 'pagination.showing.label' | translate }}</span>
|
<span class="align-middle hidden-xs-down">{{ 'pagination.showing.label' | translate }}</span>
|
||||||
|
@@ -8,6 +8,7 @@
|
|||||||
[selectable]="selectable"
|
[selectable]="selectable"
|
||||||
[selectionConfig]="selectionConfig"
|
[selectionConfig]="selectionConfig"
|
||||||
[context]="context"
|
[context]="context"
|
||||||
|
[hidePaginationDetail]="hidePaginationDetail"
|
||||||
(deselectObject)="deselectObject.emit($event)"
|
(deselectObject)="deselectObject.emit($event)"
|
||||||
(selectObject)="selectObject.emit($event)"
|
(selectObject)="selectObject.emit($event)"
|
||||||
>
|
>
|
||||||
|
@@ -67,6 +67,11 @@ export class SearchResultsComponent {
|
|||||||
|
|
||||||
@Input() context: Context;
|
@Input() context: Context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Option for hiding the pagination detail
|
||||||
|
*/
|
||||||
|
@Input() hidePaginationDetail = false;
|
||||||
|
|
||||||
@Input() selectionConfig: {repeatable: boolean, listId: string};
|
@Input() selectionConfig: {repeatable: boolean, listId: string};
|
||||||
|
|
||||||
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
||||||
|
@@ -173,6 +173,8 @@ import { SidebarFilterComponent } from './sidebar/filter/sidebar-filter.componen
|
|||||||
import { SidebarFilterSelectedOptionComponent } from './sidebar/filter/sidebar-filter-selected-option.component';
|
import { SidebarFilterSelectedOptionComponent } from './sidebar/filter/sidebar-filter-selected-option.component';
|
||||||
import { SelectableListItemControlComponent } from './object-collection/shared/selectable-list-item-control/selectable-list-item-control.component';
|
import { SelectableListItemControlComponent } from './object-collection/shared/selectable-list-item-control/selectable-list-item-control.component';
|
||||||
import { DsDynamicLookupRelationExternalSourceTabComponent } from './form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/dynamic-lookup-relation-external-source-tab.component';
|
import { DsDynamicLookupRelationExternalSourceTabComponent } from './form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/dynamic-lookup-relation-external-source-tab.component';
|
||||||
|
import { ExternalSourceEntryImportModalComponent } from './form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/external-source-entry-import-modal/external-source-entry-import-modal.component';
|
||||||
|
import { ImportableListItemControlComponent } from './object-collection/shared/importable-list-item-control/importable-list-item-control.component';
|
||||||
import { DragDropModule } from '@angular/cdk/drag-drop';
|
import { DragDropModule } from '@angular/cdk/drag-drop';
|
||||||
import { ExistingMetadataListElementComponent } from './form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component';
|
import { ExistingMetadataListElementComponent } from './form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component';
|
||||||
|
|
||||||
@@ -335,6 +337,8 @@ const COMPONENTS = [
|
|||||||
CollectionSelectComponent,
|
CollectionSelectComponent,
|
||||||
MetadataRepresentationLoaderComponent,
|
MetadataRepresentationLoaderComponent,
|
||||||
SelectableListItemControlComponent,
|
SelectableListItemControlComponent,
|
||||||
|
ExternalSourceEntryImportModalComponent,
|
||||||
|
ImportableListItemControlComponent,
|
||||||
ExistingMetadataListElementComponent
|
ExistingMetadataListElementComponent
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -397,7 +401,8 @@ const ENTRY_COMPONENTS = [
|
|||||||
SearchAuthorityFilterComponent,
|
SearchAuthorityFilterComponent,
|
||||||
DsDynamicLookupRelationSearchTabComponent,
|
DsDynamicLookupRelationSearchTabComponent,
|
||||||
DsDynamicLookupRelationSelectionTabComponent,
|
DsDynamicLookupRelationSelectionTabComponent,
|
||||||
DsDynamicLookupRelationExternalSourceTabComponent
|
DsDynamicLookupRelationExternalSourceTabComponent,
|
||||||
|
ExternalSourceEntryImportModalComponent
|
||||||
];
|
];
|
||||||
|
|
||||||
const SHARED_ITEM_PAGE_COMPONENTS = [
|
const SHARED_ITEM_PAGE_COMPONENTS = [
|
||||||
|
Reference in New Issue
Block a user