Skip to content

Commit

Permalink
feat(client-vue): boolean filters support (#4314)
Browse files Browse the repository at this point in the history
  • Loading branch information
ThomasKientz committed Apr 26, 2022
1 parent 0932cda commit 8a3bb3d
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 69 deletions.
88 changes: 50 additions & 38 deletions packages/cubejs-client-vue/src/QueryBuilder.js
Expand Up @@ -7,7 +7,7 @@ import {
getOrderMembersFromOrder,
moveItemInArray,
movePivotItem,
areQueriesEqual
areQueriesEqual,
} from '@cubejs-client/core';
import { clone, equals } from 'ramda';

Expand All @@ -23,35 +23,49 @@ const toOrderMember = (member) => ({
const reduceOrderMembers = (array) =>
array.reduce((acc, { id, order }) => (order !== 'none' ? [...acc, [id, order]] : acc), []);

const validateFilter = (f) => f.operator
const operators = ['and', 'or'];

const operators = [ 'and', 'or' ]
const validateFilters = (filters) =>
filters.reduce((acc, raw) => {
if (raw.operator) {
return [...acc, raw];
}

const validateFilters = (filters) => filters.reduce((acc, raw) => {
const validated = { ...raw }
const validBooleanFilter = operators.reduce((acc, operator) => {
const filters = raw[operator];

operators.reduce((acc, operator) => {
const filters = raw[operator]
if (filters) {
acc[operator] = filters.filter(validateFilter)
}
return acc
}, validated)
const booleanFilters = validateFilters(filters || []);

if (booleanFilters.length) {
return { ...acc, [operator]: booleanFilters };
}

return acc;
}, {});

if (validated.operator || operators.some((operator) => validated[operator] && validated[operator].length)) {
acc.push(validated)
}
if (operators.some((operator) => validBooleanFilter[operator])) {
return [...acc, validBooleanFilter];
}

return acc
}, [])
return acc;
}, []);

const getDimensionOrMeasure = (meta, m) => {
const memberName = m.member || m.dimension
return memberName && meta.resolveMember(memberName, [ 'dimensions', 'measures' ])
}
const memberName = m.member || m.dimension;
return memberName && meta.resolveMember(memberName, ['dimensions', 'measures']);
};

const resolveMembers = (meta, arr) =>
arr && arr.map((e, index) => ({ ...e, member: getDimensionOrMeasure(meta, e), index }))
arr &&
arr.map((e, index) => {
return {
...e,
member: getDimensionOrMeasure(meta, e),
index,
and: resolveMembers(meta, e.and),
or: resolveMembers(meta, e.or),
};
});

export default {
components: {
Expand Down Expand Up @@ -207,7 +221,7 @@ export default {
this.pivotConfig = {
...this.pivotConfig,
...pivotConfig,
}
};
},
},
};
Expand All @@ -234,9 +248,7 @@ export default {
}

if (!this.wrapWithQueryRenderer && this.$scopedSlots.builder) {
return createElement('div', {}, [
this.$scopedSlots.builder(builderProps),
]);
return createElement('div', {}, [this.$scopedSlots.builder(builderProps)]);
}

// Pass parent slots to child QueryRenderer component
Expand All @@ -256,8 +268,8 @@ export default {
on: {
queryStatus: (event) => {
this.$emit('queryStatus', event);
}
}
},
},
},
children
);
Expand Down Expand Up @@ -330,7 +342,7 @@ export default {
});

if (validatedQuery.filters) {
validatedQuery.filters = validateFilters(validatedQuery.filters)
validatedQuery.filters = validateFilters(validatedQuery.filters);
}

// only set limit and offset if there are elements otherwise an invalid request with just limit/offset
Expand Down Expand Up @@ -363,7 +375,7 @@ export default {
const { query, chartType, shouldApplyHeuristicOrder, pivotConfig } = heuristicsFn(
{
query: validatedQuery,
chartType: this.chartType
chartType: this.chartType,
},
this.prevValidatedQuery,
{
Expand Down Expand Up @@ -406,11 +418,11 @@ export default {
const dryRunResponse = await this.cubejsApi.dryRun(this.initialQuery);

this.pivotConfig = ResultSet.getNormalizedPivotConfig(
dryRunResponse?.pivotQuery || {},
this.pivotConfig
dryRunResponse?.pivotQuery || {},
this.pivotConfig
);
} catch (error) {
console.error(error)
console.error(error);
}
}
},
Expand Down Expand Up @@ -449,15 +461,15 @@ export default {
},
index,
}));
const memberTypes = ['dimensions', 'measures']
const memberTypes = ['dimensions', 'measures'];
this.filters = filters.map((m, index) => {
const memberName = m.member || m.dimension
const memberName = m.member || m.dimension;
return {
...m,
member: memberName && this.meta.resolveMember(memberName, memberTypes),
operators: memberName && this.meta.filterOperatorsForMember(memberName, memberTypes),
index,
}
};
});

