Cube.js是一个用于构建分析 web 应用程序的开源框架,主要用于构建内部的商业智能工具或将面向客户的分析添加到现有的应用程序当中。大多数情况下,构建此类应用程序的第一步是分析仪表板。通常从“在管理面板中添加一个分析仪表板”开始,然后就像软件开发中经常发生的那样,事情会变得更加复杂。
当开始使用 Cube.js 时,会想要构建一个工具,它起初很简单,但在功能、复杂性和数据量方面很容易扩展。Cube.js 为未来的分析系统奠定坚实的基础,无论是独立的应用程序还是嵌入到现有的分析系统中。
本教程可以视为“Cube.js 101”,它介绍了从数据库到可视化的第一个仪表板的基本设计步骤。
最终仪表板的现场演示可在此处获得。完整的源代码在 GitHub上。
架构
大多数现代 web 应用程序都是作为单页面应用程序构建的,前端与后端分离。遵循微服务架构,后端通常也会分成多个服务。
通常,Cube.js 的后端作为服务运行,管理与数据库的连接,包括查询队列、缓存、预聚合等。同时为前端应用程序公开一个 API,用于构建仪表板和其他分析功能。
后端
分析从数据产生并驻留在数据库中开始,如果用户已经有一个适用于应用程序的数据库,通常它可以直接用于分析。现代流行的数据库,如 Postgres 或 MySQL,都可以做简单的分析工作,这里的简单指的是行数少于 10 亿的数据量。
另外,MongoDB 也可以,不过需要添加 MongoDB Connector for 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 数据 Schema
下一步是创建一个Cube.js数据Schema。Cube.js 利用数据 Schema 生成 SQL 代码,该代码将在数据库中执行。数据 Schema 不是 SQL 的替代品,它旨在使 SQL 可重用并赋予其结构,同时保留其所有功能。数据 Schema 的基本元素是 measures 和 dimensions。
,
Measure 被称为定量数据,例如单位销售数、唯一访问量、利润等。
Dimension 被称为分类数据,例如状态、性别、产品名称或时间单位(例如,日、周、月)。
通常,模式文件位于 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`
}
}
});
复制代码
现在,通过上述 Schema,可以向 Cube.js 后端发送有关用户数据的查询。Cube.js 查询是纯 JavaScript 对象。通常情况下,它有一个或多个 measures、dimensions 和 timeDimensions。
如果要回答“用户在哪里?”这个问题,可以将以下查询发送到 Cube.js:
{
measures: ['Users.count'],
dimensions: ['Users.city']
}
复制代码
Cube.js 将根据 Schema 生成所需的 SQL,执行它并将结果发回。
我们可以创建一个稍微复杂的查询,添加一个 timeDimensions,看看去年不同城市的比例在每个月是如何变化的。为此,需要添加 signedUp 时间维度,按月分组,并仅包含去年的注册。
{
measures: ['Users.count'],
dimensions: ['Users.city'],
timeDimensions: [{
dimension: 'Users.signedUp',
granularity: 'month',
dateRange: ['2018-01-31', '2018-12-31']
}]
}
复制代码
Cube.js 还可以根据数据库的表生成简单的 Schema,并需要为仪表板生成所需的 Schema,启动一个供开发的服务器。
$ cubejs generate -t users,orders
$ npm run dev
复制代码
可以通过在http://localhost:4000打开开发后台,检查生成的 Schema 并发送测试的查询。
前端
通过 Cube.js 的 React 客户端,可以使用 React 来构建前端和仪表板。也可以使用任何框架或只使用 vanilla JavaScript 来构建 Cube.js 的前端,这个教程将向您展示如何在纯JavaScript中构建仪表板。我们将使用 React 团队正式支持的 Create React App 来设置所有内容,它打包了 React 应用程序的所有依赖项,可以轻松地开始使用新项目。在终端中运行以下命令:
$ npx create-react-app dashboard-frontend
$ cd cubejs-dashboard
$ npm start
复制代码
最后一行在端口 3000 上启动服务器,并通过http://localhost:3000打开 web 浏览器。
我们将使用 Reactstrap 来构建我们的 UI,它是 Bootstrap 4 的 React wrapper。用 NPM 来安装 Reactstrap 和 Bootstrap。Reactstrap 不包括 Bootstrap CSS,所以需要单独安装:
$ npm install reactstrap bootstrap --save
复制代码
导入./index.css 之前,导入 src/index.js 文件中的 Bootstrap CSS:
import 'bootstrap/dist/css/bootstrap.min.css';
复制代码
现在,我们已准备好使用 Reactstrap 组件。
下一步是安装 Cube.js 客户端,这样可以从服务器和可视化库中获取数据并进行显示。在本教程中,我们将使用 Recharts。Cube.js 是可视化不可知的,这意味着可以使用任何所需的库。我们还将使用 moment 和 numeral 来很好地格式化日期和数字。
$ npm install --save @ cubejs-client / core @ cubejs-client / react recharts moment numbers
复制代码
这样,我们搞定了依赖关系。接下来继续创建我们的第一个图表,用以下替换 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 这两个环境变量。如果环境变量以 REACT_APP_开头,Create React App 会自动从.env 文件中加载。Cube.js 后端将在启动期间打印这些 API token。
使用正确的凭据创建.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 Component 来加载 Orders 数据。
<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 提供。resultSet 是从查询获得的数据对象,如果未定义此对象,则表示仍在提取数据中。
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 位于顶部,下面是条形图和折线图。
首先,让我们重构图表,并将公共代码提取到可重用的组件中。用以下的内容创建一个 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 后端部署的更多信息,可以参考部署文档。此外,还可以在此处找到有关各种主题的更多教程。
原文链接:Cube.js: Ultimate Guide to the Open-Source Dashboard Framework
评论