From 51ada1c93377801b70b45cee9c15e589406ac4c6 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 22 Sep 2021 17:34:54 +0200 Subject: [PATCH] [CST-4591] Change collection dropdown in order to accept entity type as input --- .../collection-dropdown.component.html | 11 ++- .../collection-dropdown.component.spec.ts | 74 ++++++++++++++----- .../collection-dropdown.component.ts | 60 ++++++++++++++- ...ized-collection-selector.component.spec.ts | 24 +++++- ...uthorized-collection-selector.component.ts | 28 ++++++- ...create-item-parent-selector.component.html | 5 +- ...ate-item-parent-selector.component.spec.ts | 6 ++ .../create-item-parent-selector.component.ts | 10 ++- 8 files changed, 179 insertions(+), 39 deletions(-) diff --git a/src/app/shared/collection-dropdown/collection-dropdown.component.html b/src/app/shared/collection-dropdown/collection-dropdown.component.html index 36269294c1..ce5fe0deb8 100644 --- a/src/app/shared/collection-dropdown/collection-dropdown.component.html +++ b/src/app/shared/collection-dropdown/collection-dropdown.component.html @@ -11,14 +11,13 @@
-
-
-
\ No newline at end of file + + diff --git a/src/app/shared/collection-dropdown/collection-dropdown.component.spec.ts b/src/app/shared/collection-dropdown/collection-dropdown.component.spec.ts index f08df65ca4..612f5a1733 100644 --- a/src/app/shared/collection-dropdown/collection-dropdown.component.spec.ts +++ b/src/app/shared/collection-dropdown/collection-dropdown.component.spec.ts @@ -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[]): Observable>> { - 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); + }); }); diff --git a/src/app/shared/collection-dropdown/collection-dropdown.component.ts b/src/app/shared/collection-dropdown/collection-dropdown.component.ts index c91ddbdb0a..6c11613512 100644 --- a/src/app/shared/collection-dropdown/collection-dropdown.component.ts +++ b/src/app/shared/collection-dropdown/collection-dropdown.component.ts @@ -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(); + + /** + * Emit to notify the only selectable collection. + */ + @Output() theOnlySelectable = new EventEmitter(); + 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>> = 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>) => { 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>) { + 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 } + }); + }); + } + } + } diff --git a/src/app/shared/dso-selector/dso-selector/authorized-collection-selector/authorized-collection-selector.component.spec.ts b/src/app/shared/dso-selector/dso-selector/authorized-collection-selector/authorized-collection-selector.component.spec.ts index 55634dbf7f..b46df8ff36 100644 --- a/src/app/shared/dso-selector/dso-selector/authorized-collection-selector/authorized-collection-selector.component.spec.ts +++ b/src/app/shared/dso-selector/dso-selector/authorized-collection-selector/authorized-collection-selector.component.spec.ts @@ -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(); + }); }); }); }); diff --git a/src/app/shared/dso-selector/dso-selector/authorized-collection-selector/authorized-collection-selector.component.ts b/src/app/shared/dso-selector/dso-selector/authorized-collection-selector/authorized-collection-selector.component.ts index bca1727542..b6aa0b3413 100644 --- a/src/app/shared/dso-selector/dso-selector/authorized-collection-selector/authorized-collection-selector.component.ts +++ b/src/app/shared/dso-selector/dso-selector/authorized-collection-selector/authorized-collection-selector.component.ts @@ -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>>> { - return this.collectionDataService.getAuthorizedCollection(query, Object.assign({ + let searchListService$: Observable>> = 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, diff --git a/src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component.html b/src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component.html index 8761e4eb9e..5fccd58f48 100644 --- a/src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component.html +++ b/src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component.html @@ -6,6 +6,9 @@ diff --git a/src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component.spec.ts b/src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component.spec.ts index 3afb3c5e9a..90bd07c52b 100644 --- a/src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component.spec.ts +++ b/src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component.spec.ts @@ -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 } }); + }); }); diff --git a/src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component.ts b/src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component.ts index 03d7732fb0..b109be0af2 100644 --- a/src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component.ts +++ b/src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component.ts @@ -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); } }