Skip to content

Commit

Permalink
feat: Playground templates separate repository open for third party c…
Browse files Browse the repository at this point in the history
…ontributions (#903)

* refactor: playground templates into a new repo

* packages fetching

* prettier, other fixes

* template packages fix

* move target source

* tmp path

* templates define dependencies themselves

* node 10 support

* remove outdated test
  • Loading branch information
vasilev-alex authored Aug 5, 2020
1 parent bf828c3 commit fb57bda
Show file tree
Hide file tree
Showing 96 changed files with 761 additions and 3,698 deletions.
15 changes: 8 additions & 7 deletions packages/cubejs-playground/src/DashboardSource.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class DashboardSource {
}
}

async applyTemplatePackages(templatePackages, templateConfig) {
async applyTemplatePackages(toApply, templateConfig = null) {
if (!this.playgroundContext) {
this.playgroundContext = await this.loadContext();
}
Expand All @@ -28,7 +28,7 @@ class DashboardSource {
'Content-Type': 'application/json'
},
body: JSON.stringify({
templatePackages,
toApply,
templateConfig: templateConfig || {
credentials: this.playgroundContext
}
Expand Down Expand Up @@ -70,11 +70,12 @@ class DashboardSource {
'create-react-app',
Object.keys(this.installedTemplates).find(template => template.match(/-static$/)), // TODO
'static-chart'
], {
'static-chart': {
chartCode
}
});
], { chartCode });
}

