Skip to content

Commit 2458aad

Browse files
committed
feat(playground): Static dashboard template
1 parent 8e6a38f commit 2458aad

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+521
-142
lines changed

packages/cubejs-playground/src/ChartContainer.jsx

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
/* global navigator */
22
import React from 'react';
33
import {
4-
Card, Button, Menu, Dropdown, Icon, notification
4+
Card, Button, Menu, Dropdown, Icon, notification, Modal
55
} from 'antd';
66
import { getParameters } from 'codesandbox-import-utils/lib/api/define';
77
import { fetch } from 'whatwg-fetch';
88
import { map } from 'ramda';
9-
import { Redirect } from 'react-router-dom';
9+
import { Redirect, withRouter } from 'react-router-dom';
1010
import { QueryRenderer } from '@cubejs-client/react';
1111
import sqlFormatter from "sql-formatter";
1212
import PropTypes from 'prop-types';
@@ -94,7 +94,8 @@ class ChartContainer extends React.Component {
9494
cubejsApi,
9595
chartLibrary,
9696
setChartLibrary,
97-
chartLibraries
97+
chartLibraries,
98+
history
9899
} = this.props;
99100

100101
if (redirectToDashboard) {
@@ -143,23 +144,42 @@ class ChartContainer extends React.Component {
143144
<form action="https://codesandbox.io/api/v1/sandboxes/define" method="POST" target="_blank">
144145
<input type="hidden" name="parameters" value={parameters} />
145146
<Button.Group>
146-
{/* TODO: implement add to static dashboard */}
147-
{/*{dashboardSource && (
147+
{dashboardSource && (
148148
<Button
149149
onClick={async () => {
150-
playgroundAction('Add to Dashboard');
151150
this.setState({ addingToDashboard: true });
152-
await dashboardSource.addChart(codeExample);
153-
this.setState({ redirectToDashboard: true, addingToDashboard: false });
151+
const canAddChart = await dashboardSource.canAddChart();
152+
if (typeof canAddChart === 'boolean' && canAddChart) {
153+
playgroundAction('Add to Dashboard');
154+
await dashboardSource.addChart(codeExample);
155+
this.setState({ redirectToDashboard: true, addingToDashboard: false });
156+
} else if (!canAddChart) {
157+
this.setState({ addingToDashboard: false });
158+
Modal.error({
159+
title: 'Your dashboard app does not support adding of static charts',
160+
content: 'Please use static dashboard template'
161+
});
162+
} else {
163+
this.setState({ addingToDashboard: false });
164+
Modal.error({
165+
title: 'There is an error loading your dashboard app',
166+
content: canAddChart,
167+
okText: 'Fix',
168+
okCancel: true,
169+
onOk() {
170+
history.push('/dashboard');
171+
}
172+
});
173+
}
154174
}}
155175
icon="plus"
156176
size="small"
157177
loading={addingToDashboard}
158178
disabled={!!frameworkItem.docsLink}
159179
>
160-
{addingToDashboard ? 'Installing modules. It may take a while. Please check console for progress...' : 'Add to Dashboard'}
180+
{addingToDashboard ? 'Preparing dashboard app. It may take a while. Please check console for progress...' : 'Add to Dashboard'}
161181
</Button>
162-
)}*/}
182+
)}
163183
<Dropdown overlay={frameworkMenu}>
164184
<Button size="small">
165185
{frameworkItem && frameworkItem.title}
@@ -342,6 +362,7 @@ ChartContainer.propTypes = {
342362
hideActions: PropTypes.array,
343363
query: PropTypes.object,
344364
cubejsApi: PropTypes.object,
365+
history: PropTypes.object.isRequired,
345366
chartLibrary: PropTypes.string.isRequired,
346367
setChartLibrary: PropTypes.func.isRequired,
347368
chartLibraries: PropTypes.array.isRequired
@@ -358,4 +379,4 @@ ChartContainer.defaultProps = {
358379
resultSet: null
359380
};
360381

361-
export default ChartContainer;
382+
export default withRouter(ChartContainer);

packages/cubejs-playground/src/DashboardPage.js

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ class DashboardPage extends Component {
2626
super(props);
2727
this.state = {
2828
chartLibrary: chartLibraries[0].value,
29-
framework: 'react'
29+
framework: 'react',
30+
templatePackageName: 'react-antd-dynamic'
3031
};
3132
}
3233

@@ -36,13 +37,13 @@ class DashboardPage extends Component {
3637
}
3738

3839
async loadDashboard(createApp) {
39-
const { chartLibrary } = this.state;
40+
const { chartLibrary, templatePackageName } = this.state;
4041
this.setState({
4142
appCode: null,
4243
loadError: null
4344
});
4445
try {
45-
await this.dashboardSource.load(createApp, { chartLibrary });
46+
await this.dashboardSource.load(createApp, { chartLibrary, templatePackageName });
4647
this.setState({
4748
dashboardStarting: false,
4849
appCode: !this.dashboardSource.loadError && this.dashboardSource.dashboardAppCode(),
@@ -75,9 +76,11 @@ class DashboardPage extends Component {
7576
}
7677

7778
render() {
78-
const { chartLibrary, framework } = this.state;
79+
const { chartLibrary, framework, templatePackageName } = this.state;
7980
const currentLibraryItem = chartLibraries.find(m => m.value === chartLibrary);
8081
const frameworkItem = frameworks.find(m => m.id === framework);
82+
const templatePackage = this.dashboardSource && this.dashboardSource.templatePackages
83+
.find(m => m.name === templatePackageName);
8184

8285
const chartLibrariesMenu = (
8386
<Menu
@@ -113,6 +116,23 @@ class DashboardPage extends Component {
113116
</Menu>
114117
);
115118

119+
const templatePackagesMenu = (
120+
<Menu
121+
onClick={(e) => {
122+
playgroundAction('Set Template Package', { templatePackageName: e.key });
123+
this.setState({ templatePackageName: e.key });
124+
}}
125+
>
126+
{
127+
(this.dashboardSource && this.dashboardSource.templatePackages || []).map(f => (
128+
<Menu.Item key={f.name}>
129+
{f.description}
130+
</Menu.Item>
131+
))
132+
}
133+
</Menu>
134+
);
135+
116136
const {
117137
appCode, dashboardPort, loadError, dashboardRunning, dashboardStarting, dashboardAppPath
118138
} = this.state;
@@ -131,6 +151,17 @@ class DashboardPage extends Component {
131151
</Button>
132152
</Dropdown>
133153
</Form.Item>
154+
<Form.Item>
155+
<Dropdown
156+
overlay={templatePackagesMenu}
157+
disabled={!!frameworkItem.docsLink}
158+
>
159+
<Button>
160+
{templatePackage && templatePackage.description}
161+
<Icon type="down" />
162+
</Button>
163+
</Dropdown>
164+
</Form.Item>
134165
<Form.Item>
135166
<Dropdown
136167
overlay={chartLibrariesMenu}

packages/cubejs-playground/src/DashboardSource.js

Lines changed: 60 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
/* globals window */
22
import traverse from "@babel/traverse";
3+
import { uniq } from 'ramda';
34
import fetch from './playgroundFetch';
4-
import AppSnippet from './source/AppSnippet';
55
import TargetSource from './source/TargetSource';
66
import ScaffoldingSources from "./codegen/ScaffoldingSources";
7-
import MergeScaffolding from "./source/MergeScaffolding";
8-
import IndexSnippet from "./source/IndexSnippet";
9-
import ExploreSnippet from "./source/ExploreSnippet";
10-
import ChartRendererSnippet from "./source/ChartRendererSnippet";
11-
import DashboardStoreSnippet from "./source/DashboardStoreSnippet";
127
import SourceSnippet from "./source/SourceSnippet";
8+
import ReactAntdDynamicTemplate from "./source/ReactAntdDynamicTemplate";
9+
import ReactAntdStaticTemplate from "./source/ReactAntdStaticTemplate";
10+
import AppCredentialsTemplate from "./source/AppCredentialsTemplate";
11+
import ChartRendererTemplate from "./source/ChartRendererTemplate";
12+
import StaticChartTemplate from "./source/StaticChartTemplate";
1313

1414
const indexCss = `
1515
@import '~antd/dist/antd.css';
@@ -21,7 +21,7 @@ body {
2121
const fetchWithRetry = (url, options, retries) => fetch(url, { ...options, retries });
2222

2323
class DashboardSource {
24-
async load(createApp, { chartLibrary }) {
24+
async load(createApp, { chartLibrary, templatePackageName } = {}) {
2525
this.loadError = null;
2626
if (createApp) {
2727
await fetchWithRetry('/playground/ensure-dashboard-app', undefined, 10);
@@ -37,8 +37,10 @@ class DashboardSource {
3737
this.filesToPersist = [];
3838
this.parse(result.fileContents);
3939
}
40-
if (!result.error && this.ensureDashboardIsInApp({ chartLibrary })) {
41-
await this.persist();
40+
if (chartLibrary && templatePackageName) {
41+
if (!result.error && this.ensureDashboardIsInApp({ chartLibrary, templatePackageName })) {
42+
await this.persist();
43+
}
4244
}
4345
if (!result.error) {
4446
await this.ensureDependencies({});
@@ -132,7 +134,14 @@ class DashboardSource {
132134
this.appTargetSource = this.targetSourceByFile('/src/App.js');
133135
}
134136

135-
ensureDashboardIsInApp({ chartLibrary }) {
137+
get templatePackages() {
138+
return [
139+
new ReactAntdDynamicTemplate(),
140+
new ReactAntdStaticTemplate()
141+
];
142+
}
143+
144+
ensureDashboardIsInApp({ chartLibrary, templatePackageName }) {
136145
let dashboardAdded = false;
137146
let headerElement = null;
138147
traverse(this.appTargetSource.ast, {
@@ -155,55 +164,73 @@ class DashboardSource {
155164
let merged = false;
156165
if (!dashboardAdded && headerElement) {
157166
this.appLayoutAdded = true;
158-
const scaffoldingFileToSnippet = {
159-
'react/App.js': new AppSnippet(this.playgroundContext),
160-
'react/index.js': new IndexSnippet(),
161-
'react/pages/ExplorePage.js': new ExploreSnippet(),
162-
'react/components/ChartRenderer.js': new ChartRendererSnippet(chartLibrary)
163-
};
164167

165-
const scaffoldingFileNames = Object.keys(ScaffoldingSources)
166-
.filter(fileName => fileName.indexOf('react/') === 0);
168+
const templatesToApply = [this.templatePackages.find(t => t.name === templatePackageName)].concat([
169+
new AppCredentialsTemplate(this.playgroundContext),
170+
new ChartRendererTemplate(chartLibrary)
171+
]);
172+
173+
this.applyTemplatePackages(templatesToApply);
167174

168-
scaffoldingFileNames.forEach(scaffoldingFile => {
169-
this.mergeSnippetToFile(
170-
scaffoldingFileToSnippet[scaffoldingFile] || new SourceSnippet(ScaffoldingSources[scaffoldingFile]),
171-
MergeScaffolding.targetSourceName(scaffoldingFile)
172-
);
173-
});
174175
merged = true;
175176
}
176177
return merged;
177178
}
178179

179-
targetSourceByFile(fileName) {
180+
applyTemplatePackages(templatesToApply) {
181+
templatesToApply.forEach(template => {
182+
template.initSources(ScaffoldingSources);
183+
184+
uniq(
185+
Object.keys(template.templateSources).concat(Object.keys(template.fileToSnippet))
186+
).forEach(scaffoldingFile => {
187+
this.mergeSnippetToFile(
188+
template.fileToSnippet[scaffoldingFile] || new SourceSnippet(template.templateSources[scaffoldingFile]),
189+
scaffoldingFile,
190+
template.templateSources[scaffoldingFile]
191+
);
192+
});
193+
});
194+
}
195+
196+
targetSourceByFile(fileName, content) {
180197
let file = this.sourceFiles.find(f => f.fileName === fileName);
181198
if (!file) {
182-
file = { fileName, content: ScaffoldingSources[MergeScaffolding.scaffoldingSourceName(fileName)] };
199+
file = { fileName, content };
183200
}
184201
if (!this.fileToTargetSource[fileName]) {
185202
this.fileToTargetSource[fileName] = new TargetSource(file.fileName, file.content);
186203
}
187204
return this.fileToTargetSource[fileName];
188205
}
189206

190-
mergeSnippetToFile(snippet, fileName) {
191-
const targetSource = this.targetSourceByFile(fileName);
207+
mergeSnippetToFile(snippet, fileName, content) {
208+
const targetSource = this.targetSourceByFile(fileName, content);
192209
snippet.mergeTo(targetSource);
193210
}
194211

195-
/*
212+
async canAddChart() {
213+
await this.load();
214+
if (this.loadError) {
215+
return this.loadError;
216+
}
217+
const dashboardPage = this.targetSourceByFile('/src/pages/DashboardPage.js', '');
218+
const dashboardItemsArray = dashboardPage.definitions.find(
219+
d => d.get('id').node.type === 'Identifier'
220+
&& d.get('id').node.name === 'DashboardItems'
221+
);
222+
return !!dashboardItemsArray;
223+
}
224+
196225
async addChart(chartCode) {
197226
await this.load(true);
198227
if (this.loadError) {
199228
return;
200229
}
201-
this.ensureDashboardIsInApp();
202-
const chartSnippet = new ChartSnippet(chartCode);
203-
this.mergeSnippetToFile(chartSnippet, '/src/DashboardPage.js');
230+
const staticChartTemplate = new StaticChartTemplate(chartCode);
231+
this.applyTemplatePackages([staticChartTemplate]);
204232
await this.persist();
205233
}
206-
*/
207234

208235
dashboardAppCode() {
209236
return this.appTargetSource.code();

packages/cubejs-playground/src/PlaygroundQueryBuilder.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ import {
66
import { QueryBuilder } from '@cubejs-client/react';
77
import { ChartRenderer } from './ChartRenderer';
88
import { playgroundAction } from './events';
9-
import MemberGroup from './scaffolding/react/components/QueryBuilder/MemberGroup';
10-
import FilterGroup from './scaffolding/react/components/QueryBuilder/FilterGroup';
11-
import TimeGroup from './scaffolding/react/components/QueryBuilder/TimeGroup';
12-
import SelectChartType from './scaffolding/react/components/QueryBuilder/SelectChartType';
9+
import MemberGroup from './scaffolding/react-antd-dynamic/src/components/QueryBuilder/MemberGroup';
10+
import FilterGroup from './scaffolding/react-antd-dynamic/src/components/QueryBuilder/FilterGroup';
11+
import TimeGroup from './scaffolding/react-antd-dynamic/src/components/QueryBuilder/TimeGroup';
12+
import SelectChartType from './scaffolding/react-antd-dynamic/src/components/QueryBuilder/SelectChartType';
1313

1414
const playgroundActionUpdateMethods = (updateMethods, memberName) => (
1515
Object.keys(updateMethods).map(method => ({
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
const TypeToChartComponent = {};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const API_URL = undefined;
2+
3+
const CUBEJS_TOKEN = undefined;

0 commit comments

Comments
 (0)