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

Added missing features to vue package #194

Merged
merged 4 commits into from
Sep 5, 2019
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: 2 additions & 0 deletions docs/Cube.js-Frontend/@cubejs-client-vue.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ API from Cube.js Backend.
- `isQueryPresent` - Bool indicating whether is query ready to be displayed or
not.
- `query` - current query, based on selected members.
- `setLimit`, `removeLimit` - functions to control the number of results returned
- `setOffset`, `removeOffset` - functions to control the number of rows skipped before results returned. Use with limit to control pagination

### Example
[Open in CodeSandbox](https://codesandbox.io/s/3rlxjkv2p)
Expand Down
79 changes: 64 additions & 15 deletions packages/cubejs-vue/src/QueryBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export default {
availableDimensions: [],
availableTimeDimensions: [],
availableSegments: [],
limit: null,
offset: null,
};

data.granularities = [
Expand All @@ -42,7 +44,7 @@ export default {
async mounted() {
this.meta = await this.cubejsApi.meta();

const { measures, dimensions, segments, timeDimensions, filters } = this.query;
const { measures, dimensions, segments, timeDimensions, filters, limit, offset } = this.query;

this.measures = (measures || []).map((m, i) => ({ index: i, ...this.meta.resolveMember(m, 'measures') }));
this.dimensions = (dimensions || []).map((m, i) => ({ index: i, ...this.meta.resolveMember(m, 'dimensions') }));
Expand All @@ -54,8 +56,9 @@ export default {
}));
this.filters = (filters || []).map((m, i) => ({
...m,
dimension: this.meta.resolveMember(m.dimension, ['dimensions', 'measures']),
operators: this.meta.filterOperatorsForMember(m.dimension, ['dimensions', 'measures']),
// using 'dimension' is deprecated, 'member' should be specified instead
member: this.meta.resolveMember(m.member || m.dimension, ['dimensions', 'measures']),
operators: this.meta.filterOperatorsForMember(m.member || m.dimension, ['dimensions', 'measures']),
index: i
}));

Expand All @@ -64,6 +67,8 @@ export default {
this.availableTimeDimensions = (this.meta.membersForQuery({}, 'dimensions') || [])
.filter(m => m.type === 'time');
this.availableSegments = this.meta.membersForQuery({}, 'segments') || [];
this.limit = (limit || null);
this.offset = (offset || null);
},
render(createElement) {
const {
Expand All @@ -82,6 +87,12 @@ export default {
availableTimeDimensions,
availableDimensions,
availableMeasures,
limit,
offset,
setLimit,
removeLimit,
setOffset,
removeOffset,
} = this;

let builderProps = {};
Expand All @@ -102,6 +113,12 @@ export default {
availableDimensions,
availableMeasures,
updateChartType: this.updateChart,
limit,
offset,
setLimit,
removeLimit,
setOffset,
removeOffset,
};

QUERY_ELEMENTS.forEach((e) => {
Expand Down Expand Up @@ -147,9 +164,14 @@ export default {
validatedQuery() {
const validatedQuery = {};
let toQuery = member => member.name;
// TODO: implement order, limit, timezone, renewQuery
// TODO: implement order, timezone, renewQuery

let hasElements = false;
QUERY_ELEMENTS.forEach((e) => {
if (!this[e]) {
return;
}

if (e === 'timeDimensions') {
toQuery = (member) => ({
dimension: member.dimension.name,
Expand All @@ -158,14 +180,16 @@ export default {
});
} else if (e === 'filters') {
toQuery = (member) => ({
dimension: member.dimension.name,
member: member.member.name,
operator: member.operator,
values: member.values,
});
}

if (this[e].length > 0) {
validatedQuery[e] = this[e].map(x => toQuery(x));

hasElements = true;
}
});
// TODO: implement default heuristics
Expand All @@ -174,6 +198,19 @@ export default {
validatedQuery.filters = validatedQuery.filters.filter(f => f.operator);
}

// only set limit and offset if there are elements otherwise an invalid request with just limit/offset
// gets sent when the component is first mounted, but before the actual query is constructed.
if (hasElements) {
if (this.limit) {
validatedQuery.limit = this.limit;
}

if (this.offset) {
validatedQuery.offset = this.offset;
}
// add order
}

return validatedQuery;
},
},
Expand All @@ -199,13 +236,13 @@ export default {
};
}
} else if (element === 'filters') {
const dimension = {
...this.meta.resolveMember(member.dimension, 'dimensions'),
const filterMember = {
...this.meta.resolveMember(member.member || member.dimension, ['dimensions', 'measures']),
};

mem = {
...member,
dimension,
member: filterMember,
};
} else {
mem = this[`available${name}`].find(m => m.name === member);
Expand Down Expand Up @@ -254,13 +291,13 @@ export default {
}
} else if (element === 'filters') {
index = this[element].findIndex(x => x.dimension === old);
const dimension = {
...this.meta.resolveMember(member.dimension, 'dimensions'),
const filterMember = {
...this.meta.resolveMember(member.member || member.dimension, ['dimensions', 'measures']),
};

mem = {
...member,
dimension,
member: filterMember,
};
} else {
index = this[element].findIndex(x => x.name === old);
Expand Down Expand Up @@ -294,13 +331,13 @@ export default {
};
}
} else if (element === 'filters') {
const dimension = {
...this.meta.resolveMember(m.dimension, 'dimensions'),
const member = {
...this.meta.resolveMember(m.member || m.dimension, ['dimensions', 'measures']),
};

mem = {
...m,
dimension,
member,
};
} else {
mem = this[`available${name}`].find(x => x.name === m);
Expand All @@ -311,8 +348,20 @@ export default {

this[element] = elements;
},
setLimit(limit) {
this.limit = limit;
},
removeLimit() {
this.limit = null;
},
setOffset(offset) {
this.offset = offset;
},
removeOffset() {
this.offset = null;
},
updateChart(chartType) {
this.chartType = chartType;
},
},
};
};
94 changes: 88 additions & 6 deletions packages/cubejs-vue/tests/unit/QueryBuilder.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ describe('QueryBuilder.vue', () => {
values: ['valid']
});
expect(wrapper.vm.filters.length).toBe(1);
expect(wrapper.vm.filters[0].dimension.name).toBe('Orders.status');
expect(wrapper.vm.filters[0].member.name).toBe('Orders.status');
});

it('updates filters', async () => {
Expand Down Expand Up @@ -335,11 +335,11 @@ describe('QueryBuilder.vue', () => {
await flushPromises();

expect(wrapper.vm.filters.length).toBe(1);
expect(wrapper.vm.filters[0].dimension.name).toBe('Orders.status');
expect(wrapper.vm.filters[0].member.name).toBe('Orders.status');
expect(wrapper.vm.filters[0].values).toContain('invalid');
wrapper.vm.updateMember('filters', 'Orders.status', newFilter);
expect(wrapper.vm.filters.length).toBe(1);
expect(wrapper.vm.filters[0].dimension.name).toBe('Orders.status');
expect(wrapper.vm.filters[0].member.name).toBe('Orders.status');
expect(wrapper.vm.filters[0].values).toContain('valid');
});

Expand Down Expand Up @@ -367,7 +367,7 @@ describe('QueryBuilder.vue', () => {
await flushPromises();

expect(wrapper.vm.filters.length).toBe(1);
expect(wrapper.vm.filters[0].dimension.name).toBe('Orders.status');
expect(wrapper.vm.filters[0].member.name).toBe('Orders.status');
expect(wrapper.vm.filters[0].values).toContain('invalid');
wrapper.vm.removeMember('filters', 'Orders.status');
expect(wrapper.vm.filters.length).toBe(0);
Expand Down Expand Up @@ -403,12 +403,94 @@ describe('QueryBuilder.vue', () => {
await flushPromises();

expect(wrapper.vm.filters.length).toBe(1);
expect(wrapper.vm.filters[0].dimension.name).toBe('Orders.status');
expect(wrapper.vm.filters[0].member.name).toBe('Orders.status');
expect(wrapper.vm.filters[0].values).toContain('invalid');
wrapper.vm.setMembers('filters', [newFilter]);
expect(wrapper.vm.filters.length).toBe(1);
expect(wrapper.vm.filters[0].dimension.name).toBe('Orders.status');
expect(wrapper.vm.filters[0].member.name).toBe('Orders.status');
expect(wrapper.vm.filters[0].values).toContain('valid');
});

it('sets filters when using measure', async () => {
const cube = CubejsApi('token');
jest.spyOn(cube, 'request')
.mockImplementation(fetchMock(load))
.mockImplementationOnce(fetchMock(meta));

const filter = {
member: 'Orders.number',
operator: 'gt',
values: ['1'],
};

const wrapper = mount(QueryBuilder, {
propsData: {
cubejsApi: cube,
query: {
filters: [filter],
},
},
});

await flushPromises();

expect(wrapper.vm.filters.length).toBe(1);
expect(wrapper.vm.filters[0].member.name).toBe('Orders.number');
expect(wrapper.vm.filters[0].values).toContain('1');
});

it('sets limit', async () => {
const cube = CubejsApi('token');
jest.spyOn(cube, 'request')
.mockImplementation(fetchMock(load))
.mockImplementationOnce(fetchMock(meta));

const filter = {
member: 'Orders.status',
operator: 'equals',
values: ['invalid'],
};

const wrapper = mount(QueryBuilder, {
propsData: {
cubejsApi: cube,
query: {
filters: [filter],
limit: 10
},
},
});

await flushPromises();

expect(wrapper.vm.limit).toBe(10);
});

it('sets offset', async () => {
const cube = CubejsApi('token');
jest.spyOn(cube, 'request')
.mockImplementation(fetchMock(load))
.mockImplementationOnce(fetchMock(meta));

const filter = {
member: 'Orders.status',
operator: 'equals',
values: ['invalid'],
};

const wrapper = mount(QueryBuilder, {
propsData: {
cubejsApi: cube,
query: {
filters: [filter],
offset: 10
},
},
});

await flushPromises();

expect(wrapper.vm.offset).toBe(10);
});
});
});