Skip to content

Commit 167fdbf

Browse files
richipargopaveltiunov
authored andcommitted
feat: Vue.js reactivity on query update (#70)
* Fix slot composing issue and add examples using vue library * Almost there, reactivity wasn't fully aware * re render props * docs: menu orders * update docs and member methods
1 parent da35b6a commit 167fdbf

File tree

3 files changed

+149
-104
lines changed

3 files changed

+149
-104
lines changed

docs/Cube.js-Frontend/@cubejs-client-vue.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@ selected query builder members.
4040
- `availableMeasures`, `availableDimensions`, `availableTimeDimensions`,
4141
`availableSegments` - arrays of available to select members. They are loaded via
4242
API from Cube.js Backend.
43-
- `updateMeasures`, `updateDimensions`, `updateSegments`, `updateTimeDimensions` - objects with three functions: `add`, `remove`, and `update`. They are used to control the state of the query builder. Ex: `updateMeasures.add(newMeasure)`
43+
- `addMeasures`, `addDimensions`, `addSegments`, `addTimeDimensions` - function to control the adding of new members to query builder
44+
- `removeMeasures`, `removeDimensions`, `removeSegments`, `removeTimeDimensions` - function to control the removing of member to query builder
45+
- `setMeasures`, `setDimensions`, `setSegments`, `setTimeDimensions` - function to control the set of members to query builder
46+
- `updateMeasures`, `updateDimensions`, `updateSegments`, `updateTimeDimensions` - function to control the update of member to query builder
4447
- `chartType` - string, containing currently selected chart type.
4548
- `updateChartType` - function-setter for chart type.
4649
- `isQueryPresent` - Bool indicating whether is query ready to be displayed or

packages/cubejs-vue/src/QueryBuilder.js

Lines changed: 122 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -14,127 +14,148 @@ export default Vue.component('QueryBuilder', {
1414
required: true,
1515
},
1616
},
17+
data() {
18+
return {
19+
meta: undefined,
20+
updatedQuery: this.query,
21+
granularities: [],
22+
};
23+
},
1724
async mounted() {
1825
this.meta = await this.cubejsApi.meta();
19-
},
20-
render(createElement) {
21-
const { cubejsApi } = this;
22-
const props = this.prepareRenderProps();
2326

24-
return createElement(QueryRenderer, {
25-
props: {
26-
query: this.validatedQuery,
27-
cubejsApi,
28-
builderProps: props,
29-
},
30-
scopedSlots: this.$scopedSlots,
31-
});
27+
this.granularities = [
28+
{ name: 'hour', title: 'Hour' },
29+
{ name: 'day', title: 'Day' },
30+
{ name: 'week', title: 'Week' },
31+
{ name: 'month', title: 'Month' },
32+
{ name: 'year', title: 'Year' },
33+
];
3234
},
33-
computed: {
34-
isQueryPresent() {
35-
const { query } = this;
35+
render(createElement) {
36+
const { cubejsApi, meta } = this;
3637

37-
return query.measures && query.measures.length ||
38-
query.dimensions && query.dimensions.length ||
39-
query.timeDimensions && query.timeDimensions.length;
40-
},
41-
validatedQuery() {
42-
const { query } = this;
38+
if (meta) {
39+
let toQuery = member => member.name;
40+
const queryElements = ['measures', 'dimensions', 'segments', 'timeDimensions', 'filters'];
4341

44-
return {
45-
...query,
46-
filters: (query.filters || []).filter(f => f.operator),
42+
const childProps = {
43+
meta,
44+
query: this.updatedQuery,
45+
validatedQuery: this.validatedQuery,
46+
isQueryPresent: this.isQueryPresent,
47+
chartType: this.chartType,
48+
measures: (this.updatedQuery.measures || [])
49+
.map((m, i) => ({ index: i, ...meta.resolveMember(m, 'measures') })),
50+
dimensions: (this.updatedQuery.dimensions || [])
51+
.map((m, i) => ({ index: i, ...meta.resolveMember(m, 'dimensions') })),
52+
segments: (this.updatedQuery.segments || [])
53+
.map((m, i) => ({ index: i, ...meta.resolveMember(m, 'segments') })),
54+
timeDimensions: (this.updatedQuery.timeDimensions || [])
55+
.map((m, i) => ({
56+
...m,
57+
dimension: { ...meta.resolveMember(m.dimension, 'dimensions'), granularities: this.granularities },
58+
index: i
59+
})),
60+
filters: (this.updatedQuery.filters || [])
61+
.map((m, i) => ({
62+
...m,
63+
dimension: meta.resolveMember(m.dimension, ['dimensions', 'measures']),
64+
operators: meta.filterOperatorsForMember(m.dimension, ['dimensions', 'measures']),
65+
index: i
66+
})),
67+
availableMeasures: meta.membersForQuery(this.updatedQuery, 'measures') || [],
68+
availableDimensions: meta.membersForQuery(this.updatedQuery, 'dimensions') || [],
69+
availableTimeDimensions: (meta.membersForQuery(this.updatedQuery, 'dimensions') || [])
70+
.filter(m => m.type === 'time'),
71+
availableSegments: meta.membersForQuery(this.updatedQuery, 'segments') || [],
72+
updateChartType: this.updateChart,
4773
};
48-
},
49-
},
50-
methods: {
51-
prepareRenderProps() {
52-
const getName = member => member.name;
53-
const toTimeDimension = member => ({
54-
dimension: member.dimension.name,
55-
granularity: member.granularity,
56-
dateRange: member.dateRange,
57-
});
58-
const toFilter = member => ({
59-
dimension: member.dimension.name,
60-
operator: member.operator,
61-
values: member.values,
62-
});
6374

64-
const updateMethods = (memberType, toQuery = getName) => ({
65-
add(member) {
66-
this.query = {
67-
...this.query,
68-
[memberType]: (this.query[memberType] || []).concat(toQuery(member)),
75+
queryElements.forEach((e) => {
76+
if (e === 'timeDimensions') {
77+
toQuery = (member) => ({
78+
dimension: member.dimension.name,
79+
granularity: member.granularity,
80+
dateRange: member.dateRange,
81+
});
82+
} else if (e === 'filters') {
83+
toQuery = (member) => ({
84+
dimension: member.dimension.name,
85+
operator: member.operator,
86+
values: member.values,
87+
});
88+
}
89+
90+
const name = e.charAt(0).toUpperCase() + e.slice(1);
91+
92+
childProps[`add${name}`] = (member) => {
93+
this.updatedQuery = {
94+
...this.updatedQuery,
95+
[e]: (this.updatedQuery[e] || []).concat(toQuery(member)),
6996
};
70-
},
71-
remove(member) {
72-
const members = (this.query[memberType] || []).concat([]);
97+
};
98+
99+
childProps[`update${name}`] = (member, updateWith) => {
100+
const members = (this.updatedQuery[e] || []).concat([]);
101+
members.splice(member.index, 1, toQuery(updateWith));
102+
103+
this.updatedQuery = {
104+
...this.updatedQuery,
105+
[e]: members,
106+
};
107+
};
108+
109+
childProps[`remove${name}`] = (member) => {
110+
const members = (this.updatedQuery[e] || []).concat([]);
73111
members.splice(member.index, 1);
74112

75-
this.query = {
76-
...this.query,
77-
[memberType]: members,
113+
this.updatedQuery = {
114+
...this.updatedQuery,
115+
[e]: members,
78116
};
79-
},
80-
update(member, updateWith) {
81-
const members = (this.query[memberType] || []).concat([]);
82-
members.splice(member.index, 1, toQuery(updateWith));
117+
};
83118

84-
this.query = {
85-
...this.query,
86-
[memberType]: members,
119+
childProps[`set${name}`] = (members) => {
120+
this.updatedQuery = {
121+
...this.updatedQuery,
122+
[e]: members.map(e => e.name) || [],
87123
};
124+
};
125+
});
126+
127+
return createElement(QueryRenderer, {
128+
props: {
129+
query: this.validatedQuery,
130+
cubejsApi,
131+
builderProps: childProps,
88132
},
133+
scopedSlots: this.$scopedSlots,
89134
});
135+
} else {
136+
return null;
137+
}
138+
},
139+
computed: {
140+
isQueryPresent() {
141+
const { updatedQuery: query } = this;
90142

91-
const granularities = [
92-
{ name: 'hour', title: 'Hour' },
93-
{ name: 'day', title: 'Day' },
94-
{ name: 'week', title: 'Week' },
95-
{ name: 'month', title: 'Month' },
96-
{ name: 'year', title: 'Year' },
97-
];
143+
return query.measures && query.measures.length > 0 ||
144+
query.dimensions && query.dimensions.length > 0 ||
145+
query.timeDimensions && query.timeDimensions.length > 0;
146+
},
147+
validatedQuery() {
148+
const { updatedQuery } = this;
98149

99150
return {
100-
meta: this.meta,
101-
query: this.query,
102-
validatedQuery: this.validatedQuery,
103-
isQueryPresent: this.isQueryPresent,
104-
chartType: this.chartType,
105-
measures: (this.meta && this.query.measures || [])
106-
.map((m, i) => ({ index: i, ...this.meta.resolveMember(m, 'measures') })),
107-
dimensions: (this.meta && this.query.dimensions || [])
108-
.map((m, i) => ({ index: i, ...this.meta.resolveMember(m, 'dimensions') })),
109-
segments: (this.meta && this.query.segments || [])
110-
.map((m, i) => ({ index: i, ...this.meta.resolveMember(m, 'segments') })),
111-
timeDimensions: (this.meta && this.query.timeDimensions || [])
112-
.map((m, i) => ({
113-
...m,
114-
dimension: { ...this.meta.resolveMember(m.dimension, 'dimensions'), granularities },
115-
index: i
116-
})),
117-
filters: (this.meta && this.query.filters || [])
118-
.map((m, i) => ({
119-
...m,
120-
dimension: this.meta.resolveMember(m.dimension, ['dimensions', 'measures']),
121-
operators: this.meta.filterOperatorsForMember(m.dimension, ['dimensions', 'measures']),
122-
index: i
123-
})),
124-
availableMeasures: this.meta && this.meta.membersForQuery(this.query, 'measures') || [],
125-
availableDimensions: this.meta && this.meta.membersForQuery(this.query, 'dimensions') || [],
126-
availableTimeDimensions: (
127-
this.meta && this.meta.membersForQuery(this.query, 'dimensions') || []
128-
).filter(m => m.type === 'time'),
129-
availableSegments: this.meta && this.meta.membersForQuery(this.query, 'segments') || [],
130-
131-
updateMeasures: updateMethods('measures'),
132-
updateDimensions: updateMethods('dimensions'),
133-
updateSegments: updateMethods('segments'),
134-
updateTimeDimensions: updateMethods('timeDimensions', toTimeDimension),
135-
updateFilters: updateMethods('filters', toFilter),
136-
updateChartType: (chartType) => { this.chartType = chartType; },
151+
...updatedQuery,
152+
filters: (updatedQuery.filters || []).filter(f => f.operator),
137153
};
138154
},
139155
},
156+
methods: {
157+
updateChart(chartType) {
158+
this.chartType = chartType;
159+
},
160+
},
140161
});

packages/cubejs-vue/src/QueryRenderer.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export default Vue.component('QueryRenderer', {
4343
render(createElement) {
4444
const { resultSet, error, loading, sqlQuery } = this;
4545

46-
if (!loading) {
46+
if (!loading && resultSet) {
4747
const slotProps = {
4848
resultSet: this.queries ? (resultSet || {}) : resultSet,
4949
error,
@@ -57,7 +57,16 @@ export default Vue.component('QueryRenderer', {
5757
this.$scopedSlots.default(slotProps),
5858
);
5959
} else {
60-
return null;
60+
61+
return createElement(
62+
'div',
63+
this.$scopedSlots.default({
64+
loading,
65+
error,
66+
sqlQuery,
67+
...this.builderProps,
68+
}),
69+
);
6170
}
6271
},
6372
methods: {
@@ -100,4 +109,16 @@ export default Vue.component('QueryRenderer', {
100109
}
101110
},
102111
},
112+
watch: {
113+
query(val) {
114+
if (val) {
115+
this.load(val);
116+
}
117+
},
118+
queries(val) {
119+
if (val) {
120+
this.loadQueries(val);
121+
}
122+
},
123+
},
103124
});

0 commit comments

Comments
 (0)