Skip to content

Commit

Permalink
feat: add basic vue support (#65)
Browse files Browse the repository at this point in the history
  • Loading branch information
richipargo authored and paveltiunov committed Apr 1, 2019
1 parent 7f5df92 commit f45468b
Show file tree
Hide file tree
Showing 11 changed files with 12,628 additions and 30 deletions.
21 changes: 21 additions & 0 deletions packages/cubejs-vue/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.DS_Store
node_modules
/dist

# local env files
.env.local
.env.*.local

# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw*
29 changes: 29 additions & 0 deletions packages/cubejs-vue/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# cubejs-vue

## Project setup
```
yarn install
```

### Compiles and hot-reloads for development
```
yarn run serve
```

### Compiles and minifies for production
```
yarn run build
```

### Run your tests
```
yarn run test
```

### Lints and fixes files
```
yarn run lint
```

### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
5 changes: 5 additions & 0 deletions packages/cubejs-vue/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/app'
]
}
57 changes: 57 additions & 0 deletions packages/cubejs-vue/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{
"name": "@cubejs-client/vue",
"version": "0.1.0",
"description": "Vue.js components for cube.js",
"author": "Ricardo Tapia",
"license": "MIT",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"core-js": "^2.6.5",
"ramda": "^0.26.1",
"vue": "^2.6.6"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^3.5.0",
"@vue/cli-plugin-eslint": "^3.5.0",
"@vue/cli-service": "^3.5.0",
"babel-eslint": "^10.0.1",
"eslint": "^5.8.0",
"eslint-plugin-vue": "^5.0.0",
"vue-template-compiler": "^2.5.21"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"rules": {},
"parserOptions": {
"parser": "babel-eslint"
}
},
"postcss": {
"plugins": {
"autoprefixer": {}
}
},
"main": "dist/cubejs-vue.js",
"module": "dist/cubejs-vue.esm.js",
"files": [
"dist",
"src"
],
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
]
}
139 changes: 139 additions & 0 deletions packages/cubejs-vue/src/QueryBuilder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import Vue from 'vue';
import QueryRenderer from './QueryRenderer';

export default Vue.component('QueryBuilder', {
components: {
QueryRenderer,
},
props: {
query: {
type: Object,
},
cubejsApi: {
type: Object,
required: true,
},
},
async mounted() {
this.meta = await this.cubejsApi.meta();
},
render(createElement) {
const { cubejsApi } = this;

return createElement(QueryRenderer, {
props: {
query: this.validatedQuery(),
cubejsApi,
},
// TODO: check passable props
}, this.$scopedSlots.default(this.prepareRenderProps()));
},
methods: {
isQueryPresent() {
const { query } = this;

return query.measures && query.measures.length ||
query.dimensions && query.dimensions.length ||
query.timeDimensions && query.timeDimensions.length;
},
validatedQuery() {
const { query } = this;

return {
...query,
filters: (query.filters || []).filter(f => f.operator),
};
},
prepareRenderProps(queryRendererProps) {
const getName = member => member.name;
const toTimeDimension = member => ({
dimension: member.dimension.name,
granularity: member.granularity,
dateRange: member.dateRange,
});
const toFilter = member => ({
dimension: member.dimension.name,
operator: member.operator,
values: member.values,
});

const updateMethods = (memberType, toQuery = getName) => ({
add(member) {
this.query = {
...this.query,
[memberType]: (this.query[memberType] || []).concat(toQuery(member)),
};
},
remove(member) {
const members = (this.query[memberType] || []).concat([]);
members.splice(member.index, 1);

// TODO: check return state
this.query = {
...this.query,
[memberType]: members,
};
},
update(member, updateWith) {
const members = (this.query[memberType] || []).concat([]);
members.splice(member.index, 1, toQuery(updateWith));

// TODO: check return state
this.query = {
...this.query,
[memberType]: members,
};
},
});

const granularities = [
{ name: 'hour', title: 'Hour' },
{ name: 'day', title: 'Day' },
{ name: 'week', title: 'Week' },
{ name: 'month', title: 'Month' },
{ name: 'year', title: 'Year' },
];

return {
meta: this.meta,
query: this.query,
validatedQuery: this.validatedQuery(),
isQueryPresent: this.isQueryPresent(),
chartType: this.chartType,
measures: (this.meta && this.query.measures || [])
.map((m, i) => ({ index: i, ...this.meta.resolveMember(m, 'measures') })),
dimensions: (this.meta && this.query.dimensions || [])
.map((m, i) => ({ index: i, ...this.meta.resolveMember(m, 'dimensions') })),
segments: (this.meta && this.query.segments || [])
.map((m, i) => ({ index: i, ...this.meta.resolveMember(m, 'segments') })),
timeDimensions: (this.meta && this.query.timeDimensions || [])
.map((m, i) => ({
...m,
dimension: { ...this.meta.resolveMember(m.dimension, 'dimensions'), granularities },
index: i
})),
filters: (this.meta && this.query.filters || [])
.map((m, i) => ({
...m,
dimension: this.meta.resolveMember(m.dimension, ['dimensions', 'measures']),
operators: this.meta.filterOperatorsForMember(m.dimension, ['dimensions', 'measures']),
index: i
})),
availableMeasures: this.meta && this.meta.membersForQuery(this.query, 'measures') || [],
availableDimensions: this.meta && this.meta.membersForQuery(this.query, 'dimensions') || [],
availableTimeDimensions: (
this.meta && this.meta.membersForQuery(this.query, 'dimensions') || []
).filter(m => m.type === 'time'),
availableSegments: this.meta && this.meta.membersForQuery(this.query, 'segments') || [],

updateMeasures: updateMethods('measures'),
updateDimensions: updateMethods('dimensions'),
updateSegments: updateMethods('segments'),
updateTimeDimensions: updateMethods('timeDimensions', toTimeDimension),
updateFilters: updateMethods('filters', toFilter),
updateChartType: (chartType) => { this.chartType = chartType; },
...queryRendererProps,
};
},
},
});
96 changes: 96 additions & 0 deletions packages/cubejs-vue/src/QueryRenderer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import Vue from 'vue';
import { toPairs, fromPairs } from 'ramda';

