Cube.js,开源仪表板框架:终极指南
Cube.js是一个用于构建分析型 Web 应用程序的开源框架。它主要用于构建内部商业智能工具或为现有应用程序添加面向客户的分析功能。在大多数情况下,构建此类应用程序的第一步是创建分析仪表板。通常以“让我们在管理面板中添加一个分析仪表板”开始。然后,正如软件开发中常见的情况一样,事情变得越来越复杂。
当我们开始开发 Cube.js 时,我们希望构建一个易于上手,但功能、复杂性和数据量却能轻松扩展的工具。Cube.js 为您未来的分析系统奠定了坚实的基础,无论它是独立的应用程序还是嵌入到现有应用程序中。
您可以将本教程视为“Cube.js 101”。我将引导您完成从数据库到可视化设计第一个仪表板的基本步骤。
最终仪表板的现场演示可在此处查看。 完整源代码位于 Github 上。
建筑学
大多数现代 Web 应用程序都是单页应用程序,其中前端与后端分离。后端通常也分为多个服务,遵循微服务架构。
Cube.js 采用了这种方法。通常,您可以将 Cube.js 后端作为服务运行。它管理与数据库的连接,包括查询队列、缓存、预聚合等。它还为您的前端应用提供了 API,用于构建仪表板和其他分析功能。
后端
分析始于数据,而数据驻留在数据库中。这是我们首先需要准备的。您很可能已经为您的应用程序建立了一个数据库,通常情况下,用它来进行分析就足够了。现代流行的数据库,例如 Postgres 或 MySQL,非常适合简单的分析工作负载。我所说的简单,是指数据量少于 10 亿行的情况。
MongoDB 也很好,你唯一需要添加的是 MongoDB BI 连接器。它允许在你的 MongoDB 数据上执行 SQL 代码。它是免费的,可以从 MongoDB 网站轻松下载。还有一点需要注意的是复制。对生产数据库运行分析查询被认为是一种不好的做法,主要是因为性能问题。Cube.js 可以显著减少数据库的工作负载,但我仍然建议连接到副本数据库。
总结一下:
如果您使用 Postgres 或 MySQL,只需创建一个副本即可。如果您使用 MongoDB,请下载 MongoDB Connector for BI 并创建一个副本。
如果您没有仪表板的任何数据,您可以加载我们的示例电子商务 Postgres 数据集。
$ curl http://cube.dev/downloads/ecom-dump.sql > ecom-dump.sql
$ createdb ecom
$ psql --dbname ecom -f ecom-dump.sql
现在,数据库中有了数据,我们就可以创建 Cube.js 后端服务了。在终端中运行以下命令:
$ npm install -g cubejs-cli
$ cubejs create dashboard-backend -d postgres
上述命令安装 Cube.js CLI 并创建一个新服务,配置为与 Postgres 数据库一起使用。
Cube.js 使用环境变量进行配置。它使用以 开头的环境变量CUBEJS_
。要配置与数据库的连接,我们需要指定数据库类型和名称。在 Cube.js 项目文件夹中,将 的内容替换.env
为以下内容:
CUBEJS_API_SECRET=SECRET
CUBEJS_DB_TYPE=postgres
CUBEJS_DB_NAME=ecom
Cube.js 数据模式
下一步是创建Cube.js 数据模式。Cube.js 使用数据模式生成 SQL 代码,该代码将在数据库中执行。数据模式并非 SQL 的替代品。它旨在使 SQL 可重用,并赋予其结构,同时保留其所有功能。数据模式的基本元素是measures
和dimensions
。
衡量指标指的是定量数据,例如销售单位数、独立访问次数、利润等等。
维度是指分类数据,例如状态、性别、产品名称或时间单位(例如,天、周、月)。
通常,Schema 文件位于schema
文件夹中。以下是 Schema 的示例,可用于描述用户的数据。
cube(`Users`, {
sql: `SELECT * FROM users`,
measures: {
count: {
sql: `id`,
type: `count`
}
},
dimensions: {
city: {
sql: `city`,
type: `string`
},
signedUp: {
sql: `created_at`,
type: `time`
},
companyName: {
sql: `company_name`,
type: `string`
}
}
});
现在,有了上述架构,我们可以向 Cube.js 后端发送有关用户数据的查询了。Cube.js 查询是普通的 JavaScript 对象。通常它包含一个或多个measures
、dimensions
和timeDimensions
。
如果我们想回答“我们的用户在哪里?”这个问题,我们可以向 Cube.js 发送以下查询:
{
measures: ['Users.count'],
dimensions: ['Users.city']
}
Cube.js 将根据模式生成所需的 SQL,执行它,并将结果返回。
让我们创建一个稍微复杂一点的查询。我们可以添加一个,timeDimensions
以查看过去一年中不同城市每月的注册比例变化情况。为此,我们将添加一个signedUp
时间维度,按月分组,并仅筛选去年的注册人数。
{
measures: ['Users.count'],
dimensions: ['Users.city'],
timeDimensions: [{
dimension: 'Users.signedUp',
granularity: 'month',
dateRange: ['2018-01-31', '2018-12-31']
}]
}
Cube.js 可以根据数据库表生成简单的模式。让我们先生成仪表板所需的模式,然后启动开发服务器。
$ cubejs generate -t users,orders
$ npm run dev
您可以通过打开http://localhost:4000上的开发平台来检查生成的模式并发送测试查询。
前端
我们将使用 React 和 Cube.js React 客户端构建前端和仪表板。不过,您可以使用任何框架或原生 JavaScript 来构建 Cube.js 前端。本教程将向您展示如何用纯 JavaScript 构建仪表板。
我们将使用 Create React App 设置所有内容,该应用由 React 团队官方支持。它打包了 React 应用的所有依赖项,让您可以轻松开始新项目。在终端中运行以下命令:
$ npx create-react-app dashboard-frontend
$ cd cubejs-dashboard
$ npm start
最后一行在端口 3000 上启动服务器并在http://localhost:3000打开 Web 浏览器。
我们将使用 Reactstrap 构建 UI,它是 Bootstrap 4 的 React 包装器。请从 NPM 安装 Reactstrap 和 Bootstrap。Reactstrap 不包含 Bootstrap CSS,因此需要单独安装:
$ npm install reactstrap bootstrap --save
src/index.js
导入之前在文件中导入 Bootstrap CSS ./index.css
:
import 'bootstrap/dist/css/bootstrap.min.css';
现在我们准备使用 Reactstrap 组件了。
下一步是安装 Cube.js 客户端,以便从服务器获取数据,并使用我们的可视化库进行显示。在本教程中,我们将使用 Recharts。Cube.js 不依赖于可视化,这意味着您可以使用任何您想要的库。我们还将使用 moment 和numeric 来格式化日期和数字。
$ npm install --save @cubejs-client/core @cubejs-client/react recharts moment numeral
最后,我们完成了依赖关系,接下来我们来创建第一个图表。
将 的内容替换src/App.js
为以下内容:
import React, { Component } from "react";
import {
BarChart,
Bar,
XAxis,
YAxis,
Tooltip,
ResponsiveContainer
} from "recharts";
import cubejs from "@cubejs-client/core";
import moment from "moment";
import { QueryRenderer } from "@cubejs-client/react";
const cubejsApi = cubejs(process.env.REACT_APP_CUBEJS_TOKEN, {
apiUrl: process.env.REACT_APP_API_URL
});
const dateFormatter = item => moment(item).format("MMM YY");
class App extends Component {
render() {
return (
<QueryRenderer
query={{
measures: ["Orders.count"],
timeDimensions: [
{
dimension: "Orders.createdAt",
dateRange: ["2017-01-01", "2018-12-31"],
granularity: "month"
}
]
}}
cubejsApi={cubejsApi}
render={({ resultSet }) => {
if (!resultSet) {
return "Loading...";
}
return (
<ResponsiveContainer width="100%" height={300}>
<BarChart data={resultSet.chartPivot()}>
<XAxis dataKey="x" tickFormatter={dateFormatter} />
<YAxis />
<Tooltip labelFormatter={dateFormatter} />
<Bar dataKey="Orders.count" fill="rgba(106, 110, 229)" />
</BarChart>
</ResponsiveContainer>
);
}}
/>
);
}
}
export default App;
您可以在下面的 CodeSandbox 中查看此示例。
让我们更深入地了解如何加载数据和绘制图表。
首先,我们初始化 Cube.js API 客户端:
const cubejsApi = cubejs(process.env.REACT_APP_CUBEJS_TOKEN, {
apiUrl: process.env.REACT_APP_API_URL
});
这里我们使用了REACT_APP_CUBEJS_TOKEN
和REACT_APP_API_URL
环境变量。如果环境变量.env
以 开头,Create React App 会自动从文件中加载它们REACT_APP_
。Cube.js 后端会在启动时打印开发 API 令牌。
.env
使用正确的凭据创建文件。
REACT_APP_CUBEJS_TOKEN=COPY-API-TOKEN-FROM-TERMINAL-OUTPUT
REACT_APP_API_URL=http://localhost:4000/cubejs-api/v1
接下来,我们使用 QueryRenderer Cube.js React 组件来加载订单数据。
<QueryRenderer
query={{
measures: ["Orders.count"],
timeDimensions: [
{
dimension: "Orders.createdAt",
dateRange: ["2017-01-01", "2018-12-31"],
granularity: "month"
}
]
}}
cubejsApi={cubejsApi}
render={({ resultSet }) => {
// Render result
}}
/>
QueryRenderer 向 Cube.js 后端发出 API 请求,并使用render props技术,让您以所需的方式渲染结果。我们已经在上文介绍了查询格式,如果您想回顾一下,这里是查询格式的完整参考。
QueryRenderer 的参数render
是一个 类型的函数({error, resultSet, isLoading}) => React.Node
。该函数的输出将由 QueryRenderer 渲染。AresultSet
是一个包含从查询中获取的数据的对象。如果未定义此对象,则表示数据仍在获取中。
resultSet
提供了多种数据操作方法,但在我们的例子中,我们只需要以chartPivot
Recharts 所需的格式返回数据的方法。
我们将把订单数据绘制为响应容器内的条形图。
if (!resultSet) {
return "Loading...";
}
return (
<ResponsiveContainer width="100%" height={300}>
<BarChart data={resultSet.chartPivot()}>
<XAxis dataKey="x" tickFormatter={dateFormatter} />
<YAxis />
<Tooltip labelFormatter={dateFormatter} />
<Bar dataKey="Orders.count" fill="rgba(106, 110, 229)" />
</BarChart>
</ResponsiveContainer>
);
构建仪表板
我们学习了如何使用 Cube.js 和 Recharts 构建单个图表,现在我们可以开始构建整个仪表板了。关于设计仪表板的布局,有一些最佳实践。常见的做法是将最重要、最高级的指标作为单值图表(有时称为KPI)放在顶部,然后列出这些指标的相关细分。
这是我们最终仪表板的屏幕截图,顶部是 KPI,后面是条形图和折线图。
首先,让我们重构图表并将通用代码提取到可重用的<Chart />
组件中。创建一个src/Chart.js
包含以下内容的文件:
import React from "react";
import { Card, CardTitle, CardBody, CardText } from "reactstrap";
import { QueryRenderer } from "@cubejs-client/react";
const Chart = ({ cubejsApi, title, query, render }) => (
<Card>
<CardBody>
<CardTitle tag="h5">{title}</CardTitle>
<CardText>
<QueryRenderer
query={query}
cubejsApi={cubejsApi}
render={({ resultSet }) => {
if (!resultSet) {
return <div className="loader" />;
}
return render(resultSet);
}}
/>
</CardText>
</CardBody>
</Card>
);
export default Chart;
接下来,让我们使用此组件来创建仪表板。将 的内容替换src/App.js
为以下内容:
import React, { Component } from "react";
import { Container, Row, Col } from "reactstrap";
import {
AreaChart,
Area,
XAxis,
YAxis,
Tooltip,
ResponsiveContainer,
Legend,
BarChart,
Bar
} from "recharts";
import moment from "moment";
import numeral from "numeral";
import cubejs from "@cubejs-client/core";
import Chart from "./Chart.js";
const cubejsApi = cubejs(process.env.REACT_APP_CUBEJS_TOKEN, {
apiUrl: process.env.REACT_APP_API_URL
});
const numberFormatter = item => numeral(item).format("0,0");
const dateFormatter = item => moment(item).format("MMM YY");
const renderSingleValue = (resultSet, key) => (
<h1 height={300}>{numberFormatter(resultSet.chartPivot()[0][key])}</h1>
);
class App extends Component {
render() {
return (
<Container fluid>
<Row>
<Col sm="4">
<Chart
cubejsApi={cubejsApi}
title="Total Users"
query={{ measures: ["Users.count"] }}
render={resultSet => renderSingleValue(resultSet, "Users.count")}
/>
</Col>
<Col sm="4">
<Chart
cubejsApi={cubejsApi}
title="Total Orders"
query={{ measures: ["Orders.count"] }}
render={resultSet => renderSingleValue(resultSet, "Orders.count")}
/>
</Col>
<Col sm="4">
<Chart
cubejsApi={cubejsApi}
title="Shipped Orders"
query={{
measures: ["Orders.count"],
filters: [
{
dimension: "Orders.status",
operator: "equals",
values: ["shipped"]
}
]
}}
render={resultSet => renderSingleValue(resultSet, "Orders.count")}
/>
</Col>
</Row>
<br />
<br />
<Row>
<Col sm="6">
<Chart
cubejsApi={cubejsApi}
title="New Users Over Time"
query={{
measures: ["Users.count"],
timeDimensions: [
{
dimension: "Users.createdAt",
dateRange: ["2017-01-01", "2018-12-31"],
granularity: "month"
}
]
}}
render={resultSet => (
<ResponsiveContainer width="100%" height={300}>
<AreaChart data={resultSet.chartPivot()}>
<XAxis dataKey="category" tickFormatter={dateFormatter} />
<YAxis tickFormatter={numberFormatter} />
<Tooltip labelFormatter={dateFormatter} />
<Area
type="monotone"
dataKey="Users.count"
name="Users"
stroke="rgb(106, 110, 229)"
fill="rgba(106, 110, 229, .16)"
/>
</AreaChart>
</ResponsiveContainer>
)}
/>
</Col>
<Col sm="6">
<Chart
cubejsApi={cubejsApi}
title="Orders by Status Over time"
query={{
measures: ["Orders.count"],
dimensions: ["Orders.status"],
timeDimensions: [
{
dimension: "Orders.createdAt",
dateRange: ["2017-01-01", "2018-12-31"],
granularity: "month"
}
]
}}
render={resultSet => {
return (
<ResponsiveContainer width="100%" height={300}>
<BarChart data={resultSet.chartPivot()}>
<XAxis tickFormatter={dateFormatter} dataKey="x" />
<YAxis tickFormatter={numberFormatter} />
<Bar
stackId="a"
dataKey="shipped, Orders.count"
name="Shipped"
fill="#7DB3FF"
/>
<Bar
stackId="a"
dataKey="processing, Orders.count"
name="Processing"
fill="#49457B"
/>
<Bar
stackId="a"
dataKey="completed, Orders.count"
name="Completed"
fill="#FF7C78"
/>
<Legend />
<Tooltip />
</BarChart>
</ResponsiveContainer>
);
}}
/>
</Col>
</Row>
</Container>
);
}
}
export default App;
这足以构建我们的第一个仪表板。在下面的 CodeSanbox 中尝试一下。
后续步骤
我们使用 Cube.js 构建了一个简单的概念验证仪表板。您可以点击此处查看现场演示。完整的源代码可在 Github 上获取。
要了解有关 Cube.js 后端部署的更多信息,请参阅部署文档。此外,您还可以在这里找到有关各种主题的更多教程。
加入我们的Slack 社区!这里是获取帮助和了解最新发布的好地方。
文章来源:https://dev.to/cubejs/cube-js-the-open-source-dashboard-framework-ultimate-guide-53be