mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
[CST-6494] Update filter values when a workflow action is dispatched
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
|
||||
import { BehaviorSubject, combineLatest as observableCombineLatest, Observable } from 'rxjs';
|
||||
import { distinctUntilChanged, map } from 'rxjs/operators';
|
||||
import { Injectable, InjectionToken } from '@angular/core';
|
||||
import {
|
||||
@@ -26,6 +26,7 @@ const filterStateSelector = (state: SearchFiltersState) => state.searchFilter;
|
||||
|
||||
export const FILTER_CONFIG: InjectionToken<SearchFilterConfig> = new InjectionToken<SearchFilterConfig>('filterConfig');
|
||||
export const IN_PLACE_SEARCH: InjectionToken<boolean> = new InjectionToken<boolean>('inPlaceSearch');
|
||||
export const REFRESH_FILTER: InjectionToken<BehaviorSubject<any>> = new InjectionToken<boolean>('refreshFilters');
|
||||
|
||||
/**
|
||||
* Service that performs all actions that have to do with search filters and facets
|
||||
|
@@ -262,9 +262,11 @@ export class SearchService implements OnDestroy {
|
||||
* @param {number} valuePage The page number of the filter values
|
||||
* @param {SearchOptions} searchOptions The search configuration for the current search
|
||||
* @param {string} filterQuery The optional query used to filter out filter values
|
||||
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||
* no valid cached version. Defaults to true
|
||||
* @returns {Observable<RemoteData<PaginatedList<FacetValue>>>} Emits the given page of facet values
|
||||
*/
|
||||
getFacetValuesFor(filterConfig: SearchFilterConfig, valuePage: number, searchOptions?: SearchOptions, filterQuery?: string): Observable<RemoteData<FacetValues>> {
|
||||
getFacetValuesFor(filterConfig: SearchFilterConfig, valuePage: number, searchOptions?: SearchOptions, filterQuery?: string, useCachedVersionIfAvailable = true): Observable<RemoteData<FacetValues>> {
|
||||
let href;
|
||||
const args: string[] = [`page=${valuePage - 1}`, `size=${filterConfig.pageSize}`];
|
||||
if (hasValue(filterQuery)) {
|
||||
@@ -282,7 +284,7 @@ export class SearchService implements OnDestroy {
|
||||
return FacetValueResponseParsingService;
|
||||
}
|
||||
});
|
||||
this.requestService.send(request, true);
|
||||
this.requestService.send(request, useCachedVersionIfAvailable);
|
||||
|
||||
return this.rdb.buildFromHref(href);
|
||||
}
|
||||
|
@@ -19,7 +19,7 @@
|
||||
[importable]="importable"
|
||||
[importConfig]="importConfig"
|
||||
(importObject)="importObject.emit($event)"
|
||||
(contentChange)="contentChange.emit()"
|
||||
(contentChange)="contentChange.emit($event)"
|
||||
(prev)="goPrev()"
|
||||
(next)="goNext()"
|
||||
*ngIf="(currentMode$ | async) === viewModeEnum.ListElement">
|
||||
@@ -49,6 +49,7 @@
|
||||
[context]="context"
|
||||
[hidePaginationDetail]="hidePaginationDetail"
|
||||
[showPaginator]="showPaginator"
|
||||
(contentChange)="contentChange.emit($event)"
|
||||
*ngIf="(currentMode$ | async) === viewModeEnum.DetailedListElement">
|
||||
</ds-object-detail>
|
||||
|
||||
|
@@ -50,6 +50,11 @@ export class ObjectCollectionComponent implements OnInit {
|
||||
@Input() hideGear = false;
|
||||
@Input() selectable = false;
|
||||
@Input() selectionConfig: {repeatable: boolean, listId: string};
|
||||
|
||||
/**
|
||||
* Emit custom event for listable object custom actions.
|
||||
*/
|
||||
@Output() customEvent = new EventEmitter<any>();
|
||||
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
||||
@Output() selectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
||||
|
||||
|
@@ -5,7 +5,9 @@ import { ListableObject } from '../listable-object.model';
|
||||
import { GenericConstructor } from '../../../../core/shared/generic-constructor';
|
||||
import { Context } from '../../../../core/shared/context.model';
|
||||
import { ViewMode } from '../../../../core/shared/view-mode.model';
|
||||
import { ItemListElementComponent } from '../../../object-list/item-list-element/item-types/item/item-list-element.component';
|
||||
import {
|
||||
ItemListElementComponent
|
||||
} from '../../../object-list/item-list-element/item-types/item/item-list-element.component';
|
||||
import { ListableObjectDirective } from './listable-object.directive';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
@@ -146,7 +148,7 @@ describe('ListableObjectComponentLoaderComponent', () => {
|
||||
expect((comp as any).instantiateComponent).not.toHaveBeenCalled();
|
||||
|
||||
(listableComponent as any).reloadedObject.emit(reloadedObject);
|
||||
tick();
|
||||
tick(200);
|
||||
|
||||
expect((comp as any).instantiateComponent).toHaveBeenCalledWith(reloadedObject);
|
||||
}));
|
||||
@@ -155,7 +157,7 @@ describe('ListableObjectComponentLoaderComponent', () => {
|
||||
expect((comp as any).contentChange.emit).not.toHaveBeenCalled();
|
||||
|
||||
(listableComponent as any).reloadedObject.emit(reloadedObject);
|
||||
tick();
|
||||
tick(200);
|
||||
|
||||
expect((comp as any).contentChange.emit).toHaveBeenCalledWith(reloadedObject);
|
||||
}));
|
||||
|
@@ -1,16 +1,16 @@
|
||||
import {
|
||||
Component,
|
||||
ComponentFactoryResolver,
|
||||
ComponentRef,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
ViewChild,
|
||||
EventEmitter,
|
||||
SimpleChanges,
|
||||
OnChanges,
|
||||
ComponentRef
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import { ListableObject } from '../listable-object.model';
|
||||
import { ViewMode } from '../../../../core/shared/view-mode.model';
|
||||
@@ -187,7 +187,10 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges
|
||||
this.compRef.destroy();
|
||||
this.object = reloadedObject;
|
||||
this.instantiateComponent(reloadedObject);
|
||||
// Add delay before emitting event to allow the new object is instantiated
|
||||
setTimeout(() => {
|
||||
this.contentChange.emit(reloadedObject);
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@@ -14,12 +14,14 @@
|
||||
(sortFieldChange)="onSortFieldChange($event)"
|
||||
(paginationChange)="onPaginationChange($event)"
|
||||
(prev)="goPrev()"
|
||||
(next)="goNext()"
|
||||
>
|
||||
(next)="goNext()">
|
||||
<div class="row mt-2" *ngIf="objects?.hasSucceeded" @fadeIn>
|
||||
<div class="col"
|
||||
*ngFor="let object of objects?.payload?.page">
|
||||
<ds-listable-object-component-loader [object]="object" [viewMode]="viewMode" [context]="context"></ds-listable-object-component-loader>
|
||||
<ds-listable-object-component-loader [object]="object"
|
||||
[viewMode]="viewMode"
|
||||
[context]="context"
|
||||
(contentChange)="contentChange.emit($event)"></ds-listable-object-component-loader>
|
||||
</div>
|
||||
</div>
|
||||
<ds-error *ngIf="objects.hasFailed" message="{{'error.objects' | translate}}"></ds-error>
|
||||
|
@@ -1,11 +1,4 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
Output,
|
||||
ViewEncapsulation
|
||||
} from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core';
|
||||
|
||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||
import { PaginatedList } from '../../core/data/paginated-list.model';
|
||||
@@ -71,6 +64,11 @@ export class ObjectDetailComponent {
|
||||
*/
|
||||
@Input() showPaginator = true;
|
||||
|
||||
/**
|
||||
* Emit when one of the listed object has changed.
|
||||
*/
|
||||
@Output() contentChange = new EventEmitter<any>();
|
||||
|
||||
/**
|
||||
* If showPaginator is set to true, emit when the previous button is clicked
|
||||
*/
|
||||
|
@@ -14,8 +14,7 @@
|
||||
(sortFieldChange)="onSortFieldChange($event)"
|
||||
(paginationChange)="onPaginationChange($event)"
|
||||
(prev)="goPrev()"
|
||||
(next)="goNext()"
|
||||
>
|
||||
(next)="goNext()">
|
||||
<ul *ngIf="objects?.hasSucceeded" class="list-unstyled" [ngClass]="{'ml-4': selectable}">
|
||||
<li *ngFor="let object of objects?.payload?.page; let i = index; let last = last" class="mt-4 mb-4 d-flex" [class.border-bottom]="hasBorder && !last" [attr.data-test]="'list-object' | dsBrowserOnly">
|
||||
<ds-selectable-list-item-control *ngIf="selectable" [index]="i"
|
||||
@@ -28,8 +27,7 @@
|
||||
(importObject)="importObject.emit($event)"></ds-importable-list-item-control>
|
||||
<ds-listable-object-component-loader [object]="object" [viewMode]="viewMode" [index]="i" [context]="context" [linkType]="linkType"
|
||||
[listID]="selectionConfig?.listId"
|
||||
(contentChange)="contentChange.emit()"
|
||||
></ds-listable-object-component-loader>
|
||||
(contentChange)="contentChange.emit($event)"></ds-listable-object-component-loader>
|
||||
</li>
|
||||
</ul>
|
||||
</ds-pagination>
|
||||
|
@@ -74,7 +74,7 @@ export class ObjectListComponent {
|
||||
/**
|
||||
* Config used for the import button
|
||||
*/
|
||||
@Input() importConfig: { importLabel: string };
|
||||
@Input() importConfig: { buttonLabel: string };
|
||||
|
||||
/**
|
||||
* Whether or not the pagination should be rendered as simple previous and next buttons instead of the normal pagination
|
||||
@@ -221,4 +221,5 @@ export class ObjectListComponent {
|
||||
goNext() {
|
||||
this.next.emit(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -2,9 +2,14 @@ import { Component, Injector, Input, OnInit } from '@angular/core';
|
||||
import { renderFilterType } from '../search-filter-type-decorator';
|
||||
import { FilterType } from '../../../models/filter-type.model';
|
||||
import { SearchFilterConfig } from '../../../models/search-filter-config.model';
|
||||
import { FILTER_CONFIG, IN_PLACE_SEARCH } from '../../../../../core/shared/search/search-filter.service';
|
||||
import {
|
||||
FILTER_CONFIG,
|
||||
IN_PLACE_SEARCH,
|
||||
REFRESH_FILTER
|
||||
} from '../../../../../core/shared/search/search-filter.service';
|
||||
import { GenericConstructor } from '../../../../../core/shared/generic-constructor';
|
||||
import { SearchFacetFilterComponent } from '../search-facet-filter/search-facet-filter.component';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-search-facet-filter-wrapper',
|
||||
@@ -25,6 +30,11 @@ export class SearchFacetFilterWrapperComponent implements OnInit {
|
||||
*/
|
||||
@Input() inPlaceSearch;
|
||||
|
||||
/**
|
||||
* Emits when the search filters values may be stale, and so they must be refreshed.
|
||||
*/
|
||||
@Input() refreshFilters: BehaviorSubject<boolean>;
|
||||
|
||||
/**
|
||||
* The constructor of the search facet filter that should be rendered, based on the filter config's type
|
||||
*/
|
||||
@@ -45,7 +55,8 @@ export class SearchFacetFilterWrapperComponent implements OnInit {
|
||||
this.objectInjector = Injector.create({
|
||||
providers: [
|
||||
{ provide: FILTER_CONFIG, useFactory: () => (this.filterConfig), deps: [] },
|
||||
{ provide: IN_PLACE_SEARCH, useFactory: () => (this.inPlaceSearch), deps: [] }
|
||||
{ provide: IN_PLACE_SEARCH, useFactory: () => (this.inPlaceSearch), deps: [] },
|
||||
{ provide: REFRESH_FILTER, useFactory: () => (this.refreshFilters), deps: [] }
|
||||
],
|
||||
parent: this.injector
|
||||
});
|
||||
|
@@ -5,13 +5,14 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import {
|
||||
FILTER_CONFIG,
|
||||
IN_PLACE_SEARCH,
|
||||
REFRESH_FILTER,
|
||||
SearchFilterService
|
||||
} from '../../../../../core/shared/search/search-filter.service';
|
||||
import { SearchFilterConfig } from '../../../models/search-filter-config.model';
|
||||
import { FilterType } from '../../../models/filter-type.model';
|
||||
import { FacetValue } from '../../../models/facet-value.model';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { BehaviorSubject, of as observableOf } from 'rxjs';
|
||||
import { SearchService } from '../../../../../core/shared/search/search.service';
|
||||
import { SearchServiceStub } from '../../../../testing/search-service.stub';
|
||||
import { buildPaginatedList } from '../../../../../core/data/paginated-list.model';
|
||||
@@ -97,6 +98,7 @@ describe('SearchFacetFilterComponent', () => {
|
||||
{ provide: RemoteDataBuildService, useValue: { aggregate: () => observableOf({}) } },
|
||||
{ provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() },
|
||||
{ provide: IN_PLACE_SEARCH, useValue: false },
|
||||
{ provide: REFRESH_FILTER, useValue: new BehaviorSubject<boolean>(false) },
|
||||
{
|
||||
provide: SearchFilterService, useValue: {
|
||||
getSelectedValuesForFilter: () => observableOf(selectedValues),
|
||||
|
@@ -6,7 +6,7 @@ import {
|
||||
Subject,
|
||||
Subscription
|
||||
} from 'rxjs';
|
||||
import { distinctUntilChanged, map, switchMap, take, tap } from 'rxjs/operators';
|
||||
import { distinctUntilChanged, filter, map, switchMap, take, tap } from 'rxjs/operators';
|
||||
import { animate, state, style, transition, trigger } from '@angular/animations';
|
||||
import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
@@ -21,6 +21,7 @@ import { SearchService } from '../../../../../core/shared/search/search.service'
|
||||
import {
|
||||
FILTER_CONFIG,
|
||||
IN_PLACE_SEARCH,
|
||||
REFRESH_FILTER,
|
||||
SearchFilterService
|
||||
} from '../../../../../core/shared/search/search-filter.service';
|
||||
import { SearchConfigurationService } from '../../../../../core/shared/search/search-configuration.service';
|
||||
@@ -98,7 +99,8 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
|
||||
protected router: Router,
|
||||
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService,
|
||||
@Inject(IN_PLACE_SEARCH) public inPlaceSearch: boolean,
|
||||
@Inject(FILTER_CONFIG) public filterConfig: SearchFilterConfig) {
|
||||
@Inject(FILTER_CONFIG) public filterConfig: SearchFilterConfig,
|
||||
@Inject(REFRESH_FILTER) public refreshFilters: BehaviorSubject<boolean>) {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,66 +112,15 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
|
||||
this.currentPage = this.getCurrentPage().pipe(distinctUntilChanged());
|
||||
|
||||
this.searchOptions$ = this.searchConfigService.searchOptions;
|
||||
this.subs.push(this.searchOptions$.subscribe(() => this.updateFilterValueList()));
|
||||
const facetValues$ = observableCombineLatest(this.searchOptions$, this.currentPage).pipe(
|
||||
map(([options, page]) => {
|
||||
return { options, page };
|
||||
}),
|
||||
switchMap(({ options, page }) => {
|
||||
return this.searchService.getFacetValuesFor(this.filterConfig, page, options)
|
||||
.pipe(
|
||||
getFirstSucceededRemoteData(),
|
||||
map((results) => {
|
||||
return {
|
||||
values: observableOf(results),
|
||||
page: page
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
this.subs.push(
|
||||
this.searchOptions$.subscribe(() => this.updateFilterValueList()),
|
||||
this.refreshFilters.asObservable().pipe(
|
||||
filter((toRefresh: boolean) => toRefresh),
|
||||
).subscribe(() => {
|
||||
this.retrieveFilterValues(false);
|
||||
})
|
||||
);
|
||||
|
||||
let filterValues = [];
|
||||
this.subs.push(facetValues$.subscribe((facetOutcome) => {
|
||||
const newValues$ = facetOutcome.values;
|
||||
|
||||
if (this.collapseNextUpdate) {
|
||||
this.showFirstPageOnly();
|
||||
facetOutcome.page = 1;
|
||||
this.collapseNextUpdate = false;
|
||||
}
|
||||
if (facetOutcome.page === 1) {
|
||||
filterValues = [];
|
||||
}
|
||||
|
||||
filterValues = [...filterValues, newValues$];
|
||||
|
||||
this.subs.push(this.rdbs.aggregate(filterValues).pipe(
|
||||
tap((rd: RemoteData<PaginatedList<FacetValue>[]>) => {
|
||||
this.selectedValues$ = this.filterService.getSelectedValuesForFilter(this.filterConfig).pipe(
|
||||
map((selectedValues) => {
|
||||
return selectedValues.map((value: string) => {
|
||||
const fValue = [].concat(...rd.payload.map((page) => page.page)).find((facetValue: FacetValue) => this.getFacetValue(facetValue) === value);
|
||||
if (hasValue(fValue)) {
|
||||
return fValue;
|
||||
}
|
||||
const filterValue = stripOperatorFromFilterValue(value);
|
||||
return Object.assign(new FacetValue(), { label: filterValue, value: filterValue });
|
||||
});
|
||||
})
|
||||
);
|
||||
})
|
||||
).subscribe((rd: RemoteData<PaginatedList<FacetValue>[]>) => {
|
||||
this.animationState = 'ready';
|
||||
this.filterValues$.next(rd);
|
||||
|
||||
}));
|
||||
this.subs.push(newValues$.pipe(take(1)).subscribe((rd) => {
|
||||
this.isLastPage$.next(hasNoValue(rd.payload.next));
|
||||
}));
|
||||
}));
|
||||
|
||||
this.retrieveFilterValues();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -324,6 +275,67 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
|
||||
return getFacetValueForType(facet, this.filterConfig);
|
||||
}
|
||||
|
||||
protected retrieveFilterValues(useCachedVersionIfAvailable = true) {
|
||||
const facetValues$ = observableCombineLatest([this.searchOptions$, this.currentPage]).pipe(
|
||||
map(([options, page]) => {
|
||||
return { options, page };
|
||||
}),
|
||||
switchMap(({ options, page }) => {
|
||||
return this.searchService.getFacetValuesFor(this.filterConfig, page, options, null, useCachedVersionIfAvailable)
|
||||
.pipe(
|
||||
getFirstSucceededRemoteData(),
|
||||
map((results) => {
|
||||
return {
|
||||
values: observableOf(results),
|
||||
page: page
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
let filterValues = [];
|
||||
this.subs.push(facetValues$.subscribe((facetOutcome) => {
|
||||
const newValues$ = facetOutcome.values;
|
||||
|
||||
if (this.collapseNextUpdate) {
|
||||
this.showFirstPageOnly();
|
||||
facetOutcome.page = 1;
|
||||
this.collapseNextUpdate = false;
|
||||
}
|
||||
if (facetOutcome.page === 1) {
|
||||
filterValues = [];
|
||||
}
|
||||
|
||||
filterValues = [...filterValues, newValues$];
|
||||
|
||||
this.subs.push(this.rdbs.aggregate(filterValues).pipe(
|
||||
tap((rd: RemoteData<PaginatedList<FacetValue>[]>) => {
|
||||
this.selectedValues$ = this.filterService.getSelectedValuesForFilter(this.filterConfig).pipe(
|
||||
map((selectedValues) => {
|
||||
return selectedValues.map((value: string) => {
|
||||
const fValue = [].concat(...rd.payload.map((page) => page.page)).find((facetValue: FacetValue) => this.getFacetValue(facetValue) === value);
|
||||
if (hasValue(fValue)) {
|
||||
return fValue;
|
||||
}
|
||||
const filterValue = stripOperatorFromFilterValue(value);
|
||||
return Object.assign(new FacetValue(), { label: filterValue, value: filterValue });
|
||||
});
|
||||
})
|
||||
);
|
||||
})
|
||||
).subscribe((rd: RemoteData<PaginatedList<FacetValue>[]>) => {
|
||||
this.animationState = 'ready';
|
||||
this.filterValues$.next(rd);
|
||||
|
||||
}));
|
||||
this.subs.push(newValues$.pipe(take(1)).subscribe((rd) => {
|
||||
this.isLastPage$.next(hasNoValue(rd.payload.next));
|
||||
}));
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the facet value string, so if the query matches part of the value, it's emphasized in the value
|
||||
* @param {FacetValue} facet The value of the facet as returned by the server
|
||||
|
@@ -19,7 +19,8 @@
|
||||
class="search-filter-wrapper" [ngClass]="{ 'closed' : closed, 'notab': notab }">
|
||||
<ds-search-facet-filter-wrapper
|
||||
[filterConfig]="filter"
|
||||
[inPlaceSearch]="inPlaceSearch">
|
||||
[inPlaceSearch]="inPlaceSearch"
|
||||
[refreshFilters]="refreshFilters" >
|
||||
</ds-search-facet-filter-wrapper>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { Component, Inject, Input, OnInit } from '@angular/core';
|
||||
|
||||
import { Observable, of as observableOf } from 'rxjs';
|
||||
import { BehaviorSubject, Observable, of as observableOf } from 'rxjs';
|
||||
import { filter, map, startWith, switchMap, take } from 'rxjs/operators';
|
||||
|
||||
import { SearchFilterConfig } from '../../models/search-filter-config.model';
|
||||
@@ -33,6 +33,11 @@ export class SearchFilterComponent implements OnInit {
|
||||
*/
|
||||
@Input() inPlaceSearch;
|
||||
|
||||
/**
|
||||
* Emits when the search filters values may be stale, and so they must be refreshed.
|
||||
*/
|
||||
@Input() refreshFilters: BehaviorSubject<boolean>;
|
||||
|
||||
/**
|
||||
* True when the filter is 100% collapsed in the UI
|
||||
*/
|
||||
|
@@ -1,17 +1,18 @@
|
||||
import { TestBed, ComponentFixture } from '@angular/core/testing';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { SearchHierarchyFilterComponent } from './search-hierarchy-filter.component';
|
||||
import { SearchService } from '../../../../../core/shared/search/search.service';
|
||||
import {
|
||||
SearchFilterService,
|
||||
FILTER_CONFIG,
|
||||
IN_PLACE_SEARCH
|
||||
IN_PLACE_SEARCH,
|
||||
REFRESH_FILTER,
|
||||
SearchFilterService
|
||||
} from '../../../../../core/shared/search/search-filter.service';
|
||||
import { RemoteDataBuildService } from '../../../../../core/cache/builders/remote-data-build.service';
|
||||
import { SearchFiltersComponent } from '../../search-filters.component';
|
||||
import { Router } from '@angular/router';
|
||||
import { RouterStub } from '../../../../testing/router.stub';
|
||||
import { SearchServiceStub } from '../../../../testing/search-service.stub';
|
||||
import { of as observableOf, Observable } from 'rxjs';
|
||||
import { BehaviorSubject, Observable, of as observableOf } from 'rxjs';
|
||||
import { SEARCH_CONFIG_SERVICE } from '../../../../../my-dspace-page/my-dspace-page.component';
|
||||
import { SearchConfigurationServiceStub } from '../../../../testing/search-configuration-service.stub';
|
||||
import { SearchFilterConfig } from '../../../models/search-filter-config.model';
|
||||
@@ -21,7 +22,7 @@ import {
|
||||
} from '../../../../input-suggestions/filter-suggestions/filter-input-suggestions.component';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { NO_ERRORS_SCHEMA, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../../../../remote-data.utils';
|
||||
import { FacetValue } from '../../../models/facet-value.model';
|
||||
import { FilterType } from '../../../models/filter-type.model';
|
||||
@@ -112,7 +113,8 @@ describe('SearchHierarchyFilterComponent', () => {
|
||||
{ provide: Router, useValue: new RouterStub() },
|
||||
{ provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() },
|
||||
{ provide: IN_PLACE_SEARCH, useValue: false },
|
||||
{ provide: FILTER_CONFIG, useValue: new SearchFilterConfig() }
|
||||
{ provide: FILTER_CONFIG, useValue: new SearchFilterConfig() },
|
||||
{ provide: REFRESH_FILTER, useValue: new BehaviorSubject<boolean>(false) }
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).overrideComponent(SearchHierarchyFilterComponent, {
|
||||
@@ -140,7 +142,7 @@ describe('SearchHierarchyFilterComponent', () => {
|
||||
});
|
||||
|
||||
it('should navigate to the correct filter with the query operator', () => {
|
||||
expect((comp as any).searchService.getFacetValuesFor).toHaveBeenCalledWith(comp.filterConfig, 0, {});
|
||||
expect((comp as any).searchService.getFacetValuesFor).toHaveBeenCalledWith(comp.filterConfig, 0, {}, null, true);
|
||||
|
||||
const searchQuery = 'MARVEL';
|
||||
comp.onSubmit(searchQuery);
|
||||
|
@@ -5,13 +5,14 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import {
|
||||
FILTER_CONFIG,
|
||||
IN_PLACE_SEARCH,
|
||||
REFRESH_FILTER,
|
||||
SearchFilterService
|
||||
} from '../../../../../core/shared/search/search-filter.service';
|
||||
import { SearchFilterConfig } from '../../../models/search-filter-config.model';
|
||||
import { FilterType } from '../../../models/filter-type.model';
|
||||
import { FacetValue } from '../../../models/facet-value.model';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { BehaviorSubject, of as observableOf } from 'rxjs';
|
||||
import { SearchService } from '../../../../../core/shared/search/search.service';
|
||||
import { SearchServiceStub } from '../../../../testing/search-service.stub';
|
||||
import { buildPaginatedList } from '../../../../../core/data/paginated-list.model';
|
||||
@@ -104,6 +105,7 @@ describe('SearchRangeFilterComponent', () => {
|
||||
{ provide: RouteService, useValue: { getQueryParameterValue: () => observableOf({}) } },
|
||||
{ provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() },
|
||||
{ provide: IN_PLACE_SEARCH, useValue: false },
|
||||
{ provide: REFRESH_FILTER, useValue: new BehaviorSubject<boolean>(false) },
|
||||
{
|
||||
provide: SearchFilterService, useValue: {
|
||||
getSelectedValuesForFilter: () => selectedValues,
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { combineLatest as observableCombineLatest, Subscription } from 'rxjs';
|
||||
import { BehaviorSubject, combineLatest as observableCombineLatest, Subscription } from 'rxjs';
|
||||
import { map, startWith } from 'rxjs/operators';
|
||||
import { isPlatformBrowser } from '@angular/common';
|
||||
import { Component, Inject, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core';
|
||||
@@ -10,6 +10,7 @@ import { SearchFilterConfig } from '../../../models/search-filter-config.model';
|
||||
import {
|
||||
FILTER_CONFIG,
|
||||
IN_PLACE_SEARCH,
|
||||
REFRESH_FILTER,
|
||||
SearchFilterService
|
||||
} from '../../../../../core/shared/search/search-filter.service';
|
||||
import { SearchService } from '../../../../../core/shared/search/search.service';
|
||||
@@ -86,8 +87,9 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple
|
||||
@Inject(IN_PLACE_SEARCH) public inPlaceSearch: boolean,
|
||||
@Inject(FILTER_CONFIG) public filterConfig: SearchFilterConfig,
|
||||
@Inject(PLATFORM_ID) private platformId: any,
|
||||
@Inject(REFRESH_FILTER) public refreshFilters: BehaviorSubject<boolean>,
|
||||
private route: RouteService) {
|
||||
super(searchService, filterService, rdbs, router, searchConfigService, inPlaceSearch, filterConfig);
|
||||
super(searchService, filterService, rdbs, router, searchConfigService, inPlaceSearch, filterConfig, refreshFilters);
|
||||
|
||||
}
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<h3>{{"search.filters.head" | translate}}</h3>
|
||||
<div *ngIf="(filters | async)?.hasSucceeded">
|
||||
<div *ngFor="let filter of (filters | async)?.payload; trackBy: trackUpdate">
|
||||
<ds-search-filter [filter]="filter" [inPlaceSearch]="inPlaceSearch"></ds-search-filter>
|
||||
<ds-search-filter [filter]="filter" [inPlaceSearch]="inPlaceSearch" [refreshFilters]="refreshFilters"></ds-search-filter>
|
||||
</div>
|
||||
</div>
|
||||
<a class="btn btn-primary" [routerLink]="[searchLink]" [queryParams]="clearParams | async" queryParamsHandling="merge" role="button"><i class="fas fa-undo"></i> {{"search.filters.reset" | translate}}</a>
|
||||
|
@@ -53,7 +53,7 @@ export class SearchFiltersComponent implements OnInit, OnDestroy {
|
||||
/**
|
||||
* Emits when the search filters values may be stale, and so they must be refreshed.
|
||||
*/
|
||||
@Input() refreshFilters: Observable<any>;
|
||||
@Input() refreshFilters: BehaviorSubject<boolean>;
|
||||
|
||||
/**
|
||||
* Link to the search page
|
||||
|
@@ -13,14 +13,13 @@
|
||||
[linkType]="linkType"
|
||||
[context]="context"
|
||||
[hidePaginationDetail]="hidePaginationDetail"
|
||||
(contentChange)="contentChange.emit($event)"
|
||||
(deselectObject)="deselectObject.emit($event)"
|
||||
(selectObject)="selectObject.emit($event)"
|
||||
>
|
||||
(selectObject)="selectObject.emit($event)">
|
||||
</ds-viewable-collection>
|
||||
</div>
|
||||
<ds-themed-loading *ngIf="isLoading()" message="{{'loading.search-results' | translate}}"></ds-themed-loading>
|
||||
<ds-error
|
||||
*ngIf="showError()"
|
||||
<ds-error *ngIf="showError()"
|
||||
message="{{errorMessageLabel() | translate}}"></ds-error>
|
||||
<div *ngIf="searchResults?.payload?.page.length == 0 || searchResults?.statusCode == 400">
|
||||
{{ 'search.results.no-results' | translate }}
|
||||
|
@@ -85,6 +85,11 @@ export class SearchResultsComponent {
|
||||
*/
|
||||
@Input() selectionConfig: SelectionConfig = null;
|
||||
|
||||
/**
|
||||
* Emit when one of the listed object has changed.
|
||||
*/
|
||||
@Output() contentChange = new EventEmitter<any>();
|
||||
|
||||
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
||||
|
||||
@Output() selectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
||||
|
@@ -21,7 +21,7 @@ import { ListableObject } from '../../object-collection/shared/listable-object.m
|
||||
templateUrl: '../../theme-support/themed.component.html',
|
||||
})
|
||||
export class ThemedSearchResultsComponent extends ThemedComponent<SearchResultsComponent> {
|
||||
protected inAndOutputNames: (keyof SearchResultsComponent & keyof this)[] = ['linkType', 'searchResults', 'searchConfig', 'sortConfig', 'viewMode', 'configuration', 'disableHeader', 'selectable', 'context', 'hidePaginationDetail', 'selectionConfig', 'deselectObject', 'selectObject'];
|
||||
protected inAndOutputNames: (keyof SearchResultsComponent & keyof this)[] = ['linkType', 'searchResults', 'searchConfig', 'sortConfig', 'viewMode', 'configuration', 'disableHeader', 'selectable', 'context', 'hidePaginationDetail', 'selectionConfig', 'contentChange', 'deselectObject', 'selectObject'];
|
||||
|
||||
@Input() linkType: CollectionElementLinkType;
|
||||
|
||||
@@ -45,6 +45,8 @@ export class ThemedSearchResultsComponent extends ThemedComponent<SearchResultsC
|
||||
|
||||
@Input() selectionConfig: SelectionConfig = null;
|
||||
|
||||
@Output() contentChange: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
||||
|
||||
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
||||
|
||||
@Output() selectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
||||
|
@@ -83,7 +83,7 @@ export class SearchSidebarComponent {
|
||||
/**
|
||||
* Emits when the search filters values may be stale, and so they must be refreshed.
|
||||
*/
|
||||
@Input() refreshFilters: Observable<any>;
|
||||
@Input() refreshFilters: BehaviorSubject<boolean>;
|
||||
|
||||
/**
|
||||
* Emits event when the user clicks a button to open or close the sidebar
|
||||
|
@@ -37,6 +37,7 @@
|
||||
[context]="(currentContext$ | async)"
|
||||
[selectable]="selectable"
|
||||
[selectionConfig]="selectionConfig"
|
||||
(contentChange)="onContentChange($event)"
|
||||
(deselectObject)="deselectObject.emit($event)"
|
||||
(selectObject)="selectObject.emit($event)"></ds-themed-search-results>
|
||||
</div>
|
||||
@@ -49,6 +50,7 @@
|
||||
[configuration]="(currentConfiguration$ | async)"
|
||||
[currentScope]="(currentScope$ | async)"
|
||||
[filters]="filtersRD$.asObservable()"
|
||||
[refreshFilters]="refreshFilters"
|
||||
[resultCount]="(resultsRD$ | async)?.payload?.totalElements"
|
||||
[searchOptions]="(searchOptions$ | async)"
|
||||
[sortOptionsList]="(sortOptionsList$ | async)"
|
||||
@@ -63,6 +65,7 @@
|
||||
[configuration]="(currentConfiguration$ | async)"
|
||||
[currentScope]="(currentScope$ | async)"
|
||||
[filters]="filtersRD$.asObservable()"
|
||||
[refreshFilters]="refreshFilters"
|
||||
[resultCount]="(resultsRD$ | async)?.payload.totalElements"
|
||||
[searchOptions]="(searchOptions$ | async)"
|
||||
[sortOptionsList]="(sortOptionsList$ | async)"
|
||||
|
@@ -201,6 +201,11 @@ export class SearchComponent implements OnInit {
|
||||
*/
|
||||
isXsOrSm$: Observable<boolean>;
|
||||
|
||||
/**
|
||||
* Emits when the search filters values may be stale, and so they must be refreshed.
|
||||
*/
|
||||
refreshFilters: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
|
||||
/**
|
||||
* Link to the search page
|
||||
*/
|
||||
@@ -339,6 +344,15 @@ export class SearchComponent implements OnInit {
|
||||
this.sidebarService.expand();
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit event to refresh filter content
|
||||
* @param $event
|
||||
*/
|
||||
public onContentChange($event: any) {
|
||||
this.retrieveFilters(this.lastSearchOptions);
|
||||
this.refreshFilters.next(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe from the subscription
|
||||
*/
|
||||
|
Reference in New Issue
Block a user