mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-16 06:23:03 +00:00
[CST-4591] Change collection dropdown in order to accept entity type as input
This commit is contained in:
@@ -11,14 +11,13 @@
|
||||
<div
|
||||
class="scrollable-menu"
|
||||
aria-labelledby="dropdownMenuButton"
|
||||
(scroll)="onScroll($event)">
|
||||
<div
|
||||
(scroll)="onScroll($event)"
|
||||
infiniteScroll
|
||||
[infiniteScrollDistance]="2"
|
||||
[infiniteScrollDistance]="5"
|
||||
[infiniteScrollThrottle]="300"
|
||||
[infiniteScrollUpDistance]="1.5"
|
||||
[infiniteScrollContainer]="'.scrollable-menu'"
|
||||
[fromRoot]="true"
|
||||
[scrollWindow]="false"
|
||||
(scrolled)="onScrollDown()">
|
||||
<button class="dropdown-item disabled" *ngIf="searchListCollection?.length == 0 && !(isLoadingList | async)">
|
||||
{{'submission.sections.general.no-collection' | translate}}
|
||||
@@ -39,5 +38,5 @@
|
||||
<ds-loading message="{{'loading.default' | translate}}">
|
||||
</ds-loading>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
@@ -5,21 +5,16 @@ import { By } from '@angular/platform-browser';
|
||||
import { getTestScheduler } from 'jasmine-marbles';
|
||||
import { TestScheduler } from 'rxjs/testing';
|
||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
|
||||
import { CollectionDropdownComponent } from './collection-dropdown.component';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { buildPaginatedList, PaginatedList } from '../../core/data/paginated-list.model';
|
||||
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../remote-data.utils';
|
||||
import { buildPaginatedList } from '../../core/data/paginated-list.model';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../remote-data.utils';
|
||||
import { PageInfo } from '../../core/shared/page-info.model';
|
||||
import { Collection } from '../../core/shared/collection.model';
|
||||
import { CollectionDataService } from '../../core/data/collection-data.service';
|
||||
import { TranslateLoaderMock } from '../mocks/translate-loader.mock';
|
||||
import { Community } from '../../core/shared/community.model';
|
||||
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(), {
|
||||
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', () => {
|
||||
let component: CollectionDropdownComponent;
|
||||
let componentAsAny: any;
|
||||
@@ -117,12 +101,19 @@ describe('CollectionDropdownComponent', () => {
|
||||
let scheduler: TestScheduler;
|
||||
|
||||
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 paginatedCollectionRD$ = createSuccessfulRemoteDataObject$(paginatedCollection);
|
||||
|
||||
const paginatedEmptyCollection = buildPaginatedList(new PageInfo(), []);
|
||||
const paginatedEmptyCollectionRD$ = createSuccessfulRemoteDataObject$(paginatedEmptyCollection);
|
||||
|
||||
const paginatedOneElementCollection = buildPaginatedList(new PageInfo(), [collections[0]]);
|
||||
const paginatedOneElementCollectionRD$ = createSuccessfulRemoteDataObject$(paginatedOneElementCollection);
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
@@ -150,6 +141,7 @@ describe('CollectionDropdownComponent', () => {
|
||||
component = fixture.componentInstance;
|
||||
componentAsAny = component;
|
||||
componentAsAny.collectionDataService.getAuthorizedCollection.and.returnValue(paginatedCollectionRD$);
|
||||
componentAsAny.collectionDataService.getAuthorizedCollectionByEntityType.and.returnValue(paginatedCollectionRD$);
|
||||
});
|
||||
|
||||
it('should init component with collection list', () => {
|
||||
@@ -225,4 +217,48 @@ describe('CollectionDropdownComponent', () => {
|
||||
expect(component.hasNextPage).toEqual(true);
|
||||
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.hasChoice, 'emit').and.callThrough();
|
||||
component.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.hasChoice.emit).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it('should emit hasChoice false when totalElements is not greater then one', () => {
|
||||
|
||||
componentAsAny.collectionDataService.getAuthorizedCollection.and.returnValue(paginatedEmptyCollectionRD$);
|
||||
componentAsAny.collectionDataService.getAuthorizedCollectionByEntityType.and.returnValue(paginatedEmptyCollectionRD$);
|
||||
|
||||
spyOn(component.hasChoice, 'emit').and.callThrough();
|
||||
component.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.hasChoice.emit).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
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,6 +4,7 @@ import {
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
HostListener,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output
|
||||
@@ -11,7 +12,7 @@ import {
|
||||
import { FormControl } from '@angular/forms';
|
||||
|
||||
import { BehaviorSubject, Observable, 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 { RemoteData } from '../../core/data/remote-data';
|
||||
@@ -106,6 +107,21 @@ export class CollectionDropdownComponent implements OnInit, OnDestroy {
|
||||
*/
|
||||
currentQuery: string;
|
||||
|
||||
/**
|
||||
* If present this value is used to filter collection list by entity type
|
||||
*/
|
||||
@Input() entityType: string;
|
||||
|
||||
/**
|
||||
* Emit to notify whether collections to choice from are more than one
|
||||
*/
|
||||
@Output() hasChoice = new EventEmitter<boolean>();
|
||||
|
||||
/**
|
||||
* Emit to notify the only selectable collection.
|
||||
*/
|
||||
@Output() theOnlySelectable = new EventEmitter<CollectionListEntry>();
|
||||
|
||||
constructor(
|
||||
private changeDetectorRef: ChangeDetectorRef,
|
||||
private collectionDataService: CollectionDataService,
|
||||
@@ -190,14 +206,26 @@ export class CollectionDropdownComponent implements OnInit, OnDestroy {
|
||||
elementsPerPage: 10,
|
||||
currentPage: page
|
||||
};
|
||||
this.searchListCollection$ = this.collectionDataService
|
||||
.getAuthorizedCollection(query, findOptions, true, false, followLink('parentCommunity'))
|
||||
.pipe(
|
||||
let searchListService$: Observable<RemoteData<PaginatedList<Collection>>> = null;
|
||||
if (this.entityType) {
|
||||
searchListService$ = this.collectionDataService
|
||||
.getAuthorizedCollectionByEntityType(
|
||||
query,
|
||||
this.entityType,
|
||||
findOptions,
|
||||
false,
|
||||
followLink('parentCommunity'));
|
||||
} else {
|
||||
searchListService$ = this.collectionDataService
|
||||
.getAuthorizedCollection(query, findOptions, true, false, followLink('parentCommunity'));
|
||||
}
|
||||
this.searchListCollection$ = searchListService$.pipe(
|
||||
getFirstSucceededRemoteWithNotEmptyData(),
|
||||
switchMap((collections: RemoteData<PaginatedList<Collection>>) => {
|
||||
if ( (this.searchListCollection.length + findOptions.elementsPerPage) >= collections.payload.totalElements ) {
|
||||
this.hasNextPage = false;
|
||||
}
|
||||
this.emitSelectionEvents(collections);
|
||||
return collections.payload.page;
|
||||
}),
|
||||
mergeMap((collection: Collection) => collection.parentCommunity.pipe(
|
||||
@@ -247,4 +275,28 @@ export class CollectionDropdownComponent implements OnInit, OnDestroy {
|
||||
hideShowLoader(hideShow: boolean) {
|
||||
this.isLoadingList.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>>) {
|
||||
this.hasChoice.emit(collections.payload.totalElements > 1);
|
||||
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'
|
||||
});
|
||||
collectionService = jasmine.createSpyObj('collectionService', {
|
||||
getAuthorizedCollection: createSuccessfulRemoteDataObject$(createPaginatedList([collection]))
|
||||
getAuthorizedCollection: createSuccessfulRemoteDataObject$(createPaginatedList([collection])),
|
||||
getAuthorizedCollectionByEntityType: createSuccessfulRemoteDataObject$(createPaginatedList([collection]))
|
||||
});
|
||||
notificationsService = jasmine.createSpyObj('notificationsService', ['error']);
|
||||
TestBed.configureTestingModule({
|
||||
@@ -49,12 +50,27 @@ describe('AuthorizedCollectionSelectorComponent', () => {
|
||||
});
|
||||
|
||||
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) => {
|
||||
expect(collectionService.getAuthorizedCollection).toHaveBeenCalled();
|
||||
expect(collectionService.getAuthorizedCollection).toHaveBeenCalled();
|
||||
expect(resultRD.payload.page.length).toEqual(1);
|
||||
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 { SearchService } from '../../../../core/shared/search/search.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 { map } from 'rxjs/operators';
|
||||
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 { NotificationsService } from '../../../notifications/notifications.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Collection } from '../../../../core/shared/collection.model';
|
||||
import { FindListOptions } from '../../../../core/data/request.models';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-authorized-collection-selector',
|
||||
@@ -24,6 +26,11 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
* Component rendering a list of collections to select from
|
||||
*/
|
||||
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,
|
||||
protected collectionDataService: CollectionDataService,
|
||||
protected notifcationsService: NotificationsService,
|
||||
@@ -44,10 +51,23 @@ export class AuthorizedCollectionSelectorComponent extends DSOSelectorComponent
|
||||
* @param page Page to retrieve
|
||||
*/
|
||||
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,
|
||||
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(),
|
||||
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,
|
||||
|
@@ -6,6 +6,9 @@
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<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>
|
||||
|
@@ -69,4 +69,10 @@ describe('CreateItemParentSelectorComponent', () => {
|
||||
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 { DSpaceObjectType } from '../../../../core/shared/dspace-object-type.model';
|
||||
import { DSpaceObject } from '../../../../core/shared/dspace-object.model';
|
||||
@@ -22,6 +22,11 @@ export class CreateItemParentSelectorComponent extends DSOSelectorModalWrapperCo
|
||||
action = SelectorActionType.CREATE;
|
||||
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) {
|
||||
super(activeModal, route);
|
||||
}
|
||||
@@ -35,6 +40,9 @@ export class CreateItemParentSelectorComponent extends DSOSelectorModalWrapperCo
|
||||
['collection']: dso.uuid,
|
||||
}
|
||||
};
|
||||
if (this.entityType) {
|
||||
navigationExtras.queryParams.entityType = this.entityType;
|
||||
}
|
||||
this.router.navigate(['/submit'], navigationExtras);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user