mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-08 10:34:15 +00:00
Merge branch 'w2p-54053_Active-filter-labels' into search-features
This commit is contained in:
@@ -0,0 +1,10 @@
|
|||||||
|
<div>
|
||||||
|
<div class="labels col-sm-9 offset-sm-3">
|
||||||
|
<a *ngFor="let label of (appliedFilters | async)" class="badge badge-primary mr-1"
|
||||||
|
[routerLink]="getSearchLink()"
|
||||||
|
[queryParams]="(getRemoveParams(label) | async)" queryParamsHandling="merge">
|
||||||
|
{{label.value}}
|
||||||
|
<span> ×</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -0,0 +1,66 @@
|
|||||||
|
import { SearchLabelsComponent } from './search-labels.component';
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { SearchService } from '../search-service/search.service';
|
||||||
|
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { SearchServiceStub } from '../../shared/testing/search-service-stub';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import { FilterLabel } from '../search-service/filter-label.model';
|
||||||
|
import { Params } from '@angular/router';
|
||||||
|
|
||||||
|
describe('SearchLabelsComponent', () => {
|
||||||
|
let comp: SearchLabelsComponent;
|
||||||
|
let fixture: ComponentFixture<SearchLabelsComponent>;
|
||||||
|
|
||||||
|
const searchLink = '/search';
|
||||||
|
let searchService;
|
||||||
|
|
||||||
|
const field1 = 'author';
|
||||||
|
const field2 = 'subject';
|
||||||
|
const value1 = 'TestAuthor';
|
||||||
|
const value2 = 'TestSubject';
|
||||||
|
const filter1 = new FilterLabel(value1, field1);
|
||||||
|
const filter2 = new FilterLabel(value2, field2);
|
||||||
|
const mockFilters = [
|
||||||
|
filter1,
|
||||||
|
filter2
|
||||||
|
];
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot(), NoopAnimationsModule, FormsModule],
|
||||||
|
declarations: [SearchLabelsComponent],
|
||||||
|
providers: [
|
||||||
|
{ provide: SearchService, useValue: new SearchServiceStub(searchLink) }
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).overrideComponent(SearchLabelsComponent, {
|
||||||
|
set: { changeDetection: ChangeDetectionStrategy.Default }
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(SearchLabelsComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
searchService = (comp as any).searchService;
|
||||||
|
(comp as any).appliedFilters = Observable.of(mockFilters);
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when getRemoveParams is called', () => {
|
||||||
|
let obs: Observable<Params>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
obs = comp.getRemoveParams(filter1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return all params but the provided filter', () => {
|
||||||
|
obs.subscribe((params) => {
|
||||||
|
// Should contain only filter2 and page: length == 2
|
||||||
|
expect(Object.keys(params).length).toBe(2);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,40 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { SearchService } from '../search-service/search.service';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import { Params } from '@angular/router';
|
||||||
|
import { FilterLabel } from '../search-service/filter-label.model';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-search-labels',
|
||||||
|
templateUrl: './search-labels.component.html',
|
||||||
|
})
|
||||||
|
|
||||||
|
export class SearchLabelsComponent {
|
||||||
|
appliedFilters: Observable<FilterLabel[]>;
|
||||||
|
|
||||||
|
constructor(private searchService: SearchService) {
|
||||||
|
this.appliedFilters = this.searchService.getFilterLabels();
|
||||||
|
}
|
||||||
|
|
||||||
|
getRemoveParams(filterLabel: FilterLabel): Observable<Params> {
|
||||||
|
return this.appliedFilters.pipe(
|
||||||
|
map((filters) => {
|
||||||
|
const values = [];
|
||||||
|
filters.forEach((filter) => {
|
||||||
|
if (filter.field === filterLabel.field && filter.value !== filterLabel.value) {
|
||||||
|
values.push(filter.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
[filterLabel.field]: values,
|
||||||
|
page: 1
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getSearchLink() {
|
||||||
|
return this.searchService.getSearchLink();
|
||||||
|
}
|
||||||
|
}
|
@@ -10,6 +10,7 @@
|
|||||||
[currentUrl]="getSearchLink()"
|
[currentUrl]="getSearchLink()"
|
||||||
[scopes]="(scopeListRD$ | async)?.payload?.page">
|
[scopes]="(scopeListRD$ | async)?.payload?.page">
|
||||||
</ds-search-form>
|
</ds-search-form>
|
||||||
|
<ds-search-labels></ds-search-labels>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div id="search-body"
|
<div id="search-body"
|
||||||
class="row-offcanvas row-offcanvas-left"
|
class="row-offcanvas row-offcanvas-left"
|
||||||
|
@@ -21,6 +21,7 @@ import { SearchFiltersComponent } from './search-filters/search-filters.componen
|
|||||||
import { SearchFilterComponent } from './search-filters/search-filter/search-filter.component';
|
import { SearchFilterComponent } from './search-filters/search-filter/search-filter.component';
|
||||||
import { SearchFacetFilterComponent } from './search-filters/search-filter/search-facet-filter/search-facet-filter.component';
|
import { SearchFacetFilterComponent } from './search-filters/search-filter/search-facet-filter/search-facet-filter.component';
|
||||||
import { SearchFilterService } from './search-filters/search-filter/search-filter.service';
|
import { SearchFilterService } from './search-filters/search-filter/search-filter.service';
|
||||||
|
import { SearchLabelsComponent } from './search-labels/search-labels.component';
|
||||||
|
|
||||||
const effects = [
|
const effects = [
|
||||||
SearchSidebarEffects
|
SearchSidebarEffects
|
||||||
@@ -48,7 +49,8 @@ const effects = [
|
|||||||
CommunitySearchResultListElementComponent,
|
CommunitySearchResultListElementComponent,
|
||||||
SearchFiltersComponent,
|
SearchFiltersComponent,
|
||||||
SearchFilterComponent,
|
SearchFilterComponent,
|
||||||
SearchFacetFilterComponent
|
SearchFacetFilterComponent,
|
||||||
|
SearchLabelsComponent
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
SearchService,
|
SearchService,
|
||||||
|
@@ -0,0 +1,8 @@
|
|||||||
|
export class FilterLabel {
|
||||||
|
value: string;
|
||||||
|
field: string;
|
||||||
|
constructor(value: string, field: string) {
|
||||||
|
this.value = value;
|
||||||
|
this.field = field;
|
||||||
|
}
|
||||||
|
}
|
@@ -32,6 +32,8 @@ import {
|
|||||||
} from '../../core/cache/response-cache.models';
|
} from '../../core/cache/response-cache.models';
|
||||||
import { SearchQueryResponse } from './search-query-response.model';
|
import { SearchQueryResponse } from './search-query-response.model';
|
||||||
import { SearchFilterConfig } from './search-filter-config.model';
|
import { SearchFilterConfig } from './search-filter-config.model';
|
||||||
|
import { FilterLabel } from './filter-label.model';
|
||||||
|
import createSpyObj = jasmine.createSpyObj;
|
||||||
|
|
||||||
@Component({ template: '' })
|
@Component({ template: '' })
|
||||||
class DummyComponent {
|
class DummyComponent {
|
||||||
@@ -245,5 +247,36 @@ describe('SearchService', () => {
|
|||||||
expect((searchService as any).responseCache.get).toHaveBeenCalledWith(requestUrl);
|
expect((searchService as any).responseCache.get).toHaveBeenCalledWith(requestUrl);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('when getFilterLabels is called', () => {
|
||||||
|
let obs: Observable<FilterLabel[]>;
|
||||||
|
const value = 'Test';
|
||||||
|
const orgField = 'author';
|
||||||
|
const field = 'f.' + orgField;
|
||||||
|
const mockConfig = new RemoteData(false, false, true, null, [
|
||||||
|
{
|
||||||
|
name: orgField,
|
||||||
|
type: null,
|
||||||
|
hasFacets: false,
|
||||||
|
pageSize: 5,
|
||||||
|
isOpenByDefault: false,
|
||||||
|
paramName: field
|
||||||
|
} as SearchFilterConfig
|
||||||
|
]);
|
||||||
|
const mockParams = [];
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn((searchService as any), 'getConfig').and.returnValue(Observable.of(mockConfig));
|
||||||
|
mockParams[field] = value;
|
||||||
|
(searchService as any).route.queryParams = Observable.of(mockParams);
|
||||||
|
obs = searchService.getFilterLabels();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the correct labels', () => {
|
||||||
|
obs.subscribe((filters) => {
|
||||||
|
expect(filters[0].value).toEqual(value);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { Injectable, OnDestroy } from '@angular/core';
|
import { Injectable, OnDestroy } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ActivatedRoute, NavigationExtras, PRIMARY_OUTLET, Router,
|
ActivatedRoute, NavigationExtras, Params, PRIMARY_OUTLET, Router,
|
||||||
UrlSegmentGroup
|
UrlSegmentGroup
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
@@ -40,7 +40,8 @@ import { ListableObject } from '../../shared/object-collection/shared/listable-o
|
|||||||
import { FacetValueResponseParsingService } from '../../core/data/facet-value-response-parsing.service';
|
import { FacetValueResponseParsingService } from '../../core/data/facet-value-response-parsing.service';
|
||||||
import { FacetConfigResponseParsingService } from '../../core/data/facet-config-response-parsing.service';
|
import { FacetConfigResponseParsingService } from '../../core/data/facet-config-response-parsing.service';
|
||||||
import { PaginatedSearchOptions } from '../paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../paginated-search-options.model';
|
||||||
import { observable } from 'rxjs/symbol/observable';
|
import { FilterLabel } from './filter-label.model';
|
||||||
|
import { combineLatest } from 'rxjs/observable/combineLatest';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SearchService implements OnDestroy {
|
export class SearchService implements OnDestroy {
|
||||||
@@ -223,6 +224,27 @@ export class SearchService implements OnDestroy {
|
|||||||
return this.rdb.toRemoteDataObservable(requestEntryObs, responseCacheObs, payloadObs);
|
return this.rdb.toRemoteDataObservable(requestEntryObs, responseCacheObs, payloadObs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getFilterLabels(): Observable<FilterLabel[]> {
|
||||||
|
return combineLatest(this.getConfig(), this.route.queryParams).pipe(
|
||||||
|
map(([rd, params]) => {
|
||||||
|
const filterLabels: FilterLabel[] = [];
|
||||||
|
rd.payload.forEach((config: SearchFilterConfig) => {
|
||||||
|
const param = params[config.paramName];
|
||||||
|
if (param !== undefined) {
|
||||||
|
if (param instanceof Array && param.length > 1) {
|
||||||
|
param.forEach((p: string) => {
|
||||||
|
filterLabels.push(new FilterLabel(p, config.paramName))
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
filterLabels.push(new FilterLabel(param, config.paramName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return filterLabels.filter((n) => n !== undefined && n.value.length > 0);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
getViewMode(): Observable<ViewMode> {
|
getViewMode(): Observable<ViewMode> {
|
||||||
return this.route.queryParams.map((params) => {
|
return this.route.queryParams.map((params) => {
|
||||||
if (isNotEmpty(params.view) && hasValue(params.view)) {
|
if (isNotEmpty(params.view) && hasValue(params.view)) {
|
||||||
|
@@ -53,6 +53,7 @@ import { DebounceDirective } from './utils/debounce.directive';
|
|||||||
import { ClickOutsideDirective } from './utils/click-outside.directive';
|
import { ClickOutsideDirective } from './utils/click-outside.directive';
|
||||||
import { EmphasizePipe } from './utils/emphasize.pipe';
|
import { EmphasizePipe } from './utils/emphasize.pipe';
|
||||||
import { InputSuggestionsComponent } from './input-suggestions/input-suggestions.component';
|
import { InputSuggestionsComponent } from './input-suggestions/input-suggestions.component';
|
||||||
|
import { CapitalizePipe } from './utils/capitalize.pipe';
|
||||||
|
|
||||||
const MODULES = [
|
const MODULES = [
|
||||||
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
||||||
@@ -71,7 +72,8 @@ const PIPES = [
|
|||||||
FileSizePipe,
|
FileSizePipe,
|
||||||
SafeUrlPipe,
|
SafeUrlPipe,
|
||||||
TruncatePipe,
|
TruncatePipe,
|
||||||
EmphasizePipe
|
EmphasizePipe,
|
||||||
|
CapitalizePipe
|
||||||
];
|
];
|
||||||
|
|
||||||
const COMPONENTS = [
|
const COMPONENTS = [
|
||||||
|
@@ -37,4 +37,8 @@ export class SearchServiceStub {
|
|||||||
getSearchLink() {
|
getSearchLink() {
|
||||||
return this.searchLink;
|
return this.searchLink;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getFilterLabels() {
|
||||||
|
return Observable.of([]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
18
src/app/shared/utils/capitalize.pipe.ts
Normal file
18
src/app/shared/utils/capitalize.pipe.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { Pipe, PipeTransform } from '@angular/core'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pipe to truncate a value in Angular. (Take a substring, starting at 0)
|
||||||
|
* Default value: 10
|
||||||
|
*/
|
||||||
|
@Pipe({
|
||||||
|
name: 'dsCapitalize'
|
||||||
|
})
|
||||||
|
export class CapitalizePipe implements PipeTransform {
|
||||||
|
transform(value: string, args: string[]): string {
|
||||||
|
if (value) {
|
||||||
|
return value.charAt(0).toUpperCase() + value.slice(1);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user