Merge pull request #952 from atmire/Filter-queries-correct-operators

Filter queries using correct operators
This commit is contained in:
Tim Donohue
2020-12-10 16:39:02 -06:00
committed by GitHub
11 changed files with 132 additions and 55 deletions

View File

@@ -1,9 +1,7 @@
import { Component, OnInit } from '@angular/core';
import { FilterType } from '../../../filter-type.model';
import { facetLoad, SearchFacetFilterComponent } from '../search-facet-filter/search-facet-filter.component';
import { renderFacetFor } from '../search-filter-type-decorator';
import { FacetValue } from '../../../facet-value.model';
@Component({
selector: 'ds-search-authority-filter',
@@ -17,20 +15,4 @@ import { FacetValue } from '../../../facet-value.model';
*/
@renderFacetFor(FilterType.authority)
export class SearchAuthorityFilterComponent extends SearchFacetFilterComponent implements OnInit {
/**
* TODO to review after https://github.com/DSpace/dspace-angular/issues/368 is resolved
* Retrieve facet value from search link
*/
protected getFacetValue(facet: FacetValue): string {
const search = facet._links.search.href;
const hashes = search.slice(search.indexOf('?') + 1).split('&');
const params = {};
hashes.map((hash) => {
const [key, val] = hash.split('=');
params[key] = decodeURIComponent(val)
});
return params[this.filterConfig.paramName];
}
}

View File

@@ -130,7 +130,7 @@ describe('SearchFacetOptionComponent', () => {
comp.addQueryParams = {};
(comp as any).updateAddParams(selectedValues);
expect(comp.addQueryParams).toEqual({
[mockFilterConfig.paramName]: [value1, value.value],
[mockFilterConfig.paramName]: [`${value1},${operator}`, value.value + ',equals'],
page: 1
});
});
@@ -145,7 +145,7 @@ describe('SearchFacetOptionComponent', () => {
comp.addQueryParams = {};
(comp as any).updateAddParams(selectedValues);
expect(comp.addQueryParams).toEqual({
[mockAuthorityFilterConfig.paramName]: [value1, `${value2},${operator}`],
[mockAuthorityFilterConfig.paramName]: [value1 + ',equals', `${value2},${operator}`],
page: 1
});
});

View File

@@ -8,8 +8,8 @@ import { SearchService } from '../../../../../../core/shared/search/search.servi
import { SearchFilterService } from '../../../../../../core/shared/search/search-filter.service';
import { SearchConfigurationService } from '../../../../../../core/shared/search/search-configuration.service';
import { hasValue } from '../../../../../empty.util';
import { FilterType } from '../../../../filter-type.model';
import { currentPath } from '../../../../../utils/route.utils';
import { getFacetValueForType } from '../../../../search.utils';
@Component({
selector: 'ds-search-facet-option',
@@ -102,7 +102,7 @@ export class SearchFacetOptionComponent implements OnInit, OnDestroy {
*/
private updateAddParams(selectedValues: FacetValue[]): void {
this.addQueryParams = {
[this.filterConfig.paramName]: [...selectedValues.map((facetValue: FacetValue) => facetValue.label), this.getFacetValue()],
[this.filterConfig.paramName]: [...selectedValues.map((facetValue: FacetValue) => getFacetValueForType(facetValue, this.filterConfig)), this.getFacetValue()],
page: 1
};
}
@@ -112,19 +112,7 @@ export class SearchFacetOptionComponent implements OnInit, OnDestroy {
* Retrieve facet value related to facet type
*/
private getFacetValue(): string {
if (this.filterConfig.type === FilterType.authority) {
const search = this.filterValue._links.search.href;
const hashes = search.slice(search.indexOf('?') + 1).split('&');
const params = {};
hashes.map((hash) => {
const [key, val] = hash.split('=');
params[key] = decodeURIComponent(val)
});
return params[this.filterConfig.paramName];
} else {
return this.filterValue.value;
}
return getFacetValueForType(this.filterValue, this.filterConfig);
}
/**

View File

@@ -3,6 +3,6 @@
[queryParams]="removeQueryParams" queryParamsHandling="merge">
<input type="checkbox" [checked]="true" class="my-1 align-self-stretch"/>
<span class="filter-value pl-1 text-capitalize">
{{ 'search.filters.' + filterConfig.name + '.' + selectedValue.value | translate: {default: selectedValue.value} }}
{{ 'search.filters.' + filterConfig.name + '.' + selectedValue.value | translate: {default: selectedValue.label} }}
</span>
</a>

View File

@@ -7,8 +7,8 @@ import { SearchFilterService } from '../../../../../../core/shared/search/search
import { hasValue } from '../../../../../empty.util';
import { SearchConfigurationService } from '../../../../../../core/shared/search/search-configuration.service';
import { FacetValue } from '../../../../facet-value.model';
import { FilterType } from '../../../../filter-type.model';
import { currentPath } from '../../../../../utils/route.utils';
import { getFacetValueForType } from '../../../../search.utils';
@Component({
selector: 'ds-search-facet-selected-option',
@@ -101,19 +101,7 @@ export class SearchFacetSelectedOptionComponent implements OnInit, OnDestroy {
* Retrieve facet value related to facet type
*/
private getFacetValue(facetValue: FacetValue): string {
if (this.filterConfig.type === FilterType.authority) {
const search = facetValue._links.search.href;
const hashes = search.slice(search.indexOf('?') + 1).split('&');
const params = {};
hashes.map((hash) => {
const [key, val] = hash.split('=');
params[key] = decodeURIComponent(val)
});
return params[this.filterConfig.paramName];
} else {
return facetValue.value;
}
return getFacetValueForType(facetValue, this.filterConfig);
}
/**

View File

@@ -208,7 +208,7 @@ describe('SearchFacetFilterComponent', () => {
it('should call navigate on the router with the right searchlink and parameters', () => {
expect(router.navigate).toHaveBeenCalledWith(searchUrl.split('/'), {
queryParams: { [mockFilterConfig.paramName]: [...selectedValues, testValue] },
queryParams: { [mockFilterConfig.paramName]: [...selectedValues.map((value) => `${value},equals`), testValue] },
queryParamsHandling: 'merge'
});
});

View File

@@ -25,6 +25,7 @@ import { InputSuggestion } from '../../../../input-suggestions/input-suggestions
import { SearchOptions } from '../../../search-options.model';
import { SEARCH_CONFIG_SERVICE } from '../../../../../+my-dspace-page/my-dspace-page.component';
import { currentPath } from '../../../../utils/route.utils';
import { getFacetValueForType, stripOperatorFromFilterValue } from '../../../search.utils';
@Component({
selector: 'ds-search-facet-filter',
@@ -148,7 +149,7 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
if (hasValue(fValue)) {
return fValue;
}
return Object.assign(new FacetValue(), { label: value, value: value });
return Object.assign(new FacetValue(), { label: stripOperatorFromFilterValue(value), value: value });
});
})
);
@@ -287,7 +288,7 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
return rd.payload.page.map((facet) => {
return {
displayValue: this.getDisplayValue(facet, data),
value: this.getFacetValue(facet)
value: stripOperatorFromFilterValue(this.getFacetValue(facet))
}
})
}
@@ -303,7 +304,7 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
* Retrieve facet value
*/
protected getFacetValue(facet: FacetValue): string {
return facet.value;
return getFacetValueForType(facet, this.filterConfig);
}
/**

View File

@@ -5,6 +5,9 @@ import {
SearchFacetFilterComponent
} from '../search-facet-filter/search-facet-filter.component';
import { renderFacetFor } from '../search-filter-type-decorator';
import {
addOperatorToFilterValue,
} from '../../../search.utils';
/**
* This component renders a simple item page.
@@ -24,4 +27,12 @@ import { renderFacetFor } from '../search-filter-type-decorator';
*/
@renderFacetFor(FilterType.text)
export class SearchTextFilterComponent extends SearchFacetFilterComponent implements OnInit {
/**
* Submits a new active custom value to the filter from the input field
* Overwritten method from parent component, adds the "query" operator to the received data before passing it on
* @param data The string from the input field
*/
onSubmit(data: any) {
super.onSubmit(addOperatorToFilterValue(data, 'query'));
}
}

View File

@@ -3,6 +3,8 @@ import { SEARCH_CONFIG_SERVICE } from '../../../+my-dspace-page/my-dspace-page.c
import { Observable } from 'rxjs';
import { Params, Router } from '@angular/router';
import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service';
import { map } from 'rxjs/operators';
import { stripOperatorFromFilterValue } from '../search.utils';
@Component({
selector: 'ds-search-labels',
@@ -30,6 +32,15 @@ export class SearchLabelsComponent {
constructor(
protected router: Router,
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService) {
this.appliedFilters = this.searchConfigService.getCurrentFrontendFilters();
this.appliedFilters = this.searchConfigService.getCurrentFrontendFilters().pipe(
map((params) => {
const labels = {};
Object.keys(params)
.forEach((key) => {
labels[key] = [...params[key].map((value) => stripOperatorFromFilterValue(value))];
});
return labels;
})
);
}
}

View File

@@ -0,0 +1,52 @@
import { FacetValue } from './facet-value.model';
import { SearchFilterConfig } from './search-filter-config.model';
import { addOperatorToFilterValue, getFacetValueForType, stripOperatorFromFilterValue } from './search.utils';
describe('Search Utils', () => {
describe('getFacetValueForType', () => {
let facetValueWithSearchHref: FacetValue;
let facetValueWithoutSearchHref: FacetValue;
let searchFilterConfig: SearchFilterConfig;
beforeEach(() => {
facetValueWithSearchHref = Object.assign(new FacetValue(), {
value: 'Value with search href',
_links: {
search: {
href: 'rest/api/search?f.otherFacet=Other facet value,operator&f.facetName=Value with search href,operator'
}
}
});
facetValueWithoutSearchHref = Object.assign(new FacetValue(), {
value: 'Value without search href'
});
searchFilterConfig = Object.assign(new SearchFilterConfig(), {
name: 'facetName'
});
});
it('should retrieve the correct value from the search href', () => {
expect(getFacetValueForType(facetValueWithSearchHref, searchFilterConfig)).toEqual('Value with search href,operator');
});
it('should return the facet value with an equals operator by default', () => {
expect(getFacetValueForType(facetValueWithoutSearchHref, searchFilterConfig)).toEqual('Value without search href,equals');
});
});
describe('stripOperatorFromFilterValue', () => {
it('should strip the operator from the value', () => {
expect(stripOperatorFromFilterValue('value,operator')).toEqual('value');
});
});
describe('addOperatorToFilterValue', () => {
it('should add the operator to the value', () => {
expect(addOperatorToFilterValue('value', 'operator')).toEqual('value,operator');
});
it('shouldn\'t add the operator to the value if it already contains the operator', () => {
expect(addOperatorToFilterValue('value,operator', 'operator')).toEqual('value,operator');
});
});
});

View File

@@ -0,0 +1,44 @@
import { FacetValue } from './facet-value.model';
import { SearchFilterConfig } from './search-filter-config.model';
import { isNotEmpty } from '../empty.util';
/**
* Get a facet's value by matching its parameter in the search href, this will include the operator of the facet value
* If the {@link FacetValue} doesn't contain a search link, its raw value will be returned as a fallback
* @param facetValue
* @param searchFilterConfig
*/
export function getFacetValueForType(facetValue: FacetValue, searchFilterConfig: SearchFilterConfig): string {
const regex = new RegExp(`[?|&]${searchFilterConfig.paramName}=(${facetValue.value}[^&]*)`, 'g');
if (isNotEmpty(facetValue._links)) {
const values = regex.exec(facetValue._links.search.href);
if (isNotEmpty(values)) {
return values[1];
}
}
return addOperatorToFilterValue(facetValue.value, 'equals');
}
/**
* Strip the operator from a filter value
* Warning: This expects the value to end with an operator, otherwise it might strip unwanted content
* @param value
*/
export function stripOperatorFromFilterValue(value: string) {
if (value.lastIndexOf(',') > -1) {
return value.substring(0, value.lastIndexOf(','));
}
return value;
}
/**
* Add an operator to a string
* @param value
* @param operator
*/
export function addOperatorToFilterValue(value: string, operator: string) {
if (!value.endsWith(`,${operator}`)) {
return `${value},${operator}`;
}
return value;
}