export default Vue.component('QueryRenderer', {
props: {
query: {
type: Object,
default: () => ({}),
},
queries: {
type: Object,
},
cubejsApi: {
type: Object,
required: true,
},
},
data() {
return {
mutexObj: {},
error: undefined,
resultSet: undefined,
loadingState: false,
sqlQuery: undefined,
};
},
async mounted() {
const { query, queries } = this;

if (query) {
await this.load(query);
}

if (queries) {
await this.loadQueries(queries);
}
},
// TODO: handle update
render(createElement) {
const { resultSet, error, loading, sqlQuery } = this;

if (this.$slots.default) {
return createElement(
'div',
this.$scopedSlots.default({
resultSet: this.queries ? (resultSet || {}) : resultSet,
error,
loadingState: { isLoading: loading, },
sqlQuery,
}),
);
} else {
return null;
}
},
methods: {
async load(query) {
try {
this.loading = true;

if (query && Object.keys(query).length) {
if (this.loadSql === 'only') {
this.sqlQuery = await this.cubejsApi.sql(query, { mutexObj: this.mutexObj, mutexKey: 'sql' });
} else if (this.loadSql) {
this.sqlQuery = await this.cubejsApi.sql(query, { mutexObj: this.mutexObj, mutexKey: 'sql' });
this.resultSet = await this.cubejsApi.load(query, { mutexObj: this.mutexObj, mutexKey: 'query' });
} else {
this.resultSet = await this.cubejsApi.load(query, { mutexObj: this.mutexObj, mutexKey: 'query' });
}
}

this.loading = false;
} catch (exc) {
this.error = exc;
this.resultSet = undefined;
this.loading = false;
}
},
async loadQueries(queries) {
try {
this.loading = true;

const resultPromises = Promise.all(toPairs(queries).map(
([name, query]) =>
this.cubejsApi.load(query, { mutexObj: this.mutexObj, mutexKey: name }).then(r => [name, r])
));

this.resultSet = fromPairs(await resultPromises);
this.loading = false;
} catch (exc) {
this.error = exc;
this.loading = false;
}
},
},
});
Empty file.
7 changes: 7 additions & 0 deletions packages/cubejs-vue/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import QueryRenderer from './QueryRenderer';
// import QueryRendererWithTotals from './QueryRendererWithTotals.vue';
import QueryBuilder from './QueryBuilder';

export { QueryRenderer, QueryBuilder };

export default {};
Loading

0 comments on commit f45468b

Please sign in to comment.