[CST-4591] Fix issue with showing empty collection results

This commit is contained in:
Giuseppe Digilio
2021-09-24 13:18:50 +02:00
parent a170f1f8a9
commit c6a73f2dcb
6 changed files with 87 additions and 97 deletions

View File

@@ -1,42 +1,40 @@
<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
infiniteScroll [infiniteScrollDistance]="5"
[infiniteScrollDistance]="5" [infiniteScrollThrottle]="300"
[infiniteScrollThrottle]="300" [infiniteScrollUpDistance]="1.5"
[infiniteScrollUpDistance]="1.5" [fromRoot]="true"
[fromRoot]="true" [scrollWindow]="false"
[scrollWindow]="false" (scrolled)="onScrollDown()">
(scrolled)="onScrollDown()">
<button class="dropdown-item disabled" *ngIf="searchListCollection?.length == 0 && !(isLoadingList | 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>
<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>
</li> </li>
<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)" > <button class="dropdown-item disabled" *ngIf="(isLoadingList | async)">
<ds-loading message="{{'loading.default' | translate}}"> <ds-loading message="{{'loading.default' | translate}}">
</ds-loading> </ds-loading>
</button> </button>
</div> </div>

View File

@@ -226,23 +226,11 @@ describe('CollectionDropdownComponent', () => {
}); });
it('should emit hasChoice true when totalElements is greater then one', () => { it('should emit hasChoice true when totalElements is greater then one', () => {
spyOn(component.hasChoice, 'emit').and.callThrough(); spyOn(component.searchComplete, 'emit').and.callThrough();
component.ngOnInit(); component.ngOnInit();
fixture.detectChanges(); fixture.detectChanges();
expect(component.hasChoice.emit).toHaveBeenCalledWith(true); expect(component.searchComplete.emit).toHaveBeenCalledWith();
});
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', () => { it('should emit theOnlySelectable when totalElements is equal to one', () => {

View File

@@ -11,7 +11,7 @@ import {
} 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, take } 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';
@@ -22,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
@@ -113,9 +110,9 @@ export class CollectionDropdownComponent implements OnInit, OnDestroy {
@Input() entityType: string; @Input() entityType: string;
/** /**
* Emit to notify whether collections to choice from are more than one * Emit to notify whether search is complete
*/ */
@Output() hasChoice = new EventEmitter<boolean>(); @Output() searchComplete = new EventEmitter<any>();
/** /**
* Emit to notify the only selectable collection. * Emit to notify the only selectable collection.
@@ -213,35 +210,44 @@ export class CollectionDropdownComponent implements OnInit, OnDestroy {
query, query,
this.entityType, this.entityType,
findOptions, findOptions,
false, true,
followLink('parentCommunity')); followLink('parentCommunity'));
} else { } else {
searchListService$ = this.collectionDataService searchListService$ = this.collectionDataService
.getAuthorizedCollection(query, findOptions, true, false, followLink('parentCommunity')); .getAuthorizedCollection(query, findOptions, true, true, followLink('parentCommunity'));
} }
this.searchListCollection$ = searchListService$.pipe( this.searchListCollection$ = searchListService$.pipe(
getFirstSucceededRemoteWithNotEmptyData(), getFirstCompletedRemoteData(),
switchMap((collections: RemoteData<PaginatedList<Collection>>) => { switchMap((collectionsRD: RemoteData<PaginatedList<Collection>>) => {
if ( (this.searchListCollection.length + findOptions.elementsPerPage) >= collections.payload.totalElements ) { 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([]);
} }
this.emitSelectionEvents(collections); })
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();
})
);
} }
/** /**
@@ -284,7 +290,6 @@ export class CollectionDropdownComponent implements OnInit, OnDestroy {
* @private * @private
*/ */
private emitSelectionEvents(collections: RemoteData<PaginatedList<Collection>>) { private emitSelectionEvents(collections: RemoteData<PaginatedList<Collection>>) {
this.hasChoice.emit(collections.payload.totalElements > 1);
if (collections.payload.totalElements === 1) { if (collections.payload.totalElements === 1) {
const collection = collections.payload.page[0]; const collection = collections.payload.page[0];
collections.payload.page[0].parentCommunity.pipe( collections.payload.page[0].parentCommunity.pipe(

View File

@@ -1,5 +1,5 @@
<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>
@@ -8,7 +8,7 @@
<ds-loading *ngIf="isLoading()"></ds-loading> <ds-loading *ngIf="isLoading()"></ds-loading>
<ds-collection-dropdown [ngClass]="{'d-none': isLoading()}" <ds-collection-dropdown [ngClass]="{'d-none': isLoading()}"
(selectionChange)="selectObject($event)" (selectionChange)="selectObject($event)"
(hasChoice)="onHasChoice($event)" (searchComplete)="searchComplete()"
(theOnlySelectable)="theOnlySelectable($event)" (theOnlySelectable)="theOnlySelectable($event)"
[entityType]="entityType"> [entityType]="entityType">
</ds-collection-dropdown> </ds-collection-dropdown>

View File

@@ -78,25 +78,23 @@ describe('SubmissionImportExternalCollectionComponent test suite', () => {
expect(compAsAny.activeModal.dismiss).toHaveBeenCalled(); expect(compAsAny.activeModal.dismiss).toHaveBeenCalled();
}); });
it('should be in loading state when hasChoice variable is different to true', () => { it('should be in loading state when search is not completed', () => {
comp.hasChoice = null; comp.loading = null;
expect(comp.isLoading()).toBeFalse();
comp.loading = true;
expect(comp.isLoading()).toBeTrue(); expect(comp.isLoading()).toBeTrue();
comp.hasChoice = false; comp.loading = false;
expect(comp.isLoading()).toBeTrue();
comp.hasChoice = true;
expect(comp.isLoading()).toBeFalse(); expect(comp.isLoading()).toBeFalse();
}); });
it('should set hasChoice variable on hasChoice event', () => { it('should set loading variable to false on searchComplete event', () => {
comp.hasChoice = null; comp.loading = null;
comp.onHasChoice(true); comp.searchComplete();
expect(comp.hasChoice).toBe(true); expect(comp.loading).toBe(false);
comp.onHasChoice(false);
expect(comp.hasChoice).toBe(false);
}); });
it('should emit theOnlySelectable', () => { it('should emit theOnlySelectable', () => {

View File

@@ -22,9 +22,9 @@ export class SubmissionImportExternalCollectionComponent {
public entityType: string; public entityType: string;
/** /**
* If a collection choice is available * If collection searching is pending or not
*/ */
public hasChoice: boolean = null; public loading = true;
/** /**
* Initialize the component variables. * Initialize the component variables.
@@ -60,14 +60,15 @@ export class SubmissionImportExternalCollectionComponent {
* Set the hasChoice state * Set the hasChoice state
* @param hasChoice * @param hasChoice
*/ */
public onHasChoice(hasChoice: boolean) { public searchComplete() {
this.hasChoice = hasChoice; this.loading = false;
} }
/** /**
* If the component is in loading state. * If the component is in loading state.
*/ */
public isLoading(): boolean { public isLoading(): boolean {
return this.hasChoice !== true; return !!this.loading;
} }
} }