Cube.js:開源儀表板框架的終極指南

weixin_33858249發表於2019-04-14

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\u0026gt; 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 = SECRETCUBEJS_DB_TYPE = postgresCUBEJS_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 \u0026quot;react\u0026quot;;import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer} from \u0026quot;recharts\u0026quot;;import cubejs from \u0026quot;@cubejs-client/core\u0026quot;;import moment from \u0026quot;moment\u0026quot;;import { QueryRenderer } from \u0026quot;@cubejs-client/react\u0026quot;;const cubejsApi = cubejs(process.env.REACT_APP_CUBEJS_TOKEN, { apiUrl: process.env.REACT_APP_API_URL});const dateFormatter = item =\u0026gt; moment(item).format(\u0026quot;MMM YY\u0026quot;);class App extends Component { render() {   return (     \u0026lt;QueryRenderer       query={{         measures: [\u0026quot;Orders.count\u0026quot;],         timeDimensions: [           {             dimension: \u0026quot;Orders.createdAt\u0026quot;,             dateRange: [\u0026quot;2017-01-01\u0026quot;, \u0026quot;2018-12-31\u0026quot;],             granularity: \u0026quot;month\u0026quot;           }         ]       }}       cubejsApi={cubejsApi}       render={({ resultSet }) =\u0026gt; {         if (!resultSet) {           return \u0026quot;Loading...\u0026quot;;         }                  return (           \u0026lt;ResponsiveContainer width=\u0026quot;100%\u0026quot; height={300}\u0026gt;             \u0026lt;BarChart data={resultSet.chartPivot()}\u0026gt;               \u0026lt;XAxis dataKey=\u0026quot;x\u0026quot; tickFormatter={dateFormatter} /\u0026gt;               \u0026lt;YAxis /\u0026gt;               \u0026lt;Tooltip labelFormatter={dateFormatter} /\u0026gt;               \u0026lt;Bar dataKey=\u0026quot;Orders.count\u0026quot; fill=\u0026quot;rgba(106, 110, 229)\u0026quot; /\u0026gt;             \u0026lt;/BarChart\u0026gt;           \u0026lt;/ResponsiveContainer\u0026gt;         );       }}     /\u0026gt;   ); }}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-OUTPUTREACT_APP_API_URL=http://localhost:4000/cubejs-api/v1

接下來,我們使用QueryRenderer Cube.js React Component來載入Orders資料。

