diff --git a/docs/Cube.js-Frontend/@cubejs-client-vue.md b/docs/Cube.js-Frontend/@cubejs-client-vue.md
index 367321a45119c..9681ed7f47108 100644
--- a/docs/Cube.js-Frontend/@cubejs-client-vue.md
+++ b/docs/Cube.js-Frontend/@cubejs-client-vue.md
@@ -15,7 +15,7 @@ into Vue.js app.
### Props
-- `query`: analytic query. [Learn more about it's format](query-format).
+- `query`: query parameters ([learn more about its format](query-format)).
- `cubejsApi`: `CubejsApi` instance to use.
### Slots
@@ -24,19 +24,21 @@ into Vue.js app.
##### Slot Props
-- `resultSet`: A `resultSet` is an object containing data obtained from the query. [ResultSet](@cubejs-client-core#result-set) object provides a convient interface for data munipulation.
+- `resultSet`: A `resultSet` is an object containing data obtained from the query. [ResultSet](@cubejs-client-core#result-set) object provides a convenient interface for data manipulation.
#### Empty Slot
-This slot functions as a empty/loading state in which if the query is loading or empty you can show
-something in the meantime
+This slot functions as a empty/loading state in which if the query is loading or empty so you can show
+something in the meantime.
#### Error Slot
+This slot will be rendered if any error happens while the query is loading or rendering.
+
##### Slot Props
-- `error`: will show the details from error.
-- `sqlQuery`: will show tried query
+- `error`: the error.
+- `sqlQuery`: the attempted query.
### Example
```js
@@ -93,11 +95,12 @@ export default {
```
## QueryBuilder
-`` is used to build interactive analytics query builders. It abstracts state management and API calls to Cube.js Backend. It uses scoped slot props technique.
+`` is used to build interactive analytics query builders. It abstracts state management and API calls to Cube.js Backend. It uses scoped slot props technique.
### Props
-- `query`: default query.
+- `query`: query parameters ([learn more about its format](query-format)). This property is reactive - if you change the object here,
+the internal query values will be overwritten. This is not two-way.
- `cubejsApi`: `CubejsApi` instance to use. Required.
- `defaultChartType`: default value of chart type. Default: 'line'.
@@ -107,38 +110,39 @@ export default {
##### Slot Props
-- `resultSet`: A `resultSet` is an object containing data obtained from the query. [ResultSet](@cubejs-client-core#result-set) object provides a convient interface for data munipulation.
+- `resultSet`: A `resultSet` is an object containing data obtained from the query. [ResultSet](@cubejs-client-core#result-set) object provides a convenient interface for data manipulation.
#### Empty Slot
This slot functions as a empty/loading state in which if the query is loading or empty you can show
-something in the meantime
+something in the meantime.
#### Error Slot
+This slot will be rendered if any error happens while the query is loading or rendering.
+
##### Slot Props
-- `error`: will show the details from error.
-- `sqlQuery`: will show tried query
+- `error`: the error.
+- `sqlQuery`: the attempted query.
#### Builder Slot
-- `measures`, `dimensions`, `segments`, `timeDimensions`, `filters` - arrays of
+- `measures`, `dimensions`, `segments`, `timeDimensions`, `filters` - arrays containing the
selected query builder members.
- `availableMeasures`, `availableDimensions`, `availableTimeDimensions`,
-`availableSegments` - arrays of available to select members. They are loaded via
+`availableSegments` - arrays containing available members to select. They are loaded via
API from Cube.js Backend.
-- `addMeasures`, `addDimensions`, `addSegments`, `addTimeDimensions` - function to control the adding of new members to query builder
-- `removeMeasures`, `removeDimensions`, `removeSegments`, `removeTimeDimensions` - function to control the removing of member to query builder
-- `setMeasures`, `setDimensions`, `setSegments`, `setTimeDimensions` - function to control the set of members to query builder
-- `updateMeasures`, `updateDimensions`, `updateSegments`, `updateTimeDimensions` - function to control the update of member to query builder
-- `chartType` - string, containing currently selected chart type.
+- `addMeasures`, `addDimensions`, `addSegments`, `addTimeDimensions` - functions to control the adding of new members to query builder.
+- `removeMeasures`, `removeDimensions`, `removeSegments`, `removeTimeDimensions` - functions to control the removing of members to query builder.
+- `setMeasures`, `setDimensions`, `setSegments`, `setTimeDimensions` - functions to control the setting of members to query builder.
+- `updateMeasures`, `updateDimensions`, `updateSegments`, `updateTimeDimensions` - functions to control the updating of members to query builder.
+- `chartType` - string containing currently selected chart type.
- `updateChartType` - function-setter for chart type.
-- `isQueryPresent` - Bool indicating whether is query ready to be displayed or
- not.
+- `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
+- `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)
diff --git a/packages/cubejs-vue/src/QueryBuilder.js b/packages/cubejs-vue/src/QueryBuilder.js
index 193cd84dafa23..b7ba770350001 100644
--- a/packages/cubejs-vue/src/QueryBuilder.js
+++ b/packages/cubejs-vue/src/QueryBuilder.js
@@ -29,6 +29,8 @@ export default {
availableSegments: [],
limit: null,
offset: null,
+ renewQuery: false,
+ order: {}
};
data.granularities = [
@@ -41,35 +43,7 @@ export default {
return data;
},
- async mounted() {
- this.meta = await this.cubejsApi.meta();
- 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') }));
- this.segments = (segments || []).map((m, i) => ({ index: i, ...this.meta.resolveMember(m, 'segments') }));
- this.timeDimensions = (timeDimensions || []).map((m, i) => ({
- ...m,
- dimension: { ...this.meta.resolveMember(m.dimension, 'dimensions'), granularities: this.granularities },
- index: i
- }));
- this.filters = (filters || []).map((m, i) => ({
- ...m,
- // 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
- }));
-
- this.availableMeasures = this.meta.membersForQuery({}, 'measures') || [];
- this.availableDimensions = this.meta.membersForQuery({}, 'dimensions') || [];
- 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 {
chartType,
@@ -93,6 +67,8 @@ export default {
removeLimit,
setOffset,
removeOffset,
+ renewQuery,
+ order
} = this;
let builderProps = {};
@@ -119,6 +95,8 @@ export default {
removeLimit,
setOffset,
removeOffset,
+ renewQuery,
+ order
};
QUERY_ELEMENTS.forEach((e) => {
@@ -164,7 +142,7 @@ export default {
validatedQuery() {
const validatedQuery = {};
let toQuery = member => member.name;
- // TODO: implement order, timezone, renewQuery
+ // TODO: implement timezone
let hasElements = false;
QUERY_ELEMENTS.forEach((e) => {
@@ -208,13 +186,55 @@ export default {
if (this.offset) {
validatedQuery.offset = this.offset;
}
- // add order
+
+ if (this.order) {
+ validatedQuery.order = this.order;
+ }
+
+ if (this.renewQuery) {
+ validatedQuery.renewQuery = this.renewQuery;
+ }
}
return validatedQuery;
},
},
+
+ async mounted() {
+ this.meta = await this.cubejsApi.meta();
+
+ this.copyQueryFromProps();
+ },
+
methods: {
+ copyQueryFromProps() {
+ const { measures, dimensions, segments, timeDimensions, filters, limit, offset, renewQuery, order } = 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') }));
+ this.segments = (segments || []).map((m, i) => ({ index: i, ...this.meta.resolveMember(m, 'segments') }));
+ this.timeDimensions = (timeDimensions || []).map((m, i) => ({
+ ...m,
+ dimension: { ...this.meta.resolveMember(m.dimension, 'dimensions'), granularities: this.granularities },
+ index: i
+ }));
+ this.filters = (filters || []).map((m, i) => ({
+ ...m,
+ member: this.meta.resolveMember(m.member || m.dimension, ['dimensions', 'measures']),
+ operators: this.meta.filterOperatorsForMember(m.member || m.dimension, ['dimensions', 'measures']),
+ index: i
+ }));
+
+ this.availableMeasures = this.meta.membersForQuery({}, 'measures') || [];
+ this.availableDimensions = this.meta.membersForQuery({}, 'dimensions') || [];
+ 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);
+ this.renewQuery = (renewQuery || false);
+ this.order = (order || {});
+ },
addMember(element, member) {
const name = element.charAt(0).toUpperCase() + element.slice(1);
let mem;
@@ -364,4 +384,17 @@ export default {
this.chartType = chartType;
},
},
+
+ watch: {
+ query() {
+ if (!this.meta) {
+ // this is ok as if meta has not been loaded by the time query prop has changed,
+ // then the promise for loading meta (found in mounted()) will call
+ // copyQueryFromProps and will therefore update anyway.
+ return;
+ }
+
+ this.copyQueryFromProps();
+ }
+ }
};
\ No newline at end of file
diff --git a/packages/cubejs-vue/tests/unit/QueryBuilder.spec.js b/packages/cubejs-vue/tests/unit/QueryBuilder.spec.js
index a741cca153ffb..a7e95140a5615 100644
--- a/packages/cubejs-vue/tests/unit/QueryBuilder.spec.js
+++ b/packages/cubejs-vue/tests/unit/QueryBuilder.spec.js
@@ -492,5 +492,107 @@ describe('QueryBuilder.vue', () => {
expect(wrapper.vm.offset).toBe(10);
});
+
+ it('sets renewQuery', 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],
+ renewQuery: true
+ },
+ },
+ });
+
+ await flushPromises();
+
+ expect(wrapper.vm.renewQuery).toBe(true);
+ });
+
+ it('sets order', 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],
+ order: {
+ 'Orders.status': 'desc'
+ }
+ },
+ },
+ });
+
+ await flushPromises();
+
+ expect(wrapper.vm.order['Orders.status']).toBe('desc');
+ });
+
+ it('is reactive when filter is changed', 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 newFilter = {
+ dimension: 'Orders.number',
+ operator: 'equals',
+ 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.status');
+ expect(wrapper.vm.filters[0].values).toContain('invalid');
+
+ wrapper.setProps({
+ query: {
+ filters: [newFilter]
+ }
+ });
+
+ 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');
+ });
});
});