Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(filters): add possibility to filter by text range like "a..e" #279

Merged
merged 5 commits into from
Mar 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
"jquery": "^3.5.1",
"jquery-ui-dist": "^1.12.1",
"moment-mini": "^2.24.0",
"multiple-select-modified": "^1.3.10",
"multiple-select-modified": "^1.3.11",
"slickgrid": "^2.4.33"
},
"devDependencies": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,6 @@ describe('getParsedSearchTermsByFieldType method', () => {
const input = 'world';
const result = getParsedSearchTermsByFieldType([input], 'string');

expect(result).toBe(input);
expect(result).toEqual([input]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,46 @@ describe('executeStringFilterCondition method', () => {
const output = executeStringFilterCondition(options, getFilterParsedText(searchTerms));
expect(output).toBe(true);
});

it('should return True when input value is in the range of search terms using 2 dots (..) notation', () => {
const searchTerms = ['b..e'];
const options = { dataKey: '', operator: 'EQ', cellValue: 'c', fieldType: FieldType.string, searchTerms } as FilterConditionOption;
const output = executeStringFilterCondition(options, getFilterParsedText(searchTerms));
expect(output).toBe(true);
});

it('should return True when input value is on the inclusive limit range of search terms using 2 dots (..) notation AND no operator provided except a defaultFilterRangeOperator is rangeInclusive', () => {
const searchTerms = ['b..e'];
const options = { dataKey: '', defaultFilterRangeOperator: OperatorType.rangeInclusive, cellValue: 'b', fieldType: FieldType.string, searchTerms } as FilterConditionOption;
const output = executeStringFilterCondition(options, getFilterParsedText(searchTerms));
expect(output).toBe(true);
});

it('should return False when input value is on the inclusive limit range of search terms using 2 dots (..) notation AND no operator provided except a defaultFilterRangeOperator is rangeExclusive', () => {
const searchTerms = ['b..e'];
const options = { dataKey: '', defaultFilterRangeOperator: OperatorType.rangeExclusive, cellValue: 'b', fieldType: FieldType.string, searchTerms } as FilterConditionOption;
const output = executeStringFilterCondition(options, getFilterParsedText(searchTerms));
expect(output).toBe(false);
});

it('should return False when input value is not in the range of search terms using 2 dots (..) notation', () => {
const searchTerms = ['b..e'];
const options = { dataKey: '', operator: 'EQ', cellValue: 'g', fieldType: FieldType.string, searchTerms } as FilterConditionOption;
const output = executeStringFilterCondition(options, getFilterParsedText(searchTerms));
expect(output).toBe(false);
});

it('should return True when input value equals the search terms min inclusive value and operator is set to "rangeInclusive" using 2 dots (..) notation', () => {
const searchTerms = ['b..e'];
const options = { dataKey: '', operator: 'RangeInclusive', cellValue: 'b', fieldType: FieldType.string, searchTerms } as FilterConditionOption;
const output = executeStringFilterCondition(options, getFilterParsedText(searchTerms));
expect(output).toBe(true);
});

it('should return False when input value equals the search terms min inclusive value and operator is set to "RangeExclusive" using 2 dots (..) notation', () => {
const searchTerms = ['b..e'];
const options = { dataKey: '', operator: 'RangeExclusive', cellValue: 'b', fieldType: FieldType.string, searchTerms } as FilterConditionOption;
const output = executeStringFilterCondition(options, getFilterParsedText(searchTerms));
expect(output).toBe(false);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const executeFilterConditionTest: FilterCondition = ((options: FilterCond
case 'text':
default:
// the parsedSearchTerms should be single value (result came from getFilterParsedText() method)
return executeStringFilterCondition(options, parsedSearchTerms as SearchTerm);
return executeStringFilterCondition(options, (parsedSearchTerms || []) as string[]);
}
}) as FilterCondition;

Expand Down Expand Up @@ -68,7 +68,7 @@ export function getParsedSearchTermsByFieldType(inputSearchTerms: SearchTerm[] |
parsedSearchValues = getFilterParsedObjectResult(inputSearchTerms);
break;
case 'text':
parsedSearchValues = getFilterParsedText(inputSearchTerms);
parsedSearchValues = getFilterParsedText(inputSearchTerms) as SearchTerm[];
break;
}
return parsedSearchValues;
Expand Down
74 changes: 56 additions & 18 deletions packages/common/src/filter-conditions/stringFilterCondition.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { OperatorType, SearchTerm } from '../enums/index';
import { OperatorString, OperatorType, SearchTerm } from '../enums/index';
import { FilterCondition, FilterConditionOption } from '../interfaces/index';
import { testFilterCondition } from './filterUtilities';

/** Execute filter condition check on each cell */
export const executeStringFilterCondition: FilterCondition = ((options: FilterConditionOption, parsedSearchValue: string | undefined) => {
if (parsedSearchValue === undefined && !options.operator) {
export const executeStringFilterCondition: FilterCondition = ((options: FilterConditionOption, parsedSearchValues: string[]) => {
let [searchValue1, searchValue2] = parsedSearchValues;

if (searchValue1 === undefined && !options.operator) {
return true;
}

Expand All @@ -13,28 +15,64 @@ export const executeStringFilterCondition: FilterCondition = ((options: FilterCo

// make both the cell value and search value lower for case insensitive comparison
const cellValue = options.cellValue.toLowerCase();
if (typeof parsedSearchValue === 'string') {
parsedSearchValue = parsedSearchValue.toLowerCase();
if (typeof searchValue1 === 'string') {
searchValue1 = searchValue1.toLowerCase();
}
if (typeof searchValue2 === 'string') {
searchValue2 = searchValue2.toLowerCase();
}

if (options.operator === '*' || options.operator === OperatorType.endsWith) {
return cellValue.endsWith(parsedSearchValue);
} else if ((options.operator === '' && options.searchInputLastChar === '*') || options.operator === OperatorType.startsWith) {
return cellValue.startsWith(parsedSearchValue);
} else if (options.operator === '' || options.operator === OperatorType.contains) {
return (cellValue.indexOf(parsedSearchValue) > -1);
} else if (options.operator === '<>' || options.operator === OperatorType.notContains) {
return (cellValue.indexOf(parsedSearchValue) === -1);
if (searchValue1 !== undefined && searchValue2 !== undefined) {
let operator = options?.operator ?? options.defaultFilterRangeOperator;
if (operator !== OperatorType.rangeInclusive && operator !== OperatorType.rangeExclusive) {
operator = options.defaultFilterRangeOperator;
}
const isInclusive = operator === OperatorType.rangeInclusive;
const searchResult1 = testStringCondition((isInclusive ? '>=' : '>'), cellValue, searchValue1, options.searchInputLastChar);
const searchResult2 = testStringCondition((isInclusive ? '<=' : '<'), cellValue, searchValue2, options.searchInputLastChar);
return searchResult1 && searchResult2;
}
return testFilterCondition(options.operator || '==', cellValue, parsedSearchValue);
const searchResult1 = testStringCondition(options.operator, cellValue, searchValue1, options.searchInputLastChar);
return searchResult1;
}) as FilterCondition;

/**
* From our search filter value(s), get the parsed value(s).
* This is called only once per filter before running the actual filter condition check on each cell
*/
export function getFilterParsedText(inputSearchTerms: SearchTerm[] | undefined): SearchTerm {
let parsedSearchValue = (Array.isArray(inputSearchTerms) && inputSearchTerms.length > 0) ? inputSearchTerms[0] : '';
parsedSearchValue = parsedSearchValue === undefined || parsedSearchValue === null ? '' : `${parsedSearchValue}`; // make sure it's a string
return parsedSearchValue;
export function getFilterParsedText(inputSearchTerms: SearchTerm[] | undefined): SearchTerm[] {
const defaultSearchTerm = ''; // when nothing is provided, we'll default to 0
const searchTerms = Array.isArray(inputSearchTerms) && inputSearchTerms || [defaultSearchTerm];
const parsedSearchValues: string[] = [];
let searchValue1;
let searchValue2;
if (searchTerms.length === 2 || (typeof searchTerms[0] === 'string' && (searchTerms[0] as string).indexOf('..') > 0)) {
const searchValues = (searchTerms.length === 2) ? searchTerms : (searchTerms[0] as string).split('..');
searchValue1 = `${Array.isArray(searchValues) ? searchValues[0] : ''}`;
searchValue2 = `${Array.isArray(searchValues) ? searchValues[1] : ''}`;
} else {
const parsedSearchValue = (Array.isArray(inputSearchTerms) && inputSearchTerms.length > 0) ? inputSearchTerms[0] : '';
searchValue1 = parsedSearchValue === undefined || parsedSearchValue === null ? '' : `${parsedSearchValue}`; // make sure it's a string
}

if (searchValue1 !== undefined && searchValue2 !== undefined) {
parsedSearchValues.push(searchValue1 as string, searchValue2 as string);
} else if (searchValue1 !== undefined) {
parsedSearchValues.push(searchValue1 as string);
}
return parsedSearchValues;
}

/** Execute the filter string test condition, returns a boolean */
function testStringCondition(operator: OperatorType | OperatorString, cellValue: string, searchValue: string, searchInputLastChar?: string): boolean {
if (operator === '*' || operator === OperatorType.endsWith) {
return cellValue.endsWith(searchValue);
} else if ((operator === '' && searchInputLastChar === '*') || operator === OperatorType.startsWith) {
return cellValue.startsWith(searchValue);
} else if (operator === '' || operator === OperatorType.contains) {
return (cellValue.indexOf(searchValue) > -1);
} else if (operator === '<>' || operator === OperatorType.notContains) {
return (cellValue.indexOf(searchValue) === -1);
}
return testFilterCondition(operator || '==', cellValue, searchValue);
}
Loading