mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
[CST-4591] Fix issue with showing empty collection results
This commit is contained in:
@@ -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>
|
||||||
|
@@ -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', () => {
|
||||||
|
@@ -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(
|
||||||
|
@@ -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>
|
||||||
|
@@ -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', () => {
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user