mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 18:14:17 +00:00
Merge pull request #952 from atmire/Filter-queries-correct-operators
Filter queries using correct operators
This commit is contained in:
@@ -1,9 +1,7 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
import { FilterType } from '../../../filter-type.model';
|
import { FilterType } from '../../../filter-type.model';
|
||||||
import { facetLoad, SearchFacetFilterComponent } from '../search-facet-filter/search-facet-filter.component';
|
import { facetLoad, SearchFacetFilterComponent } from '../search-facet-filter/search-facet-filter.component';
|
||||||
import { renderFacetFor } from '../search-filter-type-decorator';
|
import { renderFacetFor } from '../search-filter-type-decorator';
|
||||||
import { FacetValue } from '../../../facet-value.model';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-search-authority-filter',
|
selector: 'ds-search-authority-filter',
|
||||||
@@ -17,20 +15,4 @@ import { FacetValue } from '../../../facet-value.model';
|
|||||||
*/
|
*/
|
||||||
@renderFacetFor(FilterType.authority)
|
@renderFacetFor(FilterType.authority)
|
||||||
export class SearchAuthorityFilterComponent extends SearchFacetFilterComponent implements OnInit {
|
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];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -130,7 +130,7 @@ describe('SearchFacetOptionComponent', () => {
|
|||||||
comp.addQueryParams = {};
|
comp.addQueryParams = {};
|
||||||
(comp as any).updateAddParams(selectedValues);
|
(comp as any).updateAddParams(selectedValues);
|
||||||
expect(comp.addQueryParams).toEqual({
|
expect(comp.addQueryParams).toEqual({
|
||||||
[mockFilterConfig.paramName]: [value1, value.value],
|
[mockFilterConfig.paramName]: [`${value1},${operator}`, value.value + ',equals'],
|
||||||
page: 1
|
page: 1
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -145,7 +145,7 @@ describe('SearchFacetOptionComponent', () => {
|
|||||||
comp.addQueryParams = {};
|
comp.addQueryParams = {};
|
||||||
(comp as any).updateAddParams(selectedValues);
|
(comp as any).updateAddParams(selectedValues);
|
||||||
expect(comp.addQueryParams).toEqual({
|
expect(comp.addQueryParams).toEqual({
|
||||||
[mockAuthorityFilterConfig.paramName]: [value1, `${value2},${operator}`],
|
[mockAuthorityFilterConfig.paramName]: [value1 + ',equals', `${value2},${operator}`],
|
||||||
page: 1
|
page: 1
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -8,8 +8,8 @@ import { SearchService } from '../../../../../../core/shared/search/search.servi
|
|||||||
import { SearchFilterService } from '../../../../../../core/shared/search/search-filter.service';
|
import { SearchFilterService } from '../../../../../../core/shared/search/search-filter.service';
|
||||||
import { SearchConfigurationService } from '../../../../../../core/shared/search/search-configuration.service';
|
import { SearchConfigurationService } from '../../../../../../core/shared/search/search-configuration.service';
|
||||||
import { hasValue } from '../../../../../empty.util';
|
import { hasValue } from '../../../../../empty.util';
|
||||||
import { FilterType } from '../../../../filter-type.model';
|
|
||||||
import { currentPath } from '../../../../../utils/route.utils';
|
import { currentPath } from '../../../../../utils/route.utils';
|
||||||
|
import { getFacetValueForType } from '../../../../search.utils';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-search-facet-option',
|
selector: 'ds-search-facet-option',
|
||||||
@@ -102,7 +102,7 @@ export class SearchFacetOptionComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
private updateAddParams(selectedValues: FacetValue[]): void {
|
private updateAddParams(selectedValues: FacetValue[]): void {
|
||||||
this.addQueryParams = {
|
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
|
page: 1
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -112,19 +112,7 @@ export class SearchFacetOptionComponent implements OnInit, OnDestroy {
|
|||||||
* Retrieve facet value related to facet type
|
* Retrieve facet value related to facet type
|
||||||
*/
|
*/
|
||||||
private getFacetValue(): string {
|
private getFacetValue(): string {
|
||||||
if (this.filterConfig.type === FilterType.authority) {
|
return getFacetValueForType(this.filterValue, this.filterConfig);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -3,6 +3,6 @@
|
|||||||
[queryParams]="removeQueryParams" queryParamsHandling="merge">
|
[queryParams]="removeQueryParams" queryParamsHandling="merge">
|
||||||
<input type="checkbox" [checked]="true" class="my-1 align-self-stretch"/>
|
<input type="checkbox" [checked]="true" class="my-1 align-self-stretch"/>
|
||||||
<span class="filter-value pl-1 text-capitalize">
|
<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>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
|
@@ -7,8 +7,8 @@ import { SearchFilterService } from '../../../../../../core/shared/search/search
|
|||||||
import { hasValue } from '../../../../../empty.util';
|
import { hasValue } from '../../../../../empty.util';
|
||||||
import { SearchConfigurationService } from '../../../../../../core/shared/search/search-configuration.service';
|
import { SearchConfigurationService } from '../../../../../../core/shared/search/search-configuration.service';
|
||||||
import { FacetValue } from '../../../../facet-value.model';
|
import { FacetValue } from '../../../../facet-value.model';
|
||||||
import { FilterType } from '../../../../filter-type.model';
|
|
||||||
import { currentPath } from '../../../../../utils/route.utils';
|
import { currentPath } from '../../../../../utils/route.utils';
|
||||||
|
import { getFacetValueForType } from '../../../../search.utils';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-search-facet-selected-option',
|
selector: 'ds-search-facet-selected-option',
|
||||||
@@ -101,19 +101,7 @@ export class SearchFacetSelectedOptionComponent implements OnInit, OnDestroy {
|
|||||||
* Retrieve facet value related to facet type
|
* Retrieve facet value related to facet type
|
||||||
*/
|
*/
|
||||||
private getFacetValue(facetValue: FacetValue): string {
|
private getFacetValue(facetValue: FacetValue): string {
|
||||||
if (this.filterConfig.type === FilterType.authority) {
|
return getFacetValueForType(facetValue, this.filterConfig);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -208,7 +208,7 @@ describe('SearchFacetFilterComponent', () => {
|
|||||||
|
|
||||||
it('should call navigate on the router with the right searchlink and parameters', () => {
|
it('should call navigate on the router with the right searchlink and parameters', () => {
|
||||||
expect(router.navigate).toHaveBeenCalledWith(searchUrl.split('/'), {
|
expect(router.navigate).toHaveBeenCalledWith(searchUrl.split('/'), {
|
||||||
queryParams: { [mockFilterConfig.paramName]: [...selectedValues, testValue] },
|
queryParams: { [mockFilterConfig.paramName]: [...selectedValues.map((value) => `${value},equals`), testValue] },
|
||||||
queryParamsHandling: 'merge'
|
queryParamsHandling: 'merge'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -25,6 +25,7 @@ import { InputSuggestion } from '../../../../input-suggestions/input-suggestions
|
|||||||
import { SearchOptions } from '../../../search-options.model';
|
import { SearchOptions } from '../../../search-options.model';
|
||||||
import { SEARCH_CONFIG_SERVICE } from '../../../../../+my-dspace-page/my-dspace-page.component';
|
import { SEARCH_CONFIG_SERVICE } from '../../../../../+my-dspace-page/my-dspace-page.component';
|
||||||
import { currentPath } from '../../../../utils/route.utils';
|
import { currentPath } from '../../../../utils/route.utils';
|
||||||
|
import { getFacetValueForType, stripOperatorFromFilterValue } from '../../../search.utils';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-search-facet-filter',
|
selector: 'ds-search-facet-filter',
|
||||||
@@ -148,7 +149,7 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
|
|||||||
if (hasValue(fValue)) {
|
if (hasValue(fValue)) {
|
||||||
return 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 rd.payload.page.map((facet) => {
|
||||||
return {
|
return {
|
||||||
displayValue: this.getDisplayValue(facet, data),
|
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
|
* Retrieve facet value
|
||||||
*/
|
*/
|
||||||
protected getFacetValue(facet: FacetValue): string {
|
protected getFacetValue(facet: FacetValue): string {
|
||||||
return facet.value;
|
return getFacetValueForType(facet, this.filterConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -5,6 +5,9 @@ import {
|
|||||||
SearchFacetFilterComponent
|
SearchFacetFilterComponent
|
||||||
} from '../search-facet-filter/search-facet-filter.component';
|
} from '../search-facet-filter/search-facet-filter.component';
|
||||||
import { renderFacetFor } from '../search-filter-type-decorator';
|
import { renderFacetFor } from '../search-filter-type-decorator';
|
||||||
|
import {
|
||||||
|
addOperatorToFilterValue,
|
||||||
|
} from '../../../search.utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component renders a simple item page.
|
* This component renders a simple item page.
|
||||||
@@ -24,4 +27,12 @@ import { renderFacetFor } from '../search-filter-type-decorator';
|
|||||||
*/
|
*/
|
||||||
@renderFacetFor(FilterType.text)
|
@renderFacetFor(FilterType.text)
|
||||||
export class SearchTextFilterComponent extends SearchFacetFilterComponent implements OnInit {
|
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'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,8 @@ import { SEARCH_CONFIG_SERVICE } from '../../../+my-dspace-page/my-dspace-page.c
|
|||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { Params, Router } from '@angular/router';
|
import { Params, Router } from '@angular/router';
|
||||||
import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service';
|
import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { stripOperatorFromFilterValue } from '../search.utils';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-search-labels',
|
selector: 'ds-search-labels',
|
||||||
@@ -30,6 +32,15 @@ export class SearchLabelsComponent {
|
|||||||
constructor(
|
constructor(
|
||||||
protected router: Router,
|
protected router: Router,
|
||||||
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService) {
|
@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;
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
52
src/app/shared/search/search.utils.spec.ts
Normal file
52
src/app/shared/search/search.utils.spec.ts
Normal 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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
44
src/app/shared/search/search.utils.ts
Normal file
44
src/app/shared/search/search.utils.ts
Normal 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;
|
||||||
|
}
|
Reference in New Issue
Block a user