async templates() {
const { templates } = await (await fetch('/playground/manifest')).json();
return templates;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { Component } from 'react';
import styled from 'styled-components';
import '@ant-design/compatible/assets/index.css';
import { Card, Col, Row, Typography } from 'antd';
import { Card, Col, Row, Typography, Spin } from 'antd';
import { Redirect, withRouter } from 'react-router-dom';
import DashboardSource from '../DashboardSource';
import { frameworks } from '../ChartContainer';
Expand Down Expand Up @@ -113,26 +113,35 @@ class TemplateGalleryPage extends Component {
this.state = {
chartLibrary: chartLibraries[0].value,
framework: 'react',
templatePackageName: 'react-antd-dynamic'
templatePackageName: 'react-antd-dynamic',
templates: null
};
}

async componentDidMount() {
this.dashboardSource = new DashboardSource();
await this.dashboardSource.load(true);

this.setState({
loadError: this.dashboardSource.loadError
loadError: this.dashboardSource.loadError,
templates: await this.dashboardSource.templates()
});
}

render() {
const {
loadError
loadError,
templates
} = this.state;

if (loadError && loadError.indexOf('Dashboard app not found') === -1) {
return <Redirect to="/dashboard" />;
}

if (templates == null) {
return <Spin />
}

const {
chartLibrary, framework, templatePackageName, createOwnModalVisible, enableWebSocketTransport
} = this.state;
Expand All @@ -142,30 +151,8 @@ class TemplateGalleryPage extends Component {
const templatePackage = this.dashboardSource && this.dashboardSource.templatePackages
.find(m => m.name === templatePackageName);

const recipes = [{
name: 'Dynamic Dashboard with React, AntD, and Recharts',
description: 'Use this template to create a dynamic dashboard application with React, AntD, and Chart.js. It comes with a dynamic query builder and Apollo GraphQL client. Use it when you want to allow users to edit dashboards.',
coverUrl: 'https://cube.dev/downloads/template-react-dashboard.png',
templatePackages: ['create-react-app', 'react-antd-dynamic', 'recharts-charts', 'antd-tables', 'credentials']
}, {
name: 'Real-Time Dashboard with React, AntD, and Chart.js',
description: 'Use this template to create a static dashboard application with real-time WebSocket transport. Use it when users should not be allowed to edit dashboards and you want to provide them with real-time data refresh.',
templatePackages: ['create-react-app', 'react-antd-static', 'chartjs-charts', 'antd-tables', 'credentials', 'web-socket-transport'],
coverUrl: 'https://cube.dev/downloads/template-real-time-dashboard.png'
}, {
name: 'Material UI React Dashboard',
coverUrl: 'https://cube.dev/downloads/template-material-ui.jpg',
description: 'Use this template to create a Material UI–based static dashboard application and add charts to it by editing the source code or via Cube.js Playground. Use it when users should not be allowed to edit dashboards.',
templatePackages: ['create-react-app', 'react-material-static', 'recharts-charts', 'material-tables', 'credentials']
}, {
name: 'Material UI D3 Dashboard',
coverUrl: 'https://cube.dev/downloads/template-material-d3.png',
description: 'Use this template to create a Material UI–based dashboard with D3.js. Add charts to it by editing the source code or via Cube.js Playground. Use it when users should not be allowed to edit dashboards.',
templatePackages: ['create-react-app', 'react-material-static', 'd3-charts', 'material-tables', 'credentials']
}];


const recipeCards = recipes.map(({ name, description, templatePackages, coverUrl }) => (

const recipeCards = templates.map(({ name, description, templatePackages, coverUrl }) => (
<Col xs={{ span: 24 }} md={{span: 12 }} lg={{ span: 8 }} xl={{ span: 6 }} key={name}>
<RecipeCard
hoverable
Expand All @@ -176,7 +163,7 @@ class TemplateGalleryPage extends Component {
<Button
type="primary"
onClick={async () => {
await this.dashboardSource.applyTemplatePackages(templatePackages);
await this.dashboardSource.applyTemplatePackages(name);
history.push('/dashboard');
}}
>
Expand Down Expand Up @@ -207,8 +194,8 @@ class TemplateGalleryPage extends Component {
templatePackageName,
`${chartLibrary}-charts`,
`${templatePackageName.match(/^react-(\w+)/)[1]}-tables`, // TODO
'credentials'
].concat(enableWebSocketTransport ? ['web-socket-transport'] : []);
'react-credentials'
].concat(enableWebSocketTransport ? ['react-web-socket-transport'] : []);
await this.dashboardSource.applyTemplatePackages(templatePackages);
history.push('/dashboard');
}}
Expand All @@ -229,10 +216,10 @@ class TemplateGalleryPage extends Component {
return (
<MarginFrame>
<StyledTitle>
Build your app from one the popular templates below or
Build your app from one the popular templates below or{' '}
<a onClick={() => this.setState({ createOwnModalVisible: true })}>create your own</a>
</StyledTitle>
<Row type="flex" align="top" gutter={24}>
<Row align="top" gutter={24}>
{recipeCards}
</Row>
</MarginFrame>
Expand Down
3 changes: 2 additions & 1 deletion packages/cubejs-server-core/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
test-output/
test-output/
/dev/__tmp__
61 changes: 51 additions & 10 deletions packages/cubejs-server-core/core/DevServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@
const fs = require('fs-extra');
const path = require('path');
const spawn = require('cross-spawn');
const AppContainer = require('../dev/templates/AppContainer');
const AppContainer = require('../dev/AppContainer');
const DependencyTree = require('../dev/DependencyTree');
const PackageFetcher = require('../dev/PackageFetcher');

const repo = {
owner: 'cube-js',
name: 'cubejs-playground-templates'
};

class DevServer {
constructor(cubejsServer) {
Expand Down Expand Up @@ -90,8 +97,8 @@ class DevServer {
let lastApplyTemplatePackagesError = null;

app.get('/playground/dashboard-app-create-status', catchErrors(async (req, res) => {
const sourcePath = await path.join(dashboardAppPath, 'src');

const sourcePath = path.join(dashboardAppPath, 'src');
if (lastApplyTemplatePackagesError) {
const toThrow = lastApplyTemplatePackagesError;
lastApplyTemplatePackagesError = null;
Expand All @@ -106,9 +113,9 @@ class DevServer {
await this.applyTemplatePackagesPromise;
}

if (!(await fs.pathExists(sourcePath))) {
if (!(fs.pathExistsSync(sourcePath))) {
res.status(404).json({
error: await fs.pathExists(dashboardAppPath) ?
error: fs.pathExistsSync(dashboardAppPath) ?
`Dashboard app corrupted. Please remove '${path.resolve(dashboardAppPath)}' directory and recreate it` :
`Dashboard app not found in '${path.resolve(dashboardAppPath)}' directory`
});
Expand All @@ -120,11 +127,9 @@ class DevServer {
return;
}

const appContainer = new AppContainer(dashboardAppPath);

res.json({
status: 'created',
installedTemplates: await appContainer.getPackageVersions()
installedTemplates: AppContainer.getPackageVersions(dashboardAppPath)
});
}));

Expand Down Expand Up @@ -173,24 +178,55 @@ class DevServer {
}));

app.post('/playground/apply-template-packages', catchErrors(async (req, res) => {
this.cubejsServer.event('Dev Server Download Template Packages');

const fetcher = new PackageFetcher(repo);

this.cubejsServer.event('Dev Server App File Write');
const { templatePackages, templateConfig } = req.body;
const appContainer = new AppContainer(dashboardAppPath, templatePackages, templateConfig);
const { toApply, templateConfig } = req.body;

const applyTemplates = async () => {
const manifestJson = await fetcher.manifestJSON();
const response = await fetcher.downloadPackages();

let templatePackages = [];
if (typeof toApply === 'string') {
const template = manifestJson.templates.find(({ name }) => name === toApply);
templatePackages = template.templatePackages;
} else {
templatePackages = toApply;
}

const dt = new DependencyTree(manifestJson, templatePackages);

const appContainer = new AppContainer(
dt.getRootNode(),
{
appPath: dashboardAppPath,
packagesPath: response.packagesPath
},
templateConfig
);

this.cubejsServer.event('Dev Server Create Dashboard App');
await appContainer.applyTemplates();
this.cubejsServer.event('Dev Server Create Dashboard App Success');

this.cubejsServer.event('Dev Server Dashboard Npm Install');

await appContainer.ensureDependencies();
this.cubejsServer.event('Dev Server Dashboard Npm Install Success');

fetcher.cleanup();
};

if (this.applyTemplatePackagesPromise) {
this.applyTemplatePackagesPromise = this.applyTemplatePackagesPromise.then(applyTemplates);
} else {
this.applyTemplatePackagesPromise = applyTemplates();
}
const promise = this.applyTemplatePackagesPromise;

promise.then(() => {
if (promise === this.applyTemplatePackagesPromise) {
this.applyTemplatePackagesPromise = null;
Expand All @@ -203,6 +239,11 @@ class DevServer {
});
res.json(true); // TODO
}));

app.get('/playground/manifest', catchErrors(async (_, res) => {
const fetcher = new PackageFetcher(repo);
res.json(await fetcher.manifestJSON());
}));

app.use(serveStatic(path.join(__dirname, '../playground'), {
lastModified: false,
Expand Down
Loading

0 comments on commit fb57bda

Please sign in to comment.