Skip to content

Commit

Permalink
feat: Property filter token groups (#74)
Browse files Browse the repository at this point in the history
  • Loading branch information
pan-kot authored Aug 29, 2024
1 parent e537686 commit 0ec7db3
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 8 deletions.
166 changes: 166 additions & 0 deletions src/__tests__/operations/property-filter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -586,3 +586,169 @@ describe('extended operators', () => {
).toThrow('Unsupported `operator.match` type given.');
});
});

describe('Token groups', () => {
test('token groups have precedence over tokens', () => {
const { items: processed } = processItems(
[{ field: 'A' }, { field: 'B' }],
{
propertyFilteringQuery: {
operation: 'and',
tokens: [{ propertyKey: 'field', operator: '=', value: 'A' }],
tokenGroups: [{ propertyKey: 'field', operator: '=', value: 'B' }],
},
},
{ propertyFiltering }
);
expect(processed).toEqual([{ field: 'B' }]);
});

test('filters by two OR token groups', () => {
const { items: processed } = processItems(
[
{ field: 'A1', anotherField: 'A2' },
{ field: 'A2', anotherField: 'A1' },
{ field: 'A1', anotherField: 'A3' },
{ field: 'A3', anotherField: 'A1' },
{ field: 'A2', anotherField: 'A3' },
{ field: 'A3', anotherField: 'A2' },
{ field: 'A3', anotherField: 'A3' },
],
{
propertyFilteringQuery: {
operation: 'and',
tokens: [],
tokenGroups: [
{
operation: 'or',
tokens: [
{ propertyKey: 'field', operator: '=', value: 'A1' },
{ propertyKey: 'anotherField', operator: '=', value: 'A1' },
],
},
{
operation: 'or',
tokens: [
{ propertyKey: 'field', operator: '=', value: 'A2' },
{ propertyKey: 'anotherField', operator: '=', value: 'A2' },
],
},
],
},
},
{ propertyFiltering }
);
expect(processed).toEqual([
{ field: 'A1', anotherField: 'A2' },
{ field: 'A2', anotherField: 'A1' },
]);
});

test('filters by two AND token groups', () => {
const { items: processed } = processItems(
[
{ field: 'A1', anotherField: 'A1' },
{ field: 'A2', anotherField: 'A2' },
{ field: 'A1', anotherField: 'A2' },
{ field: 'A2', anotherField: 'A1' },
{ field: 'A3', anotherField: 'A3' },
],
{
propertyFilteringQuery: {
operation: 'or',
tokens: [],
tokenGroups: [
{
operation: 'and',
tokens: [
{ propertyKey: 'field', operator: '=', value: 'A1' },
{ propertyKey: 'anotherField', operator: '=', value: 'A1' },
],
},
{
operation: 'and',
tokens: [
{ propertyKey: 'field', operator: '=', value: 'A2' },
{ propertyKey: 'anotherField', operator: '=', value: 'A2' },
],
},
],
},
},
{ propertyFiltering }
);
expect(processed).toEqual([
{ field: 'A1', anotherField: 'A1' },
{ field: 'A2', anotherField: 'A2' },
]);
});

test('filters by a deeply nested group', () => {
const { items: processed } = processItems(
[
{ field: 'A1', anotherField: 'A1' },
{ field: 'A2', anotherField: 'A2' },
{ field: 'A1', anotherField: 'A2' },
{ field: 'A2', anotherField: 'A1' },
{ field: 'A1', anotherField: 'A3' },
{ field: 'A3', anotherField: 'A1' },
{ field: 'A2', anotherField: 'A3' },
{ field: 'A3', anotherField: 'A2' },
{ field: 'A3', anotherField: 'A3' },
],
{
propertyFilteringQuery: {
operation: 'or',
tokens: [],
tokenGroups: [
{
operation: 'and',
tokens: [
{
operation: 'or',
tokens: [
{ propertyKey: 'field', operator: '=', value: 'A1' },
{ propertyKey: 'anotherField', operator: '=', value: 'A1' },
],
},
{
operation: 'or',
tokens: [
{ propertyKey: 'field', operator: '=', value: 'A2' },
{ propertyKey: 'anotherField', operator: '=', value: 'A2' },
],
},
],
},
{
operation: 'or',
tokens: [
{
operation: 'and',
tokens: [
{ propertyKey: 'field', operator: '=', value: 'A1' },
{ propertyKey: 'anotherField', operator: '=', value: 'A1' },
],
},
{
operation: 'and',
tokens: [
{ propertyKey: 'field', operator: '=', value: 'A2' },
{ propertyKey: 'anotherField', operator: '=', value: 'A2' },
],
},
],
},
],
},
},
{ propertyFiltering }
);
expect(processed).toEqual([
{ field: 'A1', anotherField: 'A1' },
{ field: 'A2', anotherField: 'A2' },
{ field: 'A1', anotherField: 'A2' },
{ field: 'A2', anotherField: 'A1' },
]);
});
});
5 changes: 5 additions & 0 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,14 @@ export interface PropertyFilterToken {
propertyKey?: string;
operator: PropertyFilterOperator;
}
export interface PropertyFilterTokenGroup {
operation: PropertyFilterOperation;
tokens: readonly (PropertyFilterToken | PropertyFilterTokenGroup)[];
}
export interface PropertyFilterQuery {
tokens: readonly PropertyFilterToken[];
operation: PropertyFilterOperation;
tokenGroups?: readonly (PropertyFilterToken | PropertyFilterTokenGroup)[];
}
export interface PropertyFilterProperty<TokenValue = any> {
key: string;
Expand Down
24 changes: 16 additions & 8 deletions src/operations/property-filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
PropertyFilterToken,
UseCollectionOptions,
PropertyFilterProperty,
PropertyFilterTokenGroup,
} from '../interfaces';
import { compareDates, compareTimestamps } from '../date-utils/compare-dates.js';
import { Predicate } from './compose-filters';
Expand Down Expand Up @@ -110,15 +111,22 @@ function filterByToken<T>(token: PropertyFilterToken, item: T, filteringProperti
}

function defaultFilteringFunction<T>(filteringPropertiesMap: FilteringPropertiesMap<T>) {
return (item: T, { tokens, operation }: PropertyFilterQuery) => {
let result = operation === 'and' ? true : !tokens.length;
for (const token of tokens) {
result =
operation === 'and'
? result && filterByToken(token, item, filteringPropertiesMap)
: result || filterByToken(token, item, filteringPropertiesMap);
return (item: T, query: PropertyFilterQuery) => {
function evaluate(tokenOrGroup: PropertyFilterToken | PropertyFilterTokenGroup): boolean {
if ('operation' in tokenOrGroup) {
let result = tokenOrGroup.operation === 'and' ? true : !tokenOrGroup.tokens.length;
for (const group of tokenOrGroup.tokens) {
result = tokenOrGroup.operation === 'and' ? result && evaluate(group) : result || evaluate(group);
}
return result;
} else {
return filterByToken(tokenOrGroup, item, filteringPropertiesMap);
}
}
return result;
return evaluate({
operation: query.operation,
tokens: query.tokenGroups ?? query.tokens,
});
};
}

Expand Down

0 comments on commit 0ec7db3

Please sign in to comment.