mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
Merge pull request #1332 from 4Science/CST-4510-entity-selection-porting
Assign an entity type to a collection and map external providers
This commit is contained in:
@@ -1,18 +1,27 @@
|
|||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import {
|
import {
|
||||||
DynamicFormControlModel,
|
DynamicFormControlModel,
|
||||||
|
DynamicFormOptionConfig,
|
||||||
DynamicFormService,
|
DynamicFormService,
|
||||||
DynamicInputModel,
|
DynamicSelectModel
|
||||||
DynamicTextAreaModel
|
|
||||||
} from '@ng-dynamic-forms/core';
|
} from '@ng-dynamic-forms/core';
|
||||||
|
|
||||||
import { Collection } from '../../core/shared/collection.model';
|
import { Collection } from '../../core/shared/collection.model';
|
||||||
import { ComColFormComponent } from '../../shared/comcol-forms/comcol-form/comcol-form.component';
|
import { ComColFormComponent } from '../../shared/comcol-forms/comcol-form/comcol-form.component';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||||
import { AuthService } from '../../core/auth/auth.service';
|
import { AuthService } from '../../core/auth/auth.service';
|
||||||
import { RequestService } from '../../core/data/request.service';
|
import { RequestService } from '../../core/data/request.service';
|
||||||
import { ObjectCacheService } from '../../core/cache/object-cache.service';
|
import { ObjectCacheService } from '../../core/cache/object-cache.service';
|
||||||
|
import { EntityTypeService } from '../../core/data/entity-type.service';
|
||||||
|
import { ItemType } from '../../core/shared/item-relationships/item-type.model';
|
||||||
|
import { MetadataValue } from '../../core/shared/metadata.models';
|
||||||
|
import { getFirstSucceededRemoteListPayload } from '../../core/shared/operators';
|
||||||
|
import { collectionFormEntityTypeSelectionConfig, collectionFormModels, } from './collection-form.models';
|
||||||
|
import { NONE_ENTITY_TYPE } from '../../core/shared/item-relationships/item-type.resource-type';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Form used for creating and editing collections
|
* Form used for creating and editing collections
|
||||||
@@ -22,7 +31,7 @@ import { ObjectCacheService } from '../../core/cache/object-cache.service';
|
|||||||
styleUrls: ['../../shared/comcol-forms/comcol-form/comcol-form.component.scss'],
|
styleUrls: ['../../shared/comcol-forms/comcol-form/comcol-form.component.scss'],
|
||||||
templateUrl: '../../shared/comcol-forms/comcol-form/comcol-form.component.html'
|
templateUrl: '../../shared/comcol-forms/comcol-form/comcol-form.component.html'
|
||||||
})
|
})
|
||||||
export class CollectionFormComponent extends ComColFormComponent<Collection> {
|
export class CollectionFormComponent extends ComColFormComponent<Collection> implements OnInit {
|
||||||
/**
|
/**
|
||||||
* @type {Collection} A new collection when a collection is being created, an existing Input collection when a collection is being edited
|
* @type {Collection} A new collection when a collection is being created, an existing Input collection when a collection is being edited
|
||||||
*/
|
*/
|
||||||
@@ -34,46 +43,16 @@ export class CollectionFormComponent extends ComColFormComponent<Collection> {
|
|||||||
type = Collection.type;
|
type = Collection.type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The dynamic form fields used for creating/editing a collection
|
* The dynamic form field used for entity type selection
|
||||||
* @type {(DynamicInputModel | DynamicTextAreaModel)[]}
|
* @type {DynamicSelectModel<string>}
|
||||||
*/
|
*/
|
||||||
formModel: DynamicFormControlModel[] = [
|
entityTypeSelection: DynamicSelectModel<string> = new DynamicSelectModel(collectionFormEntityTypeSelectionConfig);
|
||||||
new DynamicInputModel({
|
|
||||||
id: 'title',
|
/**
|
||||||
name: 'dc.title',
|
* The dynamic form fields used for creating/editing a collection
|
||||||
required: true,
|
* @type {DynamicFormControlModel[]}
|
||||||
validators: {
|
*/
|
||||||
required: null
|
formModel: DynamicFormControlModel[];
|
||||||
},
|
|
||||||
errorMessages: {
|
|
||||||
required: 'Please enter a name for this title'
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
new DynamicTextAreaModel({
|
|
||||||
id: 'description',
|
|
||||||
name: 'dc.description',
|
|
||||||
}),
|
|
||||||
new DynamicTextAreaModel({
|
|
||||||
id: 'abstract',
|
|
||||||
name: 'dc.description.abstract',
|
|
||||||
}),
|
|
||||||
new DynamicTextAreaModel({
|
|
||||||
id: 'rights',
|
|
||||||
name: 'dc.rights',
|
|
||||||
}),
|
|
||||||
new DynamicTextAreaModel({
|
|
||||||
id: 'tableofcontents',
|
|
||||||
name: 'dc.description.tableofcontents',
|
|
||||||
}),
|
|
||||||
new DynamicTextAreaModel({
|
|
||||||
id: 'license',
|
|
||||||
name: 'dc.rights.license',
|
|
||||||
}),
|
|
||||||
new DynamicTextAreaModel({
|
|
||||||
id: 'provenance',
|
|
||||||
name: 'dc.description.provenance',
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
|
|
||||||
public constructor(protected formService: DynamicFormService,
|
public constructor(protected formService: DynamicFormService,
|
||||||
protected translate: TranslateService,
|
protected translate: TranslateService,
|
||||||
@@ -81,7 +60,43 @@ export class CollectionFormComponent extends ComColFormComponent<Collection> {
|
|||||||
protected authService: AuthService,
|
protected authService: AuthService,
|
||||||
protected dsoService: CommunityDataService,
|
protected dsoService: CommunityDataService,
|
||||||
protected requestService: RequestService,
|
protected requestService: RequestService,
|
||||||
protected objectCache: ObjectCacheService) {
|
protected objectCache: ObjectCacheService,
|
||||||
|
protected entityTypeService: EntityTypeService) {
|
||||||
super(formService, translate, notificationsService, authService, requestService, objectCache);
|
super(formService, translate, notificationsService, authService, requestService, objectCache);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
|
||||||
|
let currentRelationshipValue: MetadataValue[];
|
||||||
|
if (this.dso && this.dso.metadata) {
|
||||||
|
currentRelationshipValue = this.dso.metadata['dspace.entity.type'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const entities$: Observable<ItemType[]> = this.entityTypeService.findAll({ elementsPerPage: 100, currentPage: 1 }).pipe(
|
||||||
|
getFirstSucceededRemoteListPayload()
|
||||||
|
);
|
||||||
|
|
||||||
|
// retrieve all entity types to populate the dropdowns selection
|
||||||
|
entities$.subscribe((entityTypes: ItemType[]) => {
|
||||||
|
|
||||||
|
entityTypes
|
||||||
|
.filter((type: ItemType) => type.label !== NONE_ENTITY_TYPE)
|
||||||
|
.forEach((type: ItemType, index: number) => {
|
||||||
|
this.entityTypeSelection.add({
|
||||||
|
disabled: false,
|
||||||
|
label: type.label,
|
||||||
|
value: type.label
|
||||||
|
} as DynamicFormOptionConfig<string>);
|
||||||
|
if (currentRelationshipValue && currentRelationshipValue.length > 0 && currentRelationshipValue[0].value === type.label) {
|
||||||
|
this.entityTypeSelection.select(index);
|
||||||
|
this.entityTypeSelection.disabled = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.formModel = [...collectionFormModels, this.entityTypeSelection];
|
||||||
|
|
||||||
|
super.ngOnInit();
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,46 @@
|
|||||||
|
import { DynamicFormControlModel, DynamicInputModel, DynamicTextAreaModel } from '@ng-dynamic-forms/core';
|
||||||
|
import { DynamicSelectModelConfig } from '@ng-dynamic-forms/core/lib/model/select/dynamic-select.model';
|
||||||
|
|
||||||
|
export const collectionFormEntityTypeSelectionConfig: DynamicSelectModelConfig<string> = {
|
||||||
|
id: 'entityType',
|
||||||
|
name: 'dspace.entity.type',
|
||||||
|
disabled: false
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The dynamic form fields used for creating/editing a collection
|
||||||
|
* @type {(DynamicInputModel | DynamicTextAreaModel)[]}
|
||||||
|
*/
|
||||||
|
export const collectionFormModels: DynamicFormControlModel[] = [
|
||||||
|
new DynamicInputModel({
|
||||||
|
id: 'title',
|
||||||
|
name: 'dc.title',
|
||||||
|
required: true,
|
||||||
|
validators: {
|
||||||
|
required: null
|
||||||
|
},
|
||||||
|
errorMessages: {
|
||||||
|
required: 'Please enter a name for this title'
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
new DynamicTextAreaModel({
|
||||||
|
id: 'description',
|
||||||
|
name: 'dc.description',
|
||||||
|
}),
|
||||||
|
new DynamicTextAreaModel({
|
||||||
|
id: 'abstract',
|
||||||
|
name: 'dc.description.abstract',
|
||||||
|
}),
|
||||||
|
new DynamicTextAreaModel({
|
||||||
|
id: 'rights',
|
||||||
|
name: 'dc.rights',
|
||||||
|
}),
|
||||||
|
new DynamicTextAreaModel({
|
||||||
|
id: 'tableofcontents',
|
||||||
|
name: 'dc.description.tableofcontents',
|
||||||
|
}),
|
||||||
|
new DynamicTextAreaModel({
|
||||||
|
id: 'license',
|
||||||
|
name: 'dc.rights.license',
|
||||||
|
})
|
||||||
|
];
|
@@ -17,7 +17,7 @@ import {
|
|||||||
createFailedRemoteDataObject$,
|
createFailedRemoteDataObject$,
|
||||||
createSuccessfulRemoteDataObject,
|
createSuccessfulRemoteDataObject,
|
||||||
createSuccessfulRemoteDataObject$
|
createSuccessfulRemoteDataObject$
|
||||||
} from 'src/app/shared/remote-data.utils';
|
} from '../../shared/remote-data.utils';
|
||||||
import { cold, getTestScheduler, hot } from 'jasmine-marbles';
|
import { cold, getTestScheduler, hot } from 'jasmine-marbles';
|
||||||
import { TestScheduler } from 'rxjs/testing';
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
@@ -27,12 +27,7 @@ import { CommunityDataService } from './community-data.service';
|
|||||||
import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
|
import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
|
||||||
import { PaginatedList } from './paginated-list.model';
|
import { PaginatedList } from './paginated-list.model';
|
||||||
import { RemoteData } from './remote-data';
|
import { RemoteData } from './remote-data';
|
||||||
import {
|
import { ContentSourceRequest, FindListOptions, RestRequest, UpdateContentSourceRequest } from './request.models';
|
||||||
ContentSourceRequest,
|
|
||||||
FindListOptions,
|
|
||||||
UpdateContentSourceRequest,
|
|
||||||
RestRequest
|
|
||||||
} from './request.models';
|
|
||||||
import { RequestService } from './request.service';
|
import { RequestService } from './request.service';
|
||||||
import { BitstreamDataService } from './bitstream-data.service';
|
import { BitstreamDataService } from './bitstream-data.service';
|
||||||
|
|
||||||
@@ -84,16 +79,48 @@ export class CollectionDataService extends ComColDataService<Collection> {
|
|||||||
filter((collections: RemoteData<PaginatedList<Collection>>) => !collections.isResponsePending));
|
filter((collections: RemoteData<PaginatedList<Collection>>) => !collections.isResponsePending));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all collections the user is authorized to submit to
|
||||||
|
*
|
||||||
|
* @param query limit the returned collection to those with metadata values matching the query terms.
|
||||||
|
* @param entityType The entity type used to limit the returned collection
|
||||||
|
* @param options The [[FindListOptions]] object
|
||||||
|
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
|
||||||
|
* the response becomes stale
|
||||||
|
* @param linksToFollow The array of [[FollowLinkConfig]]
|
||||||
|
* @return Observable<RemoteData<PaginatedList<Collection>>>
|
||||||
|
* collection list
|
||||||
|
*/
|
||||||
|
getAuthorizedCollectionByEntityType(
|
||||||
|
query: string,
|
||||||
|
entityType: string,
|
||||||
|
options: FindListOptions = {},
|
||||||
|
reRequestOnStale = true,
|
||||||
|
...linksToFollow: FollowLinkConfig<Collection>[]): Observable<RemoteData<PaginatedList<Collection>>> {
|
||||||
|
const searchHref = 'findSubmitAuthorizedByEntityType';
|
||||||
|
options = Object.assign({}, options, {
|
||||||
|
searchParams: [
|
||||||
|
new RequestParam('query', query),
|
||||||
|
new RequestParam('entityType', entityType)
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.searchBy(searchHref, options, true, reRequestOnStale, ...linksToFollow).pipe(
|
||||||
|
filter((collections: RemoteData<PaginatedList<Collection>>) => !collections.isResponsePending));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all collections the user is authorized to submit to, by community
|
* Get all collections the user is authorized to submit to, by community
|
||||||
*
|
*
|
||||||
* @param communityId The community id
|
* @param communityId The community id
|
||||||
* @param query limit the returned collection to those with metadata values matching the query terms.
|
* @param query limit the returned collection to those with metadata values matching the query terms.
|
||||||
* @param options The [[FindListOptions]] object
|
* @param options The [[FindListOptions]] object
|
||||||
|
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||||
|
* requested after the response becomes stale
|
||||||
* @return Observable<RemoteData<PaginatedList<Collection>>>
|
* @return Observable<RemoteData<PaginatedList<Collection>>>
|
||||||
* collection list
|
* collection list
|
||||||
*/
|
*/
|
||||||
getAuthorizedCollectionByCommunity(communityId: string, query: string, options: FindListOptions = {}): Observable<RemoteData<PaginatedList<Collection>>> {
|
getAuthorizedCollectionByCommunity(communityId: string, query: string, options: FindListOptions = {}, reRequestOnStale = true,): Observable<RemoteData<PaginatedList<Collection>>> {
|
||||||
const searchHref = 'findSubmitAuthorizedByCommunity';
|
const searchHref = 'findSubmitAuthorizedByCommunity';
|
||||||
options = Object.assign({}, options, {
|
options = Object.assign({}, options, {
|
||||||
searchParams: [
|
searchParams: [
|
||||||
@@ -102,7 +129,38 @@ export class CollectionDataService extends ComColDataService<Collection> {
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.searchBy(searchHref, options).pipe(
|
return this.searchBy(searchHref, options, reRequestOnStale).pipe(
|
||||||
|
filter((collections: RemoteData<PaginatedList<Collection>>) => !collections.isResponsePending));
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get all collections the user is authorized to submit to, by community and has the metadata
|
||||||
|
*
|
||||||
|
* @param communityId The community id
|
||||||
|
* @param entityType The entity type used to limit the returned collection
|
||||||
|
* @param options The [[FindListOptions]] object
|
||||||
|
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
|
||||||
|
* the response becomes stale
|
||||||
|
* @param linksToFollow The array of [[FollowLinkConfig]]
|
||||||
|
* @return Observable<RemoteData<PaginatedList<Collection>>>
|
||||||
|
* collection list
|
||||||
|
*/
|
||||||
|
getAuthorizedCollectionByCommunityAndEntityType(
|
||||||
|
communityId: string,
|
||||||
|
entityType: string,
|
||||||
|
options: FindListOptions = {},
|
||||||
|
reRequestOnStale = true,
|
||||||
|
...linksToFollow: FollowLinkConfig<Collection>[]): Observable<RemoteData<PaginatedList<Collection>>> {
|
||||||
|
const searchHref = 'findSubmitAuthorizedByCommunityAndEntityType';
|
||||||
|
const searchParams = [
|
||||||
|
new RequestParam('uuid', communityId),
|
||||||
|
new RequestParam('entityType', entityType)
|
||||||
|
];
|
||||||
|
|
||||||
|
options = Object.assign({}, options, {
|
||||||
|
searchParams: searchParams
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.searchBy(searchHref, options, true, reRequestOnStale, ...linksToFollow).pipe(
|
||||||
filter((collections: RemoteData<PaginatedList<Collection>>) => !collections.isResponsePending));
|
filter((collections: RemoteData<PaginatedList<Collection>>) => !collections.isResponsePending));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -10,13 +10,14 @@ import { NotificationsService } from '../../shared/notifications/notifications.s
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
|
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
import { FindListOptions } from './request.models';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { switchMap, take, map } from 'rxjs/operators';
|
import { filter, map, switchMap, take } from 'rxjs/operators';
|
||||||
import { RemoteData } from './remote-data';
|
import { RemoteData } from './remote-data';
|
||||||
import { RelationshipType } from '../shared/item-relationships/relationship-type.model';
|
import { RelationshipType } from '../shared/item-relationships/relationship-type.model';
|
||||||
import { PaginatedList } from './paginated-list.model';
|
import { PaginatedList } from './paginated-list.model';
|
||||||
import { ItemType } from '../shared/item-relationships/item-type.model';
|
import { ItemType } from '../shared/item-relationships/item-type.model';
|
||||||
import { getRemoteDataPayload, getFirstSucceededRemoteData } from '../shared/operators';
|
import { getFirstSucceededRemoteData, getRemoteDataPayload } from '../shared/operators';
|
||||||
import { RelationshipTypeService } from './relationship-type.service';
|
import { RelationshipTypeService } from './relationship-type.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,7 +57,7 @@ export class EntityTypeService extends DataService<ItemType> {
|
|||||||
/**
|
/**
|
||||||
* Check whether a given entity type is the left type of a given relationship type, as an observable boolean
|
* Check whether a given entity type is the left type of a given relationship type, as an observable boolean
|
||||||
* @param relationshipType the relationship type for which to check whether the given entity type is the left type
|
* @param relationshipType the relationship type for which to check whether the given entity type is the left type
|
||||||
* @param entityType the entity type for which to check whether it is the left type of the given relationship type
|
* @param itemType the entity type for which to check whether it is the left type of the given relationship type
|
||||||
*/
|
*/
|
||||||
isLeftType(relationshipType: RelationshipType, itemType: ItemType): Observable<boolean> {
|
isLeftType(relationshipType: RelationshipType, itemType: ItemType): Observable<boolean> {
|
||||||
|
|
||||||
@@ -67,6 +68,73 @@ export class EntityTypeService extends DataService<ItemType> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of entity types for which there is at least one collection in which the user is authorized to submit
|
||||||
|
*
|
||||||
|
* @param {FindListOptions} options
|
||||||
|
*/
|
||||||
|
getAllAuthorizedRelationshipType(options: FindListOptions = {}): Observable<RemoteData<PaginatedList<ItemType>>> {
|
||||||
|
const searchHref = 'findAllByAuthorizedCollection';
|
||||||
|
|
||||||
|
return this.searchBy(searchHref, options).pipe(
|
||||||
|
filter((type: RemoteData<PaginatedList<ItemType>>) => !type.isResponsePending));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to verify if there are one or more entities available
|
||||||
|
*/
|
||||||
|
hasMoreThanOneAuthorized(): Observable<boolean> {
|
||||||
|
const findListOptions: FindListOptions = {
|
||||||
|
elementsPerPage: 2,
|
||||||
|
currentPage: 1
|
||||||
|
};
|
||||||
|
return this.getAllAuthorizedRelationshipType(findListOptions).pipe(
|
||||||
|
map((result: RemoteData<PaginatedList<ItemType>>) => {
|
||||||
|
let output: boolean;
|
||||||
|
if (result.payload) {
|
||||||
|
output = ( result.payload.page.length > 1 );
|
||||||
|
} else {
|
||||||
|
output = false;
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It returns a list of entity types for which there is at least one collection
|
||||||
|
* in which the user is authorized to submit supported by at least one external data source provider
|
||||||
|
*
|
||||||
|
* @param {FindListOptions} options
|
||||||
|
*/
|
||||||
|
getAllAuthorizedRelationshipTypeImport(options: FindListOptions = {}): Observable<RemoteData<PaginatedList<ItemType>>> {
|
||||||
|
const searchHref = 'findAllByAuthorizedExternalSource';
|
||||||
|
|
||||||
|
return this.searchBy(searchHref, options).pipe(
|
||||||
|
filter((type: RemoteData<PaginatedList<ItemType>>) => !type.isResponsePending));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to verify if there are one or more entities available. To use with external source import.
|
||||||
|
*/
|
||||||
|
hasMoreThanOneAuthorizedImport(): Observable<boolean> {
|
||||||
|
const findListOptions: FindListOptions = {
|
||||||
|
elementsPerPage: 2,
|
||||||
|
currentPage: 1
|
||||||
|
};
|
||||||
|
return this.getAllAuthorizedRelationshipTypeImport(findListOptions).pipe(
|
||||||
|
map((result: RemoteData<PaginatedList<ItemType>>) => {
|
||||||
|
let output: boolean;
|
||||||
|
if (result.payload) {
|
||||||
|
output = ( result.payload.page.length > 1 );
|
||||||
|
} else {
|
||||||
|
output = false;
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the allowed relationship types for an entity type
|
* Get the allowed relationship types for an entity type
|
||||||
* @param entityTypeId
|
* @param entityTypeId
|
||||||
|
@@ -1,10 +1,15 @@
|
|||||||
import { autoserialize, deserialize } from 'cerialize';
|
import { autoserialize, deserialize } from 'cerialize';
|
||||||
import { typedObject } from '../cache/builders/build-decorators';
|
import { link, typedObject } from '../cache/builders/build-decorators';
|
||||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||||
import { excludeFromEquals } from '../utilities/equals.decorators';
|
import { excludeFromEquals } from '../utilities/equals.decorators';
|
||||||
import { EXTERNAL_SOURCE } from './external-source.resource-type';
|
import { EXTERNAL_SOURCE } from './external-source.resource-type';
|
||||||
import { HALLink } from './hal-link.model';
|
import { HALLink } from './hal-link.model';
|
||||||
import { ResourceType } from './resource-type';
|
import { ResourceType } from './resource-type';
|
||||||
|
import { RemoteData } from '../data/remote-data';
|
||||||
|
import { PaginatedList } from '../data/paginated-list.model';
|
||||||
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
|
import { ITEM_TYPE } from './item-relationships/item-type.resource-type';
|
||||||
|
import { ItemType } from './item-relationships/item-type.model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model class for an external source
|
* Model class for an external source
|
||||||
@@ -38,6 +43,13 @@ export class ExternalSource extends CacheableObject {
|
|||||||
@autoserialize
|
@autoserialize
|
||||||
hierarchical: boolean;
|
hierarchical: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of entity types that are compatible with this external source
|
||||||
|
* Will be undefined unless the entityTypes {@link HALLink} has been resolved.
|
||||||
|
*/
|
||||||
|
@link(ITEM_TYPE, true)
|
||||||
|
entityTypes?: Observable<RemoteData<PaginatedList<ItemType>>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link HALLink}s for this ExternalSource
|
* The {@link HALLink}s for this ExternalSource
|
||||||
*/
|
*/
|
||||||
@@ -45,5 +57,6 @@ export class ExternalSource extends CacheableObject {
|
|||||||
_links: {
|
_links: {
|
||||||
self: HALLink;
|
self: HALLink;
|
||||||
entries: HALLink;
|
entries: HALLink;
|
||||||
|
entityTypes: HALLink;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -6,5 +6,9 @@ import { ResourceType } from '../resource-type';
|
|||||||
* Needs to be in a separate file to prevent circular
|
* Needs to be in a separate file to prevent circular
|
||||||
* dependencies in webpack.
|
* dependencies in webpack.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const ITEM_TYPE = new ResourceType('entitytype');
|
export const ITEM_TYPE = new ResourceType('entitytype');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The unset entity type
|
||||||
|
*/
|
||||||
|
export const NONE_ENTITY_TYPE = 'none';
|
||||||
|
@@ -0,0 +1,24 @@
|
|||||||
|
<div class="add" *ngIf="!(moreThanOne$ | async)">
|
||||||
|
<button class="btn btn-lg btn-outline-primary mt-1 ml-2" [disabled]="!(initialized$|async)"
|
||||||
|
(click)="openPage(singleEntity)" role="button"
|
||||||
|
title="{{'mydspace.new-submission-external' | translate}}">
|
||||||
|
<i class="fa fa-file-import" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="add w-100" display="dynamic" placement="bottom-right"
|
||||||
|
ngbDropdown
|
||||||
|
*ngIf="(moreThanOne$ | async)">
|
||||||
|
<button class="btn btn-lg btn-outline-primary mt-1 ml-2" id="dropdownSubmission" ngbDropdownToggle
|
||||||
|
type="button" [disabled]="!(initialized$|async)"
|
||||||
|
attr.aria-label="{{'mydspace.new-submission-external' | translate}}"
|
||||||
|
title="{{'mydspace.new-submission-external' | translate}}">
|
||||||
|
<i class="fa fa-file-import" aria-hidden="true"></i>
|
||||||
|
<span class="caret"></span>
|
||||||
|
</button>
|
||||||
|
<div ngbDropdownMenu
|
||||||
|
class="dropdown-menu"
|
||||||
|
id="entityControlsDropdownMenu"
|
||||||
|
aria-labelledby="dropdownSubmission">
|
||||||
|
<ds-entity-dropdown [isSubmission]="false" (selectionChange)="openPage($event)"></ds-entity-dropdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -0,0 +1,16 @@
|
|||||||
|
.parent {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload {
|
||||||
|
flex: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add {
|
||||||
|
flex: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
#entityControlsDropdownMenu {
|
||||||
|
min-width: 18rem;
|
||||||
|
box-shadow: $btn-focus-box-shadow;
|
||||||
|
}
|
@@ -0,0 +1,189 @@
|
|||||||
|
import { Component, DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { createPaginatedList, createTestComponent } from '../../../shared/testing/utils.test';
|
||||||
|
import { MyDSpaceNewExternalDropdownComponent } from './my-dspace-new-external-dropdown.component';
|
||||||
|
import { EntityTypeService } from '../../../core/data/entity-type.service';
|
||||||
|
import { ItemType } from '../../../core/shared/item-relationships/item-type.model';
|
||||||
|
import { ResourceType } from '../../../core/shared/resource-type';
|
||||||
|
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||||
|
import { PageInfo } from '../../../core/shared/page-info.model';
|
||||||
|
import { RouterStub } from '../../../shared/testing/router.stub';
|
||||||
|
|
||||||
|
export function getMockEntityTypeService(): EntityTypeService {
|
||||||
|
const pageInfo = { elementsPerPage: 20, totalElements: 4, totalPages: 1, currentPage: 0 } as PageInfo;
|
||||||
|
const type1: ItemType = {
|
||||||
|
id: '1',
|
||||||
|
label: 'Publication',
|
||||||
|
uuid: '1',
|
||||||
|
type: new ResourceType('entitytype'),
|
||||||
|
_links: undefined
|
||||||
|
};
|
||||||
|
const type2: ItemType = {
|
||||||
|
id: '2',
|
||||||
|
label: 'Journal',
|
||||||
|
uuid: '2',
|
||||||
|
type: new ResourceType('entitytype'),
|
||||||
|
_links: undefined
|
||||||
|
};
|
||||||
|
const type3: ItemType = {
|
||||||
|
id: '2',
|
||||||
|
label: 'DataPackage',
|
||||||
|
uuid: '2',
|
||||||
|
type: new ResourceType('entitytype'),
|
||||||
|
_links: undefined
|
||||||
|
};
|
||||||
|
const rd$ = createSuccessfulRemoteDataObject$(createPaginatedList([type1, type2, type3]));
|
||||||
|
return jasmine.createSpyObj('entityTypeService', {
|
||||||
|
getAllAuthorizedRelationshipTypeImport: rd$,
|
||||||
|
hasMoreThanOneAuthorizedImport: observableOf(true)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getMockEmptyEntityTypeService(): EntityTypeService {
|
||||||
|
const pageInfo = { elementsPerPage: 20, totalElements: 1, totalPages: 1, currentPage: 0 } as PageInfo;
|
||||||
|
const type1: ItemType = {
|
||||||
|
id: '1',
|
||||||
|
label: 'Publication',
|
||||||
|
uuid: '1',
|
||||||
|
type: new ResourceType('entitytype'),
|
||||||
|
_links: undefined
|
||||||
|
};
|
||||||
|
const rd$ = createSuccessfulRemoteDataObject$(createPaginatedList([type1]));
|
||||||
|
return jasmine.createSpyObj('entityTypeService', {
|
||||||
|
getAllAuthorizedRelationshipTypeImport: rd$,
|
||||||
|
hasMoreThanOneAuthorizedImport: observableOf(false)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('MyDSpaceNewExternalDropdownComponent test', () => {
|
||||||
|
let testComp: TestComponent;
|
||||||
|
let testFixture: ComponentFixture<TestComponent>;
|
||||||
|
let submissionComponent: MyDSpaceNewExternalDropdownComponent;
|
||||||
|
let submissionComponentFixture: ComponentFixture<MyDSpaceNewExternalDropdownComponent>;
|
||||||
|
|
||||||
|
const entityType1: ItemType = {
|
||||||
|
id: '1',
|
||||||
|
label: 'Publication',
|
||||||
|
uuid: '1',
|
||||||
|
type: new ResourceType('entitytype'),
|
||||||
|
_links: undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('With only one Entity', () => {
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
TranslateModule.forRoot(),
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
MyDSpaceNewExternalDropdownComponent,
|
||||||
|
TestComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: EntityTypeService, useValue: getMockEmptyEntityTypeService() },
|
||||||
|
{ provide: Router, useValue: new RouterStub() },
|
||||||
|
MyDSpaceNewExternalDropdownComponent
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
const html = `<ds-my-dspace-new-submission (uploadEnd)="reload($event)"></ds-my-dspace-new-submission>`;
|
||||||
|
|
||||||
|
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
||||||
|
testComp = testFixture.componentInstance;
|
||||||
|
|
||||||
|
submissionComponentFixture = TestBed.createComponent(MyDSpaceNewExternalDropdownComponent);
|
||||||
|
submissionComponent = submissionComponentFixture.componentInstance;
|
||||||
|
submissionComponentFixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
testFixture.destroy();
|
||||||
|
submissionComponentFixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create MyDSpaceNewExternalDropdownComponent', inject([MyDSpaceNewExternalDropdownComponent], (app: MyDSpaceNewExternalDropdownComponent) => {
|
||||||
|
expect(app).toBeDefined();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should be a single button', inject([MyDSpaceNewExternalDropdownComponent], (app: MyDSpaceNewExternalDropdownComponent) => {
|
||||||
|
submissionComponentFixture.detectChanges();
|
||||||
|
const addDivElement: DebugElement = submissionComponentFixture.debugElement.query(By.css('.add'));
|
||||||
|
const addDiv = addDivElement.nativeElement;
|
||||||
|
expect(addDiv.innerHTML).toBeDefined();
|
||||||
|
const buttonElement: DebugElement = addDivElement.query(By.css('.btn'));
|
||||||
|
const button = buttonElement.nativeElement;
|
||||||
|
expect(button.innerHTML).toBeDefined();
|
||||||
|
const dropdownElement: DebugElement = submissionComponentFixture.debugElement.query(By.css('.dropdown-menu'));
|
||||||
|
expect(dropdownElement).toBeNull();
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('With more than one Entity', () => {
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
TranslateModule.forRoot(),
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
MyDSpaceNewExternalDropdownComponent,
|
||||||
|
TestComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: EntityTypeService, useValue: getMockEntityTypeService() },
|
||||||
|
{ provide: Router, useValue: new RouterStub() },
|
||||||
|
MyDSpaceNewExternalDropdownComponent
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
const html = `<ds-my-dspace-new-submission (uploadEnd)="reload($event)"></ds-my-dspace-new-submission>`;
|
||||||
|
|
||||||
|
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
||||||
|
testComp = testFixture.componentInstance;
|
||||||
|
|
||||||
|
submissionComponentFixture = TestBed.createComponent(MyDSpaceNewExternalDropdownComponent);
|
||||||
|
submissionComponent = submissionComponentFixture.componentInstance;
|
||||||
|
submissionComponentFixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
testFixture.destroy();
|
||||||
|
submissionComponentFixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create MyDSpaceNewExternalDropdownComponent', inject([MyDSpaceNewExternalDropdownComponent], (app: MyDSpaceNewExternalDropdownComponent) => {
|
||||||
|
expect(app).toBeDefined();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should be a dropdown button', inject([MyDSpaceNewExternalDropdownComponent], (app: MyDSpaceNewExternalDropdownComponent) => {
|
||||||
|
const dropdownElement: DebugElement = submissionComponentFixture.debugElement.query(By.css('.dropdown-menu'));
|
||||||
|
const dropdown = dropdownElement.nativeElement;
|
||||||
|
expect(dropdown.innerHTML).toBeDefined();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should invoke modalService.open', () => {
|
||||||
|
submissionComponent.openPage(entityType1);
|
||||||
|
|
||||||
|
expect((submissionComponent as any).router.navigate).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// declare a test component
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-test-cmp',
|
||||||
|
template: ``
|
||||||
|
})
|
||||||
|
class TestComponent {
|
||||||
|
reload = (event) => {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,110 @@
|
|||||||
|
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
|
import { Observable, of as observableOf, Subscription } from 'rxjs';
|
||||||
|
import { map, mergeMap, take } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { EntityTypeService } from '../../../core/data/entity-type.service';
|
||||||
|
import { ItemType } from '../../../core/shared/item-relationships/item-type.model';
|
||||||
|
import { FindListOptions } from '../../../core/data/request.models';
|
||||||
|
import { hasValue } from '../../../shared/empty.util';
|
||||||
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
|
import { PaginatedList } from '../../../core/data/paginated-list.model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component represents the 'Import metadata from external source' dropdown menu
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-my-dspace-new-external-dropdown',
|
||||||
|
styleUrls: ['./my-dspace-new-external-dropdown.component.scss'],
|
||||||
|
templateUrl: './my-dspace-new-external-dropdown.component.html'
|
||||||
|
})
|
||||||
|
export class MyDSpaceNewExternalDropdownComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to verify if there are one or more entities available
|
||||||
|
*/
|
||||||
|
public moreThanOne$: Observable<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The entity observble (only if there is only one entity available)
|
||||||
|
*/
|
||||||
|
public singleEntity$: Observable<ItemType>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The entity object (only if there is only one entity available)
|
||||||
|
*/
|
||||||
|
public singleEntity: ItemType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TRUE if the page is initialized
|
||||||
|
*/
|
||||||
|
public initialized$: Observable<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array to track all subscriptions and unsubscribe them onDestroy
|
||||||
|
* @type {Array}
|
||||||
|
*/
|
||||||
|
public subs: Subscription[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize instance variables
|
||||||
|
*
|
||||||
|
* @param {EntityTypeService} entityTypeService
|
||||||
|
* @param {Router} router
|
||||||
|
*/
|
||||||
|
constructor(private entityTypeService: EntityTypeService,
|
||||||
|
private router: Router) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize entity type list
|
||||||
|
*/
|
||||||
|
ngOnInit() {
|
||||||
|
this.initialized$ = observableOf(false);
|
||||||
|
this.moreThanOne$ = this.entityTypeService.hasMoreThanOneAuthorizedImport();
|
||||||
|
this.singleEntity$ = this.moreThanOne$.pipe(
|
||||||
|
mergeMap((response: boolean) => {
|
||||||
|
if (!response) {
|
||||||
|
const findListOptions: FindListOptions = {
|
||||||
|
elementsPerPage: 1,
|
||||||
|
currentPage: 1
|
||||||
|
};
|
||||||
|
return this.entityTypeService.getAllAuthorizedRelationshipTypeImport(findListOptions).pipe(
|
||||||
|
map((entities: RemoteData<PaginatedList<ItemType>>) => {
|
||||||
|
this.initialized$ = observableOf(true);
|
||||||
|
return entities.payload.page[0];
|
||||||
|
}),
|
||||||
|
take(1)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.initialized$ = observableOf(true);
|
||||||
|
return observableOf(null);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
take(1)
|
||||||
|
);
|
||||||
|
this.subs.push(
|
||||||
|
this.singleEntity$.subscribe((result) => this.singleEntity = result )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method called on clicking the button 'Import metadata from external source'. It opens the page of the external import.
|
||||||
|
*/
|
||||||
|
openPage(entity: ItemType) {
|
||||||
|
const params = Object.create({});
|
||||||
|
if (entity) {
|
||||||
|
params.entity = entity.label;
|
||||||
|
}
|
||||||
|
this.router.navigate(['/import-external'], { queryParams: params });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsubscribe from the subscription
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.subs
|
||||||
|
.filter((subscription) => hasValue(subscription))
|
||||||
|
.forEach((subscription) => subscription.unsubscribe());
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,20 @@
|
|||||||
|
<div class="add" *ngIf="!(moreThanOne$ | async)">
|
||||||
|
<button class="btn btn-lg btn-primary mt-1 ml-2" [disabled]="!(initialized$|async)" (click)="openDialog(singleEntity)" role="button">
|
||||||
|
<i class="fa fa-plus-circle" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="add w-100" display="dynamic" placement="bottom-right"
|
||||||
|
ngbDropdown
|
||||||
|
*ngIf="(moreThanOne$ | async)">
|
||||||
|
<button class="btn btn-lg btn-primary mt-1 ml-2" id="dropdownSubmission" ngbDropdownToggle
|
||||||
|
type="button" [disabled]="!(initialized$|async)">
|
||||||
|
<i class="fa fa-plus-circle" aria-hidden="true"></i>
|
||||||
|
<span class="caret"></span>
|
||||||
|
</button>
|
||||||
|
<div ngbDropdownMenu
|
||||||
|
class="dropdown-menu"
|
||||||
|
id="entityControlsDropdownMenu"
|
||||||
|
aria-labelledby="dropdownSubmission">
|
||||||
|
<ds-entity-dropdown [isSubmission]="true" (selectionChange)="openDialog($event)"></ds-entity-dropdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -0,0 +1,16 @@
|
|||||||
|
.parent {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload {
|
||||||
|
flex: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add {
|
||||||
|
flex: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
#entityControlsDropdownMenu {
|
||||||
|
min-width: 18rem;
|
||||||
|
box-shadow: $btn-focus-box-shadow;
|
||||||
|
}
|
@@ -0,0 +1,194 @@
|
|||||||
|
import { Component, DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { createPaginatedList, createTestComponent } from '../../../shared/testing/utils.test';
|
||||||
|
import { MyDSpaceNewSubmissionDropdownComponent } from './my-dspace-new-submission-dropdown.component';
|
||||||
|
import { EntityTypeService } from '../../../core/data/entity-type.service';
|
||||||
|
import { ItemType } from '../../../core/shared/item-relationships/item-type.model';
|
||||||
|
import { ResourceType } from '../../../core/shared/resource-type';
|
||||||
|
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||||
|
import { PageInfo } from '../../../core/shared/page-info.model';
|
||||||
|
|
||||||
|
export function getMockEntityTypeService(): EntityTypeService {
|
||||||
|
const type1: ItemType = {
|
||||||
|
id: '1',
|
||||||
|
label: 'Publication',
|
||||||
|
uuid: '1',
|
||||||
|
type: new ResourceType('entitytype'),
|
||||||
|
_links: undefined
|
||||||
|
};
|
||||||
|
const type2: ItemType = {
|
||||||
|
id: '2',
|
||||||
|
label: 'Journal',
|
||||||
|
uuid: '2',
|
||||||
|
type: new ResourceType('entitytype'),
|
||||||
|
_links: undefined
|
||||||
|
};
|
||||||
|
const type3: ItemType = {
|
||||||
|
id: '2',
|
||||||
|
label: 'DataPackage',
|
||||||
|
uuid: '2',
|
||||||
|
type: new ResourceType('entitytype'),
|
||||||
|
_links: undefined
|
||||||
|
};
|
||||||
|
const rd$ = createSuccessfulRemoteDataObject$(createPaginatedList([type1, type2, type3]));
|
||||||
|
return jasmine.createSpyObj('entityTypeService', {
|
||||||
|
getAllAuthorizedRelationshipType: rd$,
|
||||||
|
hasMoreThanOneAuthorized: observableOf(true)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getMockEmptyEntityTypeService(): EntityTypeService {
|
||||||
|
const pageInfo = { elementsPerPage: 20, totalElements: 1, totalPages: 1, currentPage: 0 } as PageInfo;
|
||||||
|
const type1: ItemType = {
|
||||||
|
id: '1',
|
||||||
|
label: 'Publication',
|
||||||
|
uuid: '1',
|
||||||
|
type: new ResourceType('entitytype'),
|
||||||
|
_links: undefined
|
||||||
|
};
|
||||||
|
const rd$ = createSuccessfulRemoteDataObject$(createPaginatedList([type1]));
|
||||||
|
return jasmine.createSpyObj('entityTypeService', {
|
||||||
|
getAllAuthorizedRelationshipType: rd$,
|
||||||
|
hasMoreThanOneAuthorized: observableOf(false)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('MyDSpaceNewSubmissionDropdownComponent test', () => {
|
||||||
|
let testComp: TestComponent;
|
||||||
|
let testFixture: ComponentFixture<TestComponent>;
|
||||||
|
let submissionComponent: MyDSpaceNewSubmissionDropdownComponent;
|
||||||
|
let submissionComponentFixture: ComponentFixture<MyDSpaceNewSubmissionDropdownComponent>;
|
||||||
|
|
||||||
|
const entityType1: ItemType = {
|
||||||
|
id: '1',
|
||||||
|
label: 'Publication',
|
||||||
|
uuid: '1',
|
||||||
|
type: new ResourceType('entitytype'),
|
||||||
|
_links: undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
const modalStub = {
|
||||||
|
open: () => null,
|
||||||
|
close: () => null,
|
||||||
|
dismiss: () => null
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('With only one Entity', () => {
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
TranslateModule.forRoot(),
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
MyDSpaceNewSubmissionDropdownComponent,
|
||||||
|
TestComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: EntityTypeService, useValue: getMockEmptyEntityTypeService() },
|
||||||
|
{ provide: NgbModal, useValue: modalStub },
|
||||||
|
MyDSpaceNewSubmissionDropdownComponent
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
const html = `<ds-my-dspace-new-submission (uploadEnd)="reload($event)"></ds-my-dspace-new-submission>`;
|
||||||
|
|
||||||
|
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
||||||
|
testComp = testFixture.componentInstance;
|
||||||
|
|
||||||
|
submissionComponentFixture = TestBed.createComponent(MyDSpaceNewSubmissionDropdownComponent);
|
||||||
|
submissionComponent = submissionComponentFixture.componentInstance;
|
||||||
|
submissionComponentFixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
testFixture.destroy();
|
||||||
|
submissionComponentFixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create MyDSpaceNewSubmissionDropdownComponent', inject([MyDSpaceNewSubmissionDropdownComponent], (app: MyDSpaceNewSubmissionDropdownComponent) => {
|
||||||
|
expect(app).toBeDefined();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should be a single button', inject([MyDSpaceNewSubmissionDropdownComponent], (app: MyDSpaceNewSubmissionDropdownComponent) => {
|
||||||
|
submissionComponentFixture.detectChanges();
|
||||||
|
const addDivElement: DebugElement = submissionComponentFixture.debugElement.query(By.css('.add'));
|
||||||
|
const addDiv = addDivElement.nativeElement;
|
||||||
|
expect(addDiv.innerHTML).toBeDefined();
|
||||||
|
const buttonElement: DebugElement = addDivElement.query(By.css('.btn'));
|
||||||
|
const button = buttonElement.nativeElement;
|
||||||
|
expect(button.innerHTML).toBeDefined();
|
||||||
|
const dropdownElement: DebugElement = submissionComponentFixture.debugElement.query(By.css('.dropdown-menu'));
|
||||||
|
expect(dropdownElement).toBeNull();
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('With more than one Entity', () => {
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
TranslateModule.forRoot(),
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
MyDSpaceNewSubmissionDropdownComponent,
|
||||||
|
TestComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: EntityTypeService, useValue: getMockEntityTypeService() },
|
||||||
|
{ provide: NgbModal, useValue: modalStub },
|
||||||
|
MyDSpaceNewSubmissionDropdownComponent
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
const html = `<ds-my-dspace-new-submission (uploadEnd)="reload($event)"></ds-my-dspace-new-submission>`;
|
||||||
|
|
||||||
|
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
||||||
|
testComp = testFixture.componentInstance;
|
||||||
|
|
||||||
|
submissionComponentFixture = TestBed.createComponent(MyDSpaceNewSubmissionDropdownComponent);
|
||||||
|
submissionComponent = submissionComponentFixture.componentInstance;
|
||||||
|
submissionComponentFixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
testFixture.destroy();
|
||||||
|
submissionComponentFixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create MyDSpaceNewSubmissionDropdownComponent', inject([MyDSpaceNewSubmissionDropdownComponent], (app: MyDSpaceNewSubmissionDropdownComponent) => {
|
||||||
|
expect(app).toBeDefined();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should be a dropdown button', inject([MyDSpaceNewSubmissionDropdownComponent], (app: MyDSpaceNewSubmissionDropdownComponent) => {
|
||||||
|
const dropdownElement: DebugElement = submissionComponentFixture.debugElement.query(By.css('.dropdown-menu'));
|
||||||
|
const dropdown = dropdownElement.nativeElement;
|
||||||
|
expect(dropdown.innerHTML).toBeDefined();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should invoke modalService.open', () => {
|
||||||
|
spyOn((submissionComponent as any).modalService, 'open').and.returnValue({ componentInstance: { } });
|
||||||
|
submissionComponent.openDialog(entityType1);
|
||||||
|
|
||||||
|
expect((submissionComponent as any).modalService.open).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// declare a test component
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-test-cmp',
|
||||||
|
template: ``
|
||||||
|
})
|
||||||
|
class TestComponent {
|
||||||
|
reload = (event) => {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,109 @@
|
|||||||
|
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
import { Observable, of as observableOf, Subscription } from 'rxjs';
|
||||||
|
import { map, mergeMap, take } from 'rxjs/operators';
|
||||||
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
|
||||||
|
import { EntityTypeService } from '../../../core/data/entity-type.service';
|
||||||
|
import { ItemType } from '../../../core/shared/item-relationships/item-type.model';
|
||||||
|
import { FindListOptions } from '../../../core/data/request.models';
|
||||||
|
import { hasValue } from '../../../shared/empty.util';
|
||||||
|
import { CreateItemParentSelectorComponent } from '../../../shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component';
|
||||||
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
|
import { PaginatedList } from '../../../core/data/paginated-list.model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component represents the new submission dropdown
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-my-dspace-new-submission-dropdown',
|
||||||
|
styleUrls: ['./my-dspace-new-submission-dropdown.component.scss'],
|
||||||
|
templateUrl: './my-dspace-new-submission-dropdown.component.html'
|
||||||
|
})
|
||||||
|
export class MyDSpaceNewSubmissionDropdownComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to verify if there are one or more entities available
|
||||||
|
*/
|
||||||
|
public moreThanOne$: Observable<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The entity observble (only if there is only one entity available)
|
||||||
|
*/
|
||||||
|
public singleEntity$: Observable<ItemType>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The entity object (only if there is only one entity available)
|
||||||
|
*/
|
||||||
|
public singleEntity: ItemType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TRUE if the page is initialized
|
||||||
|
*/
|
||||||
|
public initialized$: Observable<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array to track all subscriptions and unsubscribe them onDestroy
|
||||||
|
* @type {Array}
|
||||||
|
*/
|
||||||
|
public subs: Subscription[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize instance variables
|
||||||
|
*
|
||||||
|
* @param {EntityTypeService} entityTypeService
|
||||||
|
* @param {NgbModal} modalService
|
||||||
|
*/
|
||||||
|
constructor(private entityTypeService: EntityTypeService,
|
||||||
|
private modalService: NgbModal) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize entity type list
|
||||||
|
*/
|
||||||
|
ngOnInit() {
|
||||||
|
this.initialized$ = observableOf(false);
|
||||||
|
this.moreThanOne$ = this.entityTypeService.hasMoreThanOneAuthorized();
|
||||||
|
this.singleEntity$ = this.moreThanOne$.pipe(
|
||||||
|
mergeMap((response: boolean) => {
|
||||||
|
if (!response) {
|
||||||
|
const findListOptions: FindListOptions = {
|
||||||
|
elementsPerPage: 1,
|
||||||
|
currentPage: 1
|
||||||
|
};
|
||||||
|
return this.entityTypeService.getAllAuthorizedRelationshipType(findListOptions).pipe(
|
||||||
|
map((entities: RemoteData<PaginatedList<ItemType>>) => {
|
||||||
|
this.initialized$ = observableOf(true);
|
||||||
|
return entities.payload.page[0];
|
||||||
|
}),
|
||||||
|
take(1)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.initialized$ = observableOf(true);
|
||||||
|
return observableOf(null);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
take(1)
|
||||||
|
);
|
||||||
|
this.subs.push(
|
||||||
|
this.singleEntity$.subscribe((result) => this.singleEntity = result )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method called on clicking the button "New Submition", It opens a dialog for
|
||||||
|
* select a collection.
|
||||||
|
*/
|
||||||
|
openDialog(entity: ItemType) {
|
||||||
|
const modalRef = this.modalService.open(CreateItemParentSelectorComponent);
|
||||||
|
modalRef.componentInstance.entityType = entity.label;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsubscribe from the subscription
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.subs
|
||||||
|
.filter((subscription) => hasValue(subscription))
|
||||||
|
.forEach((subscription) => subscription.unsubscribe());
|
||||||
|
}
|
||||||
|
}
|
@@ -8,14 +8,10 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="add">
|
<div class="add">
|
||||||
<button class="btn btn-lg btn-primary mt-1 ml-2" (click)="openDialog()" attr.aria-label="'mydspace.new-submission' | translate" title="{{'mydspace.new-submission' | translate}}">
|
<ds-my-dspace-new-submission-dropdown></ds-my-dspace-new-submission-dropdown>
|
||||||
<i class="fa fa-plus" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="add">
|
<div class="add">
|
||||||
<a class="btn btn-lg btn-outline-primary mt-1 ml-2" [routerLink]="['/import-external']" role="button" attr.aria-label="{{'mydspace.new-submission-external' | translate}}" title="{{'mydspace.new-submission-external' | translate}}">
|
<ds-my-dspace-new-external-dropdown></ds-my-dspace-new-external-dropdown>
|
||||||
<i class="fa fa-file-import" aria-hidden="true"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import { ChangeDetectorRef, Component, NO_ERRORS_SCHEMA } from '@angular/core';
|
import { ChangeDetectorRef, Component, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { ComponentFixture, inject, TestBed, waitForAsync } from '@angular/core/testing';
|
import { ComponentFixture, inject, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { By } from '@angular/platform-browser';
|
|
||||||
|
|
||||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||||
import { ScrollToService } from '@nicky-lenaers/ngx-scroll-to';
|
import { ScrollToService } from '@nicky-lenaers/ngx-scroll-to';
|
||||||
@@ -25,6 +24,8 @@ import { HttpXsrfTokenExtractor } from '@angular/common/http';
|
|||||||
import { CookieService } from '../../core/services/cookie.service';
|
import { CookieService } from '../../core/services/cookie.service';
|
||||||
import { CookieServiceMock } from '../../shared/mocks/cookie.service.mock';
|
import { CookieServiceMock } from '../../shared/mocks/cookie.service.mock';
|
||||||
import { HttpXsrfTokenExtractorMock } from '../../shared/mocks/http-xsrf-token-extractor.mock';
|
import { HttpXsrfTokenExtractorMock } from '../../shared/mocks/http-xsrf-token-extractor.mock';
|
||||||
|
import { getMockEntityTypeService } from './my-dspace-new-submission-dropdown/my-dspace-new-submission-dropdown.component.spec';
|
||||||
|
import { EntityTypeService } from '../../core/data/entity-type.service';
|
||||||
|
|
||||||
describe('MyDSpaceNewSubmissionComponent test', () => {
|
describe('MyDSpaceNewSubmissionComponent test', () => {
|
||||||
|
|
||||||
@@ -62,6 +63,7 @@ describe('MyDSpaceNewSubmissionComponent test', () => {
|
|||||||
{ provide: HttpXsrfTokenExtractor, useValue: new HttpXsrfTokenExtractorMock('mock-token') },
|
{ provide: HttpXsrfTokenExtractor, useValue: new HttpXsrfTokenExtractorMock('mock-token') },
|
||||||
{ provide: CookieService, useValue: new CookieServiceMock() },
|
{ provide: CookieService, useValue: new CookieServiceMock() },
|
||||||
{ provide: HostWindowService, useValue: new HostWindowServiceStub(800) },
|
{ provide: HostWindowService, useValue: new HostWindowServiceStub(800) },
|
||||||
|
{ provide: EntityTypeService, useValue: getMockEntityTypeService() },
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
@@ -104,20 +106,6 @@ describe('MyDSpaceNewSubmissionComponent test', () => {
|
|||||||
comp.uploaderComponent.uploader = uploader;
|
comp.uploaderComponent.uploader = uploader;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call app.openDialog', (done) => {
|
|
||||||
spyOn(comp, 'openDialog');
|
|
||||||
const submissionButton = fixture.debugElement.query(By.css('button.btn-primary'));
|
|
||||||
submissionButton.triggerEventHandler('click', null);
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
fixture.whenStable().then(() => {
|
|
||||||
expect(comp.openDialog).toHaveBeenCalled();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should show a collection selector if only one file are uploaded', (done) => {
|
it('should show a collection selector if only one file are uploaded', (done) => {
|
||||||
spyOn((comp as any).modalService, 'open').and.returnValue({ result: new Promise((res, rej) => {/****/}) });
|
spyOn((comp as any).modalService, 'open').and.returnValue({ result: new Promise((res, rej) => {/****/}) });
|
||||||
comp.afterFileLoaded(['']);
|
comp.afterFileLoaded(['']);
|
||||||
|
@@ -14,7 +14,6 @@ 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 { CreateItemParentSelectorComponent } from '../../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 '../../shared/uploader/uploader.component';
|
import { UploaderComponent } from '../../shared/uploader/uploader.component';
|
||||||
import { UploaderError } from '../../shared/uploader/uploader-error.model';
|
import { UploaderError } from '../../shared/uploader/uploader-error.model';
|
||||||
@@ -118,14 +117,6 @@ export class MyDSpaceNewSubmissionComponent implements OnDestroy, OnInit {
|
|||||||
this.notificationsService.error(null, this.translate.get(errorMessageKey));
|
this.notificationsService.error(null, this.translate.get(errorMessageKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method called on clicking the button "New Submission", It opens a dialog for
|
|
||||||
* select a collection.
|
|
||||||
*/
|
|
||||||
openDialog() {
|
|
||||||
this.modalService.open(CreateItemParentSelectorComponent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method invoked after all file are loaded from upload plugin
|
* Method invoked after all file are loaded from upload plugin
|
||||||
*/
|
*/
|
||||||
|
@@ -11,6 +11,8 @@ import { MyDSpaceGuard } from './my-dspace.guard';
|
|||||||
import { MyDSpaceConfigurationService } from './my-dspace-configuration.service';
|
import { MyDSpaceConfigurationService } from './my-dspace-configuration.service';
|
||||||
import { CollectionSelectorComponent } from './collection-selector/collection-selector.component';
|
import { CollectionSelectorComponent } from './collection-selector/collection-selector.component';
|
||||||
import { MyDspaceSearchModule } from './my-dspace-search.module';
|
import { MyDspaceSearchModule } from './my-dspace-search.module';
|
||||||
|
import { MyDSpaceNewSubmissionDropdownComponent } from './my-dspace-new-submission/my-dspace-new-submission-dropdown/my-dspace-new-submission-dropdown.component';
|
||||||
|
import { MyDSpaceNewExternalDropdownComponent } from './my-dspace-new-submission/my-dspace-new-external-dropdown/my-dspace-new-external-dropdown.component';
|
||||||
import { ThemedMyDSpacePageComponent } from './themed-my-dspace-page.component';
|
import { ThemedMyDSpacePageComponent } from './themed-my-dspace-page.component';
|
||||||
|
|
||||||
const DECLARATIONS = [
|
const DECLARATIONS = [
|
||||||
@@ -18,7 +20,9 @@ const DECLARATIONS = [
|
|||||||
ThemedMyDSpacePageComponent,
|
ThemedMyDSpacePageComponent,
|
||||||
MyDSpaceResultsComponent,
|
MyDSpaceResultsComponent,
|
||||||
MyDSpaceNewSubmissionComponent,
|
MyDSpaceNewSubmissionComponent,
|
||||||
CollectionSelectorComponent
|
CollectionSelectorComponent,
|
||||||
|
MyDSpaceNewSubmissionDropdownComponent,
|
||||||
|
MyDSpaceNewExternalDropdownComponent
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@@ -1,33 +1,31 @@
|
|||||||
<div class="form-group w-100 pr-2 pl-2">
|
<div *ngIf="searchField" class="form-group w-100 pr-2 pl-2">
|
||||||
<input *ngIf="searchField"
|
<input type="search"
|
||||||
type="search"
|
class="form-control w-100"
|
||||||
class="form-control w-100"
|
(click)="$event.stopPropagation();"
|
||||||
(click)="$event.stopPropagation();"
|
placeholder="{{ 'submission.sections.general.search-collection' | translate }}"
|
||||||
placeholder="{{ 'submission.sections.general.search-collection' | translate }}"
|
[formControl]="searchField"
|
||||||
[formControl]="searchField"
|
#searchFieldEl>
|
||||||
#searchFieldEl>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
<div
|
<div class="scrollable-menu"
|
||||||
class="scrollable-menu"
|
aria-labelledby="dropdownMenuButton"
|
||||||
aria-labelledby="dropdownMenuButton"
|
(scroll)="onScroll($event)"
|
||||||
(scroll)="onScroll($event)">
|
infiniteScroll
|
||||||
<div
|
[infiniteScrollDistance]="5"
|
||||||
infiniteScroll
|
[infiniteScrollThrottle]="300"
|
||||||
[infiniteScrollDistance]="2"
|
[infiniteScrollUpDistance]="1.5"
|
||||||
[infiniteScrollThrottle]="300"
|
[fromRoot]="true"
|
||||||
[infiniteScrollUpDistance]="1.5"
|
[scrollWindow]="false"
|
||||||
[infiniteScrollContainer]="'.scrollable-menu'"
|
(scrolled)="onScrollDown()">
|
||||||
[fromRoot]="true"
|
|
||||||
(scrolled)="onScrollDown()">
|
<button class="dropdown-item disabled" *ngIf="searchListCollection?.length == 0 && !(isLoading | async)">
|
||||||
<button class="dropdown-item disabled" *ngIf="searchListCollection?.length == 0 && !(isLoadingList | async)">
|
{{'submission.sections.general.no-collection' | translate}}
|
||||||
{{'submission.sections.general.no-collection' | translate}}
|
</button>
|
||||||
</button>
|
<ng-container *ngIf="searchListCollection?.length > 0 && !(isLoading | async)">
|
||||||
<button
|
<button *ngFor="let listItem of searchListCollection"
|
||||||
*ngFor="let listItem of searchListCollection"
|
class="dropdown-item collection-item"
|
||||||
class="dropdown-item collection-item"
|
title="{{ listItem.collection.name }}"
|
||||||
title="{{ listItem.collection.name }}"
|
(click)="onSelect(listItem)">
|
||||||
(click)="onSelect(listItem)">
|
|
||||||
<ul class="list-unstyled mb-0">
|
<ul class="list-unstyled mb-0">
|
||||||
<li class="list-item text-truncate text-secondary" *ngFor="let item of listItem.communities">
|
<li class="list-item text-truncate text-secondary" *ngFor="let item of listItem.communities">
|
||||||
{{ item.name}} <i class="fa fa-level-down" aria-hidden="true"></i>
|
{{ item.name}} <i class="fa fa-level-down" aria-hidden="true"></i>
|
||||||
@@ -35,9 +33,10 @@
|
|||||||
<li class="list-item text-truncate text-primary font-weight-bold">{{ listItem.collection.name}}</li>
|
<li class="list-item text-truncate text-primary font-weight-bold">{{ listItem.collection.name}}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</button>
|
</button>
|
||||||
<button class="dropdown-item disabled" *ngIf="(isLoadingList | async)" >
|
</ng-container>
|
||||||
<ds-loading message="{{'loading.default' | translate}}">
|
<button class="dropdown-item disabled" *ngIf="(isLoading | async)">
|
||||||
</ds-loading>
|
<ds-loading message="{{'loading.default' | translate}}">
|
||||||
</button>
|
</ds-loading>
|
||||||
</div>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
@@ -5,21 +5,16 @@ import { By } from '@angular/platform-browser';
|
|||||||
import { getTestScheduler } from 'jasmine-marbles';
|
import { getTestScheduler } from 'jasmine-marbles';
|
||||||
import { TestScheduler } from 'rxjs/testing';
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||||
import { of as observableOf } from 'rxjs';
|
|
||||||
|
|
||||||
import { CollectionDropdownComponent } from './collection-dropdown.component';
|
import { CollectionDropdownComponent } from './collection-dropdown.component';
|
||||||
import { RemoteData } from '../../core/data/remote-data';
|
import { buildPaginatedList } from '../../core/data/paginated-list.model';
|
||||||
import { buildPaginatedList, PaginatedList } from '../../core/data/paginated-list.model';
|
import { createSuccessfulRemoteDataObject$ } from '../remote-data.utils';
|
||||||
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../remote-data.utils';
|
|
||||||
import { PageInfo } from '../../core/shared/page-info.model';
|
import { PageInfo } from '../../core/shared/page-info.model';
|
||||||
import { Collection } from '../../core/shared/collection.model';
|
import { Collection } from '../../core/shared/collection.model';
|
||||||
import { CollectionDataService } from '../../core/data/collection-data.service';
|
import { CollectionDataService } from '../../core/data/collection-data.service';
|
||||||
import { TranslateLoaderMock } from '../mocks/translate-loader.mock';
|
import { TranslateLoaderMock } from '../mocks/translate-loader.mock';
|
||||||
import { Community } from '../../core/shared/community.model';
|
import { Community } from '../../core/shared/community.model';
|
||||||
import { MockElementRef } from '../testing/element-ref.mock';
|
import { MockElementRef } from '../testing/element-ref.mock';
|
||||||
import { FollowLinkConfig } from '../utils/follow-link-config.model';
|
|
||||||
import { FindListOptions } from '../../core/data/request.models';
|
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
|
||||||
|
|
||||||
const community: Community = Object.assign(new Community(), {
|
const community: Community = Object.assign(new Community(), {
|
||||||
id: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88',
|
id: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88',
|
||||||
@@ -99,17 +94,6 @@ const listElementMock = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// tslint:disable-next-line: max-classes-per-file
|
|
||||||
class CollectionDataServiceMock {
|
|
||||||
getAuthorizedCollection(query: string, options: FindListOptions = {}, ...linksToFollow: FollowLinkConfig<Collection>[]): Observable<RemoteData<PaginatedList<Collection>>> {
|
|
||||||
return observableOf(
|
|
||||||
createSuccessfulRemoteDataObject(
|
|
||||||
buildPaginatedList(new PageInfo(), collections)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('CollectionDropdownComponent', () => {
|
describe('CollectionDropdownComponent', () => {
|
||||||
let component: CollectionDropdownComponent;
|
let component: CollectionDropdownComponent;
|
||||||
let componentAsAny: any;
|
let componentAsAny: any;
|
||||||
@@ -117,12 +101,16 @@ describe('CollectionDropdownComponent', () => {
|
|||||||
let scheduler: TestScheduler;
|
let scheduler: TestScheduler;
|
||||||
|
|
||||||
const collectionDataServiceMock: any = jasmine.createSpyObj('CollectionDataService', {
|
const collectionDataServiceMock: any = jasmine.createSpyObj('CollectionDataService', {
|
||||||
getAuthorizedCollection: jasmine.createSpy('getAuthorizedCollection')
|
getAuthorizedCollection: jasmine.createSpy('getAuthorizedCollection'),
|
||||||
|
getAuthorizedCollectionByEntityType: jasmine.createSpy('getAuthorizedCollectionByEntityType')
|
||||||
});
|
});
|
||||||
|
|
||||||
const paginatedCollection = buildPaginatedList(new PageInfo(), collections);
|
const paginatedCollection = buildPaginatedList(new PageInfo(), collections);
|
||||||
const paginatedCollectionRD$ = createSuccessfulRemoteDataObject$(paginatedCollection);
|
const paginatedCollectionRD$ = createSuccessfulRemoteDataObject$(paginatedCollection);
|
||||||
|
|
||||||
|
const paginatedOneElementCollection = buildPaginatedList(new PageInfo(), [collections[0]]);
|
||||||
|
const paginatedOneElementCollectionRD$ = createSuccessfulRemoteDataObject$(paginatedOneElementCollection);
|
||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -150,6 +138,7 @@ describe('CollectionDropdownComponent', () => {
|
|||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
componentAsAny = component;
|
componentAsAny = component;
|
||||||
componentAsAny.collectionDataService.getAuthorizedCollection.and.returnValue(paginatedCollectionRD$);
|
componentAsAny.collectionDataService.getAuthorizedCollection.and.returnValue(paginatedCollectionRD$);
|
||||||
|
componentAsAny.collectionDataService.getAuthorizedCollectionByEntityType.and.returnValue(paginatedCollectionRD$);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should init component with collection list', () => {
|
it('should init component with collection list', () => {
|
||||||
@@ -211,10 +200,10 @@ describe('CollectionDropdownComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should change loader status', () => {
|
it('should change loader status', () => {
|
||||||
spyOn(component.isLoadingList, 'next').and.callThrough();
|
spyOn(component.isLoading, 'next').and.callThrough();
|
||||||
component.hideShowLoader(true);
|
component.hideShowLoader(true);
|
||||||
|
|
||||||
expect(component.isLoadingList.next).toHaveBeenCalledWith(true);
|
expect(component.isLoading.next).toHaveBeenCalledWith(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('reset pagination fields', () => {
|
it('reset pagination fields', () => {
|
||||||
@@ -225,4 +214,36 @@ describe('CollectionDropdownComponent', () => {
|
|||||||
expect(component.hasNextPage).toEqual(true);
|
expect(component.hasNextPage).toEqual(true);
|
||||||
expect(component.searchListCollection).toEqual([]);
|
expect(component.searchListCollection).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should invoke the method getAuthorizedCollectionByEntityType of CollectionDataService when entityType is set',() => {
|
||||||
|
component.entityType = 'rel';
|
||||||
|
scheduler.schedule(() => fixture.detectChanges());
|
||||||
|
scheduler.flush();
|
||||||
|
expect((component as any).collectionDataService.getAuthorizedCollectionByEntityType).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit hasChoice true when totalElements is greater then one', () => {
|
||||||
|
spyOn(component.searchComplete, 'emit').and.callThrough();
|
||||||
|
component.ngOnInit();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(component.searchComplete.emit).toHaveBeenCalledWith();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit theOnlySelectable when totalElements is equal to one', () => {
|
||||||
|
|
||||||
|
componentAsAny.collectionDataService.getAuthorizedCollection.and.returnValue(paginatedOneElementCollectionRD$);
|
||||||
|
componentAsAny.collectionDataService.getAuthorizedCollectionByEntityType.and.returnValue(paginatedOneElementCollectionRD$);
|
||||||
|
|
||||||
|
spyOn(component.theOnlySelectable, 'emit').and.callThrough();
|
||||||
|
component.ngOnInit();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const expectedTheOnlySelectable = {
|
||||||
|
communities: [ { id: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88', name: 'Community 1', uuid: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88' } ],
|
||||||
|
collection: { id: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88', uuid: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88', name: 'Collection 1' }
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(component.theOnlySelectable.emit).toHaveBeenCalledWith(expectedTheOnlySelectable);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -4,14 +4,15 @@ import {
|
|||||||
ElementRef,
|
ElementRef,
|
||||||
EventEmitter,
|
EventEmitter,
|
||||||
HostListener,
|
HostListener,
|
||||||
|
Input,
|
||||||
OnDestroy,
|
OnDestroy,
|
||||||
OnInit,
|
OnInit,
|
||||||
Output
|
Output
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { FormControl } from '@angular/forms';
|
import { FormControl } from '@angular/forms';
|
||||||
|
|
||||||
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
|
import { BehaviorSubject, from as observableFrom, Observable, of as observableOf, Subscription } from 'rxjs';
|
||||||
import { debounceTime, distinctUntilChanged, map, mergeMap, reduce, startWith, switchMap } from 'rxjs/operators';
|
import { debounceTime, distinctUntilChanged, map, mergeMap, reduce, startWith, switchMap, take } from 'rxjs/operators';
|
||||||
|
|
||||||
import { hasValue } from '../empty.util';
|
import { hasValue } from '../empty.util';
|
||||||
import { RemoteData } from '../../core/data/remote-data';
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
@@ -21,10 +22,7 @@ import { Community } from '../../core/shared/community.model';
|
|||||||
import { CollectionDataService } from '../../core/data/collection-data.service';
|
import { CollectionDataService } from '../../core/data/collection-data.service';
|
||||||
import { Collection } from '../../core/shared/collection.model';
|
import { Collection } from '../../core/shared/collection.model';
|
||||||
import { followLink } from '../utils/follow-link-config.model';
|
import { followLink } from '../utils/follow-link-config.model';
|
||||||
import {
|
import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../../core/shared/operators';
|
||||||
getFirstSucceededRemoteDataPayload,
|
|
||||||
getFirstSucceededRemoteWithNotEmptyData
|
|
||||||
} from '../../core/shared/operators';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An interface to represent a collection entry
|
* An interface to represent a collection entry
|
||||||
@@ -89,10 +87,10 @@ export class CollectionDropdownComponent implements OnInit, OnDestroy {
|
|||||||
/**
|
/**
|
||||||
* A boolean representing if the loader is visible or not
|
* A boolean representing if the loader is visible or not
|
||||||
*/
|
*/
|
||||||
isLoadingList: BehaviorSubject<boolean> = new BehaviorSubject(false);
|
isLoading: BehaviorSubject<boolean> = new BehaviorSubject(false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A numeric representig current page
|
* A numeric representing current page
|
||||||
*/
|
*/
|
||||||
currentPage: number;
|
currentPage: number;
|
||||||
|
|
||||||
@@ -102,10 +100,25 @@ export class CollectionDropdownComponent implements OnInit, OnDestroy {
|
|||||||
hasNextPage: boolean;
|
hasNextPage: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Current seach query used to filter collection list
|
* Current search query used to filter collection list
|
||||||
*/
|
*/
|
||||||
currentQuery: string;
|
currentQuery: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If present this value is used to filter collection list by entity type
|
||||||
|
*/
|
||||||
|
@Input() entityType: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit to notify whether search is complete
|
||||||
|
*/
|
||||||
|
@Output() searchComplete = new EventEmitter<any>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit to notify the only selectable collection.
|
||||||
|
*/
|
||||||
|
@Output() theOnlySelectable = new EventEmitter<CollectionListEntry>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private changeDetectorRef: ChangeDetectorRef,
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
private collectionDataService: CollectionDataService,
|
private collectionDataService: CollectionDataService,
|
||||||
@@ -132,6 +145,7 @@ export class CollectionDropdownComponent implements OnInit, OnDestroy {
|
|||||||
* Initialize collection list
|
* Initialize collection list
|
||||||
*/
|
*/
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
this.isLoading.next(false);
|
||||||
this.subs.push(this.searchField.valueChanges.pipe(
|
this.subs.push(this.searchField.valueChanges.pipe(
|
||||||
debounceTime(500),
|
debounceTime(500),
|
||||||
distinctUntilChanged(),
|
distinctUntilChanged(),
|
||||||
@@ -160,7 +174,7 @@ export class CollectionDropdownComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method used from infitity scroll for retrive more data on scroll down
|
* Method used from infinity scroll for retrieve more data on scroll down
|
||||||
*/
|
*/
|
||||||
onScrollDown() {
|
onScrollDown() {
|
||||||
if ( this.hasNextPage ) {
|
if ( this.hasNextPage ) {
|
||||||
@@ -175,6 +189,7 @@ export class CollectionDropdownComponent implements OnInit, OnDestroy {
|
|||||||
* the selected [CollectionListEntry]
|
* the selected [CollectionListEntry]
|
||||||
*/
|
*/
|
||||||
onSelect(event: CollectionListEntry) {
|
onSelect(event: CollectionListEntry) {
|
||||||
|
this.isLoading.next(true);
|
||||||
this.selectionChange.emit(event);
|
this.selectionChange.emit(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,36 +199,57 @@ export class CollectionDropdownComponent implements OnInit, OnDestroy {
|
|||||||
* @param page page number
|
* @param page page number
|
||||||
*/
|
*/
|
||||||
populateCollectionList(query: string, page: number) {
|
populateCollectionList(query: string, page: number) {
|
||||||
this.isLoadingList.next(true);
|
this.isLoading.next(true);
|
||||||
// Set the pagination info
|
// Set the pagination info
|
||||||
const findOptions: FindListOptions = {
|
const findOptions: FindListOptions = {
|
||||||
elementsPerPage: 10,
|
elementsPerPage: 10,
|
||||||
currentPage: page
|
currentPage: page
|
||||||
};
|
};
|
||||||
this.searchListCollection$ = this.collectionDataService
|
let searchListService$: Observable<RemoteData<PaginatedList<Collection>>>;
|
||||||
.getAuthorizedCollection(query, findOptions, true, false, followLink('parentCommunity'))
|
if (this.entityType) {
|
||||||
.pipe(
|
searchListService$ = this.collectionDataService
|
||||||
getFirstSucceededRemoteWithNotEmptyData(),
|
.getAuthorizedCollectionByEntityType(
|
||||||
switchMap((collections: RemoteData<PaginatedList<Collection>>) => {
|
query,
|
||||||
if ( (this.searchListCollection.length + findOptions.elementsPerPage) >= collections.payload.totalElements ) {
|
this.entityType,
|
||||||
|
findOptions,
|
||||||
|
true,
|
||||||
|
followLink('parentCommunity'));
|
||||||
|
} else {
|
||||||
|
searchListService$ = this.collectionDataService
|
||||||
|
.getAuthorizedCollection(query, findOptions, true, true, followLink('parentCommunity'));
|
||||||
|
}
|
||||||
|
this.searchListCollection$ = searchListService$.pipe(
|
||||||
|
getFirstCompletedRemoteData(),
|
||||||
|
switchMap((collectionsRD: RemoteData<PaginatedList<Collection>>) => {
|
||||||
|
this.searchComplete.emit();
|
||||||
|
if (collectionsRD.hasSucceeded && collectionsRD.payload.totalElements > 0) {
|
||||||
|
if ( (this.searchListCollection.length + findOptions.elementsPerPage) >= collectionsRD.payload.totalElements ) {
|
||||||
|
this.hasNextPage = false;
|
||||||
|
this.emitSelectionEvents(collectionsRD);
|
||||||
|
return observableFrom(collectionsRD.payload.page).pipe(
|
||||||
|
mergeMap((collection: Collection) => collection.parentCommunity.pipe(
|
||||||
|
getFirstSucceededRemoteDataPayload(),
|
||||||
|
map((community: Community) => ({
|
||||||
|
communities: [{ id: community.id, name: community.name }],
|
||||||
|
collection: { id: collection.id, uuid: collection.id, name: collection.name }
|
||||||
|
})
|
||||||
|
))),
|
||||||
|
reduce((acc: any, value: any) => [...acc, value], []),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
this.hasNextPage = false;
|
this.hasNextPage = false;
|
||||||
|
return observableOf([]);
|
||||||
}
|
}
|
||||||
return collections.payload.page;
|
})
|
||||||
}),
|
|
||||||
mergeMap((collection: Collection) => collection.parentCommunity.pipe(
|
|
||||||
getFirstSucceededRemoteDataPayload(),
|
|
||||||
map((community: Community) => ({
|
|
||||||
communities: [{ id: community.id, name: community.name }],
|
|
||||||
collection: { id: collection.id, uuid: collection.id, name: collection.name }
|
|
||||||
})
|
|
||||||
))),
|
|
||||||
reduce((acc: any, value: any) => [...acc, value], []),
|
|
||||||
startWith([])
|
|
||||||
);
|
);
|
||||||
this.subs.push(this.searchListCollection$.subscribe(
|
this.subs.push(
|
||||||
(next) => { this.searchListCollection.push(...next); }, undefined,
|
this.searchListCollection$.subscribe((list: CollectionListEntry[]) => {
|
||||||
() => { this.hideShowLoader(false); this.changeDetectorRef.detectChanges(); }
|
this.searchListCollection.push(...list);
|
||||||
));
|
this.hideShowLoader(false);
|
||||||
|
this.changeDetectorRef.detectChanges();
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -245,6 +281,29 @@ export class CollectionDropdownComponent implements OnInit, OnDestroy {
|
|||||||
* @param hideShow true for show, false otherwise
|
* @param hideShow true for show, false otherwise
|
||||||
*/
|
*/
|
||||||
hideShowLoader(hideShow: boolean) {
|
hideShowLoader(hideShow: boolean) {
|
||||||
this.isLoadingList.next(hideShow);
|
this.isLoading.next(hideShow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit events related to the number of selectable collections.
|
||||||
|
* hasChoice containing whether there are more then one selectable collections.
|
||||||
|
* theOnlySelectable containing the only collection available.
|
||||||
|
* @param collections
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private emitSelectionEvents(collections: RemoteData<PaginatedList<Collection>>) {
|
||||||
|
if (collections.payload.totalElements === 1) {
|
||||||
|
const collection = collections.payload.page[0];
|
||||||
|
collections.payload.page[0].parentCommunity.pipe(
|
||||||
|
getFirstSucceededRemoteDataPayload(),
|
||||||
|
take(1)
|
||||||
|
).subscribe((community: Community) => {
|
||||||
|
this.theOnlySelectable.emit({
|
||||||
|
communities: [{ id: community.id, name: community.name, uuid: community.id }],
|
||||||
|
collection: { id: collection.id, uuid: collection.id, name: collection.name }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -26,7 +26,8 @@ describe('AuthorizedCollectionSelectorComponent', () => {
|
|||||||
id: 'authorized-collection'
|
id: 'authorized-collection'
|
||||||
});
|
});
|
||||||
collectionService = jasmine.createSpyObj('collectionService', {
|
collectionService = jasmine.createSpyObj('collectionService', {
|
||||||
getAuthorizedCollection: createSuccessfulRemoteDataObject$(createPaginatedList([collection]))
|
getAuthorizedCollection: createSuccessfulRemoteDataObject$(createPaginatedList([collection])),
|
||||||
|
getAuthorizedCollectionByEntityType: createSuccessfulRemoteDataObject$(createPaginatedList([collection]))
|
||||||
});
|
});
|
||||||
notificationsService = jasmine.createSpyObj('notificationsService', ['error']);
|
notificationsService = jasmine.createSpyObj('notificationsService', ['error']);
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
@@ -49,12 +50,27 @@ describe('AuthorizedCollectionSelectorComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('search', () => {
|
describe('search', () => {
|
||||||
it('should call getAuthorizedCollection and return the authorized collection in a SearchResult', (done) => {
|
describe('when has no entity type', () => {
|
||||||
|
it('should call getAuthorizedCollection and return the authorized collection in a SearchResult', (done) => {
|
||||||
component.search('', 1).subscribe((resultRD) => {
|
component.search('', 1).subscribe((resultRD) => {
|
||||||
expect(collectionService.getAuthorizedCollection).toHaveBeenCalled();
|
expect(collectionService.getAuthorizedCollection).toHaveBeenCalled();
|
||||||
expect(resultRD.payload.page.length).toEqual(1);
|
expect(resultRD.payload.page.length).toEqual(1);
|
||||||
expect(resultRD.payload.page[0].indexableObject).toEqual(collection);
|
expect(resultRD.payload.page[0].indexableObject).toEqual(collection);
|
||||||
done();
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when has entity type', () => {
|
||||||
|
it('should call getAuthorizedCollectionByEntityType and return the authorized collection in a SearchResult', (done) => {
|
||||||
|
component.entityType = 'test';
|
||||||
|
fixture.detectChanges();
|
||||||
|
component.search('', 1).subscribe((resultRD) => {
|
||||||
|
expect(collectionService.getAuthorizedCollectionByEntityType).toHaveBeenCalled();
|
||||||
|
expect(resultRD.payload.page.length).toEqual(1);
|
||||||
|
expect(resultRD.payload.page[0].indexableObject).toEqual(collection);
|
||||||
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
import { DSOSelectorComponent } from '../dso-selector.component';
|
import { DSOSelectorComponent } from '../dso-selector.component';
|
||||||
import { SearchService } from '../../../../core/shared/search/search.service';
|
import { SearchService } from '../../../../core/shared/search/search.service';
|
||||||
import { CollectionDataService } from '../../../../core/data/collection-data.service';
|
import { CollectionDataService } from '../../../../core/data/collection-data.service';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs';
|
||||||
import { getFirstCompletedRemoteData } from '../../../../core/shared/operators';
|
import { getFirstCompletedRemoteData } from '../../../../core/shared/operators';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import { CollectionSearchResult } from '../../../object-collection/shared/collection-search-result.model';
|
import { CollectionSearchResult } from '../../../object-collection/shared/collection-search-result.model';
|
||||||
@@ -14,6 +14,8 @@ import { RemoteData } from '../../../../core/data/remote-data';
|
|||||||
import { hasValue } from '../../../empty.util';
|
import { hasValue } from '../../../empty.util';
|
||||||
import { NotificationsService } from '../../../notifications/notifications.service';
|
import { NotificationsService } from '../../../notifications/notifications.service';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { Collection } from '../../../../core/shared/collection.model';
|
||||||
|
import { FindListOptions } from '../../../../core/data/request.models';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-authorized-collection-selector',
|
selector: 'ds-authorized-collection-selector',
|
||||||
@@ -24,6 +26,11 @@ import { TranslateService } from '@ngx-translate/core';
|
|||||||
* Component rendering a list of collections to select from
|
* Component rendering a list of collections to select from
|
||||||
*/
|
*/
|
||||||
export class AuthorizedCollectionSelectorComponent extends DSOSelectorComponent {
|
export class AuthorizedCollectionSelectorComponent extends DSOSelectorComponent {
|
||||||
|
/**
|
||||||
|
* If present this value is used to filter collection list by entity type
|
||||||
|
*/
|
||||||
|
@Input() entityType: string;
|
||||||
|
|
||||||
constructor(protected searchService: SearchService,
|
constructor(protected searchService: SearchService,
|
||||||
protected collectionDataService: CollectionDataService,
|
protected collectionDataService: CollectionDataService,
|
||||||
protected notifcationsService: NotificationsService,
|
protected notifcationsService: NotificationsService,
|
||||||
@@ -44,10 +51,23 @@ export class AuthorizedCollectionSelectorComponent extends DSOSelectorComponent
|
|||||||
* @param page Page to retrieve
|
* @param page Page to retrieve
|
||||||
*/
|
*/
|
||||||
search(query: string, page: number): Observable<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>> {
|
search(query: string, page: number): Observable<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>> {
|
||||||
return this.collectionDataService.getAuthorizedCollection(query, Object.assign({
|
let searchListService$: Observable<RemoteData<PaginatedList<Collection>>> = null;
|
||||||
|
const findOptions: FindListOptions = {
|
||||||
currentPage: page,
|
currentPage: page,
|
||||||
elementsPerPage: this.defaultPagination.pageSize
|
elementsPerPage: this.defaultPagination.pageSize
|
||||||
}),true, false, followLink('parentCommunity')).pipe(
|
};
|
||||||
|
|
||||||
|
if (this.entityType) {
|
||||||
|
searchListService$ = this.collectionDataService
|
||||||
|
.getAuthorizedCollectionByEntityType(
|
||||||
|
query,
|
||||||
|
this.entityType,
|
||||||
|
findOptions);
|
||||||
|
} else {
|
||||||
|
searchListService$ = this.collectionDataService
|
||||||
|
.getAuthorizedCollection(query, findOptions, true, false, followLink('parentCommunity'));
|
||||||
|
}
|
||||||
|
return searchListService$.pipe(
|
||||||
getFirstCompletedRemoteData(),
|
getFirstCompletedRemoteData(),
|
||||||
map((rd) => Object.assign(new RemoteData(null, null, null, null), rd, {
|
map((rd) => Object.assign(new RemoteData(null, null, null, null), rd, {
|
||||||
payload: hasValue(rd.payload) ? buildPaginatedList(rd.payload.pageInfo, rd.payload.page.map((col) => Object.assign(new CollectionSearchResult(), { indexableObject: col }))) : null,
|
payload: hasValue(rd.payload) ? buildPaginatedList(rd.payload.pageInfo, rd.payload.page.map((col) => Object.assign(new CollectionSearchResult(), { indexableObject: col }))) : null,
|
||||||
|
@@ -6,6 +6,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<h5 *ngIf="header" class="px-2">{{header | translate}}</h5>
|
<h5 *ngIf="header" class="px-2">{{header | translate}}</h5>
|
||||||
<ds-authorized-collection-selector [currentDSOId]="dsoRD?.payload.uuid" [types]="selectorTypes" (onSelect)="selectObject($event)"></ds-authorized-collection-selector>
|
<ds-authorized-collection-selector [currentDSOId]="dsoRD?.payload.uuid"
|
||||||
|
[entityType]="entityType"
|
||||||
|
[types]="selectorTypes"
|
||||||
|
(onSelect)="selectObject($event)"></ds-authorized-collection-selector>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -69,4 +69,10 @@ describe('CreateItemParentSelectorComponent', () => {
|
|||||||
expect(router.navigate).toHaveBeenCalledWith(['/submit'], { queryParams: { collection: collection.uuid } });
|
expect(router.navigate).toHaveBeenCalledWith(['/submit'], { queryParams: { collection: collection.uuid } });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should call navigate on the router with entityType parameter', () => {
|
||||||
|
const entityType = 'Person';
|
||||||
|
component.entityType = entityType;
|
||||||
|
component.navigate(collection);
|
||||||
|
expect(router.navigate).toHaveBeenCalledWith(['/submit'], { queryParams: { collection: collection.uuid, entityType: entityType } });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute, NavigationExtras, Router } from '@angular/router';
|
import { ActivatedRoute, NavigationExtras, Router } from '@angular/router';
|
||||||
import { DSpaceObjectType } from '../../../../core/shared/dspace-object-type.model';
|
import { DSpaceObjectType } from '../../../../core/shared/dspace-object-type.model';
|
||||||
import { DSpaceObject } from '../../../../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../../../../core/shared/dspace-object.model';
|
||||||
@@ -22,6 +22,11 @@ export class CreateItemParentSelectorComponent extends DSOSelectorModalWrapperCo
|
|||||||
action = SelectorActionType.CREATE;
|
action = SelectorActionType.CREATE;
|
||||||
header = 'dso-selector.create.item.sub-level';
|
header = 'dso-selector.create.item.sub-level';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If present this value is used to filter collection list by entity type
|
||||||
|
*/
|
||||||
|
@Input() entityType: string;
|
||||||
|
|
||||||
constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute, private router: Router) {
|
constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute, private router: Router) {
|
||||||
super(activeModal, route);
|
super(activeModal, route);
|
||||||
}
|
}
|
||||||
@@ -35,6 +40,9 @@ export class CreateItemParentSelectorComponent extends DSOSelectorModalWrapperCo
|
|||||||
['collection']: dso.uuid,
|
['collection']: dso.uuid,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
if (this.entityType) {
|
||||||
|
navigationExtras.queryParams.entityType = this.entityType;
|
||||||
|
}
|
||||||
this.router.navigate(['/submit'], navigationExtras);
|
this.router.navigate(['/submit'], navigationExtras);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,28 @@
|
|||||||
|
<div
|
||||||
|
class="scrollable-menu"
|
||||||
|
aria-labelledby="dropdownMenuButton"
|
||||||
|
(scroll)="onScroll($event)"
|
||||||
|
infiniteScroll
|
||||||
|
[infiniteScrollDistance]="5"
|
||||||
|
[infiniteScrollThrottle]="300"
|
||||||
|
[infiniteScrollUpDistance]="1.5"
|
||||||
|
[fromRoot]="true"
|
||||||
|
[scrollWindow]="false"
|
||||||
|
(scrolled)="onScrollDown()">
|
||||||
|
<button class="dropdown-item disabled" *ngIf="searchListEntity?.length == 0 && !(isLoadingList | async)">
|
||||||
|
{{'submission.sections.general.no-entity' | translate}}
|
||||||
|
</button>
|
||||||
|
<button *ngFor="let listItem of searchListEntity"
|
||||||
|
class="dropdown-item entity-item"
|
||||||
|
title="{{ listItem.label }}"
|
||||||
|
(click)="onSelect(listItem)">
|
||||||
|
<ul class="list-unstyled mb-0">
|
||||||
|
<li class="list-item text-truncate text-primary font-weight-bold">{{ listItem.label.toLowerCase() + '.listelement.badge' | translate }}</li>
|
||||||
|
</ul>
|
||||||
|
</button>
|
||||||
|
<button class="dropdown-item disabled" *ngIf="(isLoadingList | async)" >
|
||||||
|
<ds-loading message="{{'loading.default' | translate}}">
|
||||||
|
</ds-loading>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div>
|
@@ -0,0 +1,19 @@
|
|||||||
|
.list-item:active {
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollable-menu {
|
||||||
|
height: auto;
|
||||||
|
max-height: var(--ds-dropdown-menu-max-height);
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entity-item {
|
||||||
|
border-bottom: var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
#entityControlsDropdownMenu {
|
||||||
|
outline: 0;
|
||||||
|
left: 0 !important;
|
||||||
|
box-shadow: var(--bs-btn-focus-box-shadow);
|
||||||
|
}
|
167
src/app/shared/entity-dropdown/entity-dropdown.component.spec.ts
Normal file
167
src/app/shared/entity-dropdown/entity-dropdown.component.spec.ts
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
|
import { EntityDropdownComponent } from './entity-dropdown.component';
|
||||||
|
import { getTestScheduler } from 'jasmine-marbles';
|
||||||
|
import { createSuccessfulRemoteDataObject$ } from '../remote-data.utils';
|
||||||
|
import { ItemType } from '../../core/shared/item-relationships/item-type.model';
|
||||||
|
import { ChangeDetectorRef, NO_ERRORS_SCHEMA, Pipe, PipeTransform } from '@angular/core';
|
||||||
|
import { EntityTypeService } from '../../core/data/entity-type.service';
|
||||||
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { createPaginatedList } from '../testing/utils.test';
|
||||||
|
|
||||||
|
// tslint:disable-next-line:pipe-prefix
|
||||||
|
@Pipe({ name: 'translate' })
|
||||||
|
class MockTranslatePipe implements PipeTransform {
|
||||||
|
transform(value: string): string {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const entities: ItemType[] = [
|
||||||
|
Object.assign(new ItemType(), {
|
||||||
|
id: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88',
|
||||||
|
label: 'Entity_1',
|
||||||
|
uuid: 'UUID-ce64f48e-2c9b-411a-ac36-ee429c0e6a88'
|
||||||
|
}),
|
||||||
|
Object.assign(new ItemType(), {
|
||||||
|
id: '59ee713b-ee53-4220-8c3f-9860dc84fe33',
|
||||||
|
label: 'Entity_2',
|
||||||
|
uuid: 'UUID-59ee713b-ee53-4220-8c3f-9860dc84fe33'
|
||||||
|
}),
|
||||||
|
Object.assign(new ItemType(), {
|
||||||
|
id: 'e9dbf393-7127-415f-8919-55be34a6e9ed',
|
||||||
|
label: 'Entity_3',
|
||||||
|
uuid: 'UUID-7127-415f-8919-55be34a6e9ed'
|
||||||
|
}),
|
||||||
|
Object.assign(new ItemType(), {
|
||||||
|
id: '59da2ff0-9bf4-45bf-88be-e35abd33f304',
|
||||||
|
label: 'Entity_4',
|
||||||
|
uuid: 'UUID-59da2ff0-9bf4-45bf-88be-e35abd33f304'
|
||||||
|
}),
|
||||||
|
Object.assign(new ItemType(), {
|
||||||
|
id: 'a5159760-f362-4659-9e81-e3253ad91ede',
|
||||||
|
label: 'Entity_5',
|
||||||
|
uuid: 'UUID-a5159760-f362-4659-9e81-e3253ad91ede'
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
const listElementMock: ItemType = Object.assign(
|
||||||
|
new ItemType(), {
|
||||||
|
id: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88',
|
||||||
|
label: 'Entity_1',
|
||||||
|
uuid: 'UUID-ce64f48e-2c9b-411a-ac36-ee429c0e6a88'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
describe('EntityDropdownComponent', () => {
|
||||||
|
let component: EntityDropdownComponent;
|
||||||
|
let componentAsAny: any;
|
||||||
|
let fixture: ComponentFixture<EntityDropdownComponent>;
|
||||||
|
let scheduler: TestScheduler;
|
||||||
|
|
||||||
|
const entityTypeServiceMock: any = jasmine.createSpyObj('EntityTypeService', {
|
||||||
|
getAllAuthorizedRelationshipType: jasmine.createSpy('getAllAuthorizedRelationshipType'),
|
||||||
|
getAllAuthorizedRelationshipTypeImport: jasmine.createSpy('getAllAuthorizedRelationshipTypeImport')
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
let translatePipeSpy: jasmine.Spy;
|
||||||
|
|
||||||
|
const paginatedEntities = createPaginatedList(entities);
|
||||||
|
const paginatedEntitiesRD$ = createSuccessfulRemoteDataObject$(paginatedEntities);
|
||||||
|
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [],
|
||||||
|
declarations: [EntityDropdownComponent, MockTranslatePipe],
|
||||||
|
providers: [
|
||||||
|
{ provide: EntityTypeService, useValue: entityTypeServiceMock },
|
||||||
|
ChangeDetectorRef
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
scheduler = getTestScheduler();
|
||||||
|
fixture = TestBed.createComponent(EntityDropdownComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
componentAsAny = fixture.componentInstance;
|
||||||
|
componentAsAny.entityTypeService.getAllAuthorizedRelationshipType.and.returnValue(paginatedEntitiesRD$);
|
||||||
|
componentAsAny.entityTypeService.getAllAuthorizedRelationshipTypeImport.and.returnValue(paginatedEntitiesRD$);
|
||||||
|
component.isSubmission = true;
|
||||||
|
|
||||||
|
translatePipeSpy = spyOn(MockTranslatePipe.prototype, 'transform');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should translate entries', () => {
|
||||||
|
scheduler.schedule(() => fixture.detectChanges());
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(translatePipeSpy).toHaveBeenCalledWith('entity_1.listelement.badge');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should init component with entities list', () => {
|
||||||
|
spyOn(component.subs, 'push');
|
||||||
|
spyOn(component, 'resetPagination');
|
||||||
|
spyOn(component, 'populateEntityList').and.callThrough();
|
||||||
|
|
||||||
|
scheduler.schedule(() => fixture.detectChanges());
|
||||||
|
scheduler.flush();
|
||||||
|
const elements = fixture.debugElement.queryAll(By.css('.entity-item'));
|
||||||
|
|
||||||
|
expect(elements.length).toEqual(5);
|
||||||
|
expect(component.subs.push).toHaveBeenCalled();
|
||||||
|
expect(component.resetPagination).toHaveBeenCalled();
|
||||||
|
expect(component.populateEntityList).toHaveBeenCalled();
|
||||||
|
expect((component as any).entityTypeService.getAllAuthorizedRelationshipType).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should trigger onSelect method when select a new entity from list', () => {
|
||||||
|
scheduler.schedule(() => fixture.detectChanges());
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
spyOn(component, 'onSelect');
|
||||||
|
const entityItem = fixture.debugElement.query(By.css('.entity-item:nth-child(2)'));
|
||||||
|
entityItem.triggerEventHandler('click', null);
|
||||||
|
|
||||||
|
scheduler.schedule(() => fixture.detectChanges());
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(component.onSelect).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit selectionChange event when selecting a new entity', () => {
|
||||||
|
spyOn(component.selectionChange, 'emit').and.callThrough();
|
||||||
|
component.ngOnInit();
|
||||||
|
component.onSelect(listElementMock as any);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(component.selectionChange.emit).toHaveBeenCalledWith(listElementMock as any);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should change loader status', () => {
|
||||||
|
spyOn(component.isLoadingList, 'next').and.callThrough();
|
||||||
|
component.hideShowLoader(true);
|
||||||
|
|
||||||
|
expect(component.isLoadingList.next).toHaveBeenCalledWith(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('reset pagination fields', () => {
|
||||||
|
component.resetPagination();
|
||||||
|
|
||||||
|
expect(component.currentPage).toEqual(1);
|
||||||
|
expect(component.hasNextPage).toEqual(true);
|
||||||
|
expect(component.searchListEntity).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should invoke the method getAllAuthorizedRelationshipTypeImport of EntityTypeService when isSubmission is false', () => {
|
||||||
|
component.isSubmission = false;
|
||||||
|
|
||||||
|
scheduler.schedule(() => fixture.detectChanges());
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect((component as any).entityTypeService.getAllAuthorizedRelationshipTypeImport).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
207
src/app/shared/entity-dropdown/entity-dropdown.component.ts
Normal file
207
src/app/shared/entity-dropdown/entity-dropdown.component.ts
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
import {
|
||||||
|
ChangeDetectorRef,
|
||||||
|
Component,
|
||||||
|
ElementRef,
|
||||||
|
EventEmitter,
|
||||||
|
HostListener,
|
||||||
|
Input,
|
||||||
|
OnDestroy,
|
||||||
|
OnInit,
|
||||||
|
Output
|
||||||
|
} from '@angular/core';
|
||||||
|
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
|
||||||
|
import { hasValue } from '../empty.util';
|
||||||
|
import { reduce, startWith, switchMap } from 'rxjs/operators';
|
||||||
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
|
import { FindListOptions } from '../../core/data/request.models';
|
||||||
|
import { PaginatedList } from '../../core/data/paginated-list.model';
|
||||||
|
import { EntityTypeService } from '../../core/data/entity-type.service';
|
||||||
|
import { ItemType } from '../../core/shared/item-relationships/item-type.model';
|
||||||
|
import { getFirstSucceededRemoteWithNotEmptyData } from '../../core/shared/operators';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-entity-dropdown',
|
||||||
|
templateUrl: './entity-dropdown.component.html',
|
||||||
|
styleUrls: ['./entity-dropdown.component.scss']
|
||||||
|
})
|
||||||
|
export class EntityDropdownComponent implements OnInit, OnDestroy {
|
||||||
|
/**
|
||||||
|
* The entity list obtained from a search
|
||||||
|
* @type {Observable<ItemType[]>}
|
||||||
|
*/
|
||||||
|
public searchListEntity$: Observable<ItemType[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A boolean representing if dropdown list is scrollable to the bottom
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
private scrollableBottom = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A boolean representing if dropdown list is scrollable to the top
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
private scrollableTop = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of entity to render
|
||||||
|
*/
|
||||||
|
public searchListEntity: ItemType[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TRUE if the parent operation is a 'new submission' operation, FALSE otherwise (eg.: is an 'Import metadata from an external source' operation).
|
||||||
|
*/
|
||||||
|
@Input() isSubmission: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The entity to output to the parent component
|
||||||
|
*/
|
||||||
|
@Output() selectionChange = new EventEmitter<ItemType>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A boolean representing if the loader is visible or not
|
||||||
|
*/
|
||||||
|
public isLoadingList: BehaviorSubject<boolean> = new BehaviorSubject(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A numeric representig current page
|
||||||
|
*/
|
||||||
|
public currentPage: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A boolean representing if exist another page to render
|
||||||
|
*/
|
||||||
|
public hasNextPage: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array to track all subscriptions and unsubscribe them onDestroy
|
||||||
|
* @type {Array}
|
||||||
|
*/
|
||||||
|
public subs: Subscription[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize instance variables
|
||||||
|
*
|
||||||
|
* @param {ChangeDetectorRef} changeDetectorRef
|
||||||
|
* @param {EntityTypeService} entityTypeService
|
||||||
|
* @param {ElementRef} el
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
|
private entityTypeService: EntityTypeService,
|
||||||
|
private el: ElementRef
|
||||||
|
) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method called on mousewheel event, it prevent the page scroll
|
||||||
|
* when arriving at the top/bottom of dropdown menu
|
||||||
|
*
|
||||||
|
* @param event
|
||||||
|
* mousewheel event
|
||||||
|
*/
|
||||||
|
@HostListener('mousewheel', ['$event']) onMousewheel(event) {
|
||||||
|
if (event.wheelDelta > 0 && this.scrollableTop) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
if (event.wheelDelta < 0 && this.scrollableBottom) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize entity list
|
||||||
|
*/
|
||||||
|
ngOnInit() {
|
||||||
|
this.resetPagination();
|
||||||
|
this.populateEntityList(this.currentPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if dropdown scrollbar is at the top or bottom of the dropdown list
|
||||||
|
*
|
||||||
|
* @param event
|
||||||
|
*/
|
||||||
|
public onScroll(event) {
|
||||||
|
this.scrollableBottom = (event.target.scrollTop + event.target.clientHeight === event.target.scrollHeight);
|
||||||
|
this.scrollableTop = (event.target.scrollTop === 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method used from infitity scroll for retrive more data on scroll down
|
||||||
|
*/
|
||||||
|
public onScrollDown() {
|
||||||
|
if ( this.hasNextPage ) {
|
||||||
|
this.populateEntityList(++this.currentPage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit a [selectionChange] event when a new entity is selected from list
|
||||||
|
*
|
||||||
|
* @param event
|
||||||
|
* the selected [ItemType]
|
||||||
|
*/
|
||||||
|
public onSelect(event: ItemType) {
|
||||||
|
this.selectionChange.emit(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method called for populate the entity list
|
||||||
|
* @param page page number
|
||||||
|
*/
|
||||||
|
public populateEntityList(page: number) {
|
||||||
|
this.isLoadingList.next(true);
|
||||||
|
// Set the pagination info
|
||||||
|
const findOptions: FindListOptions = {
|
||||||
|
elementsPerPage: 10,
|
||||||
|
currentPage: page
|
||||||
|
};
|
||||||
|
let searchListEntity$;
|
||||||
|
if (this.isSubmission) {
|
||||||
|
searchListEntity$ = this.entityTypeService.getAllAuthorizedRelationshipType(findOptions);
|
||||||
|
} else {
|
||||||
|
searchListEntity$ = this.entityTypeService.getAllAuthorizedRelationshipTypeImport(findOptions);
|
||||||
|
}
|
||||||
|
this.searchListEntity$ = searchListEntity$.pipe(
|
||||||
|
getFirstSucceededRemoteWithNotEmptyData(),
|
||||||
|
switchMap((entityType: RemoteData<PaginatedList<ItemType>>) => {
|
||||||
|
if ( (this.searchListEntity.length + findOptions.elementsPerPage) >= entityType.payload.totalElements ) {
|
||||||
|
this.hasNextPage = false;
|
||||||
|
}
|
||||||
|
return entityType.payload.page;
|
||||||
|
}),
|
||||||
|
reduce((acc: any, value: any) => [...acc, value], []),
|
||||||
|
startWith([])
|
||||||
|
);
|
||||||
|
this.subs.push(
|
||||||
|
this.searchListEntity$.subscribe(
|
||||||
|
(next) => { this.searchListEntity.push(...next); }, undefined,
|
||||||
|
() => { this.hideShowLoader(false); this.changeDetectorRef.detectChanges(); }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset pagination values
|
||||||
|
*/
|
||||||
|
public resetPagination() {
|
||||||
|
this.currentPage = 1;
|
||||||
|
this.hasNextPage = true;
|
||||||
|
this.searchListEntity = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide/Show the entity list loader
|
||||||
|
* @param hideShow true for show, false otherwise
|
||||||
|
*/
|
||||||
|
public hideShowLoader(hideShow: boolean) {
|
||||||
|
this.isLoadingList.next(hideShow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsubscribe from all subscriptions
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
||||||
|
}
|
||||||
|
}
|
@@ -12,7 +12,9 @@ import { RelationshipOptions } from '../../models/relationship-options.model';
|
|||||||
import { SearchResult } from '../../../../search/search-result.model';
|
import { SearchResult } from '../../../../search/search-result.model';
|
||||||
import { Item } from '../../../../../core/shared/item.model';
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
import {
|
import {
|
||||||
AddRelationshipAction, RemoveRelationshipAction, UpdateRelationshipNameVariantAction,
|
AddRelationshipAction,
|
||||||
|
RemoveRelationshipAction,
|
||||||
|
UpdateRelationshipNameVariantAction,
|
||||||
} from './relationship.actions';
|
} from './relationship.actions';
|
||||||
import { RelationshipService } from '../../../../../core/data/relationship.service';
|
import { RelationshipService } from '../../../../../core/data/relationship.service';
|
||||||
import { RelationshipTypeService } from '../../../../../core/data/relationship-type.service';
|
import { RelationshipTypeService } from '../../../../../core/data/relationship-type.service';
|
||||||
@@ -25,6 +27,7 @@ import { ExternalSourceService } from '../../../../../core/data/external-source.
|
|||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { RemoteDataBuildService } from '../../../../../core/cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../../../../../core/cache/builders/remote-data-build.service';
|
||||||
import { getAllSucceededRemoteDataPayload } from '../../../../../core/shared/operators';
|
import { getAllSucceededRemoteDataPayload } from '../../../../../core/shared/operators';
|
||||||
|
import { followLink } from '../../../../utils/follow-link-config.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-dynamic-lookup-relation-modal',
|
selector: 'ds-dynamic-lookup-relation-modal',
|
||||||
@@ -146,7 +149,14 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy
|
|||||||
|
|
||||||
if (isNotEmpty(this.relationshipOptions.externalSources)) {
|
if (isNotEmpty(this.relationshipOptions.externalSources)) {
|
||||||
this.externalSourcesRD$ = this.rdbService.aggregate(
|
this.externalSourcesRD$ = this.rdbService.aggregate(
|
||||||
this.relationshipOptions.externalSources.map((source) => this.externalSourceService.findById(source))
|
this.relationshipOptions.externalSources.map((source) => {
|
||||||
|
return this.externalSourceService.findById(
|
||||||
|
source,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
followLink('entityTypes')
|
||||||
|
);
|
||||||
|
})
|
||||||
).pipe(
|
).pipe(
|
||||||
getAllSucceededRemoteDataPayload()
|
getAllSucceededRemoteDataPayload()
|
||||||
);
|
);
|
||||||
|
@@ -26,6 +26,7 @@ import { ExternalSourceEntryImportModalComponent } from './external-source-entry
|
|||||||
import { createPaginatedList } from '../../../../../testing/utils.test';
|
import { createPaginatedList } from '../../../../../testing/utils.test';
|
||||||
import { PaginationService } from '../../../../../../core/pagination/pagination.service';
|
import { PaginationService } from '../../../../../../core/pagination/pagination.service';
|
||||||
import { PaginationServiceStub } from '../../../../../testing/pagination-service.stub';
|
import { PaginationServiceStub } from '../../../../../testing/pagination-service.stub';
|
||||||
|
import { ItemType } from '../../../../../../core/shared/item-relationships/item-type.model';
|
||||||
|
|
||||||
describe('DsDynamicLookupRelationExternalSourceTabComponent', () => {
|
describe('DsDynamicLookupRelationExternalSourceTabComponent', () => {
|
||||||
let component: DsDynamicLookupRelationExternalSourceTabComponent;
|
let component: DsDynamicLookupRelationExternalSourceTabComponent;
|
||||||
@@ -35,10 +36,12 @@ describe('DsDynamicLookupRelationExternalSourceTabComponent', () => {
|
|||||||
let selectableListService;
|
let selectableListService;
|
||||||
let modalService;
|
let modalService;
|
||||||
|
|
||||||
|
const itemType = Object.assign(new ItemType(), { label: 'Person' });
|
||||||
const externalSource = {
|
const externalSource = {
|
||||||
id: 'orcidV2',
|
id: 'orcidV2',
|
||||||
name: 'orcidV2',
|
name: 'orcidV2',
|
||||||
hierarchical: false
|
hierarchical: false,
|
||||||
|
entityTypes: createSuccessfulRemoteDataObject$(createPaginatedList([itemType]))
|
||||||
} as ExternalSource;
|
} as ExternalSource;
|
||||||
const externalEntries = [
|
const externalEntries = [
|
||||||
Object.assign({
|
Object.assign({
|
||||||
|
@@ -7,7 +7,7 @@ import { RemoteData } from '../../../../../../core/data/remote-data';
|
|||||||
import { PaginatedList } from '../../../../../../core/data/paginated-list.model';
|
import { PaginatedList } from '../../../../../../core/data/paginated-list.model';
|
||||||
import { ExternalSourceEntry } from '../../../../../../core/shared/external-source-entry.model';
|
import { ExternalSourceEntry } from '../../../../../../core/shared/external-source-entry.model';
|
||||||
import { ExternalSource } from '../../../../../../core/shared/external-source.model';
|
import { ExternalSource } from '../../../../../../core/shared/external-source.model';
|
||||||
import { startWith, switchMap } from 'rxjs/operators';
|
import { map, startWith, switchMap } from 'rxjs/operators';
|
||||||
import { PaginatedSearchOptions } from '../../../../../search/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../../../../../search/paginated-search-options.model';
|
||||||
import { Context } from '../../../../../../core/shared/context.model';
|
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';
|
||||||
@@ -22,6 +22,8 @@ import { Item } from '../../../../../../core/shared/item.model';
|
|||||||
import { Collection } from '../../../../../../core/shared/collection.model';
|
import { Collection } from '../../../../../../core/shared/collection.model';
|
||||||
import { PaginationService } from '../../../../../../core/pagination/pagination.service';
|
import { PaginationService } from '../../../../../../core/pagination/pagination.service';
|
||||||
import { Observable, Subscription } from 'rxjs';
|
import { Observable, Subscription } from 'rxjs';
|
||||||
|
import { ItemType } from '../../../../../../core/shared/item-relationships/item-type.model';
|
||||||
|
import { getFirstCompletedRemoteData } from '../../../../../../core/shared/operators';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-dynamic-lookup-relation-external-source-tab',
|
selector: 'ds-dynamic-lookup-relation-external-source-tab',
|
||||||
@@ -116,6 +118,11 @@ export class DsDynamicLookupRelationExternalSourceTabComponent implements OnInit
|
|||||||
*/
|
*/
|
||||||
importObjectSub: Subscription;
|
importObjectSub: Subscription;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The entity types compatible with the given external source
|
||||||
|
*/
|
||||||
|
relatedEntityType: ItemType;
|
||||||
|
|
||||||
constructor(private router: Router,
|
constructor(private router: Router,
|
||||||
public searchConfigService: SearchConfigurationService,
|
public searchConfigService: SearchConfigurationService,
|
||||||
private externalSourceService: ExternalSourceService,
|
private externalSourceService: ExternalSourceService,
|
||||||
@@ -129,6 +136,15 @@ export class DsDynamicLookupRelationExternalSourceTabComponent implements OnInit
|
|||||||
* Get the entries for the selected external source
|
* Get the entries for the selected external source
|
||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
this.externalSource.entityTypes.pipe(
|
||||||
|
getFirstCompletedRemoteData(),
|
||||||
|
map((entityTypesRD: RemoteData<PaginatedList<ItemType>>) => {
|
||||||
|
return (entityTypesRD.hasSucceeded && entityTypesRD.payload.totalElements > 0) ? entityTypesRD.payload.page[0] : null;
|
||||||
|
})
|
||||||
|
).subscribe((entityType: ItemType) => {
|
||||||
|
this.relatedEntityType = entityType;
|
||||||
|
});
|
||||||
|
|
||||||
this.resetRoute();
|
this.resetRoute();
|
||||||
this.entriesRD$ = this.searchConfigService.paginatedSearchOptions.pipe(
|
this.entriesRD$ = this.searchConfigService.paginatedSearchOptions.pipe(
|
||||||
switchMap((searchOptions: PaginatedSearchOptions) =>
|
switchMap((searchOptions: PaginatedSearchOptions) =>
|
||||||
@@ -155,6 +171,7 @@ export class DsDynamicLookupRelationExternalSourceTabComponent implements OnInit
|
|||||||
modalComp.collection = this.collection;
|
modalComp.collection = this.collection;
|
||||||
modalComp.relationship = this.relationship;
|
modalComp.relationship = this.relationship;
|
||||||
modalComp.label = this.label;
|
modalComp.label = this.label;
|
||||||
|
modalComp.relatedEntityType = this.relatedEntityType;
|
||||||
this.importObjectSub = modalComp.importedObject.subscribe((object) => {
|
this.importObjectSub = modalComp.importedObject.subscribe((object) => {
|
||||||
this.selectableListService.selectSingle(this.listId, object);
|
this.selectableListService.selectSingle(this.listId, object);
|
||||||
this.importedObject.emit(object);
|
this.importedObject.emit(object);
|
||||||
|
@@ -17,13 +17,6 @@
|
|||||||
<div id="external-source-entry-entities" class="mb-3">
|
<div id="external-source-entry-entities" class="mb-3">
|
||||||
<h5 class="font-weight-bold">{{ (labelPrefix + 'entities' | translate) }}</h5>
|
<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"
|
<ds-search-results *ngIf="(localEntitiesRD$ | async)?.payload?.page?.length > 0"
|
||||||
[searchResults]="(localEntitiesRD$ | async)"
|
[searchResults]="(localEntitiesRD$ | async)"
|
||||||
[sortConfig]="this.lookupRelationService.searchConfig?.sort"
|
[sortConfig]="this.lookupRelationService.searchConfig?.sort"
|
||||||
|
@@ -86,7 +86,6 @@ describe('DsDynamicLookupRelationExternalSourceTabComponent', () => {
|
|||||||
component.externalSourceEntry = entry;
|
component.externalSourceEntry = entry;
|
||||||
component.label = label;
|
component.label = label;
|
||||||
component.relationship = relationship;
|
component.relationship = relationship;
|
||||||
component.collection = submissionCollection;
|
|
||||||
component.item = submissionItem;
|
component.item = submissionItem;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Component, EventEmitter, OnInit } from '@angular/core';
|
import { Component, EventEmitter, OnInit } from '@angular/core';
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbActiveModal, NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { ExternalSourceEntry } from '../../../../../../../core/shared/external-source-entry.model';
|
import { ExternalSourceEntry } from '../../../../../../../core/shared/external-source-entry.model';
|
||||||
import { MetadataValue } from '../../../../../../../core/shared/metadata.models';
|
import { MetadataValue } from '../../../../../../../core/shared/metadata.models';
|
||||||
import { Metadata } from '../../../../../../../core/shared/metadata.utils';
|
import { Metadata } from '../../../../../../../core/shared/metadata.utils';
|
||||||
@@ -15,14 +15,16 @@ import { CollectionElementLinkType } from '../../../../../../object-collection/c
|
|||||||
import { Context } from '../../../../../../../core/shared/context.model';
|
import { Context } from '../../../../../../../core/shared/context.model';
|
||||||
import { SelectableListService } from '../../../../../../object-list/selectable-list/selectable-list.service';
|
import { SelectableListService } from '../../../../../../object-list/selectable-list/selectable-list.service';
|
||||||
import { ListableObject } from '../../../../../../object-collection/shared/listable-object.model';
|
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 { ItemDataService } from '../../../../../../../core/data/item-data.service';
|
||||||
import { PaginationComponentOptions } from '../../../../../../pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../../../../../pagination/pagination-component-options.model';
|
||||||
import { getRemoteDataPayload, getFirstSucceededRemoteData } from '../../../../../../../core/shared/operators';
|
import { getFirstSucceededRemoteData, getRemoteDataPayload } from '../../../../../../../core/shared/operators';
|
||||||
import { take } from 'rxjs/operators';
|
import { switchMap, take } from 'rxjs/operators';
|
||||||
import { ItemSearchResult } from '../../../../../../object-collection/shared/item-search-result.model';
|
import { ItemSearchResult } from '../../../../../../object-collection/shared/item-search-result.model';
|
||||||
import { NotificationsService } from '../../../../../../notifications/notifications.service';
|
import { NotificationsService } from '../../../../../../notifications/notifications.service';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { ItemType } from '../../../../../../../core/shared/item-relationships/item-type.model';
|
||||||
|
import { SubmissionImportExternalCollectionComponent } from '../../../../../../../submission/import-external/import-external-collection/submission-import-external-collection.component';
|
||||||
|
import { CollectionListEntry } from '../../../../../../collection-dropdown/collection-dropdown.component';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The possible types of import for the external entry
|
* The possible types of import for the external entry
|
||||||
@@ -67,16 +69,6 @@ export class ExternalSourceEntryImportModalComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
item: Item;
|
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
|
* The current relationship-options used for filtering results
|
||||||
*/
|
*/
|
||||||
@@ -147,8 +139,19 @@ export class ExternalSourceEntryImportModalComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
authorityEnabled = false;
|
authorityEnabled = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The entity types compatible with the given external source
|
||||||
|
*/
|
||||||
|
relatedEntityType: ItemType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The modal for the collection selection
|
||||||
|
*/
|
||||||
|
modalRef: NgbModalRef;
|
||||||
|
|
||||||
constructor(public modal: NgbActiveModal,
|
constructor(public modal: NgbActiveModal,
|
||||||
public lookupRelationService: LookupRelationService,
|
public lookupRelationService: LookupRelationService,
|
||||||
|
private modalService: NgbModal,
|
||||||
private selectService: SelectableListService,
|
private selectService: SelectableListService,
|
||||||
private itemService: ItemDataService,
|
private itemService: ItemDataService,
|
||||||
private notificationsService: NotificationsService,
|
private notificationsService: NotificationsService,
|
||||||
@@ -160,7 +163,6 @@ export class ExternalSourceEntryImportModalComponent implements OnInit {
|
|||||||
const pagination = Object.assign(new PaginationComponentOptions(), { id: 'external-entry-import', pageSize: 5 });
|
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.searchOptions = Object.assign(new PaginatedSearchOptions({ query: this.externalSourceEntry.value, pagination: pagination }));
|
||||||
this.localEntitiesRD$ = this.lookupRelationService.getLocalResults(this.relationship, this.searchOptions);
|
this.localEntitiesRD$ = this.lookupRelationService.getLocalResults(this.relationship, this.searchOptions);
|
||||||
this.collectionId = this.collection.id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -211,16 +213,26 @@ export class ExternalSourceEntryImportModalComponent implements OnInit {
|
|||||||
* Create and import a new entity from the external entry
|
* Create and import a new entity from the external entry
|
||||||
*/
|
*/
|
||||||
importNewEntity() {
|
importNewEntity() {
|
||||||
this.itemService.importExternalSourceEntry(this.externalSourceEntry, this.collectionId).pipe(
|
this.modalRef = this.modalService.open(SubmissionImportExternalCollectionComponent, {
|
||||||
getFirstSucceededRemoteData(),
|
size: 'lg',
|
||||||
getRemoteDataPayload(),
|
});
|
||||||
take(1)
|
this.modalRef.componentInstance.entityType = this.relatedEntityType.label;
|
||||||
|
|
||||||
|
this.modalRef.componentInstance.selectedEvent.pipe(
|
||||||
|
switchMap((collectionListEntry: CollectionListEntry) => {
|
||||||
|
return this.itemService.importExternalSourceEntry(this.externalSourceEntry, collectionListEntry.collection.id).pipe(
|
||||||
|
getFirstSucceededRemoteData(),
|
||||||
|
getRemoteDataPayload(),
|
||||||
|
take(1)
|
||||||
|
);
|
||||||
|
})
|
||||||
).subscribe((item: Item) => {
|
).subscribe((item: Item) => {
|
||||||
this.lookupRelationService.removeLocalResultsCache();
|
this.lookupRelationService.removeLocalResultsCache();
|
||||||
const searchResult = Object.assign(new ItemSearchResult(), {
|
const searchResult = Object.assign(new ItemSearchResult(), {
|
||||||
indexableObject: item
|
indexableObject: item
|
||||||
});
|
});
|
||||||
this.notificationsService.success(this.translateService.get(this.labelPrefix + this.label + '.added.new-entity'));
|
this.notificationsService.success(this.translateService.get(this.labelPrefix + this.label + '.added.new-entity'));
|
||||||
|
this.modalRef.close();
|
||||||
this.importedObject.emit(searchResult);
|
this.importedObject.emit(searchResult);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -11,6 +11,9 @@ export const externalSourceOrcid: ExternalSource = {
|
|||||||
entries: {
|
entries: {
|
||||||
href: 'https://dspace7.4science.cloud/server/api/integration/externalsources/orcid/entries'
|
href: 'https://dspace7.4science.cloud/server/api/integration/externalsources/orcid/entries'
|
||||||
},
|
},
|
||||||
|
entityTypes: {
|
||||||
|
href: 'https://dspace7.4science.cloud/server/api/integration/externalsources/my_staff_db/entityTypes'
|
||||||
|
},
|
||||||
self: {
|
self: {
|
||||||
href: 'https://dspace7.4science.cloud/server/api/integration/externalsources/orcid'
|
href: 'https://dspace7.4science.cloud/server/api/integration/externalsources/orcid'
|
||||||
}
|
}
|
||||||
@@ -26,6 +29,9 @@ export const externalSourceCiencia: ExternalSource = {
|
|||||||
entries: {
|
entries: {
|
||||||
href: 'https://dspace7.4science.cloud/server/api/integration/externalsources/ciencia/entries'
|
href: 'https://dspace7.4science.cloud/server/api/integration/externalsources/ciencia/entries'
|
||||||
},
|
},
|
||||||
|
entityTypes: {
|
||||||
|
href: 'https://dspace7.4science.cloud/server/api/integration/externalsources/my_staff_db/entityTypes'
|
||||||
|
},
|
||||||
self: {
|
self: {
|
||||||
href: 'https://dspace7.4science.cloud/server/api/integration/externalsources/ciencia'
|
href: 'https://dspace7.4science.cloud/server/api/integration/externalsources/ciencia'
|
||||||
}
|
}
|
||||||
@@ -41,6 +47,9 @@ export const externalSourceMyStaffDb: ExternalSource = {
|
|||||||
entries: {
|
entries: {
|
||||||
href: 'https://dspace7.4science.cloud/server/api/integration/externalsources/my_staff_db/entries'
|
href: 'https://dspace7.4science.cloud/server/api/integration/externalsources/my_staff_db/entries'
|
||||||
},
|
},
|
||||||
|
entityTypes: {
|
||||||
|
href: 'https://dspace7.4science.cloud/server/api/integration/externalsources/my_staff_db/entityTypes'
|
||||||
|
},
|
||||||
self: {
|
self: {
|
||||||
href: 'https://dspace7.4science.cloud/server/api/integration/externalsources/my_staff_db'
|
href: 'https://dspace7.4science.cloud/server/api/integration/externalsources/my_staff_db'
|
||||||
}
|
}
|
||||||
@@ -53,6 +62,7 @@ export const externalSourceMyStaffDb: ExternalSource = {
|
|||||||
export function getMockExternalSourceService(): ExternalSourceService {
|
export function getMockExternalSourceService(): ExternalSourceService {
|
||||||
return jasmine.createSpyObj('ExternalSourceService', {
|
return jasmine.createSpyObj('ExternalSourceService', {
|
||||||
findAll: jasmine.createSpy('findAll'),
|
findAll: jasmine.createSpy('findAll'),
|
||||||
|
searchBy: jasmine.createSpy('searchBy'),
|
||||||
getExternalSourceEntries: jasmine.createSpy('getExternalSourceEntries'),
|
getExternalSourceEntries: jasmine.createSpy('getExternalSourceEntries'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -53,7 +53,8 @@ import { FormComponent } from './form/form.component';
|
|||||||
import { DsDynamicOneboxComponent } from './form/builder/ds-dynamic-form-ui/models/onebox/dynamic-onebox.component';
|
import { DsDynamicOneboxComponent } from './form/builder/ds-dynamic-form-ui/models/onebox/dynamic-onebox.component';
|
||||||
import { DsDynamicScrollableDropdownComponent } from './form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component';
|
import { DsDynamicScrollableDropdownComponent } from './form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component';
|
||||||
import {
|
import {
|
||||||
DsDynamicFormControlContainerComponent, dsDynamicFormControlMapFn,
|
DsDynamicFormControlContainerComponent,
|
||||||
|
dsDynamicFormControlMapFn,
|
||||||
} from './form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component';
|
} from './form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component';
|
||||||
import { DsDynamicFormComponent } from './form/builder/ds-dynamic-form-ui/ds-dynamic-form.component';
|
import { DsDynamicFormComponent } from './form/builder/ds-dynamic-form-ui/ds-dynamic-form.component';
|
||||||
import { DragClickDirective } from './utils/drag-click.directive';
|
import { DragClickDirective } from './utils/drag-click.directive';
|
||||||
@@ -202,6 +203,7 @@ import { EpersonSearchBoxComponent } from './resource-policies/form/eperson-grou
|
|||||||
import { GroupSearchBoxComponent } from './resource-policies/form/eperson-group-list/group-search-box/group-search-box.component';
|
import { GroupSearchBoxComponent } from './resource-policies/form/eperson-group-list/group-search-box/group-search-box.component';
|
||||||
import { FileDownloadLinkComponent } from './file-download-link/file-download-link.component';
|
import { FileDownloadLinkComponent } from './file-download-link/file-download-link.component';
|
||||||
import { CollectionDropdownComponent } from './collection-dropdown/collection-dropdown.component';
|
import { CollectionDropdownComponent } from './collection-dropdown/collection-dropdown.component';
|
||||||
|
import { EntityDropdownComponent } from './entity-dropdown/entity-dropdown.component';
|
||||||
import { DsSelectComponent } from './ds-select/ds-select.component';
|
import { DsSelectComponent } from './ds-select/ds-select.component';
|
||||||
import { VocabularyTreeviewComponent } from './vocabulary-treeview/vocabulary-treeview.component';
|
import { VocabularyTreeviewComponent } from './vocabulary-treeview/vocabulary-treeview.component';
|
||||||
import { CurationFormComponent } from '../curation-form/curation-form.component';
|
import { CurationFormComponent } from '../curation-form/curation-form.component';
|
||||||
@@ -438,6 +440,7 @@ const COMPONENTS = [
|
|||||||
BitstreamDownloadPageComponent,
|
BitstreamDownloadPageComponent,
|
||||||
BitstreamRequestACopyPageComponent,
|
BitstreamRequestACopyPageComponent,
|
||||||
CollectionDropdownComponent,
|
CollectionDropdownComponent,
|
||||||
|
EntityDropdownComponent,
|
||||||
ExportMetadataSelectorComponent,
|
ExportMetadataSelectorComponent,
|
||||||
ConfirmationModalComponent,
|
ConfirmationModalComponent,
|
||||||
VocabularyTreeviewComponent,
|
VocabularyTreeviewComponent,
|
||||||
|
@@ -1,11 +1,16 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="modal-header">{{'dso-selector.create.collection.head' | translate}}
|
<div class="modal-header">{{'dso-selector.select.collection.head' | translate}}
|
||||||
<button type="button" class="close" (click)="closeCollectionModal()" aria-label="Close">
|
<button type="button" class="close" (click)="closeCollectionModal()" aria-label="Close">
|
||||||
<span aria-hidden="true">×</span>
|
<span aria-hidden="true">×</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<ds-collection-dropdown (selectionChange)="selectObject($event)">
|
<ds-loading *ngIf="isLoading()"></ds-loading>
|
||||||
|
<ds-collection-dropdown [ngClass]="{'d-none': isLoading()}"
|
||||||
|
(selectionChange)="selectObject($event)"
|
||||||
|
(searchComplete)="searchComplete()"
|
||||||
|
(theOnlySelectable)="theOnlySelectable($event)"
|
||||||
|
[entityType]="entityType">
|
||||||
</ds-collection-dropdown>
|
</ds-collection-dropdown>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
import { Component, NO_ERRORS_SCHEMA, EventEmitter } from '@angular/core';
|
import { Component, EventEmitter, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { waitForAsync, TestBed, ComponentFixture, inject } from '@angular/core/testing';
|
import { ComponentFixture, fakeAsync, inject, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { createTestComponent } from '../../../shared/testing/utils.test';
|
import { createTestComponent } from '../../../shared/testing/utils.test';
|
||||||
import { SubmissionImportExternalCollectionComponent } from './submission-import-external-collection.component';
|
import { SubmissionImportExternalCollectionComponent } from './submission-import-external-collection.component';
|
||||||
import { CollectionListEntry } from '../../../shared/collection-dropdown/collection-dropdown.component';
|
import { CollectionListEntry } from '../../../shared/collection-dropdown/collection-dropdown.component';
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
|
||||||
describe('SubmissionImportExternalCollectionComponent test suite', () => {
|
describe('SubmissionImportExternalCollectionComponent test suite', () => {
|
||||||
let comp: SubmissionImportExternalCollectionComponent;
|
let comp: SubmissionImportExternalCollectionComponent;
|
||||||
@@ -76,6 +77,46 @@ describe('SubmissionImportExternalCollectionComponent test suite', () => {
|
|||||||
|
|
||||||
expect(compAsAny.activeModal.dismiss).toHaveBeenCalled();
|
expect(compAsAny.activeModal.dismiss).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should be in loading state when search is not completed', () => {
|
||||||
|
comp.loading = null;
|
||||||
|
expect(comp.isLoading()).toBeFalse();
|
||||||
|
|
||||||
|
comp.loading = true;
|
||||||
|
expect(comp.isLoading()).toBeTrue();
|
||||||
|
|
||||||
|
comp.loading = false;
|
||||||
|
expect(comp.isLoading()).toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set loading variable to false on searchComplete event', () => {
|
||||||
|
comp.loading = null;
|
||||||
|
|
||||||
|
comp.searchComplete();
|
||||||
|
expect(comp.loading).toBe(false);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit theOnlySelectable', () => {
|
||||||
|
spyOn(comp.selectedEvent, 'emit').and.callThrough();
|
||||||
|
|
||||||
|
const selected: any = {};
|
||||||
|
comp.theOnlySelectable(selected);
|
||||||
|
|
||||||
|
expect(comp.selectedEvent.emit).toHaveBeenCalledWith(selected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('dropdown should be invisible when the component is loading', fakeAsync(() => {
|
||||||
|
|
||||||
|
spyOn(comp, 'isLoading').and.returnValue(true);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
const dropdownMenu = fixture.debugElement.query(By.css('ds-collection-dropdown')).nativeElement;
|
||||||
|
expect(dropdownMenu.classList).toContain('d-none');
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { Component, Output, EventEmitter } from '@angular/core';
|
import { Component, EventEmitter, Output } from '@angular/core';
|
||||||
import { CollectionListEntry } from '../../../shared/collection-dropdown/collection-dropdown.component';
|
import { CollectionListEntry } from '../../../shared/collection-dropdown/collection-dropdown.component';
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
|
||||||
@@ -16,6 +16,16 @@ export class SubmissionImportExternalCollectionComponent {
|
|||||||
*/
|
*/
|
||||||
@Output() public selectedEvent = new EventEmitter<CollectionListEntry>();
|
@Output() public selectedEvent = new EventEmitter<CollectionListEntry>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If present this value is used to filter collection list by entity type
|
||||||
|
*/
|
||||||
|
public entityType: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If collection searching is pending or not
|
||||||
|
*/
|
||||||
|
public loading = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the component variables.
|
* Initialize the component variables.
|
||||||
* @param {NgbActiveModal} activeModal
|
* @param {NgbActiveModal} activeModal
|
||||||
@@ -37,4 +47,28 @@ export class SubmissionImportExternalCollectionComponent {
|
|||||||
public closeCollectionModal(): void {
|
public closeCollectionModal(): void {
|
||||||
this.activeModal.dismiss(false);
|
this.activeModal.dismiss(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Propagate the onlySelectable collection
|
||||||
|
* @param theOnlySelectable
|
||||||
|
*/
|
||||||
|
public theOnlySelectable(theOnlySelectable: CollectionListEntry) {
|
||||||
|
this.selectedEvent.emit(theOnlySelectable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the hasChoice state
|
||||||
|
* @param hasChoice
|
||||||
|
*/
|
||||||
|
public searchComplete() {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the component is in loading state.
|
||||||
|
*/
|
||||||
|
public isLoading(): boolean {
|
||||||
|
return !!this.loading;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h2>{{'submission.import-external.preview.title' | translate}}</h2>
|
<h2>{{'submission.import-external.preview.title.' + labelPrefix | translate}}</h2>
|
||||||
<button type="button" class="close"
|
<button type="button" class="close"
|
||||||
(click)="closeMetadataModal()" aria-label="Close">
|
(click)="closeMetadataModal()" aria-label="Close">
|
||||||
<span aria-hidden="true">×</span>
|
<span aria-hidden="true">×</span>
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { Component, Input, OnInit } from '@angular/core';
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { NgbActiveModal, NgbModalRef, NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbActiveModal, NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { ExternalSourceEntry } from '../../../core/shared/external-source-entry.model';
|
import { ExternalSourceEntry } from '../../../core/shared/external-source-entry.model';
|
||||||
import { MetadataValue } from '../../../core/shared/metadata.models';
|
import { MetadataValue } from '../../../core/shared/metadata.models';
|
||||||
import { Metadata } from '../../../core/shared/metadata.utils';
|
import { Metadata } from '../../../core/shared/metadata.utils';
|
||||||
@@ -28,6 +28,10 @@ export class SubmissionImportExternalPreviewComponent implements OnInit {
|
|||||||
* The entry metadata list
|
* The entry metadata list
|
||||||
*/
|
*/
|
||||||
public metadataList: { key: string, value: MetadataValue }[];
|
public metadataList: { key: string, value: MetadataValue }[];
|
||||||
|
/**
|
||||||
|
* The label prefix to use to generate the translation label
|
||||||
|
*/
|
||||||
|
public labelPrefix: string;
|
||||||
/**
|
/**
|
||||||
* The modal for the entry preview
|
* The modal for the entry preview
|
||||||
*/
|
*/
|
||||||
@@ -77,6 +81,7 @@ export class SubmissionImportExternalPreviewComponent implements OnInit {
|
|||||||
this.modalRef = this.modalService.open(SubmissionImportExternalCollectionComponent, {
|
this.modalRef = this.modalService.open(SubmissionImportExternalCollectionComponent, {
|
||||||
size: 'lg',
|
size: 'lg',
|
||||||
});
|
});
|
||||||
|
this.modalRef.componentInstance.entityType = this.labelPrefix;
|
||||||
this.closeMetadataModal();
|
this.closeMetadataModal();
|
||||||
|
|
||||||
this.modalRef.componentInstance.selectedEvent.pipe(
|
this.modalRef.componentInstance.selectedEvent.pipe(
|
||||||
|
@@ -15,7 +15,7 @@ import {
|
|||||||
getMockExternalSourceService
|
getMockExternalSourceService
|
||||||
} from '../../../shared/mocks/external-source.service.mock';
|
} from '../../../shared/mocks/external-source.service.mock';
|
||||||
import { PageInfo } from '../../../core/shared/page-info.model';
|
import { PageInfo } from '../../../core/shared/page-info.model';
|
||||||
import { PaginatedList, buildPaginatedList } from '../../../core/data/paginated-list.model';
|
import { buildPaginatedList, PaginatedList } from '../../../core/data/paginated-list.model';
|
||||||
import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils';
|
||||||
import { ExternalSource } from '../../../core/shared/external-source.model';
|
import { ExternalSource } from '../../../core/shared/external-source.model';
|
||||||
import { FindListOptions } from '../../../core/data/request.models';
|
import { FindListOptions } from '../../../core/data/request.models';
|
||||||
@@ -23,6 +23,7 @@ import { HostWindowService } from '../../../shared/host-window.service';
|
|||||||
import { HostWindowServiceStub } from '../../../shared/testing/host-window-service.stub';
|
import { HostWindowServiceStub } from '../../../shared/testing/host-window-service.stub';
|
||||||
import { getTestScheduler } from 'jasmine-marbles';
|
import { getTestScheduler } from 'jasmine-marbles';
|
||||||
import { TestScheduler } from 'rxjs/testing';
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
|
import { RequestParam } from '../../../core/cache/models/request-param.model';
|
||||||
|
|
||||||
describe('SubmissionImportExternalSearchbarComponent test suite', () => {
|
describe('SubmissionImportExternalSearchbarComponent test suite', () => {
|
||||||
let comp: SubmissionImportExternalSearchbarComponent;
|
let comp: SubmissionImportExternalSearchbarComponent;
|
||||||
@@ -63,9 +64,9 @@ describe('SubmissionImportExternalSearchbarComponent test suite', () => {
|
|||||||
|
|
||||||
// synchronous beforeEach
|
// synchronous beforeEach
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockExternalSourceService.findAll.and.returnValue(observableOf(paginatedListRD));
|
mockExternalSourceService.searchBy.and.returnValue(observableOf(paginatedListRD));
|
||||||
const html = `
|
const html = `
|
||||||
<ds-submission-import-external-searchbar></ds-submission-import-external-searchbar>`;
|
<ds-submission-import-external-searchbar [initExternalSourceData]="initExternalSourceData"></ds-submission-import-external-searchbar>`;
|
||||||
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
||||||
testComp = testFixture.componentInstance;
|
testComp = testFixture.componentInstance;
|
||||||
});
|
});
|
||||||
@@ -88,7 +89,7 @@ describe('SubmissionImportExternalSearchbarComponent test suite', () => {
|
|||||||
const pageInfo = new PageInfo();
|
const pageInfo = new PageInfo();
|
||||||
paginatedList = buildPaginatedList(pageInfo, [externalSourceOrcid, externalSourceCiencia, externalSourceMyStaffDb]);
|
paginatedList = buildPaginatedList(pageInfo, [externalSourceOrcid, externalSourceCiencia, externalSourceMyStaffDb]);
|
||||||
paginatedListRD = createSuccessfulRemoteDataObject(paginatedList);
|
paginatedListRD = createSuccessfulRemoteDataObject(paginatedList);
|
||||||
compAsAny.externalService.findAll.and.returnValue(observableOf(paginatedListRD));
|
compAsAny.externalService.searchBy.and.returnValue(observableOf(paginatedListRD));
|
||||||
sourceList = [
|
sourceList = [
|
||||||
{id: 'orcid', name: 'orcid'},
|
{id: 'orcid', name: 'orcid'},
|
||||||
{id: 'ciencia', name: 'ciencia'},
|
{id: 'ciencia', name: 'ciencia'},
|
||||||
@@ -103,7 +104,7 @@ describe('SubmissionImportExternalSearchbarComponent test suite', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Should init component properly (without initExternalSourceData)', () => {
|
it('Should init component properly (without initExternalSourceData)', () => {
|
||||||
comp.initExternalSourceData = { sourceId: '', query: '' };
|
comp.initExternalSourceData = { entity: 'Publication', sourceId: '', query: '' };
|
||||||
scheduler.schedule(() => fixture.detectChanges());
|
scheduler.schedule(() => fixture.detectChanges());
|
||||||
scheduler.flush();
|
scheduler.flush();
|
||||||
|
|
||||||
@@ -113,7 +114,7 @@ describe('SubmissionImportExternalSearchbarComponent test suite', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Should init component properly (with initExternalSourceData populated)', () => {
|
it('Should init component properly (with initExternalSourceData populated)', () => {
|
||||||
comp.initExternalSourceData = { query: 'dummy', sourceId: 'ciencia' };
|
comp.initExternalSourceData = { entity: 'Publication', query: 'dummy', sourceId: 'ciencia' };
|
||||||
scheduler.schedule(() => fixture.detectChanges());
|
scheduler.schedule(() => fixture.detectChanges());
|
||||||
scheduler.flush();
|
scheduler.flush();
|
||||||
|
|
||||||
@@ -129,6 +130,7 @@ describe('SubmissionImportExternalSearchbarComponent test suite', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Should load additional external sources', () => {
|
it('Should load additional external sources', () => {
|
||||||
|
comp.initExternalSourceData = { entity: 'Publication', query: 'dummy', sourceId: 'ciencia' };
|
||||||
comp.sourceListLoading = false;
|
comp.sourceListLoading = false;
|
||||||
compAsAny.pageInfo = new PageInfo({
|
compAsAny.pageInfo = new PageInfo({
|
||||||
elementsPerPage: 3,
|
elementsPerPage: 3,
|
||||||
@@ -139,6 +141,9 @@ describe('SubmissionImportExternalSearchbarComponent test suite', () => {
|
|||||||
compAsAny.findListOptions = Object.assign({}, new FindListOptions(), {
|
compAsAny.findListOptions = Object.assign({}, new FindListOptions(), {
|
||||||
elementsPerPage: 3,
|
elementsPerPage: 3,
|
||||||
currentPage: 0,
|
currentPage: 0,
|
||||||
|
searchParams: [
|
||||||
|
new RequestParam('entityType', 'Publication')
|
||||||
|
]
|
||||||
});
|
});
|
||||||
comp.sourceList = sourceList;
|
comp.sourceList = sourceList;
|
||||||
const expected = sourceList.concat(sourceList);
|
const expected = sourceList.concat(sourceList);
|
||||||
@@ -150,9 +155,10 @@ describe('SubmissionImportExternalSearchbarComponent test suite', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('The \'search\' method should call \'emit\'', () => {
|
it('The \'search\' method should call \'emit\'', () => {
|
||||||
|
comp.initExternalSourceData = { entity: 'Publication', query: 'dummy', sourceId: 'ciencia' };
|
||||||
comp.selectedElement = { id: 'orcidV2', name: 'orcidV2' };
|
comp.selectedElement = { id: 'orcidV2', name: 'orcidV2' };
|
||||||
comp.searchString = 'dummy';
|
comp.searchString = 'dummy';
|
||||||
const expected = { sourceId: comp.selectedElement.id, query: comp.searchString };
|
const expected = { entity: 'Publication', sourceId: comp.selectedElement.id, query: comp.searchString };
|
||||||
spyOn(comp.externalSourceData, 'emit');
|
spyOn(comp.externalSourceData, 'emit');
|
||||||
comp.search();
|
comp.search();
|
||||||
|
|
||||||
@@ -167,5 +173,5 @@ describe('SubmissionImportExternalSearchbarComponent test suite', () => {
|
|||||||
template: ``
|
template: ``
|
||||||
})
|
})
|
||||||
class TestComponent {
|
class TestComponent {
|
||||||
|
initExternalSourceData = { entity: 'Publication', query: 'dummy', sourceId: 'ciencia' };
|
||||||
}
|
}
|
||||||
|
@@ -1,19 +1,12 @@
|
|||||||
import {
|
import { ChangeDetectorRef, Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
|
||||||
ChangeDetectorRef,
|
|
||||||
Component,
|
|
||||||
EventEmitter,
|
|
||||||
Input,
|
|
||||||
OnDestroy,
|
|
||||||
OnInit,
|
|
||||||
Output
|
|
||||||
} from '@angular/core';
|
|
||||||
|
|
||||||
import { Observable, of as observableOf, Subscription } from 'rxjs';
|
import { Observable, of as observableOf, Subscription } from 'rxjs';
|
||||||
import { catchError, tap } from 'rxjs/operators';
|
import { catchError, tap } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { RequestParam } from '../../../core/cache/models/request-param.model';
|
||||||
import { ExternalSourceService } from '../../../core/data/external-source.service';
|
import { ExternalSourceService } from '../../../core/data/external-source.service';
|
||||||
import { ExternalSource } from '../../../core/shared/external-source.model';
|
import { ExternalSource } from '../../../core/shared/external-source.model';
|
||||||
import { PaginatedList, buildPaginatedList } from '../../../core/data/paginated-list.model';
|
import { buildPaginatedList, PaginatedList } from '../../../core/data/paginated-list.model';
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
import { PageInfo } from '../../../core/shared/page-info.model';
|
import { PageInfo } from '../../../core/shared/page-info.model';
|
||||||
import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils';
|
||||||
@@ -34,6 +27,7 @@ export interface SourceElement {
|
|||||||
* Interface for the external source data to export.
|
* Interface for the external source data to export.
|
||||||
*/
|
*/
|
||||||
export interface ExternalSourceData {
|
export interface ExternalSourceData {
|
||||||
|
entity: string;
|
||||||
query: string;
|
query: string;
|
||||||
sourceId: string;
|
sourceId: string;
|
||||||
}
|
}
|
||||||
@@ -116,8 +110,11 @@ export class SubmissionImportExternalSearchbarComponent implements OnInit, OnDes
|
|||||||
this.findListOptions = Object.assign({}, new FindListOptions(), {
|
this.findListOptions = Object.assign({}, new FindListOptions(), {
|
||||||
elementsPerPage: 5,
|
elementsPerPage: 5,
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
|
searchParams: [
|
||||||
|
new RequestParam('entityType', this.initExternalSourceData.entity)
|
||||||
|
]
|
||||||
});
|
});
|
||||||
this.externalService.findAll(this.findListOptions).pipe(
|
this.externalService.searchBy('findByEntityType', this.findListOptions).pipe(
|
||||||
catchError(() => {
|
catchError(() => {
|
||||||
const pageInfo = new PageInfo();
|
const pageInfo = new PageInfo();
|
||||||
const paginatedList = buildPaginatedList(pageInfo, []);
|
const paginatedList = buildPaginatedList(pageInfo, []);
|
||||||
@@ -158,8 +155,11 @@ export class SubmissionImportExternalSearchbarComponent implements OnInit, OnDes
|
|||||||
this.findListOptions = Object.assign({}, new FindListOptions(), {
|
this.findListOptions = Object.assign({}, new FindListOptions(), {
|
||||||
elementsPerPage: 5,
|
elementsPerPage: 5,
|
||||||
currentPage: this.findListOptions.currentPage + 1,
|
currentPage: this.findListOptions.currentPage + 1,
|
||||||
|
searchParams: [
|
||||||
|
new RequestParam('entityType', this.initExternalSourceData.entity)
|
||||||
|
]
|
||||||
});
|
});
|
||||||
this.sub = this.externalService.findAll(this.findListOptions).pipe(
|
this.externalService.searchBy('findByEntityType', this.findListOptions).pipe(
|
||||||
catchError(() => {
|
catchError(() => {
|
||||||
const pageInfo = new PageInfo();
|
const pageInfo = new PageInfo();
|
||||||
const paginatedList = buildPaginatedList(pageInfo, []);
|
const paginatedList = buildPaginatedList(pageInfo, []);
|
||||||
@@ -182,7 +182,13 @@ export class SubmissionImportExternalSearchbarComponent implements OnInit, OnDes
|
|||||||
* Passes the search parameters to the parent component.
|
* Passes the search parameters to the parent component.
|
||||||
*/
|
*/
|
||||||
public search(): void {
|
public search(): void {
|
||||||
this.externalSourceData.emit({ sourceId: this.selectedElement.id, query: this.searchString });
|
this.externalSourceData.emit(
|
||||||
|
{
|
||||||
|
entity: this.initExternalSourceData.entity,
|
||||||
|
sourceId: this.selectedElement.id,
|
||||||
|
query: this.searchString
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -193,4 +199,5 @@ export class SubmissionImportExternalSearchbarComponent implements OnInit, OnDes
|
|||||||
this.sub.unsubscribe();
|
this.sub.unsubscribe();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,17 +1,17 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<h2 id="header" class="pb-2">{{'submission.import-external.title' | translate}}</h2>
|
<h2 id="header" class="pb-2">{{'submission.import-external.title' + ((label) ? '.' + label : '') | translate}}</h2>
|
||||||
<ds-submission-import-external-searchbar
|
<ds-submission-import-external-searchbar
|
||||||
[initExternalSourceData]="routeData"
|
[initExternalSourceData]="reload$.value"
|
||||||
(externalSourceData) = "getExternalSourceData($event)">
|
(externalSourceData) = "getExternalSourceData($event)">
|
||||||
</ds-submission-import-external-searchbar>
|
</ds-submission-import-external-searchbar>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row" *ngIf="reload$.value.entity">
|
||||||
<div *ngIf="routeData.sourceId !== ''" class="col-md-12">
|
<div *ngIf="reload$.value.sourceId !== ''" class="col-md-12">
|
||||||
<ng-container *ngVar="(entriesRD$ | async) as entriesRD">
|
<ng-container *ngVar="(entriesRD$ | async) as entriesRD">
|
||||||
<h3 *ngIf="entriesRD && entriesRD?.payload?.page?.length !== 0">{{ 'submission.sections.describe.relationship-lookup.selection-tab.title.' + routeData.sourceId | translate}}</h3>
|
<h3 *ngIf="entriesRD && entriesRD?.payload?.page?.length !== 0">{{ 'submission.sections.describe.relationship-lookup.selection-tab.title' | translate}}</h3>
|
||||||
<ds-viewable-collection *ngIf="entriesRD?.hasSucceeded && !(isLoading$ | async) && entriesRD?.payload?.page?.length > 0" @fadeIn
|
<ds-viewable-collection *ngIf="entriesRD?.hasSucceeded && !(isLoading$ | async) && entriesRD?.payload?.page?.length > 0" @fadeIn
|
||||||
[objects]="entriesRD"
|
[objects]="entriesRD"
|
||||||
[selectionConfig]="{ repeatable: repeatable, listId: listId }"
|
[selectionConfig]="{ repeatable: repeatable, listId: listId }"
|
||||||
@@ -25,16 +25,17 @@
|
|||||||
<ds-loading *ngIf="(isLoading$ | async)"
|
<ds-loading *ngIf="(isLoading$ | async)"
|
||||||
message="{{'loading.search-results' | translate}}"></ds-loading>
|
message="{{'loading.search-results' | translate}}"></ds-loading>
|
||||||
<div *ngIf="!(isLoading$ | async) && entriesRD?.payload?.page?.length === 0" id="empty-external-entry-list">
|
<div *ngIf="!(isLoading$ | async) && entriesRD?.payload?.page?.length === 0" id="empty-external-entry-list">
|
||||||
{{ 'search.results.empty' | translate }}
|
<ds-alert [type]="'alert-info'">{{ 'search.results.empty' | translate }}</ds-alert>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="routeData.sourceId === ''" class="col-md-12">
|
<div *ngIf="reload$.value.sourceId === ''" class="col-md-12">
|
||||||
<ds-alert [type]="'alert-info'">
|
<ds-alert [type]="'alert-info'">
|
||||||
<p class="lead mb-0">{{'submission.import-external.page.hint' | translate}}</p>
|
<p class="lead mb-0">{{'submission.import-external.page.hint' | translate}}</p>
|
||||||
</ds-alert>
|
</ds-alert>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<hr>
|
<hr>
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Component, NO_ERRORS_SCHEMA } from '@angular/core';
|
import { Component, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { waitForAsync, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, inject, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
|
|
||||||
import { getTestScheduler } from 'jasmine-marbles';
|
import { getTestScheduler } from 'jasmine-marbles';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
@@ -102,17 +102,19 @@ describe('SubmissionImportExternalComponent test suite', () => {
|
|||||||
|
|
||||||
it('Should init component properly (without route data)', () => {
|
it('Should init component properly (without route data)', () => {
|
||||||
const expectedEntries = createSuccessfulRemoteDataObject(createPaginatedList([]));
|
const expectedEntries = createSuccessfulRemoteDataObject(createPaginatedList([]));
|
||||||
|
comp.routeData = {entity: '', sourceId: '', query: '' };
|
||||||
spyOn(compAsAny.routeService, 'getQueryParameterValue').and.returnValue(observableOf(''));
|
spyOn(compAsAny.routeService, 'getQueryParameterValue').and.returnValue(observableOf(''));
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
expect(comp.routeData).toEqual({ sourceId: '', query: '' });
|
expect(comp.routeData).toEqual({entity: '', sourceId: '', query: '' });
|
||||||
expect(comp.isLoading$.value).toBe(false);
|
expect(comp.isLoading$.value).toBe(false);
|
||||||
expect(comp.entriesRD$.value).toEqual(expectedEntries);
|
expect(comp.entriesRD$.value).toEqual(expectedEntries);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should init component properly (with route data)', () => {
|
it('Should init component properly (with route data)', () => {
|
||||||
|
comp.routeData = {entity: '', sourceId: '', query: '' };
|
||||||
spyOn(compAsAny, 'retrieveExternalSources');
|
spyOn(compAsAny, 'retrieveExternalSources');
|
||||||
spyOn(compAsAny.routeService, 'getQueryParameterValue').and.returnValues(observableOf('source'), observableOf('dummy'));
|
spyOn(compAsAny.routeService, 'getQueryParameterValue').and.returnValues(observableOf('entity'), observableOf('source'), observableOf('dummy'));
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
expect(compAsAny.retrieveExternalSources).toHaveBeenCalled();
|
expect(compAsAny.retrieveExternalSources).toHaveBeenCalled();
|
||||||
@@ -120,7 +122,7 @@ describe('SubmissionImportExternalComponent test suite', () => {
|
|||||||
|
|
||||||
it('Should call \'getExternalSourceEntries\' properly', () => {
|
it('Should call \'getExternalSourceEntries\' properly', () => {
|
||||||
spyOn(routeServiceStub, 'getQueryParameterValue').and.callFake((param) => {
|
spyOn(routeServiceStub, 'getQueryParameterValue').and.callFake((param) => {
|
||||||
if (param === 'source') {
|
if (param === 'sourceId') {
|
||||||
return observableOf('orcidV2');
|
return observableOf('orcidV2');
|
||||||
} else if (param === 'query') {
|
} else if (param === 'query') {
|
||||||
return observableOf('test');
|
return observableOf('test');
|
||||||
@@ -136,15 +138,15 @@ describe('SubmissionImportExternalComponent test suite', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Should call \'router.navigate\'', () => {
|
it('Should call \'router.navigate\'', () => {
|
||||||
comp.routeData = { sourceId: '', query: '' };
|
comp.routeData = {entity: 'Person', sourceId: '', query: '' };
|
||||||
spyOn(compAsAny, 'retrieveExternalSources').and.callFake(() => null);
|
spyOn(compAsAny, 'retrieveExternalSources').and.callFake(() => null);
|
||||||
compAsAny.router.navigate.and.returnValue( new Promise(() => {return;}));
|
compAsAny.router.navigate.and.returnValue( new Promise(() => {return;}));
|
||||||
const event = { sourceId: 'orcidV2', query: 'dummy' };
|
const event = {entity: 'Person', sourceId: 'orcidV2', query: 'dummy' };
|
||||||
|
|
||||||
scheduler.schedule(() => comp.getExternalSourceData(event));
|
scheduler.schedule(() => comp.getExternalSourceData(event));
|
||||||
scheduler.flush();
|
scheduler.flush();
|
||||||
|
|
||||||
expect(compAsAny.router.navigate).toHaveBeenCalledWith([], { queryParams: { source: event.sourceId, query: event.query }, replaceUrl: true });
|
expect(compAsAny.router.navigate).toHaveBeenCalledWith([], { queryParams: { entity: event.entity, sourceId: event.sourceId, query: event.query }, replaceUrl: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Entry should be passed to the component loaded inside the modal', () => {
|
it('Entry should be passed to the component loaded inside the modal', () => {
|
||||||
@@ -166,6 +168,13 @@ describe('SubmissionImportExternalComponent test suite', () => {
|
|||||||
expect(compAsAny.modalService.open).toHaveBeenCalledWith(SubmissionImportExternalPreviewComponent, { size: 'lg' });
|
expect(compAsAny.modalService.open).toHaveBeenCalledWith(SubmissionImportExternalPreviewComponent, { size: 'lg' });
|
||||||
expect(comp.modalRef.componentInstance.externalSourceEntry).toEqual(entry);
|
expect(comp.modalRef.componentInstance.externalSourceEntry).toEqual(entry);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Should set the correct label', () => {
|
||||||
|
const label = 'Person';
|
||||||
|
compAsAny.selectLabel(label);
|
||||||
|
|
||||||
|
expect(comp.label).toEqual(label);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -20,6 +20,7 @@ import { fadeIn } from '../../shared/animations/fade';
|
|||||||
import { PageInfo } from '../../core/shared/page-info.model';
|
import { PageInfo } from '../../core/shared/page-info.model';
|
||||||
import { hasValue, isNotEmpty } from '../../shared/empty.util';
|
import { hasValue, isNotEmpty } from '../../shared/empty.util';
|
||||||
import { getFinishedRemoteData } from '../../core/shared/operators';
|
import { getFinishedRemoteData } from '../../core/shared/operators';
|
||||||
|
import { NONE_ENTITY_TYPE } from '../../core/shared/item-relationships/item-type.resource-type';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component allows to submit a new workspaceitem importing the data from an external source.
|
* This component allows to submit a new workspaceitem importing the data from an external source.
|
||||||
@@ -45,9 +46,10 @@ export class SubmissionImportExternalComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
public isLoading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
public isLoading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||||
|
|
||||||
public reload$: BehaviorSubject<{ query: string, source: string }> = new BehaviorSubject<{ query: string; source: string }>({
|
public reload$: BehaviorSubject<ExternalSourceData> = new BehaviorSubject<ExternalSourceData>({
|
||||||
|
entity: '',
|
||||||
query: '',
|
query: '',
|
||||||
source: ''
|
sourceId: ''
|
||||||
});
|
});
|
||||||
/**
|
/**
|
||||||
* Configuration to use for the import buttons
|
* Configuration to use for the import buttons
|
||||||
@@ -109,11 +111,10 @@ export class SubmissionImportExternalComponent implements OnInit, OnDestroy {
|
|||||||
* Get the entries for the selected external source and set initial configuration.
|
* Get the entries for the selected external source and set initial configuration.
|
||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.label = 'Journal';
|
|
||||||
this.listId = 'list-submission-external-sources';
|
this.listId = 'list-submission-external-sources';
|
||||||
this.context = Context.EntitySearchModalWithNameVariants;
|
this.context = Context.EntitySearchModalWithNameVariants;
|
||||||
this.repeatable = false;
|
this.repeatable = false;
|
||||||
this.routeData = {sourceId: '', query: ''};
|
this.routeData = {entity: '', sourceId: '', query: ''};
|
||||||
this.importConfig = {
|
this.importConfig = {
|
||||||
buttonLabel: 'submission.sections.describe.relationship-lookup.external-source.import-button-title.' + this.label
|
buttonLabel: 'submission.sections.describe.relationship-lookup.external-source.import-button-title.' + this.label
|
||||||
};
|
};
|
||||||
@@ -121,12 +122,14 @@ export class SubmissionImportExternalComponent implements OnInit, OnDestroy {
|
|||||||
this.isLoading$ = new BehaviorSubject(false);
|
this.isLoading$ = new BehaviorSubject(false);
|
||||||
this.subs.push(combineLatest(
|
this.subs.push(combineLatest(
|
||||||
[
|
[
|
||||||
this.routeService.getQueryParameterValue('source'),
|
this.routeService.getQueryParameterValue('entity'),
|
||||||
|
this.routeService.getQueryParameterValue('sourceId'),
|
||||||
this.routeService.getQueryParameterValue('query')
|
this.routeService.getQueryParameterValue('query')
|
||||||
]).pipe(
|
]).pipe(
|
||||||
take(1)
|
take(1)
|
||||||
).subscribe(([source, query]: [string, string]) => {
|
).subscribe(([entity, sourceId, query]: [string, string, string]) => {
|
||||||
this.reload$.next({query: query, source: source});
|
this.reload$.next({entity: entity || NONE_ENTITY_TYPE, query: query, sourceId: sourceId});
|
||||||
|
this.selectLabel(entity);
|
||||||
this.retrieveExternalSources();
|
this.retrieveExternalSources();
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -138,11 +141,11 @@ export class SubmissionImportExternalComponent implements OnInit, OnDestroy {
|
|||||||
this.router.navigate(
|
this.router.navigate(
|
||||||
[],
|
[],
|
||||||
{
|
{
|
||||||
queryParams: {source: event.sourceId, query: event.query},
|
queryParams: event,
|
||||||
replaceUrl: true
|
replaceUrl: true
|
||||||
}
|
}
|
||||||
).then(() => {
|
).then(() => {
|
||||||
this.reload$.next({source: event.sourceId, query: event.query});
|
this.reload$.next(event);
|
||||||
this.retrieveExternalSources();
|
this.retrieveExternalSources();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -157,6 +160,7 @@ export class SubmissionImportExternalComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
const modalComp = this.modalRef.componentInstance;
|
const modalComp = this.modalRef.componentInstance;
|
||||||
modalComp.externalSourceEntry = entry;
|
modalComp.externalSourceEntry = entry;
|
||||||
|
modalComp.labelPrefix = this.label;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -173,28 +177,23 @@ export class SubmissionImportExternalComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve external source entries
|
* Retrieve external source entries.
|
||||||
*
|
|
||||||
* @param source The source tupe
|
|
||||||
* @param query The query string to search
|
|
||||||
*/
|
*/
|
||||||
private retrieveExternalSources(): void {
|
private retrieveExternalSources(): void {
|
||||||
if (hasValue(this.retrieveExternalSourcesSub)) {
|
if (hasValue(this.retrieveExternalSourcesSub)) {
|
||||||
this.retrieveExternalSourcesSub.unsubscribe();
|
this.retrieveExternalSourcesSub.unsubscribe();
|
||||||
}
|
}
|
||||||
this.retrieveExternalSourcesSub = this.reload$.pipe(
|
this.retrieveExternalSourcesSub = this.reload$.pipe(
|
||||||
filter((sourceQueryObject: { source: string, query: string }) => isNotEmpty(sourceQueryObject.source) && isNotEmpty(sourceQueryObject.query)),
|
filter((sourceQueryObject: ExternalSourceData) => isNotEmpty(sourceQueryObject.sourceId) && isNotEmpty(sourceQueryObject.query)),
|
||||||
switchMap((sourceQueryObject: { source: string, query: string }) => {
|
switchMap((sourceQueryObject: ExternalSourceData) => {
|
||||||
const source = sourceQueryObject.source;
|
|
||||||
const query = sourceQueryObject.query;
|
const query = sourceQueryObject.query;
|
||||||
this.routeData.sourceId = source;
|
this.routeData = sourceQueryObject;
|
||||||
this.routeData.query = query;
|
|
||||||
return this.searchConfigService.paginatedSearchOptions.pipe(
|
return this.searchConfigService.paginatedSearchOptions.pipe(
|
||||||
tap((v) => this.isLoading$.next(true)),
|
tap(() => this.isLoading$.next(true)),
|
||||||
filter((searchOptions) => searchOptions.query === query),
|
filter((searchOptions) => searchOptions.query === query),
|
||||||
mergeMap((searchOptions) => this.externalService.getExternalSourceEntries(this.routeData.sourceId, searchOptions).pipe(
|
mergeMap((searchOptions) => this.externalService.getExternalSourceEntries(this.routeData.sourceId, searchOptions).pipe(
|
||||||
getFinishedRemoteData(),
|
getFinishedRemoteData(),
|
||||||
)),
|
))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
@@ -204,4 +203,16 @@ export class SubmissionImportExternalComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the correct button label, depending on the entity.
|
||||||
|
*
|
||||||
|
* @param entity The entity name
|
||||||
|
*/
|
||||||
|
private selectLabel(entity: string): void {
|
||||||
|
this.label = entity;
|
||||||
|
this.importConfig = {
|
||||||
|
buttonLabel: 'submission.sections.describe.relationship-lookup.external-source.import-button-title.' + this.label
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -905,6 +905,8 @@
|
|||||||
|
|
||||||
"collection.form.title": "Name",
|
"collection.form.title": "Name",
|
||||||
|
|
||||||
|
"collection.form.entityType": "Entity Type",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"collection.listelement.badge": "Collection",
|
"collection.listelement.badge": "Collection",
|
||||||
@@ -1278,6 +1280,8 @@
|
|||||||
|
|
||||||
"dso-selector.placeholder": "Search for a {{ type }}",
|
"dso-selector.placeholder": "Search for a {{ type }}",
|
||||||
|
|
||||||
|
"dso-selector.select.collection.head": "Select a collection",
|
||||||
|
|
||||||
"dso-selector.set-scope.community.head": "Select a search scope",
|
"dso-selector.set-scope.community.head": "Select a search scope",
|
||||||
|
|
||||||
"dso-selector.set-scope.community.button": "Search all of DSpace",
|
"dso-selector.set-scope.community.button": "Search all of DSpace",
|
||||||
@@ -2612,6 +2616,7 @@
|
|||||||
|
|
||||||
"nav.user.description" : "User profile bar",
|
"nav.user.description" : "User profile bar",
|
||||||
|
|
||||||
|
"none.listelement.badge": "Item",
|
||||||
|
|
||||||
|
|
||||||
"orgunit.listelement.badge": "Organizational Unit",
|
"orgunit.listelement.badge": "Organizational Unit",
|
||||||
@@ -3432,6 +3437,22 @@
|
|||||||
|
|
||||||
"submission.import-external.title": "Import metadata from an external source",
|
"submission.import-external.title": "Import metadata from an external source",
|
||||||
|
|
||||||
|
"submission.import-external.title.Journal": "Import a journal from an external source",
|
||||||
|
|
||||||
|
"submission.import-external.title.JournalIssue": "Import a journal issue from an external source",
|
||||||
|
|
||||||
|
"submission.import-external.title.JournalVolume": "Import a journal volume from an external source",
|
||||||
|
|
||||||
|
"submission.import-external.title.OrgUnit": "Import a publisher from an external source",
|
||||||
|
|
||||||
|
"submission.import-external.title.Person": "Import a person from an external source",
|
||||||
|
|
||||||
|
"submission.import-external.title.Project": "Import a project from an external source",
|
||||||
|
|
||||||
|
"submission.import-external.title.Publication": "Import a publication from an external source",
|
||||||
|
|
||||||
|
"submission.import-external.title.none": "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.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.back-to-my-dspace": "Back to MyDSpace",
|
||||||
|
Reference in New Issue
Block a user