Merge pull request #1138 from atmire/w2p-78991_Issue-1112_Fix-date-filter-bugs

Fix date filter bugs
This commit is contained in:
Tim Donohue
2021-05-04 14:19:04 -05:00
committed by GitHub
10 changed files with 86 additions and 30 deletions

View File

@@ -27,7 +27,10 @@ describe('MyDSpaceConfigurationService', () => {
scope: '' scope: ''
}); });
const backendFilters = [new SearchFilter('f.namedresourcetype', ['another value']), new SearchFilter('f.dateSubmitted', ['[2013 TO 2018]'])]; const backendFilters = [
new SearchFilter('f.namedresourcetype', ['another value']),
new SearchFilter('f.dateSubmitted', ['[2013 TO 2018]'], 'equals')
];
const spy = jasmine.createSpyObj('RouteService', { const spy = jasmine.createSpyObj('RouteService', {
getQueryParameterValue: observableOf(value1), getQueryParameterValue: observableOf(value1),

View File

@@ -23,7 +23,10 @@ describe('SearchConfigurationService', () => {
scope: '' scope: ''
}); });
const backendFilters = [new SearchFilter('f.author', ['another value']), new SearchFilter('f.date', ['[2013 TO 2018]'])]; const backendFilters = [
new SearchFilter('f.author', ['another value']),
new SearchFilter('f.date', ['[2013 TO 2018]'], 'equals')
];
const routeService = jasmine.createSpyObj('RouteService', { const routeService = jasmine.createSpyObj('RouteService', {
getQueryParameterValue: observableOf(value1), getQueryParameterValue: observableOf(value1),

View File

@@ -168,7 +168,7 @@ export class SearchConfigurationService implements OnDestroy {
if (hasNoValue(filters.find((f) => f.key === realKey))) { if (hasNoValue(filters.find((f) => f.key === realKey))) {
const min = filterParams[realKey + '.min'] ? filterParams[realKey + '.min'][0] : '*'; const min = filterParams[realKey + '.min'] ? filterParams[realKey + '.min'][0] : '*';
const max = filterParams[realKey + '.max'] ? filterParams[realKey + '.max'][0] : '*'; const max = filterParams[realKey + '.max'] ? filterParams[realKey + '.max'][0] : '*';
filters.push(new SearchFilter(realKey, ['[' + min + ' TO ' + max + ']'])); filters.push(new SearchFilter(realKey, ['[' + min + ' TO ' + max + ']'], 'equals'));
} }
} else { } else {
filters.push(new SearchFilter(key, filterParams[key])); filters.push(new SearchFilter(key, filterParams[key]));

View File

@@ -8,7 +8,12 @@ describe('PaginatedSearchOptions', () => {
let options: PaginatedSearchOptions; let options: PaginatedSearchOptions;
const sortOptions = new SortOptions('test.field', SortDirection.DESC); const sortOptions = new SortOptions('test.field', SortDirection.DESC);
const pageOptions = Object.assign(new PaginationComponentOptions(), { pageSize: 40, page: 1 }); const pageOptions = Object.assign(new PaginationComponentOptions(), { pageSize: 40, page: 1 });
const filters = [new SearchFilter('f.test', ['value']), new SearchFilter('f.example', ['another value', 'second value'])]; const filters = [
new SearchFilter('f.test', ['value']),
new SearchFilter('f.example', ['another value', 'second value']), // should be split into two arguments, spaces should be URI-encoded
new SearchFilter('f.range', ['[2002 TO 2021]'], 'equals'), // value should be URI-encoded, ',equals' should not
];
const fixedFilter = 'f.fixed=1234,5678,equals'; // '=' and ',equals' should not be URI-encoded
const query = 'search query'; const query = 'search query';
const scope = '0fde1ecb-82cc-425a-b600-ac3576d76b47'; const scope = '0fde1ecb-82cc-425a-b600-ac3576d76b47';
const baseUrl = 'www.rest.com'; const baseUrl = 'www.rest.com';
@@ -19,7 +24,8 @@ describe('PaginatedSearchOptions', () => {
filters: filters, filters: filters,
query: query, query: query,
scope: scope, scope: scope,
dsoTypes: [DSpaceObjectType.ITEM] dsoTypes: [DSpaceObjectType.ITEM],
fixedFilter: fixedFilter,
}); });
}); });
@@ -31,12 +37,14 @@ describe('PaginatedSearchOptions', () => {
'sort=test.field,DESC&' + 'sort=test.field,DESC&' +
'page=0&' + 'page=0&' +
'size=40&' + 'size=40&' +
'query=search query&' + 'f.fixed=1234%2C5678,equals&' +
'query=search%20query&' +
'scope=0fde1ecb-82cc-425a-b600-ac3576d76b47&' + 'scope=0fde1ecb-82cc-425a-b600-ac3576d76b47&' +
'dsoType=ITEM&' + 'dsoType=ITEM&' +
'f.test=value&' + 'f.test=value&' +
'f.example=another value&' + 'f.example=another%20value&' +
'f.example=second value' 'f.example=second%20value&' +
'f.range=%5B2002%20TO%202021%5D,equals'
); );
}); });

