diff --git a/packages/cubejs-playground/src/DashboardPage.js b/packages/cubejs-playground/src/DashboardPage.js
index c647920ee6940..0f720feb4f8e9 100644
--- a/packages/cubejs-playground/src/DashboardPage.js
+++ b/packages/cubejs-playground/src/DashboardPage.js
@@ -1,7 +1,8 @@
+/* globals window */
import React, { Component } from 'react';
import { Spin, Button } from 'antd';
import DashboardSource from "./DashboardSource";
-import DashboardRenderer from './DashboardRenderer';
+import fetch from './playgroundFetch';
class DashboardPage extends Component {
constructor(props) {
@@ -17,19 +18,33 @@ class DashboardPage extends Component {
async loadDashboard(createApp) {
this.setState({
appCode: null,
- sourceFiles: null,
loadError: null
});
await this.dashboardSource.load(createApp);
this.setState({
+ dashboardStarting: false,
appCode: !this.dashboardSource.loadError && this.dashboardSource.dashboardAppCode(),
- sourceFiles: this.dashboardSource.sourceFiles,
loadError: this.dashboardSource.loadError
});
+ const dashboardStatus = await (await fetch('/playground/dashboard-app-status')).json();
+ this.setState({
+ dashboardRunning: dashboardStatus.running,
+ dashboardPort: dashboardStatus.dashboardPort
+ });
+ }
+
+ async startDashboardApp() {
+ this.setState({
+ dashboardStarting: true
+ });
+ await fetch('/playground/start-dashboard-app');
+ await this.loadDashboard();
}
render() {
- const { appCode, sourceFiles, loadError } = this.state;
+ const {
+ appCode, dashboardPort, loadError, dashboardRunning, dashboardStarting
+ } = this.state;
if (loadError) {
return (
@@ -48,13 +63,45 @@ class DashboardPage extends Component {
);
}
- return appCode &&
- || (
+ if (!appCode) {
+ return (
Creating dashboard react-app. It may take several minutes...
);
+ }
+ if (!dashboardRunning) {
+ return (
+
+
+ Dashboard App is not running.
+
+ Please start dashboard app or run it manually using `$ npm run start` in dashboard-app directory.
+
+
+
+
+
+ );
+ }
+ return (
+
+ );
}
}
diff --git a/packages/cubejs-playground/src/DashboardSource.js b/packages/cubejs-playground/src/DashboardSource.js
index ffea6a2589212..b5b3610a53f06 100644
--- a/packages/cubejs-playground/src/DashboardSource.js
+++ b/packages/cubejs-playground/src/DashboardSource.js
@@ -59,7 +59,7 @@ class DashboardSource {
const dependency = importName[0].indexOf('@') === 0 ? [importName[0], importName[1]].join('/') : importName[0];
return { [dependency]: 'latest' };
}).reduce((a, b) => ({ ...a, ...b }));
- fetchWithRetry('/playground/ensure-dependencies', {
+ await fetchWithRetry('/playground/ensure-dependencies', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
diff --git a/packages/cubejs-server-core/core/DevServer.js b/packages/cubejs-server-core/core/DevServer.js
index 9f2537be63852..151ce16875e91 100644
--- a/packages/cubejs-server-core/core/DevServer.js
+++ b/packages/cubejs-server-core/core/DevServer.js
@@ -91,7 +91,8 @@ class DevServer {
if (!await fs.pathExists(dashboardAppPath) || this.createReactAppInit) {
if (!this.createReactAppInit) {
this.cubejsServer.event('Dev Server Create Dashboard App');
- this.createReactAppInit = executeCommand('npx', ['create-react-app', dashboardAppPath]);
+ this.createReactAppInit = executeCommand('npm', ['install', '-g', 'create-react-app'])
+ .then(() => executeCommand('create-react-app', [dashboardAppPath]));
}
await this.createReactAppInit;
this.cubejsServer.event('Dev Server Create Dashboard App Success');
@@ -155,31 +156,76 @@ class DevServer {
app.post('/playground/ensure-dependencies', catchErrors(async (req, res) => {
this.cubejsServer.event('Dev Server App Ensure Dependencies');
const { dependencies } = req.body;
- const packageJson = await fs.readJson(path.join(dashboardAppPath, 'package.json'));
- const toInstall = Object.keys(dependencies).filter(dependency => !packageJson.dependencies[dependency]);
- if (toInstall.length) {
- this.cubejsServer.event('Dev Server Dashboard Npm Install');
- const cmd = () => executeCommand(
- 'npm',
- ['install', '--save'].concat(toInstall),
- { cwd: path.resolve(dashboardAppPath) }
- );
- if (this.curNpmInstall) {
- this.curNpmInstall = this.curNpmInstall.then(cmd);
- } else {
- this.curNpmInstall = cmd();
+ const cmd = async () => {
+ const packageJson = await fs.readJson(path.join(dashboardAppPath, 'package.json'));
+ const toInstall = Object.keys(dependencies).filter(dependency => !packageJson.dependencies[dependency]);
+ if (toInstall.length) {
+ this.cubejsServer.event('Dev Server Dashboard Npm Install');
+ await executeCommand(
+ 'npm',
+ ['install', '--save'].concat(toInstall),
+ { cwd: path.resolve(dashboardAppPath) }
+ );
+ this.cubejsServer.event('Dev Server Dashboard Npm Install Success');
}
- const { curNpmInstall } = this;
- await this.curNpmInstall;
- if (curNpmInstall === this.curNpmInstall) {
- this.curNpmInstall = null;
- }
- await executeCommand('npm', ['install', '--save'].concat(toInstall), { cwd: path.resolve(dashboardAppPath) });
- this.cubejsServer.event('Dev Server Dashboard Npm Install Success');
+ return toInstall;
+ };
+ if (this.curNpmInstall) {
+ this.curNpmInstall = this.curNpmInstall.then(cmd);
+ } else {
+ this.curNpmInstall = cmd();
+ }
+ const { curNpmInstall } = this;
+ const toInstall = await this.curNpmInstall;
+ if (curNpmInstall === this.curNpmInstall) {
+ this.curNpmInstall = null;
}
res.json({ toInstall });
}));
+ const dashboardAppPort = this.cubejsServer.options.dashboardAppPort || 3050;
+
+ app.get('/playground/start-dashboard-app', catchErrors(async (req, res) => {
+ this.cubejsServer.event('Dev Server Start Dashboard App');
+ if (!this.dashboardAppProcess) {
+ this.dashboardAppProcess = spawn('npm', ['run', 'start'], {
+ cwd: dashboardAppPath,
+ env: {
+ ...process.env,
+ PORT: dashboardAppPort
+ }
+ });
+ this.dashboardAppProcess.dashboardUrlPromise = new Promise((resolve) => {
+ this.dashboardAppProcess.stdout.on('data', (data) => {
+ console.log(data.toString());
+ if (data.toString().match(/Compiled/)) {
+ resolve(dashboardAppPort);
+ }
+ });
+ });
+
+ this.dashboardAppProcess.on('close', exitCode => {
+ if (exitCode !== 0) {
+ console.log(`Dashboard react-app failed with exit code ${exitCode}`);
+ this.cubejsServer.event('Dev Server Dashboard App Failed', { exitCode });
+ }
+ this.dashboardAppProcess = null;
+ });
+ }
+
+ await this.dashboardAppProcess.dashboardUrlPromise;
+ res.json({ dashboardPort: dashboardAppPort });
+ }));
+
+ app.get('/playground/dashboard-app-status', catchErrors(async (req, res) => {
+ this.cubejsServer.event('Dev Server Dashboard App Status');
+ const dashboardPort = this.dashboardAppProcess && await this.dashboardAppProcess.dashboardUrlPromise;
+ res.json({
+ running: !!dashboardPort,
+ dashboardPort
+ });
+ }));
+
app.use(serveStatic(path.join(__dirname, '../playground')));
}
}