Skip to content

Commit a4ba9c5

Browse files
committed
feat(playground): Support various chart libraries for dashboard generation
1 parent ce067a9 commit a4ba9c5

File tree

5 files changed

+147
-33
lines changed

5 files changed

+147
-33
lines changed

packages/cubejs-playground/src/ChartContainer.jsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import PropTypes from 'prop-types';
1313
import PrismCode from './PrismCode';
1414
import { playgroundAction } from './events';
1515

16-
const frameworks = [{
16+
export const frameworks = [{
1717
id: 'vanilla',
1818
title: 'Vanilla JavaScript',
1919
docsLink: 'https://cube.dev/docs/@cubejs-client-core'
@@ -103,8 +103,6 @@ class ChartContainer extends React.Component {
103103

104104
const parameters = getParameters(this.codeSandboxDefinition(codeSandboxSource, dependencies));
105105

106-
console.log(chartLibraries);
107-
108106
const chartLibrariesMenu = (
109107
<Menu
110108
onClick={(e) => {

packages/cubejs-playground/src/ChartRenderer.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,12 @@ const rootElement = document.getElementById("root");
6868
ReactDOM.render(<ChartRenderer />, rootElement);
6969
`;
7070

71-
export const selectChartLibrary = (chartType, chartLibrary) => {
72-
return ['table', 'number'].indexOf(chartType) !== -1
73-
? tablesLibrary : libraryToTemplate[chartLibrary].library;
74-
};
71+
export const selectChartLibrary = (chartType, chartLibrary) => (
72+
['table', 'number'].indexOf(chartType) !== -1 ? tablesLibrary : libraryToTemplate[chartLibrary].library
73+
);
74+
75+
export const chartLibraries = Object.keys(libraryToTemplate)
76+
.map(k => ({ value: k, title: libraryToTemplate[k].title }));
7577

7678
export const ChartRenderer = (props) => {
7779
const {
@@ -121,7 +123,7 @@ export const ChartRenderer = (props) => {
121123
dashboardSource={dashboardSource}
122124
chartLibrary={chartLibrary}
123125
setChartLibrary={setChartLibrary}
124-
chartLibraries={Object.keys(libraryToTemplate).map(k => ({ value: k, title: libraryToTemplate[k].title }))}
126+
chartLibraries={chartLibraries}
125127
cubejsApi={cubejsApi}
126128
render={() => (jsCompilingError ? (
127129
<Alert

packages/cubejs-playground/src/DashboardPage.js

Lines changed: 109 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
/* globals window */
22
import React, { Component } from 'react';
3-
import { Spin, Button, Alert } from 'antd';
4-
import { Link } from "react-router-dom";
3+
import {
4+
Spin, Button, Alert, Menu, Dropdown, Icon, Form
5+
} from 'antd';
56
import DashboardSource from "./DashboardSource";
67
import fetch from './playgroundFetch';
8+
import { frameworks } from "./ChartContainer";
9+
import { playgroundAction } from "./events";
10+
import { chartLibraries } from "./ChartRenderer";
711

812
const Frame = ({ children }) => (
913
<div style={{ textAlign: 'center', marginTop: 50 }}>
@@ -12,15 +16,18 @@ const Frame = ({ children }) => (
1216
);
1317

1418
const Hint = () => (
15-
<p style={{width: 450, margin: "20px auto"}}>
19+
<p style={{ width: 450, margin: "20px auto" }}>
1620
Dashboard App is a convenient way to setup and deploy frontend app to work with Cube.js backend. You can learn more about it the <a href="https://cube.dev/docs/dashboard-app" target="_blankl">Cube.js docs</a>.
1721
</p>
1822
);
1923

2024
class DashboardPage extends Component {
2125
constructor(props) {
2226
super(props);
23-
this.state = {};
27+
this.state = {
28+
chartLibrary: chartLibraries[0].value,
29+
framework: 'react'
30+
};
2431
}
2532

2633
async componentDidMount() {
@@ -29,11 +36,12 @@ class DashboardPage extends Component {
2936
}
3037

3138
async loadDashboard(createApp) {
39+
const { chartLibrary } = this.state;
3240
this.setState({
3341
appCode: null,
3442
loadError: null
3543
});
36-
await this.dashboardSource.load(createApp);
44+
await this.dashboardSource.load(createApp, { chartLibrary });
3745
this.setState({
3846
dashboardStarting: false,
3947
appCode: !this.dashboardSource.loadError && this.dashboardSource.dashboardAppCode(),
@@ -59,6 +67,44 @@ class DashboardPage extends Component {
5967
}
6068

6169
render() {
70+
const { chartLibrary, framework } = this.state;
71+
const currentLibraryItem = chartLibraries.find(m => m.value === chartLibrary);
72+
const frameworkItem = frameworks.find(m => m.id === framework);
73+
74+
const chartLibrariesMenu = (
75+
<Menu
76+
onClick={(e) => {
77+
playgroundAction('Set Chart Library', { chartLibrary: e.key });
78+
this.setState({ chartLibrary: e.key });
79+
}}
80+
>
81+
{
82+
chartLibraries.map(library => (
83+
<Menu.Item key={library.value}>
84+
{library.title}
85+
</Menu.Item>
86+
))
87+
}
88+
</Menu>
89+
);
90+
91+
const frameworkMenu = (
92+
<Menu
93+
onClick={(e) => {
94+
playgroundAction('Set Framework', { framework: e.key });
95+
this.setState({ framework: e.key });
96+
}}
97+
>
98+
{
99+
frameworks.map(f => (
100+
<Menu.Item key={f.id}>
101+
{f.title}
102+
</Menu.Item>
103+
))
104+
}
105+
</Menu>
106+
);
107+
62108
const {
63109
appCode, dashboardPort, loadError, dashboardRunning, dashboardStarting, dashboardAppPath
64110
} = this.state;
@@ -68,11 +114,54 @@ class DashboardPage extends Component {
68114
<h2>
69115
{loadError}
70116
</h2>
71-
<p style={{marginTop: 25}}>
117+
<Form layout="inline">
118+
<Form.Item>
119+
<Dropdown overlay={frameworkMenu}>
120+
<Button>
121+
{frameworkItem && frameworkItem.title}
122+
<Icon type="down" />
123+
</Button>
124+
</Dropdown>
125+
</Form.Item>
126+
<Form.Item>
127+
<Dropdown
128+
overlay={chartLibrariesMenu}
129+
disabled={!!frameworkItem.docsLink}
130+
>
131+
<Button>
132+
{currentLibraryItem && currentLibraryItem.title}
133+
<Icon type="down" />
134+
</Button>
135+
</Dropdown>
136+
</Form.Item>
137+
</Form>
138+
{
139+
frameworkItem && frameworkItem.docsLink && (
140+
<h2 style={{ paddingTop: 24, textAlign: 'center' }}>
141+
We do not support&nbsp;
142+
{frameworkItem.title}
143+
&nbsp;dashboard scaffolding generation yet.
144+
< br/>
145+
Please refer to&nbsp;
146+
<a
147+
href={frameworkItem.docsLink}
148+
target="_blank"
149+
rel="noopener noreferrer"
150+
onClick={() => playgroundAction('Unsupported Dashboard Framework Docs', { framework })}
151+
>
152+
{frameworkItem.title}
153+
&nbsp;docs
154+
</a>
155+
&nbsp;to see on how to use it with Cube.js.
156+
</h2>
157+
)
158+
}
159+
<p style={{ marginTop: 25 }}>
72160
<Button
73161
type="primary"
74162
size="large"
75163
onClick={() => this.loadDashboard(true)}
164+
disabled={!!frameworkItem.docsLink}
76165
>
77166
Create dashboard app template in your project directory
78167
</Button>
@@ -101,7 +190,10 @@ class DashboardPage extends Component {
101190
Dashboard App is not running
102191
</h2>
103192
<h3>
104-
Please start dashboard app or run it manually using <code class="inline-code">$ npm run start</code><br /> in&nbsp;
193+
Please start dashboard app or run it manually using
194+
<code className="inline-code">$ npm run start</code>
195+
<br />
196+
in&nbsp;
105197
<b>{dashboardAppPath}</b>
106198
&nbsp;directory.
107199
</h3>
@@ -121,7 +213,11 @@ class DashboardPage extends Component {
121213
}
122214
const devServerUrl = `http://${window.location.hostname}:${dashboardPort}`;
123215
return (
124-
<div style={{ height: '100%', width: '100%', padding: "15px 30px 30px 30px", background: "#fff" }}>
216+
<div
217+
style={{
218+
height: '100%', width: '100%', padding: "15px 30px 30px 30px", background: "#fff"
219+
}}
220+
>
125221
<Alert
126222
message={(
127223
<span>
@@ -131,8 +227,11 @@ class DashboardPage extends Component {
131227
Dev server is running at&nbsp;
132228
<a href={devServerUrl} target="_blank" rel="noopener noreferrer">{devServerUrl}</a>
133229
.
134-
Learn more how to customize and deploy it at <a href="https://cube.dev/docs/dashboard-app">Cube.js&nbsp;docs</a>.&nbsp;
135-
<a onClick={() => window.location.reload()} style={{ cursor: 'pointer' }}>Refresh page</a> if it is empty.
230+
Learn more how to customize and deploy it at
231+
<a href="https://cube.dev/docs/dashboard-app">Cube.js&nbsp;docs</a>
232+
.&nbsp;
233+
<a onClick={() => window.location.reload()} style={{ cursor: 'pointer' }}>Refresh page</a>
234+
if it is empty.
136235
</span>
137236
)}
138237
type="info"

packages/cubejs-playground/src/DashboardSource.js

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import traverse from "@babel/traverse";
33
import fetch from './playgroundFetch';
44
import AppSnippet from './source/AppSnippet';
55
import TargetSource from './source/TargetSource';
6-
import ChartSnippet from "./source/ChartSnippet";
76
import ScaffoldingSources from "./codegen/ScaffoldingSources";
87
import MergeScaffolding from "./source/MergeScaffolding";
98
import IndexSnippet from "./source/IndexSnippet";
@@ -21,7 +20,7 @@ body {
2120
const fetchWithRetry = (url, options, retries) => fetch(url, { ...options, retries });
2221

2322
class DashboardSource {
24-
async load(createApp) {
23+
async load(createApp, { chartLibrary }) {
2524
this.loadError = null;
2625
if (createApp) {
2726
await fetchWithRetry('/playground/ensure-dashboard-app', undefined, 5);
@@ -37,7 +36,7 @@ class DashboardSource {
3736
this.filesToPersist = [];
3837
this.parse(result.fileContents);
3938
}
40-
if (!result.error && this.ensureDashboardIsInApp()) {
39+
if (!result.error && this.ensureDashboardIsInApp({ chartLibrary })) {
4140
await this.persist();
4241
}
4342
}
@@ -83,13 +82,7 @@ class DashboardSource {
8382
.map(i => {
8483
const importName = i.get('source').node.value.split('/');
8584
const dependency = importName[0].indexOf('@') === 0 ? [importName[0], importName[1]].join('/') : importName[0];
86-
if (dependency === 'graphql-tag') {
87-
return {
88-
graphql: 'latest',
89-
[dependency]: 'latest'
90-
};
91-
}
92-
return { [dependency]: 'latest' };
85+
return this.withPeerDependencies(dependency);
9386
}).reduce((a, b) => ({ ...a, ...b }));
9487
await fetchWithRetry('/playground/ensure-dependencies', {
9588
method: 'POST',
@@ -102,6 +95,26 @@ class DashboardSource {
10295
}, 5);
10396
}
10497

98+
// TODO move to dev server
99+
withPeerDependencies(dependency) {
100+
let result = {
101+
[dependency]: 'latest'
102+
};
103+
if (dependency === 'graphql-tag') {
104+
result = {
105+
...result,
106+
graphql: 'latest'
107+
};
108+
}
109+
if (dependency === 'react-chartjs-2') {
110+
result = {
111+
...result,
112+
'chart.js': 'latest'
113+
};
114+
}
115+
return result;
116+
}
117+
105118
parse(sourceFiles) {
106119
this.appFile = sourceFiles.find(f => f.fileName.indexOf('src/App.js') !== -1);
107120
if (!this.appFile) {
@@ -111,7 +124,7 @@ class DashboardSource {
111124
this.appTargetSource = this.targetSourceByFile('/src/App.js');
112125
}
113126

114-
ensureDashboardIsInApp() {
127+
ensureDashboardIsInApp({ chartLibrary }) {
115128
let dashboardAdded = false;
116129
let headerElement = null;
117130
traverse(this.appTargetSource.ast, {
@@ -138,7 +151,7 @@ class DashboardSource {
138151
appSnippet.mergeTo(this.appTargetSource);
139152
this.mergeSnippetToFile(new IndexSnippet(this.playgroundContext), '/src/index.js');
140153
this.mergeSnippetToFile(new ExploreSnippet(), '/src/ExplorePage.js');
141-
this.mergeSnippetToFile(new ChartRendererSnippet(), '/src/ChartRenderer.js');
154+
this.mergeSnippetToFile(new ChartRendererSnippet(chartLibrary), '/src/ChartRenderer.js');
142155
this.mergeSnippetToFile(new DashboardStoreSnippet(), '/src/DashboardStore.js');
143156
this.mergeSnippetToFile(new SourceSnippet(ScaffoldingSources['react/DashboardPage.js']), '/src/DashboardPage.js');
144157
merged = true;
@@ -174,6 +187,7 @@ class DashboardSource {
174187
snippet.mergeTo(targetSource);
175188
}
176189

190+
/*
177191
async addChart(chartCode) {
178192
await this.load(true);
179193
if (this.loadError) {
@@ -184,6 +198,7 @@ class DashboardSource {
184198
this.mergeSnippetToFile(chartSnippet, '/src/DashboardPage.js');
185199
await this.persist();
186200
}
201+
*/
187202

188203
dashboardAppCode() {
189204
return this.appTargetSource.code();

packages/cubejs-playground/src/source/ChartRendererSnippet.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@ import { selectChartLibrary } from "../ChartRenderer";
44
import ChartTypeSnippet from "./ChartTypeSnippet";
55

66
class ChartRendererSnippet extends SourceSnippet {
7-
constructor() {
7+
constructor(chartLibrary) {
88
super(ScaffoldingSources['react/ChartRenderer.js']);
9+
this.chartLibrary = chartLibrary;
910
}
1011

1112
mergeTo(targetSource) {
1213
super.mergeTo(targetSource);
1314
const chartTypes = ['line', 'bar', 'area', 'pie', 'table', 'number'];
14-
const chartLibraryId = 'bizcharts'; // TODO
1515
chartTypes.forEach(chartType => {
16-
const chartLibrary = selectChartLibrary(chartType, chartLibraryId);
16+
const chartLibrary = selectChartLibrary(chartType, this.chartLibrary);
1717
const chartSnippet = new ChartTypeSnippet(
1818
chartLibrary.sourceCodeTemplate({ chartType, renderFnName: 'render' }),
1919
chartType

0 commit comments

Comments
 (0)