this.availableMeasures = this.meta.membersForQuery({}, 'measures') || [];
Expand Down Expand Up @@ -497,7 +509,7 @@ export default {
and: resolveMembers(this.meta, member.and),
or: resolveMembers(this.meta, member.or),
member: getDimensionOrMeasure(this.meta, member),
}
};
} else {
mem = this[`available${name}`].find((m) => m.name === member);
}
Expand Down Expand Up @@ -546,7 +558,7 @@ export default {
and: resolveMembers(this.meta, member.and),
or: resolveMembers(this.meta, member.or),
member: getDimensionOrMeasure(this.meta, member),
}
};
} else {
index = this[element].findIndex((x) => x.name === old);
mem = this[`available${name}`].find((m) => m.name === member);
Expand Down Expand Up @@ -584,7 +596,7 @@ export default {
and: resolveMembers(this.meta, m.and),
or: resolveMembers(this.meta, m.or),
member: getDimensionOrMeasure(this.meta, m),
}
};
} else {
mem = this[`available${name}`].find((x) => x.name === m);
}
Expand Down
115 changes: 84 additions & 31 deletions packages/cubejs-client-vue/tests/unit/QueryBuilder.spec.js
Expand Up @@ -444,7 +444,7 @@ describe('QueryBuilder.vue', () => {
dimension: 'Orders.status',
operator: 'equals',
values: ['that'],
}
},
],
or: [
{
Expand All @@ -456,8 +456,22 @@ describe('QueryBuilder.vue', () => {
dimension: 'Orders.status',
operator: 'equals',
values: ['that'],
}
]
},
{
and: [
{
dimension: 'Orders.status',
operator: 'equals',
values: ['this'],
},
{
dimension: 'Orders.status',
operator: 'equals',
values: ['that'],
},
],
},
],
};

const wrapper = mount(QueryBuilder, {
Expand All @@ -471,7 +485,7 @@ describe('QueryBuilder.vue', () => {

await flushPromises();

expect(wrapper.vm.filters[0].or.length).toBe(2);
expect(wrapper.vm.filters[0].or.length).toBe(3);
expect(wrapper.vm.filters[0].and.length).toBe(2);
wrapper.vm.setMembers('filters', []);
expect(wrapper.vm.validatedQuery.filters).toBeUndefined();
Expand All @@ -483,7 +497,10 @@ describe('QueryBuilder.vue', () => {
expect(wrapper.vm.validatedQuery.filters[0].or[0].member).toBe('Orders.status');
expect(wrapper.vm.validatedQuery.filters[0].or[0].values).toContain('this');
expect(wrapper.vm.validatedQuery.filters[0].or[1].values).toContain('that');
})
expect(wrapper.vm.validatedQuery.filters[0].or[2].and[0].member).toBe('Orders.status');
expect(wrapper.vm.validatedQuery.filters[0].or[2].and[0].values).toContain('this');
expect(wrapper.vm.validatedQuery.filters[0].or[2].and[1].values).toContain('that');
});

it.each([
[
Expand All @@ -495,7 +512,7 @@ describe('QueryBuilder.vue', () => {
},
],
},
0
0,
],
[
{
Expand All @@ -504,9 +521,28 @@ describe('QueryBuilder.vue', () => {
dimension: 'Orders.status',
values: ['this'],
},
]
],
},
0,
],
[
{
or: [
{
dimension: 'Orders.status',
values: ['this'],
},
{
and: [
{
dimension: 'Orders.status',
values: ['this'],
},
],
},
],
},
0
0,
],
[
{
Expand All @@ -522,10 +558,30 @@ describe('QueryBuilder.vue', () => {
operator: 'equals',
values: ['this'],
},
]
],
},
1,
],
[
{
or: [
{
dimension: 'Orders.status',
values: ['this'],
},
{
and: [
{
dimension: 'Orders.status',
operator: 'equals',
values: ['this'],
},
],
},
],
},
1
]
1,
],
])('does not assign boolean logical operators having no operator', async (filter, expected) => {
const cube = createCubejsApi();
jest
Expand All @@ -546,7 +602,7 @@ describe('QueryBuilder.vue', () => {

wrapper.vm.setMembers('filters', [filter]);
expect(wrapper.vm.validatedQuery.filters.length).toBe(expected);
})
});

it('sets filters when using measure', async () => {
const cube = createCubejsApi();
Expand Down Expand Up @@ -769,14 +825,14 @@ describe('QueryBuilder.vue', () => {
});

describe('builder slot updatePivotConfig.update', () => {
it.each([
{ x: [ 'Orders.status' ] },
{ y: [ 'measures' ] },
{ x: [ 'Orders.status', 'measures' ], y: [] },
{ aliasSeries: [ 'one' ] },
{ fillMissingDates: true },
{ fillMissingDates: false }
])('sets pivotConfig', async (pivotConfig) => {
it.each([
{ x: ['Orders.status'] },
{ y: ['measures'] },
{ x: ['Orders.status', 'measures'], y: [] },
{ aliasSeries: ['one'] },
{ fillMissingDates: true },
{ fillMissingDates: false },
])('sets pivotConfig', async (pivotConfig) => {
const cube = createCubejsApi();
jest
.spyOn(cube, 'request')
Expand All @@ -792,21 +848,18 @@ describe('QueryBuilder.vue', () => {
},
},
scopedSlots: {
builder: function({ updatePivotConfig }) {
return this.$createElement(
'input',
{
on: { change: () => updatePivotConfig.update(pivotConfig) }
}
)
}
}
builder: function ({ updatePivotConfig }) {
return this.$createElement('input', {
on: { change: () => updatePivotConfig.update(pivotConfig) },
});
},
},
});

await flushPromises();

wrapper.find('input').trigger('change')
wrapper.find('input').trigger('change');
expect(wrapper.vm.pivotConfig).toMatchObject(pivotConfig);
});
})
});
});

0 comments on commit 8a3bb3d

Please sign in to comment.