\u0026lt;QueryRenderer  query={{    measures: [\u0026quot;Orders.count\u0026quot;],    timeDimensions: [      {        dimension: \u0026quot;Orders.createdAt\u0026quot;,        dateRange: [\u0026quot;2017-01-01\u0026quot;, \u0026quot;2018-12-31\u0026quot;],        granularity: \u0026quot;month\u0026quot;      }    ]  }}  cubejsApi={cubejsApi}  render={({ resultSet }) =\u0026gt; {    // Render result  }}/\u0026gt;

QueryRenderer 對Cube.js後端執行API請求,並根據需要使用render props技術來渲染結果。我們上面已經介紹了查詢格式,想得到最新的格式,這裡是查詢格式的完整參考。

QueryRenderer 的引數render,是幾個型別的函式({error, resultSet, isLoading}) =\u0026gt; React.Node。該函式的輸出將由  QueryRenderer提供。resultSet是從查詢獲得的資料物件,如果未定義此物件,則表示仍在提取資料中。

resultSet提供了多種資料操作方法,但在我們的例子中,只需要chartPivot方法,它以Recharts預期的格式返回資料。

我們將訂單資料繪製成響應式容器內的條形圖。

if (!resultSet) {  return \u0026quot;Loading...\u0026quot;;}return (  \u0026lt;ResponsiveContainer width=\u0026quot;100%\u0026quot; height={300}\u0026gt;    \u0026lt;BarChart data={resultSet.chartPivot()}\u0026gt;      \u0026lt;XAxis dataKey=\u0026quot;x\u0026quot; tickFormatter={dateFormatter} /\u0026gt;      \u0026lt;YAxis /\u0026gt;      \u0026lt;Tooltip labelFormatter={dateFormatter} /\u0026gt;      \u0026lt;Bar dataKey=\u0026quot;Orders.count\u0026quot; fill=\u0026quot;rgba(106, 110, 229)\u0026quot; /\u0026gt;    \u0026lt;/BarChart\u0026gt;  \u0026lt;/ResponsiveContainer\u0026gt;);

構建儀表板

我們學習瞭如何使用Cube.js和Recharts構建單個圖表,現​​在可以開始構建整個儀表板。可以找到設計儀表板佈局的一些最佳實踐,通常的做法是將最重要和最高階別的指標放在頂部作為單值圖表(有時稱為KPI),然後列出這些指標的相關細節。

以下是最終儀表板的螢幕截圖,其中KPI位於頂部,下面是條形圖和折線圖。

\"\"

首先,讓我們重構圖表,並將公共程式碼提取到可重用的元件中。用以下的內容建立一個src/Chart.js檔案:

import React from \u0026quot;react\u0026quot;;import { Card, CardTitle, CardBody, CardText } from \u0026quot;reactstrap\u0026quot;;import { QueryRenderer } from \u0026quot;@cubejs-client/react\u0026quot;;const Chart = ({ cubejsApi, title, query, render }) =\u0026gt; ( \u0026lt;Card\u0026gt;   \u0026lt;CardBody\u0026gt;     \u0026lt;CardTitle tag=\u0026quot;h5\u0026quot;\u0026gt;{title}\u0026lt;/CardTitle\u0026gt;     \u0026lt;CardText\u0026gt;       \u0026lt;QueryRenderer         query={query}         cubejsApi={cubejsApi}         render={({ resultSet }) =\u0026gt; {           if (!resultSet) {             return \u0026lt;div className=\u0026quot;loader\u0026quot; /\u0026gt;;           }                      return render(resultSet);         }}       /\u0026gt;     \u0026lt;/CardText\u0026gt;   \u0026lt;/CardBody\u0026gt; \u0026lt;/Card\u0026gt;);export default Chart;

接下來,讓我們使用此元件來建立儀表板。用以下內容替換src/App.js:

import React, { Component } from \u0026quot;react\u0026quot;;import { Container, Row, Col } from \u0026quot;reactstrap\u0026quot;;import { AreaChart, Area, XAxis, YAxis, Tooltip, ResponsiveContainer, Legend, BarChart, Bar} from \u0026quot;recharts\u0026quot;;import moment from \u0026quot;moment\u0026quot;;import numeral from \u0026quot;numeral\u0026quot;;import cubejs from \u0026quot;@cubejs-client/core\u0026quot;;import Chart from \u0026quot;./Chart.js\u0026quot;;const cubejsApi = cubejs(process.env.REACT_APP_CUBEJS_TOKEN, { apiUrl: process.env.REACT_APP_API_URL});const numberFormatter = item =\u0026gt; numeral(item).format(\u0026quot;0,0\u0026quot;);const dateFormatter = item =\u0026gt; moment(item).format(\u0026quot;MMM YY\u0026quot;);const renderSingleValue = (resultSet, key) =\u0026gt; ( \u0026lt;h1 height={300}\u0026gt;{numberFormatter(resultSet.chartPivot()[0][key])}\u0026lt;/h1\u0026gt;);class App extends Component { render() {   return (     \u0026lt;Container fluid\u0026gt;       \u0026lt;Row\u0026gt;         \u0026lt;Col sm=\u0026quot;4\u0026quot;\u0026gt;           \u0026lt;Chart             cubejsApi={cubejsApi}             title=\u0026quot;Total Users\u0026quot;             query={{ measures: [\u0026quot;Users.count\u0026quot;] }}             render={resultSet =\u0026gt; renderSingleValue(resultSet, \u0026quot;Users.count\u0026quot;)}           /\u0026gt;         \u0026lt;/Col\u0026gt;         \u0026lt;Col sm=\u0026quot;4\u0026quot;\u0026gt;           \u0026lt;Chart             cubejsApi={cubejsApi}             title=\u0026quot;Total Orders\u0026quot;             query={{ measures: [\u0026quot;Orders.count\u0026quot;] }}             render={resultSet =\u0026gt; renderSingleValue(resultSet, \u0026quot;Orders.count\u0026quot;)}           /\u0026gt;         \u0026lt;/Col\u0026gt;         \u0026lt;Col sm=\u0026quot;4\u0026quot;\u0026gt;           \u0026lt;Chart             cubejsApi={cubejsApi}             title=\u0026quot;Shipped Orders\u0026quot;             query={{               measures: [\u0026quot;Orders.count\u0026quot;],               filters: [                 {                   dimension: \u0026quot;Orders.status\u0026quot;,                   operator: \u0026quot;equals\u0026quot;,                   values: [\u0026quot;shipped\u0026quot;]                 }               ]             }}             render={resultSet =\u0026gt; renderSingleValue(resultSet, \u0026quot;Orders.count\u0026quot;)}           /\u0026gt;         \u0026lt;/Col\u0026gt;       \u0026lt;/Row\u0026gt;       \u0026lt;br /\u0026gt;       \u0026lt;br /\u0026gt;       \u0026lt;Row\u0026gt;         \u0026lt;Col sm=\u0026quot;6\u0026quot;\u0026gt;           \u0026lt;Chart             cubejsApi={cubejsApi}             title=\u0026quot;New Users Over Time\u0026quot;             query={{               measures: [\u0026quot;Users.count\u0026quot;],               timeDimensions: [                 {                   dimension: \u0026quot;Users.createdAt\u0026quot;,                   dateRange: [\u0026quot;2017-01-01\u0026quot;, \u0026quot;2018-12-31\u0026quot;],                   granularity: \u0026quot;month\u0026quot;                 }               ]             }}             render={resultSet =\u0026gt; (               \u0026lt;ResponsiveContainer width=\u0026quot;100%\u0026quot; height={300}\u0026gt;                 \u0026lt;AreaChart data={resultSet.chartPivot()}\u0026gt;                   \u0026lt;XAxis dataKey=\u0026quot;category\u0026quot; tickFormatter={dateFormatter} /\u0026gt;                   \u0026lt;YAxis tickFormatter={numberFormatter} /\u0026gt;                   \u0026lt;Tooltip labelFormatter={dateFormatter} /\u0026gt;                   \u0026lt;Area                     type=\u0026quot;monotone\u0026quot;                     dataKey=\u0026quot;Users.count\u0026quot;                     name=\u0026quot;Users\u0026quot;                     stroke=\u0026quot;rgb(106, 110, 229)\u0026quot;                     fill=\u0026quot;rgba(106, 110, 229, .16)\u0026quot;                   /\u0026gt;                 \u0026lt;/AreaChart\u0026gt;               \u0026lt;/ResponsiveContainer\u0026gt;             )}           /\u0026gt;         \u0026lt;/Col\u0026gt;         \u0026lt;Col sm=\u0026quot;6\u0026quot;\u0026gt;           \u0026lt;Chart             cubejsApi={cubejsApi}             title=\u0026quot;Orders by Status Over time\u0026quot;             query={{               measures: [\u0026quot;Orders.count\u0026quot;],               dimensions: [\u0026quot;Orders.status\u0026quot;],               timeDimensions: [                 {                   dimension: \u0026quot;Orders.createdAt\u0026quot;,                   dateRange: [\u0026quot;2017-01-01\u0026quot;, \u0026quot;2018-12-31\u0026quot;],                   granularity: \u0026quot;month\u0026quot;                 }               ]             }}             render={resultSet =\u0026gt; {               return (                 \u0026lt;ResponsiveContainer width=\u0026quot;100%\u0026quot; height={300}\u0026gt;                   \u0026lt;BarChart data={resultSet.chartPivot()}\u0026gt;                     \u0026lt;XAxis tickFormatter={dateFormatter} dataKey=\u0026quot;x\u0026quot; /\u0026gt;                     \u0026lt;YAxis tickFormatter={numberFormatter} /\u0026gt;                     \u0026lt;Bar                       stackId=\u0026quot;a\u0026quot;                       dataKey=\u0026quot;shipped, Orders.count\u0026quot;                       name=\u0026quot;Shipped\u0026quot;                       fill=\u0026quot;#7DB3FF\u0026quot;                     /\u0026gt;                     \u0026lt;Bar                       stackId=\u0026quot;a\u0026quot;                       dataKey=\u0026quot;processing, Orders.count\u0026quot;                       name=\u0026quot;Processing\u0026quot;                       fill=\u0026quot;#49457B\u0026quot;                     /\u0026gt;                     \u0026lt;Bar                       stackId=\u0026quot;a\u0026quot;                       dataKey=\u0026quot;completed, Orders.count\u0026quot;                       name=\u0026quot;Completed\u0026quot;                       fill=\u0026quot;#FF7C78\u0026quot;                     /\u0026gt;                     \u0026lt;Legend /\u0026gt;                     \u0026lt;Tooltip /\u0026gt;                   \u0026lt;/BarChart\u0026gt;                 \u0026lt;/ResponsiveContainer\u0026gt;               );             }}           /\u0026gt;         \u0026lt;/Col\u0026gt;       \u0026lt;/Row\u0026gt;     \u0026lt;/Container\u0026gt;   ); }}export default App;

這足以構建第一個儀表板,然後可以在下面的CodeSanbox中嘗試一下。

下一步

以上是我們使用Cube.js構建了一個簡單的驗證概念的儀表板。可以在這裡檢視現場演示。GitHub有完整的原始碼

要了解有關Cube.js後端部署的更多資訊,可以參考部署文件。此外,還可以在此處找到有關各種主題的更多教程

原文連結Cube.js: Ultimate Guide to the Open-Source Dashboard Framework

相關文章