View File

@@ -35,7 +35,7 @@ export class SearchFilterComponent implements OnInit {
/** /**
* True when the filter is 100% collapsed in the UI * True when the filter is 100% collapsed in the UI
*/ */
closed = true; closed: boolean;
/** /**
* Emits true when the filter is currently collapsed in the store * Emits true when the filter is currently collapsed in the store

View File

@@ -8,18 +8,16 @@
::ng-deep ::ng-deep
{ {
--ds-slider-handle-width: 18px;
html:not([dir=rtl]) .noUi-horizontal .noUi-handle { html:not([dir=rtl]) .noUi-horizontal .noUi-handle {
right: calc(var(--ds-slider-handle-width) / -2); right: calc(var(--ds-slider-handle-width) / -2);
} }
.noUi-horizontal .noUi-handle { .noUi-horizontal .noUi-handle {
width: var(--ds-slider-handle-width); width: var(--ds-slider-handle-width);
&:before { &:before {
left: calc(calc(calc(var(--ds-slider-handle-width) - 2) / 2) - 2); left: calc(((var(--ds-slider-handle-width) - 2px) / 2) - 2px);
} }
&:after { &:after {
left: calc(calc(calc(var(--ds-slider-handle-width) - 2) / 2) + 2); left: calc(((var(--ds-slider-handle-width) - 2px) / 2) + 2px);
} }
&:focus { &:focus {
outline: none; outline: none;

View File

@@ -56,7 +56,7 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple
/** /**
* Fallback maximum for the range * Fallback maximum for the range
*/ */
max = 2018; max = new Date().getFullYear();
/** /**
* The current range of the filter * The current range of the filter

View File

@@ -4,13 +4,25 @@ import { SearchFilter } from './search-filter.model';
import { DSpaceObjectType } from '../../core/shared/dspace-object-type.model'; import { DSpaceObjectType } from '../../core/shared/dspace-object-type.model';
describe('SearchOptions', () => { describe('SearchOptions', () => {
let options: PaginatedSearchOptions; let options: SearchOptions;
const filters = [new SearchFilter('f.test', ['value']), new SearchFilter('f.example', ['another value', 'second value'])];
const filters = [
new SearchFilter('f.test', ['value']),
new SearchFilter('f.example', ['another value', 'second value']), // should be split into two arguments, spaces should be URI-encoded
new SearchFilter('f.range', ['[2002 TO 2021]'], 'equals'), // value should be URI-encoded, ',equals' should not
];
const fixedFilter = 'f.fixed=1234,5678,equals'; // '=' and ',equals' should not be URI-encoded
const query = 'search query'; const query = 'search query';
const scope = '0fde1ecb-82cc-425a-b600-ac3576d76b47'; const scope = '0fde1ecb-82cc-425a-b600-ac3576d76b47';
const baseUrl = 'www.rest.com'; const baseUrl = 'www.rest.com';
beforeEach(() => { beforeEach(() => {
options = new SearchOptions({ filters: filters, query: query, scope: scope, dsoTypes: [DSpaceObjectType.ITEM] }); options = new SearchOptions({
filters: filters,
query: query,
scope: scope,
dsoTypes: [DSpaceObjectType.ITEM],
fixedFilter: fixedFilter,
});
}); });
describe('when toRestUrl is called', () => { describe('when toRestUrl is called', () => {
@@ -18,12 +30,14 @@ describe('SearchOptions', () => {
it('should generate a string with all parameters that are present', () => { it('should generate a string with all parameters that are present', () => {
const outcome = options.toRestUrl(baseUrl); const outcome = options.toRestUrl(baseUrl);
expect(outcome).toEqual('www.rest.com?' + expect(outcome).toEqual('www.rest.com?' +
'query=search query&' + 'f.fixed=1234%2C5678,equals&' +
'query=search%20query&' +
'scope=0fde1ecb-82cc-425a-b600-ac3576d76b47&' + 'scope=0fde1ecb-82cc-425a-b600-ac3576d76b47&' +
'dsoType=ITEM&' + 'dsoType=ITEM&' +
'f.test=value&' + 'f.test=value&' +
'f.example=another value&' + 'f.example=another%20value&' +
'f.example=second value' 'f.example=second%20value&' +
'f.range=%5B2002%20TO%202021%5D,equals'
); );
}); });

View File

@@ -1,4 +1,4 @@
import { isNotEmpty } from '../empty.util'; import { hasValue, isNotEmpty } from '../empty.util';
import { URLCombiner } from '../../core/url-combiner/url-combiner'; import { URLCombiner } from '../../core/url-combiner/url-combiner';
import { SearchFilter } from './search-filter.model'; import { SearchFilter } from './search-filter.model';
import { DSpaceObjectType } from '../../core/shared/dspace-object-type.model'; import { DSpaceObjectType } from '../../core/shared/dspace-object-type.model';
@@ -13,10 +13,15 @@ export class SearchOptions {
scope?: string; scope?: string;
query?: string; query?: string;
dsoTypes?: DSpaceObjectType[]; dsoTypes?: DSpaceObjectType[];
filters?: any; filters?: SearchFilter[];
fixedFilter?: any; fixedFilter?: string;
constructor(options: {configuration?: string, scope?: string, query?: string, dsoTypes?: DSpaceObjectType[], filters?: SearchFilter[], fixedFilter?: any}) { constructor(
options: {
configuration?: string, scope?: string, query?: string, dsoTypes?: DSpaceObjectType[], filters?: SearchFilter[],
fixedFilter?: string
}
) {
this.configuration = options.configuration; this.configuration = options.configuration;
this.scope = options.scope; this.scope = options.scope;
this.query = options.query; this.query = options.query;
@@ -33,27 +38,27 @@ export class SearchOptions {
*/ */
toRestUrl(url: string, args: string[] = []): string { toRestUrl(url: string, args: string[] = []): string {
if (isNotEmpty(this.configuration)) { if (isNotEmpty(this.configuration)) {
args.push(`configuration=${this.configuration}`); args.push(`configuration=${encodeURIComponent(this.configuration)}`);
} }
if (isNotEmpty(this.fixedFilter)) { if (isNotEmpty(this.fixedFilter)) {
args.push(this.fixedFilter); args.push(this.encodedFixedFilter);
} }
if (isNotEmpty(this.query)) { if (isNotEmpty(this.query)) {
args.push(`query=${this.query}`); args.push(`query=${encodeURIComponent(this.query)}`);
} }
if (isNotEmpty(this.scope)) { if (isNotEmpty(this.scope)) {
args.push(`scope=${this.scope}`); args.push(`scope=${encodeURIComponent(this.scope)}`);
} }
if (isNotEmpty(this.dsoTypes)) { if (isNotEmpty(this.dsoTypes)) {
this.dsoTypes.forEach((dsoType: string) => { this.dsoTypes.forEach((dsoType: string) => {
args.push(`dsoType=${dsoType}`); args.push(`dsoType=${encodeURIComponent(dsoType)}`);
}); });
} }
if (isNotEmpty(this.filters)) { if (isNotEmpty(this.filters)) {
this.filters.forEach((filter: SearchFilter) => { this.filters.forEach((filter: SearchFilter) => {
filter.values.forEach((value) => { filter.values.forEach((value) => {
const filterValue = value.includes(',') ? `${value}` : value + (filter.operator ? ',' + filter.operator : ''); const filterValue = value.includes(',') ? `${value}` : value + (filter.operator ? ',' + filter.operator : '');
args.push(`${filter.key}=${filterValue}`); args.push(`${filter.key}=${this.encodeFilterQueryValue(filterValue)}`);
}); });
}); });
} }
@@ -62,4 +67,28 @@ export class SearchOptions {
} }
return url; return url;
} }
get encodedFixedFilter(): string {
// expected format: 'arg=value'
// -> split the query agument into (arg=)(value) and only encode 'value'
const match = this.fixedFilter.match(/^([^=]+=)(.+)$/);
if (hasValue(match)) {
return match[1] + this.encodeFilterQueryValue(match[2]);
} else {
return this.encodeFilterQueryValue(this.fixedFilter);
}
}
encodeFilterQueryValue(filterQueryValue: string): string {
// expected format: 'value' or 'value,operator'
// -> split into (value)(,operator) and only encode 'value'
const match = filterQueryValue.match(/^(.*)(,\w+)$/);
if (hasValue(match)) {
return encodeURIComponent(match[1]) + match[2];
} else {
return encodeURIComponent(filterQueryValue);
}
}
} }

View File

@@ -78,4 +78,5 @@
--ds-breadcrumb-link-active-color: #{darken($cyan, 30%)}; --ds-breadcrumb-link-active-color: #{darken($cyan, 30%)};
--ds-slider-color: #{$green}; --ds-slider-color: #{$green};
--ds-slider-handle-width: 18px;
} }