<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Igor Lukanin</title>
    <description>The latest articles on Forem by Igor Lukanin (@igorlukanin).</description>
    <link>https://forem.com/igorlukanin</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F374270%2F8d416f5c-7685-4c7b-9b66-5d40e5af117a.jpg</url>
      <title>Forem: Igor Lukanin</title>
      <link>https://forem.com/igorlukanin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/igorlukanin"/>
    <language>en</language>
    <item>
      <title>Observable tutorial: Analyze data in a JavaScript-native data notebook</title>
      <dc:creator>Igor Lukanin</dc:creator>
      <pubDate>Thu, 15 Sep 2022 16:25:54 +0000</pubDate>
      <link>https://forem.com/cubejs/observable-tutorial-analyze-data-in-a-javascript-native-data-notebook-1oh</link>
      <guid>https://forem.com/cubejs/observable-tutorial-analyze-data-in-a-javascript-native-data-notebook-1oh</guid>
      <description>&lt;p&gt;Over the last few months, the Observable team has celebrated the &lt;a href="https://observablehq.com/@observablehq/analyzing-star-wars-movies-the-first-anniversary-of-observ"&gt;first anniversary&lt;/a&gt; of the Observable Plot charting library, introduced the &lt;a href="https://observablehq.com/@observablehq/introducing-data-table-cell"&gt;Data Table Cell&lt;/a&gt; type, and supercharged &lt;a href="https://observablehq.com/@observablehq/sql-cell"&gt;SQL Cells&lt;/a&gt; with summary charts.&lt;/p&gt;

&lt;p&gt;Let's see how data notebooks, built with Observable, can work with Cube and fetch data from the recently &lt;a href="https://cube.dev/blog/expanded-bi-support?ref=observable-tutorial-javascript-native-data-notebook"&gt;updated SQL API&lt;/a&gt; as well as the REST API.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sPBtsG6h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/592d7dc7-3342-4593-af73-2abab6f06829/" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sPBtsG6h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/592d7dc7-3342-4593-af73-2abab6f06829/" alt="Sample notebook" width="880" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Observable?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://observablehq.com"&gt;Observable&lt;/a&gt; is collaborative data canvas that has a few distinctive features compared to other modern &lt;a href="https://cube.dev/blog/category/notebook?ref=observable-tutorial-javascript-native-data-notebook"&gt;data notebooks&lt;/a&gt; on the market:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Observable implements the principles of interactive and reactive data exploration popularized by Bret Victor in his visionary talks such as &lt;a href="https://www.youtube.com/watch?v=PUv66718DII"&gt;Inventing on Principle&lt;/a&gt; and &lt;a href="https://www.youtube.com/watch?v=oUaOucZRlmE"&gt;Media for Thinking the Unthinkable&lt;/a&gt;. &lt;/li&gt;
&lt;li&gt;Observable is based on JavaScript which is not so common in the Python-dominated data notebooks space. Also, it natively works with &lt;a href="https://observablehq.com/@observablehq/plot"&gt;Observable Plot&lt;/a&gt;, an open-source JavaScript library for visualization of tabular data based on &lt;a href="https://github.com/d3/d3"&gt;D3.js&lt;/a&gt;. Not that surprising given that Observable is co-founded by &lt;a href="https://bost.ocks.org/mike/"&gt;Mike Bostock&lt;/a&gt;, the creator of D3.js.&lt;/li&gt;
&lt;li&gt;Observable provides a super-rich set of cell types as well as a complete library of &lt;a href="https://observablehq.com/tutorials"&gt;tutorials and examples&lt;/a&gt;, &lt;a href="https://observablehq.com/templates"&gt;templates&lt;/a&gt;, and &lt;a href="https://observablehq.com/community"&gt;notebooks&lt;/a&gt; contributed by a community of creators.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Observable notebooks run in the cloud and in your web browser. They can also be embedded as a whole and as individual blocks.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Cube?
&lt;/h2&gt;

&lt;p&gt;Cube is the &lt;a href="https://cube.dev/blog/headless-bi?ref=observable-tutorial-javascript-native-data-notebook"&gt;headless BI&lt;/a&gt; platform that sits between your data sources (e.g., data warehouses) and downstream data applications (e.g., data notebooks). Because of its position in the data stack, Cube is able to provide consistent metrics and performance for all your data flowing downstream.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RVuz77b8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/a35d923f-b2a5-455c-853c-b8fb54fea12c/" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RVuz77b8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/a35d923f-b2a5-455c-853c-b8fb54fea12c/" alt="Observable and Cube" width="880" height="289"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cube works with all &lt;a href="https://cube.dev/docs/config/databases?ref=observable-tutorial-javascript-native-data-notebook"&gt;modern data stores&lt;/a&gt;, creates a metrics layer with consistent metrics definitions, and delivers them downstream to any data notebook, application, or BI tool via its SQL, REST, or GraphQL APIs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Extended connectivity.&lt;/strong&gt; Observable natively supports &lt;a href="https://observablehq.com/@observablehq/getting-data-into-a-notebook"&gt;fetching the data&lt;/a&gt; from an API and a number of &lt;a href="https://observablehq.com/@observablehq/databases"&gt;databases&lt;/a&gt;: BigQuery, Snowflake, Postgres, and MySQL. With Cube, you can expand the range of available data sources to almost any data warehouse, query engine, or database (e.g., Athena, ClickHouse, Databricks, or Redshift).&lt;/p&gt;

&lt;h2&gt;
  
  
  Consistency for Data Explorers
&lt;/h2&gt;

&lt;p&gt;One of the benefits of using Observable and Cube together is providing a common ground for all teams working together within an organization.&lt;/p&gt;

&lt;p&gt;It's not uncommon that inconsistent data is being used across different teams within a company. It generally originates to varying metrics definitions across the many tools that each team uses. At some point, teams realize that they use different SQL queries to calculate metrics and, unfortunately, mislead their users with inconsistent insights, despite using the same data. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Let’s go through a super high-level example.&lt;/strong&gt; Imagine both the product and customer success teams want to calculate product success "over the last year". The product team queries the 2021 data (01/01/2021 to 12/31/2021), whereas customer success queries it as year-over-year (06/01/2021 to 06/01/2022). &lt;/p&gt;

&lt;p&gt;Since the data pipelines of both teams are siloed, neither team necessarily has visibility into how the other defined the query; it’s likely these teams use different tools too. This discrepancy in query results can absolutely misalign the teams working towards a single goal, but working off of disparate data.&lt;/p&gt;

&lt;p&gt;In case of Cube used upstream of Observable notebooks and all other data applications, a misalignment has no chance to happen. Cube will deliver consistent metrics via its various APIs to all tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defining Consistent Metrics
&lt;/h2&gt;

&lt;p&gt;Let’s see how Cube complements Observable in the data pipeline. In this example, we’ll use a e-commerce dataset and build a data model around it. We’ll also customize the data model to demonstrate how Cube helps maintain and evolve metrics definitions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Running Cube.&lt;/strong&gt; To keep things simple and save time, we'll run fully managed Cube in &lt;a href="https://cube.dev/cloud?ref=observable-tutorial-javascript-native-data-notebook"&gt;Cube Cloud&lt;/a&gt;. Please proceed to Cube Cloud’s &lt;a href="https://cubecloud.dev/auth/signup"&gt;sign up page&lt;/a&gt; and follow along the &lt;a href="https://cube.dev/docs/cloud/getting-started/create?ref=observable-tutorial-javascript-native-data-notebook"&gt;instructions&lt;/a&gt; to connect your own data source and generate a data model.&lt;/p&gt;

&lt;p&gt;For convenience, you can also use the following credentials to get up and running in no time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hostname:  demo-db-examples.cube.dev
Port:      5432
Database:  ecom
Username:  cube
Password:  12345
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is what your deployment in Cube will look like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--74flQkal--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/8177c022-44d5-49df-9188-28cfc71fb847/" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--74flQkal--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/8177c022-44d5-49df-9188-28cfc71fb847/" alt="Cube deployment" width="880" height="546"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Defining the data model.&lt;/strong&gt; To see the data model you created above, navigate to the Schema tab. You will see files named &lt;code&gt;LineItems.js&lt;/code&gt;, &lt;code&gt;Orders.js&lt;/code&gt;, &lt;code&gt;Users.js&lt;/code&gt;, etc. under the &lt;code&gt;schema&lt;/code&gt; folder which defines the &lt;em&gt;cubes&lt;/em&gt; in your data model.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qo47f-6W--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/53461723-80ab-4353-a940-3a6a2cb86f4c/" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qo47f-6W--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/53461723-80ab-4353-a940-3a6a2cb86f4c/" alt="Data schema" width="880" height="546"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Key ideas on what we’re seeing above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;em&gt;cube&lt;/em&gt; is a logical entity that groups measures (quantitative data) and dimensions (qualitative data) together into &lt;a href="https://cube.dev/docs/schema/reference/cube?ref=observable-tutorial-javascript-native-data-notebook"&gt;metrics definitions&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;In this case, we have defined a cube over the entire &lt;code&gt;public.line_items&lt;/code&gt; table.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cube.dev/docs/schema/reference/measures?ref=observable-tutorial-javascript-native-data-notebook"&gt;Measures&lt;/a&gt; are defined as aggregations (e.g. count, sum, etc.) over columns in the table.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cube.dev/docs/schema/reference/dimensions?ref=observable-tutorial-javascript-native-data-notebook"&gt;Dimensions&lt;/a&gt; are defined over textual, numeric, or temporal columns in the table.&lt;/li&gt;
&lt;li&gt;You can define complex measures and dimensions with custom SQL statements and references to other measures and dimensions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Customizing the data model.&lt;/strong&gt; We'll define a completely new rolling window measure in the data model.&lt;/p&gt;

&lt;p&gt;Let’s assume you want to calculate the total revenue brought in from sold line items over all time. This can be done by writing a SQL query in your local Jupyter notebook or any other BI tool you may be using. But what happens if you want to observe the total revenue over different time granularities? Or add another dimension to observe the revenue data?&lt;/p&gt;

&lt;p&gt;This is where your &lt;code&gt;LineItems&lt;/code&gt; cube can be modified to abstract away your varied data requests. Simply define the metrics in your data model once, and then use them time and again across teams to achieve your business goals.&lt;/p&gt;

&lt;p&gt;For this example, we’ll customize the data model in the cloud by adding the "total revenue" as a measure to the &lt;code&gt;LineItems&lt;/code&gt; cube.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// A rolling window measure&lt;/span&gt;
&lt;span class="nx"&gt;revenue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`price`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`sum`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;rollingWindow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;trailing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`unbounded`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While Cube Cloud has a ton of time-saving features, we'll use a particularly useful one now. Click on Enter Development Mode to unlock the schema files for modifications. This essentially creates a development environment (or "branch") where you can make changes to the data model.&lt;/p&gt;

&lt;p&gt;The following code snippet shows the contents of the &lt;code&gt;LineItems.js&lt;/code&gt; file (which defines the metrics for the &lt;code&gt;LineItems&lt;/code&gt; cube) with the &lt;code&gt;revenue&lt;/code&gt; rolling window measure added to the measures section.&lt;/p&gt;

&lt;p&gt;Navigate to the &lt;code&gt;LineItems.js&lt;/code&gt; file and replace its contents with the snippet below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;cube&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`LineItems`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`SELECT * FROM public.line_items`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;joins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;Products&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CUBE&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.product_id = &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;Products&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.id`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;relationship&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`belongsTo`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;Orders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CUBE&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.order_id = &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;Orders&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.id`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;relationship&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`belongsTo`&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="na"&gt;measures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`count`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;drillMembers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`quantity`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`sum`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`price`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`sum`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="c1"&gt;// A rolling window measure&lt;/span&gt;
    &lt;span class="na"&gt;revenue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`price`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`sum`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;rollingWindow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;trailing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`unbounded`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
     &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="na"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`id`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`number`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;primaryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`created_at`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`time`&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Click Save All to apply the changes to the development version of your API. Lastly, click Commit &amp;amp; Push to merge your changes back to the main branch. You will see your changes deployed in the Overview tab.&lt;/p&gt;

&lt;p&gt;Awesome! You’ve just built a customized data model to suit your project. The next part involves bringing your data to Observable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting Observable to Cube
&lt;/h2&gt;

&lt;p&gt;Cube offers three different APIs to deliver data (SQL, REST, and GraphQL) to downstream applications. SQL API and REST API are both fine options to access Cube from Observable notebooks, with their own pros and cons.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using SQL API
&lt;/h3&gt;

&lt;p&gt;Obviously, the very first step is to &lt;a href="https://observablehq.com"&gt;sign up&lt;/a&gt; for an Observable account if you don't have one already. Then, let's create a new blank notebook:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--c7wZLKiy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/6c346037-85dc-473e-92bc-93d0ce394ebd/" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--c7wZLKiy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/6c346037-85dc-473e-92bc-93d0ce394ebd/" alt="New notebook" width="880" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To read data from the SQL API, we need to add the Database query cell:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--x6B3ZV_L--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/7c4b3260-c444-423d-b87c-c63c175654cc/" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--x6B3ZV_L--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/7c4b3260-c444-423d-b87c-c63c175654cc/" alt="Database query" width="880" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After adding the cell, click Select a database... and then + Create database to navigate to the Databases page in Settings:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yFqOPYHg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/fb5c5e01-3e6d-4b0d-be9e-5a7a34c75f9f/" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yFqOPYHg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/fb5c5e01-3e6d-4b0d-be9e-5a7a34c75f9f/" alt="Databases in Settings" width="880" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On that page, click on + New database and then navigate back to Cube Cloud for a second. On the Overview tab, click on Deploy SQL API to generate your credentials:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--otSOAT7o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/db421dee-747c-4129-a0a7-05583132f834/" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--otSOAT7o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/db421dee-747c-4129-a0a7-05583132f834/" alt="SQL API" width="880" height="546"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can then enter these credentials to the New database popup in Observable. For convenience, you can also use the following credentials:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Host:      grim-dola.sql.aws-us-west-2.cubecloudapp.dev
Port:      5432
Database:  grim-dola
Username:  cube
Password:  4457642218d9552e74db8bafff1e3047
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--S1-iXXrS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/960f42b4-95f0-47bd-8c8f-b4ce361a5921/" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--S1-iXXrS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/960f42b4-95f0-47bd-8c8f-b4ce361a5921/" alt="New database" width="880" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you can navigate back to your notebook and select Cube from the dropdown in your Database query cell:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XR4160ro--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/932d29d6-8f65-42e4-9b54-bcb346ade792/" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XR4160ro--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/932d29d6-8f65-42e4-9b54-bcb346ade792/" alt="Database query cell" width="880" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you can &lt;a href="https://cube.dev/docs/backend/sql#querying-fundamentals"&gt;run queries&lt;/a&gt; against the SQL API:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wJmfgTCJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/ece1aa7f-4849-4e17-b2ce-1dadf292fd1e/" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wJmfgTCJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/ece1aa7f-4849-4e17-b2ce-1dadf292fd1e/" alt="Query result" width="880" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's worth noting that there's another cell type called Data table which is very convenient. Similar to Database query, you can select Cube as the database. However, you don't need to  write the SQL by hand. You can proceed by selecting a table, representing to a cube in your data model:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CyGVyYIZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/95845ac2-a048-40cc-9685-7d9bdc37bee2/" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CyGVyYIZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/95845ac2-a048-40cc-9685-7d9bdc37bee2/" alt="Data table" width="880" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, you can filter, pick select measures and dimensions, sort, and slice the data. Observable will generate the SQL for you under the hood:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--anQYtJ-c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/f144e4d2-344b-43c7-87a9-a6fa383eed53/" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--anQYtJ-c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/f144e4d2-344b-43c7-87a9-a6fa383eed53/" alt="Data table with the result" width="880" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To visualize the data, you should give the cell a name (e.g., &lt;code&gt;orders_by_date&lt;/code&gt;) and select from a plethora of available chart types:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--61TBRKv0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/8ad72b5e-0a6a-4799-8187-2debfa436868/" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--61TBRKv0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/8ad72b5e-0a6a-4799-8187-2debfa436868/" alt="Available visualizations" width="880" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If needed, you can also apply a last-mile transformation with JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;orders_by_date_t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;orders_by_date&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, you would probably pick and customize one of the many Observable Plot snippets:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--V7Tz5y0w--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/f627afdf-4a7d-4021-b72e-9124d17c595e/" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--V7Tz5y0w--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/f627afdf-4a7d-4021-b72e-9124d17c595e/" alt="Plot" width="880" height="530"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Plot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;marks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;Plot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ruleY&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="nx"&gt;Plot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lineY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orders_by_date_t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;createdAt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;count&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, you can import and use &lt;a href="https://vega.github.io/vega-lite/"&gt;Vega-Lite&lt;/a&gt; library which is widely popular and used by thousands of data enthusiasts: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sPBtsG6h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/592d7dc7-3342-4593-af73-2abab6f06829/" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sPBtsG6h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/592d7dc7-3342-4593-af73-2abab6f06829/" alt="Vega-Lite" width="880" height="448"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@vega/vega-lite-api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="nx"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;markLine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orders_by_date_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;fieldT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;createdAt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;timeUnit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utcyearmonthdate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Date&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;fieldQ&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;count&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Count&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you'd like to get inspiration for more advanced data visualizations that you can build with Observable, check out the available &lt;a href="https://observablehq.com/templates"&gt;templates&lt;/a&gt; and &lt;a href="https://observablehq.com/featured-creators"&gt;featured works&lt;/a&gt; by the Observable community. Also, consider diving deep into &lt;a href="https://observablehq.com/collection/@observablehq/plot"&gt;Observable Plot&lt;/a&gt; library, the Swiss knife of data visualization in Observable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using REST API
&lt;/h3&gt;

&lt;p&gt;Now let's see how you can also read data from the REST API. First, we need to add the generic JavaScript cell:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bfarkYt6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/ff0dfd08-ead2-44ee-8a62-a48e701b666e/" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bfarkYt6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/ff0dfd08-ead2-44ee-8a62-a48e701b666e/" alt="JavaScript cell" width="880" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, let's navigate back to Cube Cloud. On the Overview page, we can copy the endpoint URL for the REST API:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AcWvRiDK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/e0b8ae9a-676d-422f-b6ef-dbe873df5764/" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AcWvRiDK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/e0b8ae9a-676d-422f-b6ef-dbe873df5764/" alt="REST API" width="880" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's what I've copied from my Cube Cloud deployment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://awesome-ecom.gcp-us-central1.cubecloudapp.dev/cubejs-api/v1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll also need to generate a long-lasting JSON Web token. (If you need a hand, pleae check the &lt;a href="https://cube.dev/docs/security#generating-json-web-tokens-jwt"&gt;Cube docs&lt;/a&gt; on JWT generation.) Here's what I've generated for my deployment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjEwMDAwMDAwMDAsImV4cCI6NTAwMDAwMDAwMH0.OHZOpOBVKr-sCwn8sbZ5UFsqI3uCs6e4omT7P6WVMFw
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we're ready to insert this code snippet in the JavaScript cell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;orders_over_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://awesome-ecom.gcp-us-central1.cubecloudapp.dev/cubejs-api/v1/load&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjEwMDAwMDAwMDAsImV4cCI6NTAwMDAwMDAwMH0.OHZOpOBVKr-sCwn8sbZ5UFsqI3uCs6e4omT7P6WVMFw&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;measures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Orders.count&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;timeDimensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
          &lt;span class="na"&gt;dimension&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Orders.createdAt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;granularity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;month&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;}]&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Orders.count&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;utcParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Orders.createdAt.month&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;})))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's highlight a few things here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We need to append &lt;code&gt;/load&lt;/code&gt; to your API URL specified as a &lt;code&gt;fetch&lt;/code&gt; argument.&lt;/li&gt;
&lt;li&gt;We need to add the token next to the &lt;code&gt;Bearer&lt;/code&gt; part of the &lt;code&gt;Authorization&lt;/code&gt; header.&lt;/li&gt;
&lt;li&gt;We need to put the Cube query inside the &lt;code&gt;JSON.stringify&lt;/code&gt; call.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's enough to make an API call and fetch the data. In a similar fashion, here's how you can visualize the data with Vega-Lite: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sPBtsG6h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/592d7dc7-3342-4593-af73-2abab6f06829/" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sPBtsG6h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/592d7dc7-3342-4593-af73-2abab6f06829/" alt="Vega-Lite" width="880" height="448"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;my_published_cell&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;markLine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orders_over_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;fieldT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;createdAt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;timeUnit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utcyearmonth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Date&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="nx"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;fieldQ&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;count&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Orders&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Choosing an API to use
&lt;/h3&gt;

&lt;p&gt;You should pick an API with these pros and cons in mind:&lt;/p&gt;

&lt;p&gt;Cube &lt;a href="https://cube.dev/docs/backend/sql?ref=observable-tutorial-javascript-native-data-notebook"&gt;SQL API&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;allows to use Database query cells where you can write concise SQL queries&lt;/li&gt;
&lt;li&gt;allows to use Data table cells where you can compose queries in the UI&lt;/li&gt;
&lt;li&gt;requires a separate service to set up multitenancy and fetch security context&lt;/li&gt;
&lt;li&gt;won't work in published Observable notebooks on a free plan (Observable &lt;a href="https://observablehq.com/@observablehq/databases#cell-42"&gt;turns database connections off&lt;/a&gt; after a notebook &lt;em&gt;on a free plan&lt;/em&gt; is published)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cube &lt;a href="https://cube.dev/docs/rest-api?ref=observable-tutorial-javascript-native-data-notebook"&gt;REST API&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;requires to use JavaScript cells to write the code to fetch the data which can sometimes get overly verbose&lt;/li&gt;
&lt;li&gt;allows to pass security context in a JSON Web Token for multitenancy&lt;/li&gt;
&lt;li&gt;will work in published Observable notebooks &lt;em&gt;on a free plan&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, since Observable has very sound &lt;a href="https://observablehq.com/pricing"&gt;pricing plans&lt;/a&gt;, compatibility with published notebooks should not be a sole deciding factor here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Embedding Observable notebooks
&lt;/h2&gt;

&lt;p&gt;Observable provides a very rich set of embedding options, far exceeding what other data notebooks provide in terms of customizability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Notebook.&lt;/strong&gt; Obviously, you can publish the entire notebook by clicking Publish... on the top of your notebook.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xzd0ixrG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/ae1d68a5-640f-4801-965e-c3ca844aff70/" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xzd0ixrG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/ae1d68a5-640f-4801-965e-c3ca844aff70/" alt="Publish" width="880" height="618"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For instance, you can browse the &lt;a href="https://observablehq.com/@igorlukanin/cube-observable"&gt;published notebook&lt;/a&gt; that we've built during this tutorial. (Note that cells using the SQL API don't work because the notebook is owned by an account on the free plan.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cells.&lt;/strong&gt; You can publish an individual cell or a set of cells as well. An interesting quirk is that you can only publish a cell if it has a name: to do that for JavaScript cells, assign the contents to a variable. Then, you can click Embed in the dropdown menu:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tQ2hNAfO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/43aaffb0-3ff5-4bf6-b547-c9ecdb7d9661/" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tQ2hNAfO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/43aaffb0-3ff5-4bf6-b547-c9ecdb7d9661/" alt="Embed" width="880" height="618"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You are free to choose whether you're publishing all cells in the notebook, a subset of cells, or just a single one:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UPN99nlv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/d5cce206-bcf4-40e4-93d5-97d3fedc62c6/" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UPN99nlv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/d5cce206-bcf4-40e4-93d5-97d3fedc62c6/" alt="Embedding options" width="880" height="555"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, you have to choose between several powerful &lt;a href="https://observablehq.com/@observablehq/embeds"&gt;embedding options&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Iframe will give you a code snippet that you can Copy and embed into any HTML document, including a page in your content management system like Gatsby or even another Observable notebook. Wrapped into an &lt;code&gt;iframe&lt;/code&gt; tag, the code and styles of embedded content are fully isolated from your application. &lt;/li&gt;
&lt;li&gt;With Iframe, you can also Copy URL only and paste it into environments that support the &lt;a href="https://oembed.com"&gt;oEmbed&lt;/a&gt; standard, e.g., Notion, Medium, or Reddit.
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ECU36P_G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://ucarecdn.com/adde8f0b-6fe5-4b38-813f-a21470f757dc/" alt="Embedding to Notion" width="880" height="565"&gt;
&lt;/li&gt;
&lt;li&gt;Runtime with JavaScript will give you a code snippet that you can embed into an HTML document as well. The difference is that it will load a tiny JavaScript library and render the cell in place, giving you the freedom to customize how the cell is styled and displayed.&lt;/li&gt;
&lt;li&gt;Runtime with React works similarly, the only difference is that your cell is represented as a React component. If you're building a React app, it's the most native way to embed a cell into it.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;If you've picked Observable as your data notebook of choice, consider using it with Cube. It will provide consistent metrics for your data analysis and data apps you'll build and publish.&lt;/p&gt;

&lt;p&gt;Would you like to learn more about Cube? Check the &lt;a href="https://cube.dev/docs/?ref=observable-tutorial-javascript-native-data-notebook"&gt;documentation&lt;/a&gt; and create a free &lt;a href="https://cubecloud.dev/auth/signup"&gt;Cube Cloud account&lt;/a&gt; today.&lt;/p&gt;

&lt;p&gt;Also, please feel free to &lt;a href="https://slack.cube.dev/?ref=observable-tutorial-javascript-native-data-notebook"&gt;join our community&lt;/a&gt; of more than 6000 developers and data engineers on Slack, &lt;a href="https://cube.dev/contact?ref=observable-tutorial-javascript-native-data-notebook"&gt;drop us a line&lt;/a&gt;, or give Cube a &lt;a href="https://github.com/cube-js/cube.js"&gt;star on GitHub&lt;/a&gt;. Good luck!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>datascience</category>
      <category>notebook</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>What are data apps?</title>
      <dc:creator>Igor Lukanin</dc:creator>
      <pubDate>Wed, 14 Sep 2022 16:45:59 +0000</pubDate>
      <link>https://forem.com/cubejs/what-are-data-apps-2m5b</link>
      <guid>https://forem.com/cubejs/what-are-data-apps-2m5b</guid>
      <description>&lt;p&gt;Previously, we’ve &lt;a href="https://cube.dev/blog/headless-bi?ref=what-are-data-apps" rel="noopener noreferrer"&gt;described the parts of headless BI&lt;/a&gt;, taken an &lt;a href="https://cube.dev/blog/open-source-data-modeling?ref=what-are-data-apps" rel="noopener noreferrer"&gt;in-depth look at the data modeling layer&lt;/a&gt;, and explored one use case for headless BI: &lt;a href="https://cube.dev/blog/what-is-embedded-analytics?ref=what-are-data-apps" rel="noopener noreferrer"&gt;embedded analytics&lt;/a&gt;. This week, let’s take a step back and look at the category of data applications.&lt;/p&gt;

&lt;p&gt;But first…&lt;/p&gt;

&lt;h2&gt;
  
  
  What are data applications?
&lt;/h2&gt;

&lt;p&gt;“Data apps” is an umbrella term for a category of interactive tools that use data to deliver insight or automatically take action. When we talk about data apps, we frequently cite the examples of recommendation engines, data visualization built into applications, and customized internal reporting tools for business teams.&lt;/p&gt;

&lt;h2&gt;
  
  
  Isn’t this just embedded analytics?
&lt;/h2&gt;

&lt;p&gt;Embedded analytics takes the kind of exploration that used to happen in dashboards and legacy BI tools, and injects it directly into the applications that internal teams and external customers already use. Headless BI facilitates building embedded analytics more quickly. But embedded analytics is just the beginning.&lt;/p&gt;

&lt;p&gt;Despite being more accessible and customized than traditional dashboards, embedded analytics is still primarily a tool for data exploration. By contrast, data applications are capable of data explanation: highlighting trends, surfacing insights, making recommendations. This type of application entails a dynamic, purpose-built user experience, and it is typically developed by software and data engineers, not business analysts.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are some use cases of data applications?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The first type of data applications is an embedded data app.&lt;/strong&gt; Think of this as the evolution of embedded analytics, but unlike embedded analytics’ static dashboards, embedded data features tend to be highly customized, dynamic, and purpose-built. These applications surface insight within the native user experience of another application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A business’s internal data products and portals are a second kind of data applications.&lt;/strong&gt; Unlike traditional or embedded exploration dashboards, this type of data application is purpose-built for a specific business unit, and is built with relevant business context. These applications’ custom interactivity allows business users to receive insights without mastering data analysts’ workflows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The third type of data applications are end-consumer-facing applications.&lt;/strong&gt; These may be built for customers, partners, or shareholders, and they are not dissimilar from internal applications—but they tend to require a finer level of design polish and customization. Additionally, this type of app must be built for higher performance, reflecting consumers’ expectations of speed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fucarecdn.com%2Ff3b2607c-8d11-489e-a09e-df9f94c1384c%2F" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fucarecdn.com%2Ff3b2607c-8d11-489e-a09e-df9f94c1384c%2F" alt="apps"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How are data applications built?
&lt;/h2&gt;

&lt;p&gt;By their nature, data apps require recourse to large quantities of data. This has been made possible by the rise of the cloud data warehouse and an ever-growing ecosystem of data ingestion, governance, transformation, and orchestration tools.&lt;/p&gt;

&lt;p&gt;But given their complexity and power, data apps generally are built by engineering teams, and they require integration with modern engineering workflows, including version control, testing, and continuous integration and deployment practices.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building from scratch
&lt;/h3&gt;

&lt;p&gt;Embedding data app functionality into a larger application generally requires building from scratch. What is the architecture of such a solution? &lt;/p&gt;

&lt;h4&gt;
  
  
  Data store
&lt;/h4&gt;

&lt;p&gt;Naturally, a data application starts with the data—and the basis of the modern data stack is the cloud data warehouse. This can be a general purpose data warehouse like &lt;a href="https://snowflake.com" rel="noopener noreferrer"&gt;Snowflake&lt;/a&gt; or a real-time tool like &lt;a href="https://www.firebolt.io/" rel="noopener noreferrer"&gt;Firebolt&lt;/a&gt;, &lt;a href="https://clickhouse.com/" rel="noopener noreferrer"&gt;ClickHouse&lt;/a&gt;, or &lt;a href="https://materialize.com/" rel="noopener noreferrer"&gt;Materialize&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Headless BI layer
&lt;/h4&gt;

&lt;p&gt;A crucial component of a data app is the &lt;a href="https://cube.dev/blog/headless-bi?ref=what-are-data-apps" rel="noopener noreferrer"&gt;headless BI layer&lt;/a&gt;. Specifically, a major piece of this is &lt;strong&gt;access control&lt;/strong&gt; integrated with the warehouse’s security controls, because embedded analytics always require multitenancy. A second piece is &lt;strong&gt;advanced caching&lt;/strong&gt;. This is because the data warehouse is a great candidate for a backend, but itself does not support highly concurrent queries with sub-second latency that modern data consumers expect.&lt;/p&gt;

&lt;p&gt;The BI layer is also where &lt;strong&gt;data modeling&lt;/strong&gt; is handled, to ensure that a data app’s users consume the same data definitions as users of other internal or external applications. Data modeling and metrics definition should be handled once, and this must be up-stack from every application or dashboard.&lt;/p&gt;

&lt;p&gt;Data is then made available via diverse APIs—e.g., SQL, GraphQL, and REST—to be consumed by…&lt;/p&gt;

&lt;h4&gt;
  
  
  A hybrid presentation layer
&lt;/h4&gt;

&lt;p&gt;For the high customization expected of an embedded data application, and when front-end teams are looped in, different charting libraries can be used. These range from &lt;a href="https://d3js.org/" rel="noopener noreferrer"&gt;D3&lt;/a&gt; to &lt;a href="https://www.chartjs.org/" rel="noopener noreferrer"&gt;Chart.js&lt;/a&gt; and &lt;a href="https://www.highcharts.com/" rel="noopener noreferrer"&gt;Highcharts&lt;/a&gt;. These most likely will be natively integrated with frontend application frameworks like &lt;a href="http://react" rel="noopener noreferrer"&gt;React&lt;/a&gt; or &lt;a href="https://angularjs.org/" rel="noopener noreferrer"&gt;Angular&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Working with a framework
&lt;/h3&gt;

&lt;p&gt;For the second and third types of data applications, the initial layers of the data stack are the same—i.e., the base layer is a data warehouse, followed by a headless BI layer for data modeling, access control, caching, and application APIs.&lt;/p&gt;

&lt;p&gt;For the user interface, however, there’s typically less customization required. This creates the opportunity to take advantage of the new category of no code / low code tools like &lt;a href="https://www.appsmith.com/" rel="noopener noreferrer"&gt;Appsmith&lt;/a&gt; and &lt;a href="https://retool.com/" rel="noopener noreferrer"&gt;Retool&lt;/a&gt;, which can be used to quickly build analytics interfaces.&lt;/p&gt;

&lt;p&gt;There also are data application frameworks that are helpful here: tools like &lt;a href="https://plotly.com/dash/" rel="noopener noreferrer"&gt;Plotly Dash&lt;/a&gt; and &lt;a href="https://streamlit.io" rel="noopener noreferrer"&gt;Streamlit&lt;/a&gt; make it possible to turn data scripts into shareable web applications without the need for front-end development.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next?
&lt;/h2&gt;

&lt;p&gt;As it gets easier to build customized experiences, the number and types of data apps will proliferate—but the use case for a basic dashboard-centric experience won’t go away. There will always be cases where needs are best met with traditional charts, or when the quick turnaround requires making something available without tapping engineering resources for help. For these, embedded analytics are and will remain the best choice.&lt;/p&gt;

&lt;p&gt;What’s exciting, though, is all of the new opportunities that the modern data application stack makes available. Opportunities for working with ever greater quantities of data, with ever greater complexity, will only grow.&lt;/p&gt;

</description>
      <category>data</category>
      <category>analytics</category>
    </item>
    <item>
      <title>DeWitt Clause, or Can You Benchmark %DATABASE% and Get Away With It</title>
      <dc:creator>Igor Lukanin</dc:creator>
      <pubDate>Thu, 02 Jun 2022 20:36:05 +0000</pubDate>
      <link>https://forem.com/cubejs/dewitt-clause-or-can-you-benchmark-database-and-get-away-with-it-284</link>
      <guid>https://forem.com/cubejs/dewitt-clause-or-can-you-benchmark-database-and-get-away-with-it-284</guid>
      <description>&lt;p&gt;&lt;em&gt;TL;DR: Please see the list of database vendors who will either praise or punish you for doing a benchmark as well as an entertaining digression into the recent history of computer science.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;About 6 months ago, we witnessed what Andy Pavlo &lt;a href="https://twitter.com/andy_pavlo/status/1459251061085118465"&gt;wittily characterised&lt;/a&gt; as an "old school database benchmark gang war". The timeline of events goes like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://databricks.com"&gt;Databricks&lt;/a&gt;, a data lakehouse company founded by the creators of Apache Spark, published a &lt;a href="https://databricks.com/blog/2021/11/02/databricks-sets-official-data-warehousing-performance-record.html"&gt;blog post&lt;/a&gt; claiming that it set a new data warehousing performance record in 100 TB &lt;a href="https://www.tpc.org/tpcds/"&gt;TPC-DS benchmark&lt;/a&gt;. It was also mentioned that Databricks was 2.7x faster and 12x better in terms of price performance compared to Snowflake.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://snowflake.com"&gt;Snowflake&lt;/a&gt;, a data warehousing company founded by ex-Oracle and ex-VectorWise experts, responded with a &lt;a href="https://www.snowflake.com/blog/industry-benchmarks-and-competing-with-integrity/"&gt;blog post&lt;/a&gt; that critically reviewed Databricks' findings, reported different results for the same benchmark, and claimed comparable price/performance to Databricks.&lt;/li&gt;
&lt;li&gt;After two days, Databricks followed up with another &lt;a href="https://databricks.com/blog/2021/11/15/snowflake-claims-similar-price-performance-to-databricks-but-not-so-fast.html"&gt;blog post&lt;/a&gt; that confirmed that they stand by the initially reported results.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While this back-and-forth between respected data vendors was informative and amusing, its collateral damage was even more substantial. Databricks shared that they have &lt;a href="https://databricks.com/blog/2021/11/08/eliminating-the-dewitt-clause-for-database-benchmarking.html"&gt;eliminated the anti-competitive &lt;em&gt;DeWitt clause&lt;/em&gt;&lt;/a&gt; from their service terms. Snowflake followed up with a &lt;a href="https://www.snowflake.com/blog/industry-benchmarks-and-competing-with-integrity/"&gt;similar update&lt;/a&gt; to their acceptable use policy.&lt;/p&gt;

&lt;p&gt;But what is the &lt;em&gt;DeWitt clause&lt;/em&gt;?&lt;/p&gt;

&lt;p&gt;More importantly, why would you need to know if a DeWitt clause is included in terms of service in case you'd like to do and publish a performance benchmark? Let's explore.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backstory of the DeWitt Clause
&lt;/h2&gt;

&lt;p&gt;In 1982, at the very inception of relational databases, &lt;a href="https://www.linkedin.com/in/david-dewitt-30189b50/"&gt;David DeWitt&lt;/a&gt;, a researcher at the Department of Computer Sciences at the University of Wisconsin-Madison, was working on measuring database performance. His team wrote the Wisconsin Benchmark, tested a number of databases, and &lt;a href="https://pages.cs.wisc.edu/~dewitt/includes/benchmarking/vldb83.pdf"&gt;published the results&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Apparently, some folks were displeased with the results. &lt;a href="https://en.wikipedia.org/wiki/Michael_Stonebraker"&gt;Michael Stonebraker&lt;/a&gt;, a researcher at the UC Berkeley and co-creator of Ingres, called DeWitt and expressed how upset he was. However, they remained friends anyway as DeWitt recounts: in his &lt;a href="https://www.youtube.com/watch?v=-TIUGC4X2q8&amp;amp;t=420s"&gt;talk on YouTube&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/-TIUGC4X2q8?start=420"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Oracle also didn’t perform very well and reacted hastily. &lt;a href="https://www.eweek.com/development/db-test-pioneer-makes-history/"&gt;According to DeWitt&lt;/a&gt;, Oracle CEO Larry Ellison was so displeased that he called the department chair and insisted, "You have to fire this guy.” Oracle also inserted a clause in their terms of use that boiled down to the fact that one can’t publish benchmarks without getting an explicit approval from Oracle.&lt;/p&gt;

&lt;h2&gt;
  
  
  DeWitt Clause vs. DeWitt Embrace Clause
&lt;/h2&gt;

&lt;p&gt;Nowadays, the DeWitt clause is a common provision in end-user license agreements for proprietary software that prevents from publishing information about the software without an explicit approval from the vendor. Over time, it have become widespread in the software industry and its implications are drastic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;People avoid publishing benchmarks about software products with the DeWitt clause.&lt;/li&gt;
&lt;li&gt;Moreover, some won’t even bother doing a benchmark knowing they won’t be able to publish the results.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are debates whether DeWitt clauses are &lt;a href="https://www.dwheeler.com/essays/dewitt-clause.html"&gt;moral or legal&lt;/a&gt; because of the implications above as well as the overall corrosive effect they have on the industry. Once one supplier adds a DeWitt clause, the others can feel that they are at a disadvantage without one. We're lucky it can also work the other way round, like in the Databricks and Snowflake case. However, open source software (OSS) licenses don’t and can't have DeWitt clauses, meaning that the OSS can be legally critiqued, but not their proprietary competitors.&lt;/p&gt;

&lt;p&gt;At the same time, some vendors do quite the opposite to the inclusion of a DeWitt clause. Instead, they change their license to the DeWitt Embrace Clause meaning that if you disclose benchmark results for them, you automatically allow them to do the same with you. In a way, DeWitt Embrace Clause is like copyleft for the DeWitt Clause. &lt;/p&gt;

&lt;h2&gt;
  
  
  Can You Benchmark %DATABASE%?
&lt;/h2&gt;

&lt;p&gt;Now, do you wonder whether some vendor has or hasn't a DeWitt clause?&lt;/p&gt;

&lt;p&gt;Shortly after the Databricks and Snowflake debate, &lt;a href="https://twitter.com/MarkCallaghanDB"&gt;Mark Callaghan&lt;/a&gt;, an ex-MongoDB and ex-Rockset database expert, checked if some database and public cloud vendors have DeWitt clauses in their terms of use.&lt;/p&gt;

&lt;p&gt;In this blog post, I try to extend &lt;a href="http://smalldatum.blogspot.com/2021/11/dewitt-clause-vs-public-cloud.html"&gt;his research&lt;/a&gt; and maintain an ever-green list of database and similar vendors with regard to the DeWitt clause. (If you spot any inaccuracy, which is totally possible, please get in touch via &lt;a href="//mailto:igor@cube.dev?subject=DeWitt"&gt;igor@cube.dev&lt;/a&gt;, I'll be happy to update the post.)&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Last updated: June 2, 2022.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  😇 Open-source vendors (without the DeWitt clause)
&lt;/h3&gt;

&lt;p&gt;Open source licenses usually grant users permission to &lt;a href="https://opensource.org/docs/osd"&gt;use open source software for any purpose&lt;/a&gt;. So, by definition, open source software can't contain DeWitt clauses (because if it did, it wouldn't be open source).&lt;/p&gt;

&lt;p&gt;Yes, you can benchmark any of these tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Apache &lt;a href="https://github.com/apache/drill"&gt;Drill&lt;/a&gt;, &lt;a href="https://github.com/apache/druid/"&gt;Druid&lt;/a&gt;, &lt;a href="https://github.com/apache/flink"&gt;Flink&lt;/a&gt;, &lt;a href="https://github.com/apache/hive"&gt;Hive&lt;/a&gt;, &lt;a href="https://kafka.apache.org"&gt;Kafka&lt;/a&gt;, &lt;a href="https://github.com/apache/spark"&gt;Spark&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/appwrite/appwrite"&gt;appwrite&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ClickHouse/ClickHouse"&gt;ClickHouse&lt;/a&gt; (they even maintain a &lt;a href="https://github.com/ClickHouse/ClickHouse/issues/22398"&gt;list of benchmarks&lt;/a&gt; on GitHub)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/cockroachdb/cockroach"&gt;CockroachDB&lt;/a&gt; (licensed under BSL, not &lt;a href="https://opensource.org/licenses/alphabetical"&gt;OSI-approved&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/dremio/dremio-oss"&gt;Dremio&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/elastic/elasticsearch"&gt;Elasticsearch&lt;/a&gt; (licensed under SSPL, not OSI-approved)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ksqldb.io"&gt;ksqlDB&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/MaterializeInc/materialize"&gt;Materialize&lt;/a&gt; (licensed under BSL, not OSI-approved)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/mongodb/mongo"&gt;MongoDB&lt;/a&gt; (licensed under SSPL, not OSI-approved)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org"&gt;PostgreSQL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/prestodb/presto"&gt;Presto&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/redpanda-data/redpanda"&gt;Redpanda&lt;/a&gt; (licensed under BSL, not OSI-approved)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/questdb/questdb"&gt;QuestDB&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/timescale/timescaledb"&gt;TimescaleDB&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/trinodb/trino"&gt;Trino&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also, some open-source vendors collaboratively maintain benchmarking suites such as &lt;a href="https://github.com/timescale/tsbs"&gt;Time Series Benchmark Suite&lt;/a&gt; to help choose the best tools for particular workloads.&lt;/p&gt;

&lt;h3&gt;
  
  
  😇 Vendors without the DeWitt clause
&lt;/h3&gt;

&lt;p&gt;Cloud vendors can restrict you from benchmarking their services but sometimes they choose not to have the DeWitt clause.&lt;/p&gt;

&lt;p&gt;Yes, you can benchmark any of these services:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ahana.io/terms-of-service/"&gt;Ahana Cloud&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://altinity.com/legal/"&gt;Altinity Cloud&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://appwrite.io/cloud"&gt;appwrite Cloud&lt;/a&gt; (not launched to GA yet)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://clickhouse.com/cloud/"&gt;ClickHouse Cloud&lt;/a&gt; (not launched to GA yet)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://assets.confluent.io/m/103033bdcb9b3fcd/original/20220201-Confluent_Cloud_Services_Agreement.pdf"&gt;Confluent Cloud&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://d7umqicpi7263.cloudfront.net/eula/product/28a79f78-8008-4c02-a971-54ef6d8ed904/898eb1bc-7c2a-4829-b479-67f5a18a51ff.pdf"&gt;Firebolt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://materialize.com/terms-and-conditions/"&gt;Materialize Cloud&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mongodb.com/cloud-terms-and-conditions"&gt;MongoDB Cloud&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://questdb.io/terms/"&gt;QuestDB Cloud&lt;/a&gt; (not launched to GA yet)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://redpanda.com/cloud/"&gt;Redpanda Cloud&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.snowflake.com/legal/acceptable-use-policy/"&gt;Snowflake&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.starburst.io/terms/galaxy-terms-of-service/"&gt;Starburst Galaxy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://supabase.com/docs/company/terms"&gt;Supabase&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.timescale.com/legal/timescale-cloud-terms-of-service"&gt;Timescale Cloud&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, many of these cloud vendors disallow &lt;em&gt;abusive benchmarking&lt;/em&gt; in their acceptable use policies. So be careful and please don't get banned.&lt;/p&gt;

&lt;h3&gt;
  
  
  🙃 Vendors with the DeWitt Embrace clause
&lt;/h3&gt;

&lt;p&gt;Some cloud vendors permit you to benchmark their service but require reciprocity: you must make the benchmark reproducible and allow benchmarking of your own service or tool in response.&lt;/p&gt;

&lt;p&gt;Yes, you can benchmark any of these services and probably get away with it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/service-terms/"&gt;Amazon Web Services&lt;/a&gt;, including &lt;a href="https://aws.amazon.com/athena/"&gt;Athena&lt;/a&gt;, &lt;a href="https://aws.amazon.com/rds/aurora/"&gt;Aurora&lt;/a&gt;, and &lt;a href="https://aws.amazon.com/redshift/"&gt;Redshift&lt;/a&gt;. &lt;em&gt;You may perform benchmarks or comparative tests or evaluations (each, a “Benchmark”) of the Services. If you perform or disclose, or direct or permit any third party to perform or disclose, any Benchmark of any of the Services, you (i) will include in any disclosure, and will disclose to us, all information necessary to replicate such Benchmark, and (ii) agree that we may perform and disclose the results of Benchmarks of your products or services, irrespective of any restrictions on Benchmarks in the terms governing your products or services.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.cockroachlabs.com/acceptable-use-policy/"&gt;CockroachDB Serverless&lt;/a&gt;. &lt;em&gt;You can only disclose benchmarking tests if you (i) provide us the necessary information to replicate the tests, (ii) disclose the methodology for the benchmarking tests along with the results, and (iii) allow us to run our own benchmarking tests against your products and services at our discretion.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://databricks.com/blog/2021/11/08/eliminating-the-dewitt-clause-for-database-benchmarking.html"&gt;Databricks&lt;/a&gt;. &lt;em&gt;You may perform benchmarks or comparative tests or evaluations (each, a “Benchmark”) of the Platform Services and may disclose the results of the Benchmark other than for Beta Services. If you perform or disclose, or direct or permit any third party to perform or disclose, any Benchmark of any of the Platform Services, you (i) will include in any disclosure, and will disclose to us, all information necessary to replicate such Benchmark, and (ii) agree that we may perform and disclose the results of Benchmarks of your products or services, irrespective of any restrictions on Benchmarks in the terms governing your products or services.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.singlestore.com/blog/eliminating-the-dewitt-clause-for-greater-transparency-in-benchmarking/"&gt;SingleStore&lt;/a&gt;. &lt;em&gt;Customer may perform industry standard benchmarks, comparative tests or evaluations (each, a "Performance Report") of the Managed Service. If Customer performs or discloses, or directs or permits any third party to perform or disclose, any Performance Report of any of the Managed Service, Customer (i) will include in any disclosure, and will disclose to SingleStore all information necessary to replicate such Performance Report and the data from the Performance Report, and (il) agree that SingleStore may perform and disclose the results of the Performance Report of Customer's products or services, irrespective of any restrictions on Performance Report.&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🤬 Vendors with the DeWitt clause
&lt;/h3&gt;

&lt;p&gt;These vendors chose to restrict benchmarks with DeWitt clauses.&lt;/p&gt;

&lt;p&gt;No, you can't benchmark any of these tools and get away with it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.cloudera.com/legal/terms-and-conditions/cloudera-online-services-terms.html"&gt;Cloudera Data Platform&lt;/a&gt;. &lt;em&gt;Restrictions. Customer may not: [...] access or use the Services for purposes of monitoring availability, performance or functionality of the Services, or for any benchmarking or competitive purposes.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.decodable.co/legal/tos"&gt;Decodable&lt;/a&gt;. &lt;em&gt;Restrictions. Customer may not, and may not cause or permit others to: [...] disclose results of any benchmark tests or performance tests of the Decodable services hereunder without Decodable’s prior written consent.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.dremio.com/legal/terms-of-service/"&gt;Dremio Cloud&lt;/a&gt;. &lt;em&gt;Restrictions. Customer shall not, and shall not cause or allow any Authorized User or third party to: [...] except with Dremio’s prior written permission, publish any performance or benchmark tests or analysis relating to Dremio Cloud.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.elastic.co/agreements/global/cloud-services-monthly"&gt;Elastic Cloud&lt;/a&gt;. &lt;em&gt;Restrictions [...] You shall not: [...] access or use any Cloud Service for purposes of monitoring its availability, performance or functionality, or for any other benchmarking or competitive purposes, including, without limitation, for the purpose of designing and/or developing any competitive services.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cloud.google.com/terms/service-terms"&gt;Google Cloud Platform&lt;/a&gt;, including &lt;a href="https://cloud.google.com/bigquery"&gt;BigQuery&lt;/a&gt; &amp;amp; &lt;a href="https://firebase.google.com"&gt;Firebase&lt;/a&gt;. &lt;em&gt;Benchmarking. Customer may conduct benchmark tests of the Services (each a "Test"). Customer may only publicly disclose the results of such Tests if it (a) obtains Google's prior written consent, (b) provides Google all necessary information to replicate the Tests, and (c) allows Google to conduct benchmark tests of Customer's publicly available products or services and publicly disclose the results of such tests. Notwithstanding the foregoing, Customer may not do either of the following on behalf of a hyperscale public cloud provider without Google's prior written consent: (i) conduct (directly or through a third party) any Test of the Services or (ii) disclose the results of any such Test.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.influxdata.com/legal/terms-of-use/"&gt;InfluxDB Cloud&lt;/a&gt;. &lt;em&gt;Restrictions. Customer will not: [...] publish or provide any benchmark, comparison or performance test results.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://legal.mariadb.com/agreements/skysql/SkySQL_Terms_of_Service_V2_2020-12-16.pdf"&gt;MariaDB SkySQL&lt;/a&gt; and &lt;a href="https://legal.mariadb.com/agreements/xpand/xpand-license-terms-v2-2021-02-24.pdf"&gt;MariaDB Xpand&lt;/a&gt;. &lt;em&gt;Benchmarking. Customer may perform benchmarks or comparative tests or evaluations (each, a "Benchmark") of the SkySQL Services. However, Customer must obtain MariaDB's prior written approval to disclose to a third party the results of any Benchmark of the SkySQL Services.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.microsoft.com/en-us/sql-server/"&gt;Microsoft SQL Server&lt;/a&gt;. &lt;em&gt;BENCHMARK TESTING. You must obtain Microsoft’s prior written approval to disclose to a third party the results of any benchmark test of the software.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.oracle.com/us/corporate/contracts/lic-online-toma-ph-de-eng-2817945.pdf"&gt;Oracle&lt;/a&gt;. &lt;em&gt;RESTRICTIONS. [...] You may not: [...] disclose results of any Program benchmark tests without Oracle's prior written consent.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://planetscale.com/legal/aup"&gt;PlanetScale&lt;/a&gt;. &lt;em&gt;Customer will not, and will ensure that its End Users do not: [...] access any portion of the Product for the purpose of building a similar or competitive product or service, or monitor the Product for any benchmarking or competitive purpose.&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  😶 Vendors for which the author didn't find the data
&lt;/h3&gt;

&lt;p&gt;I wasn't able to identify where a few vendors stand with regard to the DeWitt clause:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.mysql.com/cloud/"&gt;MySQL HeatWave&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.yellowbrick.com/"&gt;Yellowbrick Data&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you can provide any pointers, please get in touch via &lt;a href="//mailto:igor@cube.dev?subject=DeWitt"&gt;igor@cube.dev&lt;/a&gt;, I'll be happy to update the post. Also, please reach out if you'd like more databases to be featured here.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about Cube?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://cube.dev?ref=dewitt-clause-or-can-you-benchmark-a-database"&gt;Cube&lt;/a&gt; is an open-source &lt;a href="https://cube.dev/blog/headless-bi?ref=dewitt-clause-or-can-you-benchmark-a-database"&gt;headless BI&lt;/a&gt; platform that &lt;a href="https://cube.dev/docs/config/databases?ref=dewitt-clause-or-can-you-benchmark-a-database"&gt;integrates with a lot of data sources&lt;/a&gt;: cloud data warehouses, query engines, and databases. Many of them are designed for low latency and some of them can provide high concurrency. However, sometimes they don't; you can read our &lt;a href="https://cube.dev/blog/data-warehouse-performance-and-how-cube-can-help?ref=dewitt-clause-or-can-you-benchmark-a-database"&gt;blog post&lt;/a&gt; about that.&lt;/p&gt;

&lt;p&gt;In any case, Cube will have no problem delivering your data, with sub-second latency and high concurrency, to any &lt;a href="https://cube.dev/docs/config/downstream?ref=dewitt-clause-or-can-you-benchmark-a-database"&gt;data consumer&lt;/a&gt; out there: BI tools, data notebooks, front-end apps, etc. Thanks to &lt;a href="https://cube.dev/blog/introducing-cubestore?ref=dewitt-clause-or-can-you-benchmark-a-database"&gt;Cube Store&lt;/a&gt;, Cube's REST, GraphQL, and SQL APIs are able to respond to any request within 200-300 milliseconds and allow for 100+ concurrent requests—and that can scale.&lt;/p&gt;

&lt;p&gt;Please explore how Cube integrates with &lt;a href="https://cube.dev/blog/category/data?ref=dewitt-clause-or-can-you-benchmark-a-database"&gt;databases and other tools&lt;/a&gt; in the data space in our blog. Also, don't hesitate to try &lt;a href="https://cubecloud.dev/auth/signup?ref=dewitt-clause-or-can-you-benchmark-a-database"&gt;Cube Cloud&lt;/a&gt;, join our large &lt;a href="http://slack.cube.dev?ref=dewitt-clause-or-can-you-benchmark-a-database"&gt;Slack community&lt;/a&gt;, and give Cube a &lt;a href="https://github.com/cube-js/cube.js"&gt;star on GitHub&lt;/a&gt; 🌟&lt;/p&gt;

</description>
      <category>database</category>
      <category>programming</category>
      <category>discuss</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Building dashboards over a semantic layer with Superset and Cube</title>
      <dc:creator>Igor Lukanin</dc:creator>
      <pubDate>Thu, 14 Apr 2022 18:13:54 +0000</pubDate>
      <link>https://forem.com/cubejs/building-dashboards-over-a-semantic-layer-with-superset-and-cube-57ck</link>
      <guid>https://forem.com/cubejs/building-dashboards-over-a-semantic-layer-with-superset-and-cube-57ck</guid>
      <description>&lt;p&gt;What can be more troubling and horrifying for an Apache Superset user than a scarlet error message revealing that the underlying dataset is broken and charts aren’t going to provide any insights anytime soon? Sigh.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NOMBewDZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/a52de5b9-e369-488d-a97a-4ddd8f1018d4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NOMBewDZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/a52de5b9-e369-488d-a97a-4ddd8f1018d4.png" alt="Screenshot 2022-04-10 at 22.49.20.png" width="880" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Indeed, modern data pipelines consist of many moving—and sometimes fragile—parts. Superset, the Swiss knife of data exploration and visualization, deserves rock-solid data sources but in the real world, upstream changes are inevitable. They may be connected to immature technology in the data stack as well as ever-changing business requirements and balkanized ownership of data pipelines.&lt;/p&gt;

&lt;p&gt;Within minutes, you can find conversations on Superset’s Slack or GitHub revolving around &lt;a href="https://apache-superset.slack.com/archives/CCKHMGRRB/p1646562321113199"&gt;identifying broken dashboards&lt;/a&gt; and finding ways to &lt;a href="https://github.com/apache/superset/discussions/18504"&gt;reduce the impact&lt;/a&gt; on users:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1aGu2yHf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/580211bb-e20f-40d3-86e0-ea49ac0aa4db.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1aGu2yHf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/580211bb-e20f-40d3-86e0-ea49ac0aa4db.png" alt="Screenshot 2022-04-11 at 16.55.39.png" width="880" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I’m happy to share that a solution exists.&lt;/strong&gt; You can use &lt;a href="https://cube.dev"&gt;Cube&lt;/a&gt; together with Superset and define your datasets over a data modeling layer (or a semantic layer) rather than raw data:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mnS0i3Ig--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/5a6607a9-5901-465b-af27-cf18de73ebc1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mnS0i3Ig--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/5a6607a9-5901-465b-af27-cf18de73ebc1.png" alt="Cube, Superset, and semantic layer" width="880" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this tutorial, we’ll explore how Cube, a headless BI platform, can be used together with Superset to build reliable and robust data visualizations on top of a semantic layer. You’ll learn techniques to prevent your dashboards from breaking—and tools that would notify you about incompatible upstream changes in data before your users even notice.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Headless BI?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://cube.dev/"&gt;Cube&lt;/a&gt; is an open-source &lt;a href="https://cube.dev/blog/headless-bi"&gt;headless BI platform&lt;/a&gt; with nearly 13,000 stars on &lt;a href="https://github.com/cube-js/cube.js"&gt;GitHub&lt;/a&gt; to date. It can be broken down into four layers that are complemented by a &lt;em&gt;head&lt;/em&gt; or multiple &lt;em&gt;heads&lt;/em&gt; like Apache Superset, another BI tool, or a front-end application with embedded analytics. Here are the layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data modeling.&lt;/strong&gt; Contains consistent metrics definitions and provides a view of the upstream data in terms of measures and dimensions. Makes data semantic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access control.&lt;/strong&gt; Safeguards data access and enables row-level security, role-based access, and multitenancy. Makes data secure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caching.&lt;/strong&gt; Accelerates data access and makes end-user applications responsive and rewarding to use. Makes apps fast.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;APIs.&lt;/strong&gt; Exposes data to downstream applications via REST, GraphQL, or SQL API. Makes data accessible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can configure Cube to connect to any database, declaratively define your metrics, and instantly get an API that you can use with Superset or many other BI tools.&lt;/p&gt;

&lt;p&gt;In case of any upstream change (be it a renamed column, a dropped table, or a migrated data source), you can update the data model in Cube and keep hundreds of charts and dozens of dashboards in your Superset installation intact. Sounds refreshing, huh?&lt;/p&gt;

&lt;p&gt;Let’s try Superset with Cube and see how it works!&lt;/p&gt;

&lt;h2&gt;
  
  
  Running Cube with Superset
&lt;/h2&gt;

&lt;p&gt;To keep things simple and save time, we'll run both Cube and Superset in fully managed environments: &lt;a href="https://cube.dev"&gt;Cube&lt;/a&gt; &lt;a href="https://cubecloud.dev/auth/signup"&gt;Cloud&lt;/a&gt; and &lt;a href="https://preset.io"&gt;Preset Cloud&lt;/a&gt;. Note that both tools have generous free tiers, e.g., you can use Preset Cloud forever for free on the Starter plan. (If you'd like to run Cube or Superset locally with Docker, please see &lt;a href="https://github.com/cube-js/cube.js/blob/master/examples/superset/README.md#running-cube-locally"&gt;Cube&lt;/a&gt; and &lt;a href="https://github.com/cube-js/cube.js/blob/master/examples/superset/README.md#running-superset-locally"&gt;Supeset&lt;/a&gt; instructions, respectively.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Running Cube.&lt;/strong&gt; First, please proceed to Cube Cloud’s &lt;a href="https://cubecloud.dev/auth/signup"&gt;sign up page&lt;/a&gt; and fill in your details. Note that Cube Cloud supports signing up with your GitHub account. Within a few seconds you will be taken to your account where you can create your first Cube deployment:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dvKvrlKY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/248b4afb-32f2-44fd-b4f2-fd4e21725600.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dvKvrlKY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/248b4afb-32f2-44fd-b4f2-fd4e21725600.png" alt="Screenshot 2021-11-24 at 03.06.16.png" width="880" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Proceed with providing a name for your deployment, selecting a cloud provider and a region:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Idrsaupe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/bea6c6de-b392-4154-b847-1511b75a4626.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Idrsaupe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/bea6c6de-b392-4154-b847-1511b75a4626.png" alt="Screenshot 2022-04-09 at 02.16.36.png" width="880" height="557"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At the next step, choose &lt;code&gt;Create&lt;/code&gt; to start a new Cube project from scratch. Then, pick &lt;code&gt;Postgres&lt;/code&gt; to proceed to the screen where you can enter the following credentials:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hostname:  demo-db-examples.cube.dev
Port:      5432
Database:  ecom_superset
Username:  cube
Password:  12345
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wUj_f5JP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/5d523c6a-4c09-48ed-9c41-d7d8ad460073.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wUj_f5JP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/5d523c6a-4c09-48ed-9c41-d7d8ad460073.png" alt="Screenshot 2022-04-09 at 02.18.04.png" width="880" height="611"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cube will connect to a publicly available Postgres database that I've already set up.&lt;/p&gt;

&lt;p&gt;The last part of configuration is the &lt;a href="https://cube.dev/docs/schema/getting-started"&gt;data schema&lt;/a&gt; which declaratively describes the metrics we'll be putting on the dashboard. Actually, Cube can generate it for us! Pick the top-level &lt;code&gt;public&lt;/code&gt; database from the list and select only the “orders” table:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7wXzLfJN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/56982824-e5ea-47f3-b156-ed0b2f0fffc9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7wXzLfJN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/56982824-e5ea-47f3-b156-ed0b2f0fffc9.png" alt="Screenshot 2022-04-09 at 02.18.53.png" width="880" height="478"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In a while, your Cube deployment will be up and running:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--R5JgELHh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/94a8fc56-dc47-45d1-8360-42c7fcb00709.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--R5JgELHh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/94a8fc56-dc47-45d1-8360-42c7fcb00709.png" alt="Screenshot 2022-04-09 at 02.25.27.png" width="880" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Defining metrics.&lt;/strong&gt; Please navigate to the "Schema" tab. You will see the &lt;code&gt;Orders.js&lt;/code&gt; file under the "schema" folder. Let’s review it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--g23wT2RZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/c566ec4d-da83-4c0f-af54-4b840386f181.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--g23wT2RZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/c566ec4d-da83-4c0f-af54-4b840386f181.png" alt="Screenshot 2022-04-09 at 02.27.36.png" width="880" height="496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This file defines the metrics within the "Orders" cube. It is different from the one in Cube Cloud, but we'll take care of that later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;cube&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Orders`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`SELECT * FROM public.orders`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`id`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`number`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;primaryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="c1"&gt;// 'processing', 'shipped', or 'completed'&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`status`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`string`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`created_at`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`time`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;shippedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`shipped_at`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`time`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;completedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`completed_at`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`time`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="c1"&gt;// Calculated, uses SQL functions and column references&lt;/span&gt;
    &lt;span class="na"&gt;daysBeforeShipped&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`EXTRACT(DAYS FROM (shipped_at - created_at))`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`number`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="c1"&gt;// Calculated, uses SQL functions and column references&lt;/span&gt;
    &lt;span class="na"&gt;daysBeforeCompleted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`EXTRACT(DAYS FROM (completed_at - created_at))`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`number`&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="na"&gt;measures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`count`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="c1"&gt;// Calculated, uses a dimension reference&lt;/span&gt;
    &lt;span class="na"&gt;avgDaysBeforeShipped&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;daysBeforeShipped&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`avg`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="c1"&gt;// Calculated, uses a dimension reference&lt;/span&gt;
    &lt;span class="na"&gt;avgDaysBeforeCompleted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;daysBeforeCompleted&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`avg`&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key learnings here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the cube is a logical, domain-level entity that includes measures and dimensions&lt;/li&gt;
&lt;li&gt;using the &lt;code&gt;sql&lt;/code&gt; statement, this cube is defined over the entire &lt;code&gt;public.line_items&lt;/code&gt; table; actually, cubes can be defined over arbitrary SQL statements that select data&lt;/li&gt;
&lt;li&gt;dimensions (qualitative data features) are defined over textual, temporal, or numeric columns in the dataset&lt;/li&gt;
&lt;li&gt;measures (quantitative data features) are defined as aggregations (e.g., &lt;code&gt;count&lt;/code&gt;, &lt;code&gt;avg&lt;/code&gt;, etc.) over columns or dimensions in the dataset&lt;/li&gt;
&lt;li&gt;you can define complex measures and dimensions with custom &lt;code&gt;sql&lt;/code&gt; statements or references to other measures and dimensions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Development mode.&lt;/strong&gt; Now, let's update the schema file in Cube Cloud to match the contents above. First, click &lt;code&gt;Enter Development Mode&lt;/code&gt; to unlock the schema files for editing. This essentially creates a "fork" of the Cube API that tracks your changes in the data schema.&lt;/p&gt;

&lt;p&gt;Navigate to &lt;code&gt;Orders.js&lt;/code&gt; and replace its contents with the code above. Then, save your changes by clicking &lt;code&gt;Save All&lt;/code&gt; to apply the changes to the development version of your API. You can apply as many changes as you wish, but we're done for now. Click &lt;code&gt;Commit &amp;amp; Push&lt;/code&gt; to merge your changes back to the main branch: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--P59LjiPF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/f78ee63a-a417-4321-bd73-d0ada4dda1f9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--P59LjiPF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/f78ee63a-a417-4321-bd73-d0ada4dda1f9.png" alt="Screenshot 2022-04-10 at 19.28.29.png" width="880" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the "Overview" tab you will see your changes deployed:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fEU_JEO1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/cfa17df3-f2d2-4f2e-be7c-7b2382e3df67.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fEU_JEO1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/cfa17df3-f2d2-4f2e-be7c-7b2382e3df67.png" alt="Screenshot 2022-04-10 at 19.29.40.png" width="880" height="605"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you can explore the metrics on the "Playground" tab:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fiq7QNiX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/bae3c3bf-7601-4df1-8b83-2df86ee8c6d8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fiq7QNiX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/bae3c3bf-7601-4df1-8b83-2df86ee8c6d8.png" alt="Screenshot 2022-04-10 at 20.25.46.png" width="880" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you’re ready, head over to the “Overview” tab, click “Deploy SQL API” and use the “How to connect your BI tool” link shortly thereafter.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3KEGflDZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/a032e34f-9b13-40e9-95ed-a4f6589eab59.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3KEGflDZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/a032e34f-9b13-40e9-95ed-a4f6589eab59.png" alt="Screenshot 2022-04-10 at 19.48.16.png" width="880" height="587"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You will see the credentials that we’ll use to connect the metrics store that we’ve built with Cube to Superset in a few moments. How?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Running Superset.&lt;/strong&gt; Please proceed to Preset’s &lt;a href="https://preset.io/registration/"&gt;sign up page&lt;/a&gt; and fill in your details. Note that Preset Cloud supports signing up with your Google account. Within a few seconds you will be taken to your account with a readily available workspace:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Dr143H0s--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/2588b427-df26-4e4c-8a61-8ebc8e83d7ca.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Dr143H0s--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/2588b427-df26-4e4c-8a61-8ebc8e83d7ca.png" alt="Screenshot 2021-11-24 at 02.50.55.png" width="880" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Switching to that workspace will reveal a few example dashboards that you can review later.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8oyjazFo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/0aae7118-96c9-444b-b8fa-feb6132b9198.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8oyjazFo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/0aae7118-96c9-444b-b8fa-feb6132b9198.png" alt="Screenshot 2021-11-24 at 02.54.51.png" width="880" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connect Superset to Cube.&lt;/strong&gt; Navigate to &lt;code&gt;Data / Databases&lt;/code&gt; via the top menu, click &lt;code&gt;+ Database&lt;/code&gt;, select &lt;code&gt;MySQL&lt;/code&gt;, and fill in the credentials from your Cube Cloud instance — or use the credentials below:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Host: &lt;code&gt;green-yak.sql.aws-us-east-2.cubecloudapp.dev&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Port: &lt;code&gt;3306&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Database name: &lt;code&gt;db&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Username: &lt;code&gt;cube@green-yak&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Password: &lt;code&gt;82e26064349b19c340f8b074dfcee4af&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Display name: &lt;code&gt;Cube Cloud&lt;/code&gt; (it's important)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JuS8s-cN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/f3924125-b7e7-4e89-a2d6-28226e59d780.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JuS8s-cN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/f3924125-b7e7-4e89-a2d6-28226e59d780.png" alt="Screenshot 2022-04-10 at 20.03.08.png" width="880" height="571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can press &lt;code&gt;Connect&lt;/code&gt; now.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Define the datasets.&lt;/strong&gt; Navigate to &lt;code&gt;Data / Datasets&lt;/code&gt; via the top menu, click &lt;code&gt;+ Dataset&lt;/code&gt;, and fill in the following credentials:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database: &lt;code&gt;Cube Cloud&lt;/code&gt; (the one we've just created)&lt;/li&gt;
&lt;li&gt;Schema: &lt;code&gt;db&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;See table schema: &lt;code&gt;Orders&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--76LtnHXK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/9d36a601-4645-4f01-84be-ed68ce4d45b1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--76LtnHXK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/9d36a601-4645-4f01-84be-ed68ce4d45b1.png" alt="Screenshot 2022-04-10 at 20.05.21.png" width="880" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can press &lt;code&gt;Add&lt;/code&gt; now. Then, hover over the newly added dataset and click the pencil icon in the “Actions” column. We’ll need to make sure Superset recognizes a couple of measures. First, navigate to the “Columns” tab and deselect “Is dimension” for &lt;code&gt;avgDaysBeforeShipped&lt;/code&gt; and &lt;code&gt;avgDaysBeforeCompleted&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--I6nO0hNd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/ed902529-1a06-402f-ba69-b6ad9ff3125f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--I6nO0hNd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/ed902529-1a06-402f-ba69-b6ad9ff3125f.png" alt="Screenshot 2022-04-10 at 20.21.55.png" width="880" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, switch to the “Metrics” tab and add these two:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Metric: &lt;code&gt;avgDaysBeforeShipped&lt;/code&gt;; SQL expression: &lt;code&gt;AVG(avgDaysBeforeShipped)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Metric: &lt;code&gt;avgDaysBeforeCompleted&lt;/code&gt;; SQL expression: &lt;code&gt;AVG(avgDaysBeforeCompleted)&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--H7UXC5c---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/7a533118-8074-4e0f-ba0a-03f383564be1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--H7UXC5c---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/7a533118-8074-4e0f-ba0a-03f383564be1.png" alt="Screenshot 2022-04-10 at 20.21.50.png" width="880" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Press &lt;code&gt;Save&lt;/code&gt; in the end. Whew, we’re all set! Now, let’s display the data on a chart.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Building a dashboard.&lt;/strong&gt; Navigate to &lt;code&gt;Charts&lt;/code&gt; via the top menu, click &lt;code&gt;+ Chart&lt;/code&gt;, and use the following configuration for your first chart:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Choose a dataset: &lt;code&gt;Orders&lt;/code&gt; (the one we've just created)&lt;/li&gt;
&lt;li&gt;Choose a chart type: &lt;code&gt;Table&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you’re done, click &lt;code&gt;Create new chart&lt;/code&gt;. Feel free to experiment with parameters on this screen and click &lt;code&gt;Run Query&lt;/code&gt; to explore the data. To proceed, please set up a chart like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Visualization type: &lt;code&gt;Table&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Time column: &lt;code&gt;createdAt&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Time grain: any value&lt;/li&gt;
&lt;li&gt;Group by: &lt;code&gt;status&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Metrics: &lt;code&gt;COUNT(*)&lt;/code&gt;,  &lt;code&gt;avgDaysBeforeShipped&lt;/code&gt;, &lt;code&gt;avgDaysBeforeCompleted&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s the data table you’ll get:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OsABSge1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/6acaaa2c-33d7-4cd1-830b-b07e1b98e754.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OsABSge1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/6acaaa2c-33d7-4cd1-830b-b07e1b98e754.png" alt="Screenshot 2022-04-10 at 22.19.47.png" width="880" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can click &lt;code&gt;Save&lt;/code&gt; to give your chart a name and add it to a new dashboard:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--f-PGXmd4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/ad9c076d-a692-4e8d-8c3a-bcb554c987ba.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--f-PGXmd4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/ad9c076d-a692-4e8d-8c3a-bcb554c987ba.png" alt="Screenshot 2022-04-10 at 22.21.47.png" width="880" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Looks good? We now have a dashboard in Superset displaying a chart that queries data from Cube using the measures and dimensions in Cube’s data model.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OcIgtCec--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/65a605e9-749c-47c2-afae-dd5996a4d52c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OcIgtCec--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/65a605e9-749c-47c2-afae-dd5996a4d52c.png" alt="Screenshot 2022-04-10 at 22.23.31.png" width="880" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cube translates queries coming from Superset into queries to the upstream data source. What would happen if anything happens upstream? Here’s when all the fun starts!&lt;/p&gt;

&lt;h2&gt;
  
  
  Mishap 1: Column renamed or removed
&lt;/h2&gt;

&lt;p&gt;Let’s say you (or your users) open the dashboard one day and behold this orange introduction into a few less-than-productive work hours. Apparently, the “status” column is not present in the data source anymore and a few uninformed business decisions might be made (or put on hold) until your team debugs the circumstances of the removal. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NOMBewDZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/a52de5b9-e369-488d-a97a-4ddd8f1018d4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NOMBewDZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/a52de5b9-e369-488d-a97a-4ddd8f1018d4.png" alt="Screenshot 2022-04-10 at 22.49.20.png" width="880" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What can be done to mitigate this? Well, let’s reproduce this situation first. You can do so by navigating to Cube Cloud, switching to the Development Mode, and editing the &lt;code&gt;Orders.js&lt;/code&gt; file. Just change the SQL expression to reference &lt;code&gt;orders_wo_status&lt;/code&gt; table that, obviously enough, has exactly the same data as the &lt;code&gt;orders&lt;/code&gt; table but the “status” column is removed:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SEUBBWdm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/7df74334-785d-4a48-978a-3ea96626f4e3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SEUBBWdm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/7df74334-785d-4a48-978a-3ea96626f4e3.png" alt="Screenshot 2022-04-10 at 22.43.48.png" width="880" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, you can save and commit the changes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FGAY0BQW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/1f9f670a-f2d6-4797-9ff9-8d2d5d92c757.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FGAY0BQW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/1f9f670a-f2d6-4797-9ff9-8d2d5d92c757.png" alt="Screenshot 2022-04-10 at 22.43.36.png" width="880" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the Cube Cloud deployment is redeployed in a few moments, you can refresh your dashboard in Superset to switch it into the annoying orange state. (If the dashboard doesn’t break, try pressing the button with three dots in the top right corner and requesting the dashboard to refresh.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unbreaking the dashboard, a simple way.&lt;/strong&gt; Let’s say that the “status” column was removed because it’s redundant: its values can be calculated using the values in “created_at”, “shipped_at”, and “completed_at” columns. Then, how to fix the dashboard after the column removal?&lt;/p&gt;

&lt;p&gt;One way to do so is to edit the dataset in Superset. You can delete the missing column and add it back as a calculated one using Superset’s &lt;a href="https://docs.preset.io/docs/semantic-layer#calculated-columns"&gt;semantic layer&lt;/a&gt;. (If you’d like to dig deeper, check out &lt;a href="https://preset.io/blog/understanding-superset-semantic-layer/"&gt;this post in Preset’s blog&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;However, there are a few downsides: first, this calculated column wouldn’t be visible in Superset’s &lt;a href="https://docs.preset.io/docs/sql-editor"&gt;SQL Editor&lt;/a&gt; where you might run SQL queries to perform data exploration; second, the calculation wouldn’t propagate upstream. If Superset is not the only tool you use (or would ever use) to explore your data, it’s better to apply the fix upstream rather than make duplicated fixes all over different tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unbreaking the dashboard, a better way.&lt;/strong&gt; An alternative way is to replace the “status” column with a calculation in Cube’s data model. Then, Superset and all other tools would work as if that column was never removed and your dashboard never broke.&lt;/p&gt;

&lt;p&gt;You can go back to Cube Cloud, switch to the Development Mode, and edit the &lt;code&gt;Orders.js&lt;/code&gt; file once again. Let’s replace the “status” dimension with the following code snippet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;    &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;when&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`completed_at IS NOT NULL`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`completed`&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`shipped_at IS NOT NULL`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`shipped`&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`processing`&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`string`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can save and deploy your changes. Note that we’re using a &lt;a href="https://cube.dev/docs/schema/reference/dimensions#case"&gt;case&lt;/a&gt; dimension that provides some syntactic sugar over a regular &lt;code&gt;CASE... WHEN... THEN... ELSE... END&lt;/code&gt; SQL statement and provides the ability to reference other dimensions:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uhrVxNNJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/bb871899-9940-4308-b66c-2a96d4c1454c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uhrVxNNJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/bb871899-9940-4308-b66c-2a96d4c1454c.png" alt="Screenshot 2022-04-10 at 23.44.32.png" width="880" height="590"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Congratulations! Without any changes in Superset, your dashboard is back to a healthy state:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OcIgtCec--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/65a605e9-749c-47c2-afae-dd5996a4d52c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OcIgtCec--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/65a605e9-749c-47c2-afae-dd5996a4d52c.png" alt="Screenshot 2022-04-10 at 22.23.31.png" width="880" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Moreover, if you ever need to introduce a more complex calculation to any dimension or measure, you can always do that in Cube Cloud and still won’t need to change anything in tools that consume data, including Superset.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mishap 2: Table altered or removed
&lt;/h2&gt;

&lt;p&gt;Let’s say you (or your users) open the dashboard and see this unfortunate orange notification once again. Apparently, the “shipped_at” column doesn’t exist:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--edAakH66--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/0d6dbf19-b69b-44c5-8680-e5137edc49df.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--edAakH66--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/0d6dbf19-b69b-44c5-8680-e5137edc49df.png" alt="Screenshot 2022-04-11 at 00.11.11.png" width="880" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The reason is that developers of the upstream system decided to implement the event sourcing design pattern, meaning that there’s no “orders” table anymore. Instead, there’s the “orders_events” table that, obviously enough, has exactly the same data but completely reshaped as a stream of events related to orders’ status changes. In that table, every row represents only partial information about an order and the “timestamp” column replaced previously existing “created_at”, “shipped_at”, and “completed_at” columns. Here’s the DDL statement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;orders_events&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;order_id&lt;/span&gt;  &lt;span class="nb"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt;   &lt;span class="nb"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt;    &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wanna try that yourself? You can do so by navigating to Cube Cloud, switching to the Development Mode, and editing the &lt;code&gt;Orders.js&lt;/code&gt; file. Just change the SQL expression to reference &lt;code&gt;orders_events&lt;/code&gt; table:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oWPWrmNq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/5e32734b-f45c-4967-8ed4-61cfdcf8195c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oWPWrmNq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/5e32734b-f45c-4967-8ed4-61cfdcf8195c.png" alt="Screenshot 2022-04-11 at 00.23.27.png" width="880" height="365"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the Cube Cloud deployment is redeployed, you can refresh your dashboard in Superset to switch it into the annoying orange state. (If the dashboard doesn’t break, try pressing the button with three dots in the top right corner and requesting the dashboard to refresh.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unbreaking the dashboard, a complex way.&lt;/strong&gt; One way to do so is to introduce a data transformation tool to your data pipeline (assuming you don’t use one) that would transform the data back into the desired state.&lt;/p&gt;

&lt;p&gt;However, there are a few downsides: first, this is more like a month-long project than a quick fix to the data pipeline; second, it might bring unnecessary and unwanted complexity to your technology stack. (Note that if you actually use dbt or dbt Metrics, Cube has an &lt;a href="https://cube.dev/blog/dbt-metrics-meet-cube"&gt;integration&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unbreaking the dashboard, a better way.&lt;/strong&gt; An alternative way is to update Cube’s data model to transform data back to the desired state. Then, Superset and all other tools would work as if the table was never removed and your dashboard never broke.&lt;/p&gt;

&lt;p&gt;You can go back to Cube Cloud, switch to the Development Mode, and edit the &lt;code&gt;Orders.js&lt;/code&gt; file once again. Let’s replace the source code of the file with the following code snippet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;cube&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Orders`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    WITH bareOrders AS (
        SELECT order_id AS id, user_id
        FROM public.orders_events
        GROUP BY order_id, user_id
    ),

    orders AS (
        SELECT
          bareOrders.*,
          (CASE WHEN completed.timestamp IS NOT NULL THEN completed.status ELSE (CASE WHEN shipped.timestamp IS NOT NULL THEN shipped.status ELSE processing.status END) END) AS status,
          COALESCE(processing.timestamp, shipped.timestamp, completed.timestamp) AS created_at,
          COALESCE(shipped.timestamp, completed.timestamp) AS shipped_at,
          completed.timestamp AS completed_at
        FROM bareOrders
        LEFT JOIN public.orders_events AS processing
          ON bareOrders.id = processing.order_id AND processing.status = 'processing'
        LEFT JOIN public.orders_events AS shipped
          ON bareOrders.id = shipped.order_id AND shipped.status = 'shipped'
        LEFT JOIN public.orders_events AS completed
          ON bareOrders.id = completed.order_id AND completed.status = 'completed'
    )

    SELECT * FROM orders
  `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`id`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`number`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;primaryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="c1"&gt;// 'processing', 'shipped', or 'completed'&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`status`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`string`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`created_at`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`time`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;shippedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`shipped_at`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`time`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;completedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`completed_at`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`time`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="c1"&gt;// Calculated, uses SQL functions and column references&lt;/span&gt;
    &lt;span class="na"&gt;daysBeforeShipped&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`EXTRACT(DAYS FROM (shipped_at - created_at))`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`number`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="c1"&gt;// Calculated, uses SQL functions and column references&lt;/span&gt;
    &lt;span class="na"&gt;daysBeforeCompleted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`EXTRACT(DAYS FROM (completed_at - created_at))`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`number`&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="na"&gt;measures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`count`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="c1"&gt;// Calculated, uses a dimension reference&lt;/span&gt;
    &lt;span class="na"&gt;avgDaysBeforeShipped&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;daysBeforeShipped&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`avg`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="c1"&gt;// Calculated, uses a dimension reference&lt;/span&gt;
    &lt;span class="na"&gt;avgDaysBeforeCompleted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;daysBeforeCompleted&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`avg`&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, Cube’s data model is flexible enough to allow cubes to be defined over arbitrary SQL expressions that can contain &lt;a href="https://en.wikipedia.org/wiki/Hierarchical_and_recursive_queries_in_SQL#Common_table_expression"&gt;common-table expressions&lt;/a&gt; (CTE), unions, joins, etc. The rule of thumb for structuring the data model in Cube is that cubes, measures, and dimensions should make sense at the logical, domain level. And if your data is stored differently, you should feel free to reshape it in Cube’s data model. &lt;/p&gt;

&lt;p&gt;You can save and deploy your updates in Cube Cloud. Again, without any changes in Superset, your dashboard is back to a healthy state:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OcIgtCec--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/65a605e9-749c-47c2-afae-dd5996a4d52c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OcIgtCec--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/65a605e9-749c-47c2-afae-dd5996a4d52c.png" alt="Screenshot 2022-04-10 at 22.23.31.png" width="880" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Mishap 3: Data source changed
&lt;/h2&gt;

&lt;p&gt;Let’s say something rare yet impactful happened: the team decided to upgrade the data pipeline, introduce new tools, etc. Eventually, the data will move from Postgres to BigQuery, new ETL and data transformation tools will be used. What can we do to prevent the dashboards from breaking or avoid rebuilding the dashboards in Superset?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HGJZd9Xw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/e81aecfd-e90d-448f-8950-ebb76515d33a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HGJZd9Xw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/e81aecfd-e90d-448f-8950-ebb76515d33a.png" alt="Screenshot 2022-04-11 at 16.12.03.png" width="880" height="335"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since we’ve already connected Superset to Cube, we can sort things out on Cube’s side. Just like Superset, Cube supports &lt;a href="https://cube.dev/docs/config/databases"&gt;numerous data sources&lt;/a&gt; and a Cube instance can use any subset of these data sources at the same time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unbreaking the dashboard.&lt;/strong&gt; Here’s how you can configure Cube to connect to Postgres and BigQuery and update the data model so the orders data is read from BigQuery.&lt;/p&gt;

&lt;p&gt;In Cube Cloud, you’ll need to switch to the Development Mode. First, let’s update &lt;code&gt;Orders.js&lt;/code&gt; to match the following snippet. Check out the new &lt;code&gt;dataSource&lt;/code&gt; option and the updated definitions for &lt;code&gt;daysBeforeShipped&lt;/code&gt; and &lt;code&gt;daysBeforeCompleted&lt;/code&gt; that use BigQuery-specific &lt;code&gt;TIMESTAMP_DIFF&lt;/code&gt; function instead of Postgres-specific &lt;code&gt;EXTRACT&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;cube&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Orders`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;dataSource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bigquery&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`SELECT * FROM superset_examples_2.orders`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`id`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`number`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;primaryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="c1"&gt;// 'processing', 'shipped', or 'completed'&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`status`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`string`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`created_at`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`time`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;shippedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`shipped_at`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`time`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;completedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`completed_at`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`time`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="c1"&gt;// Calculated, uses SQL functions and column references&lt;/span&gt;
    &lt;span class="na"&gt;daysBeforeShipped&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`TIMESTAMP_DIFF(shipped_at, created_at, DAY)`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`number`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="c1"&gt;// Calculated, uses SQL functions and column references&lt;/span&gt;
    &lt;span class="na"&gt;daysBeforeCompleted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`TIMESTAMP_DIFF(completed_at, created_at, DAY)`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`number`&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="na"&gt;measures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`count`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="c1"&gt;// Calculated, uses a dimension reference&lt;/span&gt;
    &lt;span class="na"&gt;avgDaysBeforeShipped&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;daysBeforeShipped&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`avg`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="c1"&gt;// Calculated, uses a dimension reference&lt;/span&gt;
    &lt;span class="na"&gt;avgDaysBeforeCompleted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;daysBeforeCompleted&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`avg`&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, let’s edit the &lt;code&gt;cube.js&lt;/code&gt; file to match the following snippet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Cube.js configuration options: https://cube.dev/docs/config&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PostgresDriver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@cubejs-backend/postgres-driver&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BigQueryDriver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@cubejs-backend/bigquery-driver&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;contextToAppId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;dataSource&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;dataSource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;dbType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;dataSource&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;dataSource&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bigquery&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bigquery&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;postgres&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="na"&gt;driverFactory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;dataSource&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;dataSource&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bigquery&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;BigQueryDriver&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;PostgresDriver&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the &lt;code&gt;driverFactory&lt;/code&gt; extension point where the database selection happens: for cubes with the &lt;code&gt;bigquery&lt;/code&gt; data source, BigQuery will be used, and Postgres will be used otherwise. Also note that we don’t need to specify the credentials for Postgres and BigQuery explicitly because they are securely stored in Cube Cloud’s settings. Not sharing BigQuery credentials with you because who would do that?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--v7trRV7t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/c250245d-4f0c-41fa-80ca-7af6ae0cad8a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--v7trRV7t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/c250245d-4f0c-41fa-80ca-7af6ae0cad8a.png" alt="Screenshot 2022-04-11 at 16.37.19.png" width="880" height="599"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With these changes in Cube Cloud and without any changes in Superset, your dashboard will be back to a healthy state:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OcIgtCec--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/65a605e9-749c-47c2-afae-dd5996a4d52c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OcIgtCec--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/65a605e9-749c-47c2-afae-dd5996a4d52c.png" alt="Screenshot 2022-04-10 at 22.23.31.png" width="880" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Oh, and there’s one more thing! Cube also supports data federation meaning that you can join data from different data sources, e.g., from Snowflake and Athena, if needed. To implement that, you can use &lt;a href="https://cube.dev/docs/caching/pre-aggregations/getting-started"&gt;pre-aggregations&lt;/a&gt; and declaratively set up a &lt;a href="https://cube.dev/docs/schema/reference/pre-aggregations#rollup-join"&gt;rollupJoin pre-aggregation&lt;/a&gt; that would seamlessly join data from multiple data sources under the hood.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cherry on top: Alerts
&lt;/h2&gt;

&lt;p&gt;In all examples above it was either us or our users who were to discover the broken dashboard. In production scenarios, that definitely should not happen. What can we do to get a notification before users see a broken dashboard?&lt;/p&gt;

&lt;p&gt;Since we’ve decided to run Cube in Cube Cloud, we can use the &lt;a href="https://cube.dev/blog/introducing-cloud-alerts"&gt;Alerts feature&lt;/a&gt; there (not to be confused with Superset’s &lt;a href="https://superset.apache.org/docs/installation/alerts-reports/"&gt;Alerts and Reports&lt;/a&gt; feature which is a convenient way to send reports with dashboard data to email). Alert conditions are checked every minute. If an alert is triggered, configured recipients will get email notifications.&lt;/p&gt;

&lt;p&gt;You can set up alerts for events like database unavailability or pre-aggregations build errors. In production scenarios, Cube is always used with pre-aggregations. It means that if a database becomes unavailable or the data within the database changes in a way that Cube can’t rebuild a pre-aggregation, you will instantly receive a notification.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configuring alerts.&lt;/strong&gt; You can go to Cube Cloud, click on your avatar in the top right corner, click “Alerts” to get to the Alerts page, then click &lt;code&gt;New Alert&lt;/code&gt; to set up one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--afJBECCm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/6501f840-7691-4a63-9156-043ad91bded5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--afJBECCm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/6501f840-7691-4a63-9156-043ad91bded5.png" alt="Screenshot 2022-04-11 at 14.51.48.png" width="880" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s go with almost default settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Event type: &lt;code&gt;Pre-aggregation build failure&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Deployments: &lt;code&gt;All&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Send alerts to: &lt;code&gt;All users on this account&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can click &lt;code&gt;Add&lt;/code&gt; now.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DQ8Xr-Mr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/bea4233b-1220-4f33-9cf1-e133618cc3ce.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DQ8Xr-Mr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/bea4233b-1220-4f33-9cf1-e133618cc3ce.png" alt="Screenshot 2022-04-11 at 15.39.18.png" width="880" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testing alerts.&lt;/strong&gt; The simplest way to test this newly setup alert is to configure &lt;a href="https://cube.dev/docs/caching/pre-aggregations/getting-started"&gt;pre-aggregations&lt;/a&gt;  and then break the data model, e.g., change the table name to a wrong one.&lt;/p&gt;

&lt;p&gt;You can go back to Cube Cloud, switch to the Development Mode, and edit the &lt;code&gt;Orders.js&lt;/code&gt; file once again. Let’s update the very beginning of the file in this fashion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;cube&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Orders`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;dataSource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bigquery&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;// An utterly wrong table name&lt;/span&gt;
  &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`SELECT * FROM superset_is_not_great`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;// A simple pre-aggregation scheduled to refresh every hour&lt;/span&gt;
  &lt;span class="na"&gt;preAggregations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;main&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;refreshKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;every&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1 hour&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nx"&gt;status&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

&lt;span class="c1"&gt;// Dimensions and measures go below&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After you deploy your changes, Cube Cloud would try to build the pre-aggregation, it probably wouldn’t find the table with this wicked name and an email notification would hit your inbox:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AanghrnM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/20d721d6-a95d-4922-bdd0-0c02ce4c6687.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AanghrnM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/20d721d6-a95d-4922-bdd0-0c02ce4c6687.png" alt="Screenshot 2022-04-11 at 15.52.53.png" width="880" height="642"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you don’t have thousands of active Supserset users, chances are that you’ll know about a broken dashboard way ahead of your users. (Superset’s built-in &lt;a href="https://superset.apache.org/docs/installation/cache/"&gt;caching mechanism&lt;/a&gt; could also help serve unbroken dashboards to users for some time.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Thanks for following this tutorial. My sincere hope is that now you have a much better understanding of these topics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How Headless BI tools (and &lt;a href="https://cube.dev"&gt;Cube&lt;/a&gt; in particular) can be helpful to build a think semantic layer and manage the data model upstream of Superset.&lt;/li&gt;
&lt;li&gt;How Cube can help make your Superset dashboards robust and unbreakable—and let you know if anything breaks in advance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Please don't hesitate to like and bookmark this post, write a comment, and give a star to &lt;a href="https://github.com/cube-js/cube.js"&gt;Cube&lt;/a&gt; and &lt;a href="https://github.com/apache/superset"&gt;Superset&lt;/a&gt; on GitHub. I hope these tools would be a part of your toolkit from now on.&lt;/p&gt;

&lt;p&gt;Good luck and have fun!&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>opensource</category>
      <category>analytics</category>
      <category>database</category>
    </item>
    <item>
      <title>Combining dbt Metrics with API, Caching, and Access Control</title>
      <dc:creator>Igor Lukanin</dc:creator>
      <pubDate>Wed, 16 Mar 2022 17:32:43 +0000</pubDate>
      <link>https://forem.com/cubejs/combining-dbt-metrics-with-api-caching-and-access-control-2li8</link>
      <guid>https://forem.com/cubejs/combining-dbt-metrics-with-api-caching-and-access-control-2li8</guid>
      <description>&lt;p&gt;&lt;em&gt;If you're using dbt to transform data in your data warehouse and you're excited about dbt Metrics feature, here's how you can add caching, access control, and a variety of APIs (such as REST, GraphQL, or SQL) on top.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;dbt quickly became the major part of the modern data stack and the favorite tool of data practitioners. As Cube community grows, especially among data engineers, we more often see dbt being used upstack from Cube to define transformations and data model.&lt;/p&gt;

&lt;p&gt;After a community-driven &lt;a href="https://github.com/dbt-labs/dbt-core/issues/4071" rel="noopener noreferrer"&gt;feature proposal&lt;/a&gt; for dbt Metrics and an insightful discussion that followed, our community &lt;a href="https://github.com/cube-js/cube.js/issues/3611" rel="noopener noreferrer"&gt;requested an integration&lt;/a&gt; with dbt that would allow keeping metrics definitions in dbt as the single source of truth while leveraging the rest of Cube.&lt;/p&gt;

&lt;h2&gt;
  
  
  dbt Metrics meet Cube, the headless BI platform
&lt;/h2&gt;

&lt;p&gt;At Cube, we are building headless business intelligence, and want to make data accessible and consistent across every application, ranging from embedded analytics to internal dashboarding and reporting tools. It’s an amazing opportunity for us to integrate with dbt Metrics, enhance it with Cube’s caching and access control, and deliver to every application via our SQL, REST, and GraphQL APIs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2Fd445d8bb-7879-48ca-9ed8-172347d85fd7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2Fd445d8bb-7879-48ca-9ed8-172347d85fd7.png" alt="dbt.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Today, we are happy to announce an integration with dbt Metrics. Cube can now read metrics from dbt, merge them into Cube’s data model, provide caching and access control, and expose metrics via our APIs to downstream applications.&lt;/p&gt;

&lt;p&gt;In the tutorial below we’ll use dbt to define metrics, Cube to apply caching (and get a ~8x performance boost), provide access control, and expose data to Apache Superset via Cube’s SQL API and to other tools and applications via our REST API or GraphQL API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a metrics layer with dbt Metrics
&lt;/h2&gt;

&lt;p&gt;Everything starts with a great dataset, so I’ve taken &lt;a href="https://console.cloud.google.com/marketplace/product/github/github-repos" rel="noopener noreferrer"&gt;GitHub Activity Data&lt;/a&gt;, a snapshot of more than 2.8 million (~10 %) open-source repositories on GitHub, publicly available on Google Cloud Platform. As of March 2022, it contains information about 257 million unique commits, their authors, messages, files, etc.&lt;/p&gt;

&lt;p&gt;Let’s say we’re interested in learning all about productivity of individuals and teams expressed in the number of commits and commit message lengths: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2Fc1300d9a-09d1-4f2b-9af8-4223481a1b4a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2Fc1300d9a-09d1-4f2b-9af8-4223481a1b4a.png" alt="Screenshot_2022-03-08_at_20.58.18.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We would start with defining a dbt model like this in &lt;code&gt;commits.sql&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;TIMESTAMP_SECONDS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;`author`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;`time_sec`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;`author`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;`email`&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;author_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;`author`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;`name`&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;author_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;REGEXP_EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;`author`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;`email`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;'@(.+&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="s1"&gt;.+)$'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;author_domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;LENGTH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;subject_length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;LENGTH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;message_length&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;`bigquery-public-data.github_repos.commits`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next step would be to add the dbt metrics definitions into &lt;code&gt;schema.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;

&lt;span class="na"&gt;metrics&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;commits_count&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Commits Count&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ref('commits')&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;count&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;metric&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;commits"&lt;/span&gt;

    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;count&lt;/span&gt;

    &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;timestamp&lt;/span&gt;
    &lt;span class="na"&gt;time_grains&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;day&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;week&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;month&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;year&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

    &lt;span class="na"&gt;dimensions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;author_domain&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;author_name&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;users_count&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Users Count&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ref('commits')&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;count&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;metric&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;users"&lt;/span&gt;

    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;count_distinct&lt;/span&gt;
        &lt;span class="s"&gt;sql&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;author_id&lt;/span&gt;

    &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;timestamp&lt;/span&gt;
    &lt;span class="na"&gt;time_grains&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;day&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;week&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;month&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;year&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

    &lt;span class="na"&gt;dimensions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;author_domain&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Defined metrics will be available for programmatic introspection via &lt;a href="https://docs.getdbt.com/docs/dbt-cloud/dbt-cloud-api/metadata/metadata-overview" rel="noopener noreferrer"&gt;Metadata API&lt;/a&gt; and manual exploration in &lt;a href="https://metadata.cloud.getdbt.com/graphiql" rel="noopener noreferrer"&gt;GraphiQL&lt;/a&gt; IDE:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2F4a98a8a5-e015-4c12-b7f9-cae97d945e92.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2F4a98a8a5-e015-4c12-b7f9-cae97d945e92.png" alt="Screenshot_2022-03-08_at_21.41.15.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Exposing dbt Metrics via an API
&lt;/h2&gt;

&lt;p&gt;As a headless BI platform, Cube consists of four logical layers: metrics, acceleration, access control, and API. Our metrics layer is able to read metrics definitions from dbt via the Metadata API and translate them into equivalent Cube data model.&lt;/p&gt;

&lt;p&gt;You can get started with Cube within minutes in &lt;a href="https://cubecloud.dev/auth/signup" rel="noopener noreferrer"&gt;Cube Cloud&lt;/a&gt; or set up Cube locally with &lt;a href="https://cube.dev/docs/getting-started/docker/compose?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=dbt-metrics-meet-cube" rel="noopener noreferrer"&gt;Docker&lt;/a&gt;. You’ll need to provide the credentials for your data warehouse; there are plenty of &lt;a href="https://cube.dev/docs/config/databases?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=dbt-metrics-meet-cube" rel="noopener noreferrer"&gt;data sources&lt;/a&gt; supported by Cube.&lt;/p&gt;

&lt;p&gt;Next, connecting Cube to dbt is as trivial as adding this code snippet to your data model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;dbt&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@cubejs-backend/dbt-schema-extension&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;dbtJobId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dbtApiKey&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nf"&gt;asyncModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;dbt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadMetricCubesFromDbtCloud&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dbtJobId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dbtApiKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In an instant, you’ll be able to explore the data in Cube Playground:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2Fbeeed292-7886-454f-af75-8dab41b3cfd1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2Fbeeed292-7886-454f-af75-8dab41b3cfd1.png" alt="Screenshot_2022-03-09_at_14.51.42.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cube will take care of SQL generation and querying the data in your data warehouse that was previously transformed with dbt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="nv"&gt;`github_commit_stats_commits`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;author_domain&lt;/span&gt; &lt;span class="nv"&gt;`author_domain`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;`github_commit_stats_commits`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;author_name&lt;/span&gt; &lt;span class="nv"&gt;`author_name`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;`commits_count`&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
  &lt;span class="nv"&gt;`cube-devrel-team`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;`dbt_prod`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;`commits`&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nv"&gt;`github_commit_stats_commits`&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;
  &lt;span class="nv"&gt;`github_commit_stats_commits`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;author_domain&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you the luxury of using a plenty of API flavors provided by Cube:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://cube.dev/docs/backend/sql?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=dbt-metrics-meet-cube" rel="noopener noreferrer"&gt;SQL API&lt;/a&gt; — to expose the metrics to popular BI tools, e.g., Apache Superset (check &lt;a href="https://cube.dev/blog/building-metrics-dashboard-with-superset/?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=dbt-metrics-meet-cube" rel="noopener noreferrer"&gt;this tutorial&lt;/a&gt; to see it in action)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cube.dev/docs/rest-api?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=dbt-metrics-meet-cube" rel="noopener noreferrer"&gt;REST API&lt;/a&gt; — to expose the metrics to even more BI tools (e.g., &lt;a href="https://cube.dev/blog/building-an-appsmith-dashboard-with-cube/?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=dbt-metrics-meet-cube" rel="noopener noreferrer"&gt;Appsmith&lt;/a&gt;, &lt;a href="https://cube.dev/blog/building-a-bubble-dashboard-with-cube/?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=dbt-metrics-meet-cube" rel="noopener noreferrer"&gt;Bubble&lt;/a&gt;, or &lt;a href="https://cube.dev/blog/building-an-internal-dashboard-with-retool-and-cube/?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=dbt-metrics-meet-cube" rel="noopener noreferrer"&gt;Retool&lt;/a&gt;) or integrate Cube into your report-generation or machine learning pipeline&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cube.dev/docs/backend/graphql?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=dbt-metrics-meet-cube" rel="noopener noreferrer"&gt;GraphQL API&lt;/a&gt; and integrations with popular front-end frameworks such as React, Angular, and Vue — for embedded analytics in custom-built applications (check one of &lt;a href="https://cube.dev/blog/category/tutorials/?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=dbt-metrics-meet-cube" rel="noopener noreferrer"&gt;many tutorials&lt;/a&gt; that we have)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fetching data via the REST API is as simple as sending a JSON-encoded query and parsing the JSON-encoded result set. Cube provides convenient JavaScript client libraries for that purpose, but even a &lt;code&gt;curl&lt;/code&gt; or &lt;code&gt;wget&lt;/code&gt; call will work just great:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2Ff336e7f9-c9ac-49e3-8c8d-1bb500fc0727.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2Ff336e7f9-c9ac-49e3-8c8d-1bb500fc0727.png" alt="Screenshot_2022-03-11_at_16.57.38.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bringing the same data into Apache Superset or another data visualization tool via Cube’s SQL API requires just a few clicks in the UI that will yield an equivalent SQL query and result set:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2Fbd3e1389-1793-4ec7-9f74-54cef22c82e1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2Fbd3e1389-1793-4ec7-9f74-54cef22c82e1.png" alt="Screenshot_2022-03-11_at_17.02.12.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Take a closer look! Superset generates a fairly straightforward SQL query (shown on the screenshot above) and sends it to Cube. Actually, you can write your custom SQL queries, like the one below, where you can reference measures and dimensions from your data model. Curious what &lt;code&gt;MEASURE(commitsCount)&lt;/code&gt; is? Well, we see a lot of value in valid SQL, that why you can list your dimensions in the &lt;code&gt;GROUP BY&lt;/code&gt; statement and enclose your measures using the &lt;code&gt;MEASURE&lt;/code&gt; aggregate function (if your BI tool of choice would generate something like &lt;code&gt;COUNT&lt;/code&gt; instead of &lt;code&gt;MEASURE&lt;/code&gt;, Cube will readily accept that, too):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;authorDomain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MEASURE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;commitsCount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;`GithubCommitStatsCommits`&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;authorDomain&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;authorDomain&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we’ve learned how dbt Metrics can be exposed to other tools via various APIs provided by Cube. Next, let’s explore how dbt users can leverage Cube’s acceleration and access control layers. &lt;/p&gt;

&lt;h2&gt;
  
  
  Accelerating dbt Metrics with pre-aggregations
&lt;/h2&gt;

&lt;p&gt;Cube’s acceleration layer serves as a smart cache on top of the metrics layer.&lt;/p&gt;

&lt;p&gt;Based on the configuration provided, Cube will pre-emptively fetch data from the data warehouse, pre-aggregate it into analytical rollups, and store them in Cube Store, a custom-built distributed data store, in a columnar format. In most cases, Cube Store enables Cube to fulfill API requests within 300 ms, and allows for concurrencies up to 100 QPS.&lt;/p&gt;

&lt;p&gt;You can see on the Apache Superset screenshot above that an unaccelerated request took more than 2 seconds. Of course, if we run the same request once again, we’ll get the result mush faster because it will be cached by Superset. Let’s see what can be done so any request would yield a sub-second response at all times.&lt;/p&gt;

&lt;p&gt;The Cube way to achieve this is to use pre-aggregations. We’ll extend the data model with a new &lt;em&gt;cube&lt;/em&gt; (think, “a namespace for metrics”) containing the same metrics and a single pre-aggregation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;cube&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GithubCommitStatsCommitsCached&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GithubCommitStatsCommits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;preAggregations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;main&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;measures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;commitsCount&lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;authorDomain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authorName&lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;timeDimension&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;granularity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;day&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;partitionGranularity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;year&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This caching configuration instructs Cube to use the &lt;code&gt;main&lt;/code&gt; pre-aggregation for all queries that &lt;em&gt;match&lt;/em&gt; it, i.e., contain a subset of listed measures and dimensions. It also says that &lt;code&gt;day&lt;/code&gt; is the minimum time grain we’re interested in using. On top of that, it advises Cube to split the pre-aggregated data into partitions (one per &lt;code&gt;year&lt;/code&gt;) and distribute these partitions across all Cube Store nodes so they all can be used in parallel to fulfil queries.&lt;/p&gt;

&lt;p&gt;That’s basically it. Cube will seamlessly build the pre-aggregation, store it in Cube Store, and transparently dispatch queries that match it against Cube Store rather that the original data source, e.g., BigQuery. Here’s a side by side comparison of the query hitting BigQuery (1.8 s) ⚠️ and the same query fulfilled by a pre-aggregation in Cube Store (215 ms) ⚡&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2F4f264d1c-6cae-4381-806c-9100a2dc2b1d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2F4f264d1c-6cae-4381-806c-9100a2dc2b1d.png" alt="Group_40193.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also note that, depending on your usage patterns, not hitting your cloud data warehouse for every client-side query might not only improve the performance of your data application but allow for a substantial total cost reduction.&lt;/p&gt;

&lt;p&gt;That’s how Cube’s acceleration layers works. What about access contol? &lt;/p&gt;

&lt;h2&gt;
  
  
  Securing dbt Metrics and implementing multitenancy
&lt;/h2&gt;

&lt;p&gt;Cube’s access control layer lets you manage who is able to access data and which data it is. Subsequently, Cube has built-in mechanisms for row-level security and &lt;a href="https://cube.dev/docs/multitenancy-setup?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=dbt-metrics-meet-cube" rel="noopener noreferrer"&gt;multitenancy&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can mandate that every query need to be accompanied by a &lt;em&gt;security context&lt;/em&gt; (think “securely signed set of credentials and meta data”) that identifies a user or an application running a query. Cube allows you to validate the security context and the query, possibly amending the contents of the query to enforce access control rules.&lt;/p&gt;

&lt;p&gt;We’ll use the &lt;code&gt;queryRewrite&lt;/code&gt; extension point to require certain meta data to be provided with every query and used in a mandatory filter. With this configuration, no query can be run “unfiltered”:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;queryRewrite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;securityContext&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;securityContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Please specify the domain&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;member&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GithubCommitStatsCommitsCached.authorDomain&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;equals&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;securityContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If anyone tries, they’ll get an error message. However, with a security context provided (see its JSON representation below), the query will yield the properly filtered results according to access control rules:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2F4b32c605-dbc7-4f39-9073-d68940c676ba.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2F4b32c605-dbc7-4f39-9073-d68940c676ba.png" alt="Group_40193 1.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that’s the shortest demo of how Cube’s access control layers works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps for dbt users
&lt;/h2&gt;

&lt;p&gt;Please feel free to check out the &lt;a href="https://github.com/cube-js/cube.js/tree/master/examples/github-commits-stats" rel="noopener noreferrer"&gt;full source code&lt;/a&gt; of dbt models and the Cube project shown above on GitHub. Also, don’t hesitate to give Cube a star on &lt;a href="https://github.com/cube-js/cube.js" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; as well as to follow us on &lt;a href="https://twitter.com/thecubejs" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; or &lt;a href="https://www.linkedin.com/company/cube-dev/mycompany/?viewAsMember=true" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; 🤘&lt;/p&gt;

&lt;p&gt;If you’re a dbt user and you haven’t tried &lt;a href="https://docs.getdbt.com/docs/building-a-dbt-project/metrics" rel="noopener noreferrer"&gt;dbt Metrics&lt;/a&gt;, please do. You’ll be able to abstract your metrics definitions from the presentation layer and keep them in the single source of truth. Note that, as of March 2022, dbt Metrics are still an experimental feature, so if things doesn’t work as expected, please provide your feedback and find the time to &lt;a href="https://github.com/dbt-labs/dbt-core/issues" rel="noopener noreferrer"&gt;file issues&lt;/a&gt; in dbt’s GitHub repository. That is much appreciated.&lt;/p&gt;

&lt;p&gt;Also, if you see the value in integrations with BI tools and custom-built applications as well as query acceleration and access control that are built into Cube, please follow the steps outlined in this tutorial and share your success or ask questions in Cube’s &lt;a href="https://slack.cube.dev?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=dbt-metrics-meet-cube" rel="noopener noreferrer"&gt;Slack community&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>database</category>
      <category>sql</category>
      <category>dbt</category>
    </item>
    <item>
      <title>Building a metrics dashboard with Superset and Cube</title>
      <dc:creator>Igor Lukanin</dc:creator>
      <pubDate>Tue, 30 Nov 2021 17:46:32 +0000</pubDate>
      <link>https://forem.com/cubejs/building-a-metrics-dashboard-with-superset-and-cube-1hc0</link>
      <guid>https://forem.com/cubejs/building-a-metrics-dashboard-with-superset-and-cube-1hc0</guid>
      <description>&lt;p&gt;&lt;em&gt;TL;DR: In this tutorial, we'll learn how to build a metrics dashboard with Apache Superset, a modern and open-source data exploration and visualization platform. We'll also use Cube, an open-source metrics store, as the data source for Superset that will enable our dashboards to load in under a second — quite the opposite to what you'd usually expect from a BI tool, right?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here's how the end result will look like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jZV2lfdf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/11d780fa-b9b3-45d2-af61-e1f3ce270731.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jZV2lfdf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/11d780fa-b9b3-45d2-af61-e1f3ce270731.png" alt="Screenshot 2021-11-24 at 05.04.36.png" width="880" height="562"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we're all set. Let's see what's on the shelves of this metrics store 🏪&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Apache Superset?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://superset.apache.org"&gt;Apache Superset&lt;/a&gt; is a data exploration and visualization platform or, in layman's terms, a tool that you can use to build dashboards with charts for internal users. Born at a hackathon at Airbnb back in 2015, with more than 41,000 stars on GitHub, now it's a leading open-source business intelligence tool.&lt;/p&gt;

&lt;p&gt;Superset has connectors for numerous databases, from Amazon Athena to Databricks to Google BigQuery to Postgres. It provides a web-based SQL IDE and no-code tools for building charts and dashboards.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Running Superset.&lt;/strong&gt; Now let's run Superset to explore these features. To keep things simple, we'll run a fully managed Superset in &lt;a href="https://preset.io"&gt;Preset Cloud&lt;/a&gt;, where you can use it forever for free on the Starter plan. (If you'd like to run Superset locally with Docker, please see &lt;a href="https://github.com/cube-js/cube.js/blob/master/examples/superset/README.md#running-superset-locally"&gt;these instructions&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;First, please proceed to the &lt;a href="https://preset.io/registration/"&gt;sign up page&lt;/a&gt; and fill in your details. Note that Preset Cloud supports signing up with your Google account. Within a few seconds you will be taken to your account with a readily available workspace:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vk-Wls2l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/8cc1e93b-3cd2-4232-b723-8f3b99f42324.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vk-Wls2l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/8cc1e93b-3cd2-4232-b723-8f3b99f42324.png" alt="Screenshot 2021-11-24 at 02.50.55.png" width="880" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Switching to that workspace will reveal a few example dashboards that you can review later.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EonV7E9d--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/91134cc7-13e7-46de-b924-75c606c9fbbd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EonV7E9d--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/91134cc7-13e7-46de-b924-75c606c9fbbd.png" alt="Screenshot 2021-11-24 at 02.54.51.png" width="880" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, let's navigate to &lt;code&gt;Data / Databases&lt;/code&gt; via the top menu and... Oops! We need a metrics store to connect to. Let's see how Cube can help us build one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Cube?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://cube.dev/?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=building-metrics-dashboard-with-superset"&gt;Cube&lt;/a&gt; is an open-source &lt;a href="https://cube.dev/blog/introducing-cube-sql/?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=building-metrics-dashboard-with-superset"&gt;metrics store&lt;/a&gt; with nearly 12,000 stars on &lt;a href="https://github.com/cube-js/cube.js"&gt;GitHub&lt;/a&gt; to date. It serves as a single source of truth for all metrics and provides APIs for powering BI tools and building data apps. You can configure Cube to connect to any database, define your metrics via a declarative data schema, and instantly get an API that you can use with Superset or many other BI tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Running Cube.&lt;/strong&gt; Similarly to Superset, let's run a fully managed Cube in &lt;a href="http://cube.dev/cloud?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=building-metrics-dashboard-with-superset"&gt;Cube Cloud&lt;/a&gt; that has a free plan as well. (If you'd like to run Cube locally with Docker, please see &lt;a href="https://github.com/cube-js/cube.js/blob/master/examples/superset/README.md#running-cube-locally"&gt;these instructions&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;First, please proceed to the &lt;a href="https://cubecloud.dev/auth/signup"&gt;sign up page&lt;/a&gt; and fill in your details. Note that Cube Cloud supports signing up with your GitHub account. Within a few seconds you will be taken to your account where you can create your first Cube deployment:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--b9WM8Wld--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/1ccba445-9cbf-4bb6-acfe-1e3c1d2fa325.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--b9WM8Wld--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/1ccba445-9cbf-4bb6-acfe-1e3c1d2fa325.png" alt="Screenshot 2021-11-24 at 03.06.16.png" width="880" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Proceed with providing a name for your deployment, selecting a cloud provider and a region:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hptldz-n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/28ad847d-10f6-47ed-8bf5-66a2b441eef3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hptldz-n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/28ad847d-10f6-47ed-8bf5-66a2b441eef3.png" alt="Screenshot 2021-11-24 at 03.21.33.png" width="880" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At the next step, choose &lt;code&gt;Create&lt;/code&gt; to start a new Cube project from scratch. Then, pick &lt;code&gt;Postgres&lt;/code&gt; to proceed to the screen where you can enter the following credentials:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hostname:  demo-db-examples.cube.dev
Port:      5432
Database:  ecom
Username:  cube
Password:  12345
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--f9lyB6RA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/835c5600-7f79-44b3-81c1-3726a787c440.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--f9lyB6RA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/835c5600-7f79-44b3-81c1-3726a787c440.png" alt="Screenshot 2021-11-24 at 03.22.40.png" width="880" height="486"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cube will connect to a publicly available Postgres database that I've already set up.&lt;/p&gt;

&lt;p&gt;The last part of configuration is the &lt;a href="https://cube.dev/docs/schema/getting-started?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=building-metrics-dashboard-with-superset"&gt;data schema&lt;/a&gt; which declaratively describes the metrics we'll be putting on the dashboard. Actually, Cube can generate it for us! Pick the top-level &lt;code&gt;public&lt;/code&gt; database from the list:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--eylAvg5G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/c721028f-e20c-425c-900d-75b9f294a3bc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eylAvg5G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/c721028f-e20c-425c-900d-75b9f294a3bc.png" alt="Screenshot 2021-11-24 at 03.22.55.png" width="880" height="489"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In a while, your Cube deployment will be up and running:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--x7Ge33SM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/5c39e57e-3311-4f40-9b89-8bc40d49df00.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--x7Ge33SM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/5c39e57e-3311-4f40-9b89-8bc40d49df00.png" alt="Screenshot 2021-11-24 at 03.24.31.png" width="880" height="470"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Defining metrics.&lt;/strong&gt; Please navigate to the "Schema" tab. You will see files like &lt;code&gt;LineItems.js&lt;/code&gt;, &lt;code&gt;Orders.js&lt;/code&gt;, &lt;code&gt;Users.js&lt;/code&gt;, etc. under the "schema" folder.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ss-Xd9bO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/8fb089b2-f33a-4795-9450-9f564dbd40d4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ss-Xd9bO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/8fb089b2-f33a-4795-9450-9f564dbd40d4.png" alt="Screenshot 2021-11-24 at 03.25.51.png" width="880" height="490"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's review &lt;code&gt;LineItems.js&lt;/code&gt; which defines the metrics within the "LineItems" cube. This file is different from the one in Cube Cloud, but we'll take care of that later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;cube&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`LineItems`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`SELECT * FROM public.line_items`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;measures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`count`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`price`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`sum`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`quantity`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`sum`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="c1"&gt;// A calculated measure that reference other measures.&lt;/span&gt;
    &lt;span class="c1"&gt;// See https://cube.dev/docs/schema/reference/measures#calculated-measures&lt;/span&gt;
    &lt;span class="na"&gt;avgPrice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CUBE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; / &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CUBE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`number`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="c1"&gt;// A rolling window measure.&lt;/span&gt;
    &lt;span class="c1"&gt;// See https://cube.dev/docs/schema/reference/measures#rolling-window&lt;/span&gt;
    &lt;span class="na"&gt;revenue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`price`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`sum`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;rollingWindow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;trailing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`unbounded`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="na"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`id`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`number`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;primaryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`created_at`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`time`&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="na"&gt;dataSource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`default`&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key learnings here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the cube is a logical entity that groups measures and dimensions together&lt;/li&gt;
&lt;li&gt;using the &lt;code&gt;sql&lt;/code&gt; statement, this cube is defined over the entire &lt;code&gt;public.line_items&lt;/code&gt; table; actually, cube can be defined over an arbitrary SQL statement that selects data&lt;/li&gt;
&lt;li&gt;measures (quantitative data) are defined as aggregations (e.g., &lt;code&gt;count&lt;/code&gt;, &lt;code&gt;sum&lt;/code&gt;, etc.) over columns in the dataset&lt;/li&gt;
&lt;li&gt;dimensions (qualitative data) are defined over textual, numeric, or temporal columns in the dataset&lt;/li&gt;
&lt;li&gt;you can define complex measures and dimensions with custom &lt;code&gt;sql&lt;/code&gt; statements or references to other measures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Development mode.&lt;/strong&gt; Now, let's update the schema file in Cube Cloud to match the contents above. First, click &lt;code&gt;Enter Development Mode&lt;/code&gt; to unlock the schema files for editing. This essentially creates a "fork" of the Cube API that tracks your changes in the data schema.&lt;/p&gt;

&lt;p&gt;Navigate to &lt;code&gt;LineItems.js&lt;/code&gt; and replace its contents with the code above. Then, save your changes by clicking &lt;code&gt;Save All&lt;/code&gt; to apply the changes to the development version of your API. You can apply as many changes as you wish, but we're done for now. Click &lt;code&gt;Commit &amp;amp; Push&lt;/code&gt; to merge your changes back to the main branch:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--E8MrCxsY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/85eeca4a-e85f-4109-8459-4dbde074d005.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--E8MrCxsY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/85eeca4a-e85f-4109-8459-4dbde074d005.png" alt="Screenshot 2021-11-24 at 03.33.47.png" width="880" height="490"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the "Overview" tab you will see your changes deployed:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8e6ipaci--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/5e7746a3-8839-47e3-bddc-a8b67460207a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8e6ipaci--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/5e7746a3-8839-47e3-bddc-a8b67460207a.png" alt="Screenshot 2021-11-24 at 03.37.57.png" width="880" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you can explore the metrics on the "Playground" tab:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--snB-CDlK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/df9a8062-4026-4476-8c65-b10e1360075e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--snB-CDlK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/df9a8062-4026-4476-8c65-b10e1360075e.png" alt="Screenshot 2021-11-24 at 03.41.38.png" width="880" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Good! We've built a metrics store that we can connect to Superset. How?&lt;/p&gt;

&lt;p&gt;Please go back to the "Overview" tab and click &lt;code&gt;How to connect&lt;/code&gt;. The "SQL API" tab will have a toggle that enables the API for Superset and other BI tools. Turning it on will provide you with all necessary credentials:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--C2Akl2g_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/e2751eeb-c7bb-4f8d-9ff9-d6184097e9ce.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--C2Akl2g_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/e2751eeb-c7bb-4f8d-9ff9-d6184097e9ce.png" alt="Screenshot 2021-11-24 at 03.46.54.png" width="880" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, let's build a dashboard!&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a dashboard in Superset
&lt;/h2&gt;

&lt;p&gt;We'll need to go through a few steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cube.dev/docs/recipes/using-apache-superset-with-cube-sql?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=building-metrics-dashboard-with-superset"&gt;connect Superset to Cube&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;define the datasets&lt;/li&gt;
&lt;li&gt;create charts and add them to a dashboard&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's go!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--d7SdokEm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/dee5d4cc-df2a-47a8-a812-9e54aef57e5d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--d7SdokEm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/dee5d4cc-df2a-47a8-a812-9e54aef57e5d.png" alt="Screenshot 2021-11-24 at 03.44.32.png" width="880" height="355"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connect Superset to Cube.&lt;/strong&gt; Switch back to the workspace we've created earlier. Then, navigate to &lt;code&gt;Data / Databases&lt;/code&gt; via the top menu, click &lt;code&gt;+ Database&lt;/code&gt;, select &lt;code&gt;MySQL&lt;/code&gt;, and fill in the credentials from your Cube Cloud instance — or use the credentials below:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Host: &lt;code&gt;aquamarine-moth.sql.aws-us-east-2.cubecloudapp.dev&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Port: &lt;code&gt;3306&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Database name: &lt;code&gt;db&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Username: &lt;code&gt;cube@aquamarine-moth&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Password: &lt;code&gt;6300005f8da3ef74fa64a5bf9b1b6fcd&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Display name: &lt;code&gt;Cube Cloud&lt;/code&gt; (it's important)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6bJHwcO9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/42812095-82d4-4a65-a918-ab173c45689e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6bJHwcO9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/42812095-82d4-4a65-a918-ab173c45689e.png" alt="Screenshot 2021-11-24 at 03.53.07.png" width="880" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can press &lt;code&gt;Connect&lt;/code&gt; now.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Define the datasets.&lt;/strong&gt; Navigate to &lt;code&gt;Data / Datasets&lt;/code&gt; via the top menu, click &lt;code&gt;+ Dataset&lt;/code&gt;, and fill in the following credentials:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database: &lt;code&gt;Cube Cloud&lt;/code&gt; (the one we've just created)&lt;/li&gt;
&lt;li&gt;Schema: &lt;code&gt;db&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;See table schema: &lt;code&gt;LineItems&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PjKGdsnb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/d8e50fc9-4df7-425b-b57b-f3904de984e0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PjKGdsnb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/d8e50fc9-4df7-425b-b57b-f3904de984e0.png" alt="Screenshot 2021-11-24 at 03.58.02.png" width="880" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can press &lt;code&gt;Add&lt;/code&gt; now. Then, please repeat this for &lt;code&gt;Users&lt;/code&gt; and &lt;code&gt;Orders&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create charts and a dashboard.&lt;/strong&gt; We'll take a leap and create everything in a single step.&lt;/p&gt;

&lt;p&gt;In Superset, you can export a dashboard with all charts as a JSON file and import it later. Navigate to &lt;code&gt;Dashboards&lt;/code&gt; and click the link with an icon on the right:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0oG8Y-ku--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/e2ea0cbd-1cd0-439d-8c4d-a36d9e61b0c7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0oG8Y-ku--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/e2ea0cbd-1cd0-439d-8c4d-a36d9e61b0c7.png" alt="Screenshot 2021-11-24 at 04.01.31.png" width="880" height="335"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Download &lt;a href="https://raw.githubusercontent.com/cube-js/cube.js/master/examples/superset/acme-dashboard.json"&gt;this file&lt;/a&gt; to your machine, select it, and click &lt;code&gt;Import&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--phoqVJh3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/5256114e-d4a4-4e4d-96b0-137aee522bcb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--phoqVJh3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/5256114e-d4a4-4e4d-96b0-137aee522bcb.png" alt="Screenshot 2021-11-24 at 04.02.38.png" width="880" height="335"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Whoa! Now we have a complete dashboard for Acme, Inc. Click on it to view:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jZV2lfdf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/11d780fa-b9b3-45d2-af61-e1f3ce270731.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jZV2lfdf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/11d780fa-b9b3-45d2-af61-e1f3ce270731.png" alt="Screenshot 2021-11-24 at 05.04.36.png" width="880" height="562"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Looks nice, doesn't it? Let's explore what's under the hood and how you can build it on your own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Diving deep into Superset
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Anatomy of a dashboard.&lt;/strong&gt; You can see that charts on the dashboard are aligned by the grid. To rearrange them, click on the pencil icon in the top right corner. You can add tabs, headers, dividers, Markdown blocks, etc. Of course, you can also add charts.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FE1lcTwz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/f8905469-8f64-4e3d-85d0-e7ae283673f3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FE1lcTwz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/f8905469-8f64-4e3d-85d0-e7ae283673f3.png" alt="Screenshot 2021-11-24 at 05.06.41.png" width="880" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The simplest chart.&lt;/strong&gt; Navigate to &lt;code&gt;Charts&lt;/code&gt; via the top menu and click on any chart with the &lt;code&gt;Big Number&lt;/code&gt; visualization type, e.g., Customers. In my opinion, that's the simplest chart you can create in Superset. It contains a single metric, and I doubt a chart can get simpler than that.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JL5mIojR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/afde957b-bdc9-4083-a704-11006c8805f1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JL5mIojR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/afde957b-bdc9-4083-a704-11006c8805f1.png" alt="Screenshot 2021-11-24 at 05.07.29.png" width="880" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's dissect how a chart is defined:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Visualization type: &lt;code&gt;Big Number&lt;/code&gt; — that's where you can select or change the chart type&lt;/li&gt;
&lt;li&gt;Time column: &lt;code&gt;createdAt&lt;/code&gt; — interestingly enough, any chart should have this time column defined even if the displayed data has no temporal components&lt;/li&gt;
&lt;li&gt;Metric: &lt;code&gt;COUNT(*)&lt;/code&gt; — that's the most important part of any chart configuration; upon clicking on this metric, you'll see that you can either select a saved definition, "simply" select a column and an aggregation, or write a "custom SQL" expression&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When all config options are set, press &lt;code&gt;Run&lt;/code&gt; to fetch the data, then &lt;code&gt;Save&lt;/code&gt; to persist a chart or add it on a dashboard (no need to do it now, it's already added).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A less simple chart.&lt;/strong&gt; Navigate to &lt;code&gt;Charts&lt;/code&gt; via the top menu and click on any chart with the &lt;code&gt;Big Number with Trendline&lt;/code&gt; visualization type, e.g., Revenue. Still, it contains a single metric as well as a sketchy chart in the bottom.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yDVre_C5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/103ea21b-ef9b-4e17-b9a9-580d24a271b1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yDVre_C5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/103ea21b-ef9b-4e17-b9a9-580d24a271b1.png" alt="Screenshot 2021-11-24 at 05.08.31.png" width="880" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's dissect how this chart is defined (only new options):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Time grain: &lt;code&gt;Month&lt;/code&gt; — defines the temporal granularity for metrics calculations&lt;/li&gt;
&lt;li&gt;Time range: &lt;code&gt;Last year&lt;/code&gt; — specifies the date range for this chart&lt;/li&gt;
&lt;li&gt;Metric: &lt;code&gt;revenue&lt;/code&gt; — it's interesting; click on this metric to learn that it's defined using "custom SQL"; that's because the aggregation has already been performed by Cube, no need to aggregate aggregated values, right?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Other charts.&lt;/strong&gt; Actually, now you know everything you need to explore and dissect other charts. Just keep in mind that Superset has plenty of customizations you can apply to charts — see the "Customize" tab for inspiration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--u9iyygU8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/eec079db-5c09-42be-822e-5ab583ce016d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--u9iyygU8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/eec079db-5c09-42be-822e-5ab583ce016d.png" alt="Screenshot 2021-11-24 at 05.09.15.png" width="880" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Viewing SQL.&lt;/strong&gt; For any chart, you can reveal the SQL query to Cube which is generated by Superset to fetch the data. Press the burger button (with triple horizontal lines) in the top right corner, then &lt;code&gt;View query&lt;/code&gt;. Good ol' SQL, nice:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xjyIUNnk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/68e680a9-3826-4597-8e21-934f901779c3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xjyIUNnk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cubedev-blog-images.s3.us-east-2.amazonaws.com/68e680a9-3826-4597-8e21-934f901779c3.png" alt="Screenshot 2021-11-24 at 05.09.42.png" width="880" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also, if you wanna use the aforementioned SQL IDE, navigate to &lt;code&gt;SQL Lab / SQL Editor&lt;/code&gt; via the top menu.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making business intelligence fast ⚡
&lt;/h2&gt;

&lt;p&gt;There's only one thing left to explore, but it's a huge one.&lt;/p&gt;

&lt;p&gt;Let's navigate back to the Acme, Inc. dashboard. It takes 2-3 seconds to load, and the infinity-shaped spinners are clearly visible. They are not annoying, but honestly — wouldn't you like this dashboard to load instantly? Yep, well under a second.&lt;/p&gt;

&lt;p&gt;Cube provies an out-of-the-box &lt;a href="https://cube.dev/docs/caching?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=building-metrics-dashboard-with-superset"&gt;caching layer&lt;/a&gt; that allows to pre-compute and materialize the data required to serve the queries. All you need to do is define which queries should be accelerated. It's done declaratively in the data schema files. (Please also note that Superset has its own lightweight &lt;a href="https://superset.apache.org/docs/installation/cache"&gt;caching layer&lt;/a&gt; that might be handy in cases when you need to push your Cube + Superset to the limit.)&lt;/p&gt;

&lt;p&gt;Please go back to your Cube Cloud instance, enter the development mode, switch to the "Schema" tab, and update your data schema files with small snippets as follows.&lt;/p&gt;

&lt;p&gt;First, &lt;code&gt;LineItems.js&lt;/code&gt; should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;cube&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`LineItems`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`SELECT * FROM public.line_items`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;// Copy me ↓&lt;/span&gt;
  &lt;span class="na"&gt;preAggregations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;main&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;measures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;CUBE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CUBE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;revenue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CUBE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CUBE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quantity&lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;timeDimension&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CUBE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;granularity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;day&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// Copy me ↑&lt;/span&gt;

  &lt;span class="na"&gt;measures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`count`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Second, &lt;code&gt;Orders.js&lt;/code&gt; should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;cube&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Orders`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`SELECT * FROM public.orders`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;// Copy me ↓&lt;/span&gt;
  &lt;span class="na"&gt;preAggregations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;main&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;measures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;CUBE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;CUBE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;timeDimension&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CUBE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;granularity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;day&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// Copy me ↑&lt;/span&gt;

  &lt;span class="na"&gt;measures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`count`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lastly, &lt;code&gt;Users.js&lt;/code&gt; should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;cube&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Users`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`SELECT * FROM public.users`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;// Copy me ↓&lt;/span&gt;
  &lt;span class="na"&gt;preAggregations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;main&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;measures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;CUBE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;CUBE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CUBE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gender&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// Copy me ↑&lt;/span&gt;

  &lt;span class="na"&gt;measures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`count`&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't forget to click &lt;code&gt;Save All&lt;/code&gt;, then &lt;code&gt;Commit &amp;amp; Push&lt;/code&gt;, and check that your changes were deployed at the "Overview" tab. In the background, Cube will build the necessary caches.&lt;/p&gt;

&lt;p&gt;It's time to get back to your dashboard and refresh it. Now refresh it one more time. See? The dashboard loads in under a second. ⚡&lt;/p&gt;

&lt;p&gt;Of course, you have plenty of options to fine-tune the caching behavior, e.g., specify the &lt;a href="https://cube.dev/docs/caching/using-pre-aggregations?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=building-metrics-dashboard-with-superset#refresh-strategy"&gt;cache rebuilding schedule&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Thanks for following this tutorial. I encourage you to spend some time &lt;a href="https://superset.apache.org/docs/intro"&gt;in the docs&lt;/a&gt; and explore other features of Apache Superset. Also, please check out Preset docs that are packed with great content, e.g., on &lt;a href="https://docs.preset.io/docs/create-a-chart"&gt;creating charts&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Also, thanks for learning more about building a metrics store with &lt;a href="https://cube.dev/?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=building-metrics-dashboard-with-superset"&gt;Cube&lt;/a&gt;. Indeed, it's a very convenient tool to serve as a &lt;a href="https://cube.dev/blog/introducing-cube-sql/?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=building-metrics-dashboard-with-superset"&gt;single source of truth for all metrics&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Please don't hesitate to like and bookmark this post, write a comment, and give a star to &lt;a href="https://github.com/cube-js/cube.js"&gt;Cube&lt;/a&gt; and &lt;a href="https://github.com/apache/superset"&gt;Superset&lt;/a&gt; on GitHub. I hope these tools would be a part of your toolkit when you decide to build a metrics store and a business intelligence application on top of it.&lt;/p&gt;

&lt;p&gt;Good luck and have fun!&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>opensource</category>
      <category>analytics</category>
      <category>dataviz</category>
    </item>
    <item>
      <title>Google Charts Dashboard: a Tutorial with an Artistic Touch of MoMA 🖼</title>
      <dc:creator>Igor Lukanin</dc:creator>
      <pubDate>Thu, 21 Oct 2021 16:52:13 +0000</pubDate>
      <link>https://forem.com/cubejs/google-charts-dashboard-a-tutorial-with-an-artistic-touch-of-moma-1jdn</link>
      <guid>https://forem.com/cubejs/google-charts-dashboard-a-tutorial-with-an-artistic-touch-of-moma-1jdn</guid>
      <description>&lt;p&gt;In this tutorial, we'll learn how to visualize data with Google Charts, a free charting service and JavaScript library by Google. We'll also use Cube, an open-source API for building data apps, to provide access to the public dataset with The Museum of Modern Art collection data. In the end, we'll have a dashboard with charts that tell all about MoMA's contemporary artworks.&lt;/p&gt;

&lt;p&gt;Here's how the end result will look like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2F8e89685a-2862-4647-adbe-dd50727ff364.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2F8e89685a-2862-4647-adbe-dd50727ff364.png" alt="Demo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Want to try it? Here's the &lt;a href="https://google-charts-dashboard-demo.cube.dev" rel="noopener noreferrer"&gt;live demo&lt;/a&gt; you can use right away.&lt;/strong&gt; Also, the full &lt;a href="https://github.com/cube-js/cube.js/tree/master/examples/google-charts-moma" rel="noopener noreferrer"&gt;source code&lt;/a&gt; is available on GitHub.&lt;/p&gt;

&lt;p&gt;Now we're all set. Please check your ticket and proceed to Floor 1, Charting Gallery 🎫&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Google Charts?
&lt;/h2&gt;

&lt;p&gt;Google Charts is a charting service by Google that provides a rich selection of data visualization types, renders charts using HTML5 and SVG, provides cross-browser compatibility and cross-platform portability (meaning that charts look nice both on desktop and mobile).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2Fe2567875-f039-4230-b414-ac974ac395b1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2Fe2567875-f039-4230-b414-ac974ac395b1.png" alt="Google Charts"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why is it a &lt;em&gt;charting service&lt;/em&gt;, not a &lt;em&gt;charting library&lt;/em&gt;?&lt;/strong&gt; Indeed, Google Charts provide a JavaScript library that takes the data and renders charts on the web page. However, unlike other data visualization tools, Google Charts don't render all the charts on the client side. Instead, for some of the charts, they pass the data to Google servers, render a chart there, and then display the result on the page.&lt;/p&gt;

&lt;p&gt;Such an approach has its pros:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rendering code is browser- and platform-independent which provides increased compatibility and portability. If it renders once, it will render anytime.&lt;/li&gt;
&lt;li&gt;The size of the JavaScript library is fixed and doesn't depend on the features used. And it's actually really tiny — less than 20 KB (gzipped).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But it also has its cons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For some charts, data has to be uploaded to Google servers for the chart to be rendered. If you deal with sensitive data, please check the &lt;a href="https://developers.google.com/terms" rel="noopener noreferrer"&gt;Google APIs Terms of Service&lt;/a&gt;. Also, make sure to always check the &lt;a href="https://developers.google.com/chart/interactive/docs/gallery/scatterchart#data-policy" rel="noopener noreferrer"&gt;Data Policy&lt;/a&gt; sections in the docs. In this tutorial, we'll be using a public dataset, so it's not a big deal.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Which charts are available?&lt;/strong&gt; Among the usual suspects like line charts, bar charts, or pie charts you can find a few distinctive ones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Calendar charts&lt;/em&gt; that you must have seen numerous times at GitHub profiles.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Gantt charts&lt;/em&gt; that you might have wished to never encounter because of their affinity to "enterprise software".&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Diff charts&lt;/em&gt; that combine a couple of scatter charts, pie charts, or bar charts into an image that visualizes the difference between two similar datasets.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Vega charts&lt;/em&gt; that provide a way to render charts defined with &lt;a href="https://awesome.cube.dev/tools/vega?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=google-charts-dashboard" rel="noopener noreferrer"&gt;Vega&lt;/a&gt; and &lt;a href="https://awesome.cube.dev/tools/vega-lite?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=google-charts-dashboard" rel="noopener noreferrer"&gt;Vega-Lite&lt;/a&gt; visual grammars in Google Charts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2F119c5b45-fca1-4e2f-a592-1ba251b4e3f6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2F119c5b45-fca1-4e2f-a592-1ba251b4e3f6.png" alt="Chart types"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enjoying the sight so far? Please proceed to Floor 2, Modern Arts 🎫&lt;/p&gt;

&lt;h2&gt;
  
  
  What is MoMA?
&lt;/h2&gt;

&lt;p&gt;The Museum of Modern Art is an art museum in New York, USA. It was established 91 years ago, on November 7, 1929, and it's often identified as one of the largest and most influential museums of modern art in the world. MoMA's collection includes almost 200,000 works of architecture and design, drawing, painting, sculpture, photography, prints, illustrated books, film, and electronic media.&lt;/p&gt;

&lt;p&gt;On GitHub, MoMA publishes and periodically updates a &lt;a href="https://github.com/MuseumofModernArt/collection" rel="noopener noreferrer"&gt;public dataset&lt;/a&gt; which contains ~140,000 records, representing all of the works that have been accessioned into MoMA’s collection and cataloged in our database. It includes basic metadata for each work (e.g., title, artist, date made, medium, dimensions, and date of acquisition). This dataset is placed in the public domain using a CC0 License (so we're free to use it in this tutorial) and available in CSV and JSON formats.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2Fc08f5a0c-4940-4ad8-b4e1-1b5506c1985c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2Fc08f5a0c-4940-4ad8-b4e1-1b5506c1985c.png" alt="MoMA dataset"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I've imported this dataset to a publicly available Postgres instance that we'll use in a few minutes to explore the data. Proceed to Floor 3, Cubism 🎫&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Cube?
&lt;/h2&gt;

&lt;p&gt;We're building a dashboard, so it would be very convenient to access the data from the front end via an API. Cube comes particularly handy for this purpose.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cube.dev?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=google-charts-dashboard" rel="noopener noreferrer"&gt;Cube&lt;/a&gt; is a popular open-source product with more than 11,000 stars on GitHub to date. It serves as an API for building data apps. You can configure Cube to connect to any database, describe your data with a declarative data schema, and instantly get an API that you can use in your app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Let's spin up an API for the MoMA dataset.&lt;/strong&gt; First, please make sure you have &lt;a href="https://www.docker.com/get-started" rel="noopener noreferrer"&gt;Docker&lt;/a&gt; installed on your machine. It's &lt;a href="https://cube.dev/docs/getting-started/docker?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=google-charts-dashboard" rel="noopener noreferrer"&gt;recommended&lt;/a&gt; to run Cube with Docker or use a managed instance in &lt;a href="https://cube.dev/cloud?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=google-charts-dashboard" rel="noopener noreferrer"&gt;Cube Cloud&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Second, let's create a new folder for your Cube app and navigate to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;google-charts-moma
&lt;span class="nb"&gt;cd &lt;/span&gt;google-charts-moma
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Third, run this snippet to create a new &lt;code&gt;docker-compose.yml&lt;/code&gt; file with the configuration. We'll also use environment variables from the &lt;code&gt;.env&lt;/code&gt; file to instruct Cube how to connect to Postgres:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; docker-compose.yml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;EOL&lt;/span&gt;&lt;span class="sh"&gt;
version: '2.2'
services:
  cube:
    image: cubejs/cube:latest
    ports:
      - 4000:4000
      - 3000:3000
    env_file: .env
    volumes:
      - .:/cube/conf
&lt;/span&gt;&lt;span class="no"&gt;EOL
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, run this snippet to create the &lt;code&gt;.env&lt;/code&gt; file with Postgres credentials. In this tutorial, we're using a publicly available Postgres database that I've already set up. Check the &lt;a href="https://cube.dev/docs/config/databases/postgres?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=google-charts-dashboard" rel="noopener noreferrer"&gt;docs&lt;/a&gt; to learn more about connecting Cube to Postgres or any other database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .env &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;EOL&lt;/span&gt;&lt;span class="sh"&gt;
CUBEJS_DB_TYPE=postgres
CUBEJS_DB_HOST=demo-db-examples.cube.dev
CUBEJS_DB_NAME=moma
CUBEJS_DB_USER=cube
CUBEJS_DB_PASS=12345
CUBEJS_API_SECRET=SECRET
CUBEJS_DEV_MODE=true
&lt;/span&gt;&lt;span class="no"&gt;EOL
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is all we need to let Cube connect to Postgres. The last part of configuration is the &lt;a href="https://cube.dev/docs/schema/getting-started?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=google-charts-dashboard" rel="noopener noreferrer"&gt;data schema&lt;/a&gt; which declaratively describes the contents of the database. Let's put it under the &lt;code&gt;schema&lt;/code&gt; folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;schema
&lt;span class="nb"&gt;touch &lt;/span&gt;Artworks.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please copy and paste this data schema into &lt;code&gt;Artworks.js&lt;/code&gt;, then follow the comments in the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;cube&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Artworks`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Cube definition.&lt;/span&gt;
  &lt;span class="c1"&gt;// It says that the data is kept in the "artworks" table.&lt;/span&gt;
  &lt;span class="c1"&gt;// Learn more in the docs: https://cube.dev/docs/schema/getting-started&lt;/span&gt;
  &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`SELECT * FROM public.artworks`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;// Quantitative information about the data, e.g., count of rows.&lt;/span&gt;
  &lt;span class="c1"&gt;// It makes sense for all rows rather than individual rows&lt;/span&gt;
  &lt;span class="na"&gt;measures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`count`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;minAgeAtAcquisition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`number`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`MIN(&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CUBE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ageAtAcquisition&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;avgAgeAtAcquisition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`number`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`SUM(&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CUBE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ageAtAcquisition&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;) / &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CUBE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;maxAgeAtAcquisition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`number`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`MAX(&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CUBE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ageAtAcquisition&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="c1"&gt;// Qualitative information about the data, e.g., an artwork's title.&lt;/span&gt;
  &lt;span class="c1"&gt;// It makes sense for individual rows of data rather than all rows&lt;/span&gt;
  &lt;span class="na"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CUBE&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;."Title"`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`string`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;artist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CUBE&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;."Artist"`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`string`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;classification&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CUBE&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;."Classification"`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`string`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;medium&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CUBE&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;."Medium"`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`string`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="c1"&gt;// We can use SQL functions here&lt;/span&gt;
    &lt;span class="na"&gt;year&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`SUBSTRING(&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CUBE&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;."Date" FROM '[0-9]{4}')`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`number`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CUBE&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;."Date"`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`number`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;dateAcquired&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CUBE&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;."DateAcquired"`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`time`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;yearAcquired&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`DATE_PART('year', &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CUBE&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;."DateAcquired")`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`number`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;ageAtAcquisition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;case&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CUBE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;yearAcquired&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;::INT - &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CUBE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;year&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;::INT &amp;gt; 0`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CUBE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;yearAcquired&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;::INT - &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CUBE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;year&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;::INT`&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0`&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`number`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;heightCm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`ROUND(&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CUBE&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;."Height (cm)")`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`number`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;widthCm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`ROUND(&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CUBE&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;."Width (cm)")`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`number`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="na"&gt;dataSource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`default`&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whew! Now we're finally ready to run Cube:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Now, let's review the data in the MoMA dataset.&lt;/strong&gt; Cube provides the &lt;a href="https://cube.dev/docs/dev-tools/dev-playground?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=google-charts-dashboard" rel="noopener noreferrer"&gt;Developer Playground&lt;/a&gt;, a convenient web-based tool that helps explore the data, at &lt;a href="http://localhost:4000/" rel="noopener noreferrer"&gt;localhost:4000&lt;/a&gt;. Navigate to the Developer Playground in your browser and explore the UI. You can retrieve arbitrary data, slice and dice the dataset by selecting dimensions and measures.&lt;/p&gt;

&lt;p&gt;For example, you can check how many artworks MoMA has for the artists that have "Sarah" in their name. To do so, select the &lt;code&gt;Artworks.count&lt;/code&gt; measure, the &lt;code&gt;Artworks.artist&lt;/code&gt; dimension, and also make sure to add a filter for the &lt;code&gt;Artworks.artist&lt;/code&gt; dimension that allows only the names containing "Sarah".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2F6408ed85-9b19-4895-a039-0b838773ef26.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2F6408ed85-9b19-4895-a039-0b838773ef26.png" alt="Developer Playground"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feel free to tinker with other measures and dimensions. Once you're done, let's get to building the dashboard. Proceed to Floor 4, Abstract Art 🎫&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a dashboard with Google Charts
&lt;/h2&gt;

&lt;p&gt;Now it's time to develop a front-end application telling the story behind the MoMA artworks collection with charts and other types of data visualizations. For simplicity, let's build it with pure JavaScript and without any frameworks (however, unofficial packages are available on NPM for &lt;a href="https://www.npmjs.com/package/react-google-charts" rel="noopener noreferrer"&gt;React&lt;/a&gt;, &lt;a href="https://www.npmjs.com/package/angular-google-charts" rel="noopener noreferrer"&gt;Angular&lt;/a&gt;, and &lt;a href="https://www.npmjs.com/package/vue-google-charts" rel="noopener noreferrer"&gt;Vue&lt;/a&gt; as well as TypeScript definitions in the &lt;a href="https://www.npmjs.com/package/@types/google.visualization" rel="noopener noreferrer"&gt;DefinitelyTyped&lt;/a&gt; repository).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Basic dashboard.&lt;/strong&gt; First, let's create a subfolder for the dashboard under the &lt;code&gt;google-charts-moma&lt;/code&gt; folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;dashboard-app
&lt;span class="nb"&gt;cd &lt;/span&gt;dashboard-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Second, let's start with an HTML file with the following contents. You can name it &lt;code&gt;basic.html&lt;/code&gt; and put in that &lt;code&gt;dashboard-app&lt;/code&gt; folder. We'll go through this file line by line. Follow the comments!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- Load Cube API library (UMD version for in-browser use) --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/javascript"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://unpkg.com/@cubejs-client/core@0.28.38/dist/cubejs-client-core.umd.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

  &lt;span class="c"&gt;&amp;lt;!-- Load Google Charts API --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/javascript"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://www.gstatic.com/charts/loader.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/javascript"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;// Cube API token and host.&lt;/span&gt;
    &lt;span class="c1"&gt;// Change to "http://localhost:4000" to use your own Cube.&lt;/span&gt;
    &lt;span class="c1"&gt;// See the docs to learn more about authentication: https://cube.dev/docs/security&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cubeToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjEwMDAwMDAwMDAsImV4cCI6NTAwMDAwMDAwMH0.OHZOpOBVKr-sCwn8sbZ5UFsqI3uCs6e4omT7P6WVMFw&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cubeHost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://heavy-lansford.gcp-us-central1.cubecloudapp.dev&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Instantiate Cube API client&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cubeApi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;cubejs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cubeToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;apiUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cubeHost&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/cubejs-api/v1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Load Google Charts API and packages ('corechart' is the main one)&lt;/span&gt;
    &lt;span class="nx"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;charts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;current&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;packages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;corechart&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;table&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;]});&lt;/span&gt;

    &lt;span class="c1"&gt;// Set a callback to run when the Google Visualization API is loaded&lt;/span&gt;
    &lt;span class="nx"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;charts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setOnLoadCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;drawCharts&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Function that creates the charts.&lt;/span&gt;
    &lt;span class="c1"&gt;// We'll start with just a couple of them&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;drawCharts&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;drawChartForArtworkArtists&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nf"&gt;drawChartForArtworkYears&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Function that creates a table chart.&lt;/span&gt;
    &lt;span class="c1"&gt;// See the docs to learn more: https://developers.google.com/chart/interactive/docs/gallery/table&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;drawChartForArtworkArtists&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Cube query that will fetch:&lt;/span&gt;
      &lt;span class="c1"&gt;// — artists' names (1)&lt;/span&gt;
      &lt;span class="c1"&gt;// — count of artworks for each artist (2)&lt;/span&gt;
      &lt;span class="c1"&gt;// — but only for those artworks that are labeled as paintings (3)&lt;/span&gt;
      &lt;span class="c1"&gt;// Learn more about query format in the docs: https://cube.dev/docs/query-format&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Artworks.artist&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;// 1&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;measures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Artworks.count&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;// 2&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;member&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Artworks.classification&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;equals&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Painting&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;// 3&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;

      &lt;span class="c1"&gt;// Let's execute the query via the Cube API...&lt;/span&gt;
      &lt;span class="nx"&gt;cubeApi&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resultSet&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// ...and process the result set.&lt;/span&gt;
          &lt;span class="c1"&gt;// First, let's turn it into an array of two-element arrays with names and counts&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;resultSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tablePivot&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Artworks.artist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Artworks.count&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
          &lt;span class="p"&gt;});&lt;/span&gt;

          &lt;span class="c1"&gt;// Second, let's convert it into Google Charts data table.&lt;/span&gt;
          &lt;span class="c1"&gt;// Note that we're passing an array of column names as the first row&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visualization&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arrayToDataTable&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Artist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Paintings&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;
          &lt;span class="p"&gt;]);&lt;/span&gt;

          &lt;span class="c1"&gt;// Third, let's specify a few options like pagination&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;showRowNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;enable&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;pageSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;100%&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
          &lt;span class="p"&gt;};&lt;/span&gt;

          &lt;span class="c1"&gt;// Eventually, let's render the chart.&lt;/span&gt;
          &lt;span class="c1"&gt;// It will be inserted in place of an HTML element with a known id&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visualization&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chart__artists&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
          &lt;span class="nx"&gt;chart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Function that creates a scatter chart.&lt;/span&gt;
    &lt;span class="c1"&gt;// See the docs to learn more: https://developers.google.com/chart/interactive/docs/gallery/scatterchart&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;drawChartForArtworkYears&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Cube query that will fetch:&lt;/span&gt;
      &lt;span class="c1"&gt;// — production years (1)&lt;/span&gt;
      &lt;span class="c1"&gt;// — acquisition years (2)&lt;/span&gt;
      &lt;span class="c1"&gt;// — count of artworks for each "year created" / "year acquired" pair (3)&lt;/span&gt;
      &lt;span class="c1"&gt;// — but only for those artworks that are labeled as paintings (4)&lt;/span&gt;
      &lt;span class="c1"&gt;// — and have known production year and acquisition year (5, 6)&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Artworks.year&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 1&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Artworks.yearAcquired&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;// 2&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;measures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Artworks.count&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;// 3&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;member&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Artworks.classification&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;equals&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Painting&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;// 4&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;member&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Artworks.yearAcquired&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;set&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;// 5&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;member&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Artworks.year&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;set&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;// 6&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;

      &lt;span class="nx"&gt;cubeApi&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resultSet&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;maxCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;resultSet&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tablePivot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Artworks.count&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Artworks.count&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;resultSet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tablePivot&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
              &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Artworks.year&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
              &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Artworks.yearAcquired&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
              &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;point { opacity: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Artworks.count&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;maxCount&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;; }&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
            &lt;span class="p"&gt;];&lt;/span&gt;
          &lt;span class="p"&gt;});&lt;/span&gt;

          &lt;span class="c1"&gt;// Note that the third "column" of data is special.&lt;/span&gt;
          &lt;span class="c1"&gt;// It says we'll be passing styles of the dots on the scatter chart&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visualization&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arrayToDataTable&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Year created&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Year acquired&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;style&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;
          &lt;span class="p"&gt;]);&lt;/span&gt;

          &lt;span class="c1"&gt;// The chart type is different, and the options are different as well&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Year created vs. Year acquired&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;hAxis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;viewWindowMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;maximized&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Year created&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="na"&gt;vAxis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;viewWindowMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;maximized&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Year acquired&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="na"&gt;pointSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;100%&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;legend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
          &lt;span class="p"&gt;};&lt;/span&gt;

          &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visualization&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ScatterChart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chart__years&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Google Charts Dashboard&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;.dashboard&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="py"&gt;grid-column-gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;.group&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="py"&gt;grid-column-gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"dashboard"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- Charts within the dashboard --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"group"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"chart__artists"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"chart__years"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's what you should get once you save the contents of this file and open it in the browser. It's a table and a fancy diagonal chart — because it's unlikely that MoMA can acquire an artwork before it was created, right?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2F69a2c838-ee29-477c-a2e7-865f63cc6093.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2F69a2c838-ee29-477c-a2e7-865f63cc6093.png" alt="Demo, first preview"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Less than 200 lines of code allowed us to include all libraries, query an API, transform the data, configure charts, and render them side-by-side on the page. Not that bad!&lt;/p&gt;

&lt;p&gt;However, I wouldn't call it a full-fledged dashboard until it allows interaction with elements and change how the data is represented. Let's explore how to work with events and cross-link the charts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Interactive dashboard.&lt;/strong&gt; Please create another file, you can call it &lt;code&gt;index.html&lt;/code&gt;. It will contain 2-3 more lines of code, so we'll have more charts and some bits of code that allow working with events. Copy and paste the &lt;a href="https://github.com/cube-js/cube.js/tree/master/examples/google-charts-moma" rel="noopener noreferrer"&gt;code from GitHub&lt;/a&gt; — and let's go through the most interesting parts together.&lt;/p&gt;

&lt;p&gt;Now we have many charts and many functions to draw them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;drawCharts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;artistFilters&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;drawChartForArtworkArtists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;artistFilters&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;drawChartForArtworkMediums&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;artistFilters&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;drawChartForArtworkYears&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;artistFilters&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;drawChartForArtworkWidthsHeights&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;artistFilters&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;drawChartForArtworkAcquisitions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;artistFilters&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;drawChartForArtworkAcquisitionsIn1964&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;artistFilters&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;drawChartForArtworkAcquisitionsAge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;artistFilters&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One of the charts, the table with artists' names, has got an event listener that is fired every time you select one or many table rows or clear the selection. As you can see, we somehow build the filter for the data using the &lt;code&gt;buildQueryFilter&lt;/code&gt; function and then pass this filter to every other chart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visualization&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;select&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;artistsFilter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildQueryFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resultSet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;chart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Artworks.artist&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;drawChartForArtworkMediums&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;artistsFilter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;drawChartForArtworkYears&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;artistsFilter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;drawChartForArtworkWidthsHeights&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;artistsFilter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;drawChartForArtworkAcquisitions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;artistsFilter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;drawChartForArtworkAcquisitionsIn1964&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;artistsFilter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;drawChartForArtworkAcquisitionsAge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;artistsFilter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's how we build the filter. We access selected rows via &lt;code&gt;chart.getSelection()&lt;/code&gt;. If there are no rows selected, the filter will be undefined. If there are any, we'll find the values of selected cells and use them as values in the Cube query filter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildQueryFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resultSet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;chart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;member&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;selectedRowNumbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;chart&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSelection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;selectedRowNumbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;resultSet&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tablePivot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;selectedRowNumbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;member&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;member&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;equals&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;values&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's all you need to add interactivity to the dashboard. See how the charts change upon the selection of one or many artists in the first table:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2Ff7729016-4b7d-4c2f-ac2e-4f7b3f2e4c9d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2Ff7729016-4b7d-4c2f-ac2e-4f7b3f2e4c9d.png" alt="Demo, second preview"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's a kind of art, isn't it? 🧑‍🎨&lt;/p&gt;

&lt;p&gt;Actually, these charts and this whole tutorial are inspired by the work of others that I'd like to mention here: "&lt;a href="https://fivethirtyeight.com/features/a-nerds-guide-to-the-2229-paintings-at-moma/" rel="noopener noreferrer"&gt;A Nerd’s Guide To The 2,229 Paintings At MoMA&lt;/a&gt;" by &lt;em&gt;FiveThirtyEight&lt;/em&gt;, "&lt;a href="http://research.kraeutli.com/index.php/2015/09/moma-on-github/" rel="noopener noreferrer"&gt;MoMA on GitHub&lt;/a&gt;" by &lt;em&gt;YYYY-MM-DD&lt;/em&gt;, and this &lt;a href="https://twitter.com/lubar/status/626349654830092289" rel="noopener noreferrer"&gt;tweet&lt;/a&gt; by &lt;em&gt;Steven Lubar&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cleaning Off the Paint
&lt;/h2&gt;

&lt;p&gt;Thank you for reading and following this tutorial! I encourage you to spend some time &lt;a href="https://developers.google.com/chart/interactive/docs" rel="noopener noreferrer"&gt;in the docs&lt;/a&gt; and explore what else Google Charts are capable of. You'll find even more chart types, configuration options, and advanced features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Also, thanks for learning about &lt;a href="https://cube.dev?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=google-charts-dashboard" rel="noopener noreferrer"&gt;Cube&lt;/a&gt; and building dashboards. I hope you enjoyed it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Please don't hesitate to like and bookmark this post, write a comment, and give a star to &lt;a href="https://github.com/cube-js/cube.js" rel="noopener noreferrer"&gt;Cube&lt;/a&gt; on GitHub. I hope that you'll try Cube and Google Charts in your next production gig or your next pet project.&lt;/p&gt;

&lt;p&gt;Good luck and have fun! Now, proceed to the exit! 🎫&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>tutorial</category>
      <category>javascript</category>
      <category>dataviz</category>
    </item>
    <item>
      <title>Awesome dataviz tools for software developers 📊📈</title>
      <dc:creator>Igor Lukanin</dc:creator>
      <pubDate>Thu, 24 Jun 2021 15:54:50 +0000</pubDate>
      <link>https://forem.com/cubejs/awesome-dataviz-tools-for-software-developers-3ppi</link>
      <guid>https://forem.com/cubejs/awesome-dataviz-tools-for-software-developers-3ppi</guid>
      <description>&lt;p&gt;&lt;em&gt;TL;DR: We've built &lt;a href="https://awesome.cube.dev/?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=awesome" rel="noopener noreferrer"&gt;awesome.cube.dev&lt;/a&gt; to help you choose the best charting libraries and other tools for your needs. Please have a look, it's awesome.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Hey friends! 👋&lt;/p&gt;

&lt;p&gt;Have you ever wondered which tool or library to choose to add a chart, a map, or maybe a data grid to your app built with the &lt;code&gt;%BEST_FRONTEND_FRAMEWORK%&lt;/code&gt; and the &lt;code&gt;%BEST_PROGRAMMING_LANGUAGE%&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;I'm happy to know for sure that you have! Just a few months ago, I talked to developers in the &lt;a href="https://cube.dev/?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=awesome" rel="noopener noreferrer"&gt;Cube.js&lt;/a&gt; community and also ran this survey that got dozens of replies:&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/cubejs" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__org__pic"&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F553%2F6ca9ad7d-1de7-42a2-a671-2f8f2cf79749.png" alt="Cube"&gt;
      &lt;div class="ltag__link__user__pic"&gt;
        &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F374270%2F8d416f5c-7685-4c7b-9b66-5d40e5af117a.jpg" alt=""&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/cubejs/dev-survey-front-end-data-visualization-tools-1f37" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;DEV Survey ✅ — Front-end Data Visualization Tools&lt;/h2&gt;
      &lt;h3&gt;Igor Lukanin for Cube ・ Mar 22 '21&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#javascript&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#discuss&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;On behalf of the &lt;a href="https://cube.dev/?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=awesome" rel="noopener noreferrer"&gt;Cube.js&lt;/a&gt; team, let me share what I learned.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing a tool is hard 😬
&lt;/h2&gt;

&lt;p&gt;I discovered that there are two types of criteria that developers evaluate when the need to choose a dataviz tool arise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First, you need to check if a particular tool is compatible with the technology stack that you're using,&lt;/strong&gt; and by that I mean the front-end framework and the programming language. Obviously, &lt;a href="https://recharts.org/en-US/" rel="noopener noreferrer"&gt;Recharts&lt;/a&gt; is a neat tool but there's no way to use it in a Vue app because it's purposefully built for React. Same is true for the language: according to the survey, &lt;em&gt;2/3 of all developers use TypeScript&lt;/em&gt;, so making sure that the tool has built-in type definitions or something available via &lt;a href="https://github.com/DefinitelyTyped/DefinitelyTyped" rel="noopener noreferrer"&gt;DefinitelyTyped&lt;/a&gt; is also crucial.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's also nice to check the license.&lt;/strong&gt; Discovering that something that you've already built into your app is not open-source and free to use may be painful. In the other case, when you specifically look for a paid tool with enterprise support, checking the license and pricing beforehand is crucial as well. According to the survey, &lt;em&gt;20 % of all developers use paid tools.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Then, you'll see how easy it is to get started, the hard way.&lt;/strong&gt; The adoption curve is heavily influenced by the ease of use and available docs and examples. &lt;em&gt;Developers particularly praised the tools that have readily available "getting started" pages with code examples as well as the tools with excessive number of examples.&lt;/em&gt; &lt;a href="https://observablehq.com/@d3/gallery" rel="noopener noreferrer"&gt;D3.js&lt;/a&gt; and &lt;a href="https://echarts.apache.org/examples/en/index.html" rel="noopener noreferrer"&gt;ECharts&lt;/a&gt; are obvious winners in this category.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In the end, you might face the sad truth: a tool is rooted in the past.&lt;/strong&gt; Your success will depend on the flexibility that a tool can provide for your use cases. And if it can't, soon you'll find yourself checking if there's much activity in the issues and pull requests sections on GitHub. Let's hope you'll find that a tool currently has a vibrant and active community.&lt;/p&gt;

&lt;h2&gt;
  
  
  Now, choosing a tool is easy 😎
&lt;/h2&gt;

&lt;p&gt;Equipped with this evidence, we've committed to solve the dataviz tools choice problem once and for all. We've built an open-source list of data visualization tools for software developers that will support you at every step.&lt;/p&gt;

&lt;p&gt;In the very beginning, you'll have the chance to pick the front-end framework of your choice and specify if you care about TypeScript support. If you're an open-source aficionado, there's an option for that, too. Also, if you come for something specific (e.g., a data grid), you'll have a filter for that as well.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fylim2ipawsvzbatze6z6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fylim2ipawsvzbatze6z6.png" alt="Filters"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In an instant, you'll see the awesome tools matching your criteria. For those tools that gathered a lot of mentions in the survey results, you'll see badges like "Easy to customize" or "Full-fledged" that you can use to set expectation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyve18kj4es537noo7s30.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyve18kj4es537noo7s30.png" alt="Cards"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Definitely feel free to pick some candidates and deep dive into their pages. You'll be able to build an understanding of what to expect in terms of flexibility and the community dynamics. Checking the last release date and the number of outstanding issues are also wise things to do.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft0z9v5w6nv65liyc6bsd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft0z9v5w6nv65liyc6bsd.png" alt="Data"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ready to get started? You'll find links to tutorials and guides that, hopefully, will help you get on track. And if you're stuck, there're a few links for that, too.&lt;/p&gt;

&lt;p&gt;As the last but not the least resort, feel free to come and join &lt;a href="https://slack.cube.dev/?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=awesome" rel="noopener noreferrer"&gt;Cube.js community&lt;/a&gt; on Slack. It has thousands of developers building apps for which Cube.js serve as the open-source API layer that delivers data to the dataviz tools on the front-end.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdtmpivikvtad3n1zf621.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdtmpivikvtad3n1zf621.png" alt="Getting Started and Getting Help"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Now, it's time to visit &lt;a href="https://awesome.cube.dev/?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=awesome" rel="noopener noreferrer"&gt;awesome.cube.dev&lt;/a&gt; and see it in action! If you like it, don't hesitate to share a link with your friends. In any case, leave us a comment: we'd love to know what you think 😇&lt;/p&gt;

&lt;p&gt;&lt;em&gt;P. S. Just like &lt;a href="https://github.com/cube-js/cube.js" rel="noopener noreferrer"&gt;Cube.js&lt;/a&gt;, awesome.cube.dev is &lt;a href="https://github.com/cube-js/awesome-tools" rel="noopener noreferrer"&gt;open-source&lt;/a&gt;. We appreciate your issues and pull requests.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Introducing Cube Store: High concurrency and sub-second latency for any database</title>
      <dc:creator>Igor Lukanin</dc:creator>
      <pubDate>Thu, 29 Apr 2021 17:49:14 +0000</pubDate>
      <link>https://forem.com/cubejs/introducing-cube-store-high-concurrency-and-sub-second-latency-for-any-database-3n6n</link>
      <guid>https://forem.com/cubejs/introducing-cube-store-high-concurrency-and-sub-second-latency-for-any-database-3n6n</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR: &lt;a href="https://cube.dev?ref=introducing-cubestore" rel="noopener noreferrer"&gt;Cube.js&lt;/a&gt; is a popular open-source tool to build analytical APIs. With the release of Cube Store, a performant distributed data store written in Rust, Cube.js is ready to provide high concurrency and sub-second latency for any SQL-compliant database, data warehouse, or query engine (e.g., PostgreSQL, BigQuery, or AWS Athena).&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can use open-source &lt;a href="https://github.com/cube-js/cube.js" rel="noopener noreferrer"&gt;Cube.js&lt;/a&gt; and &lt;a href="https://github.com/cube-js/cube.js/tree/master/rust" rel="noopener noreferrer"&gt;Cube Store&lt;/a&gt; to build an analytical API that will respond to highly concurrent requests from your application with sub-second latency regardless of data volume or workload. Even if you have a billion-scale dataset and thousands of concurrent users, Cube.js and Cube Store will get you covered.&lt;/p&gt;

&lt;p&gt;Cube Store, the custom pre-aggregation storage layer for Cube.js, is now generally available and strongly recommended for use in production. Let's explore the motivation and design decisions behind Cube Store and learn more about the benefits of using Cube Store for new and existing Cube.js users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Cube Store?
&lt;/h2&gt;

&lt;p&gt;Cube.js is an open-source analytical API platform that allows developers to build an analytical API and a semantic layer based on data schema over any SQL-compliant data store. In the simplest scenario, Cube.js uses in-memory cache and query queue to provide better performance than the data store is able to deliver. However, it's usually the least scalable and cost-effective solution, so it's not recommended for production.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2Fe8dbf9eb-5ce2-4b3f-ac44-2954ef4f6824.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2Fe8dbf9eb-5ce2-4b3f-ac44-2954ef4f6824.png" alt="cube store-04-3-03.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In a real-world scenario, Cube.js speeds up analytical queries using pre-aggregations.&lt;/strong&gt; These are the condensed representations of source data that are pre-computed, partitioned, stored, and refreshed in a way that allows for super-fast execution of select queries.&lt;/p&gt;

&lt;p&gt;Historically, pre-aggregations were stored &lt;em&gt;internally&lt;/em&gt;, alongside the source data in a database (e.g., PostgreSQL or MySQL), or &lt;em&gt;externally&lt;/em&gt;, in a custom-provisioned instance of PostgreSQL or MySQL, as the only feasible option for read-only or cost-ineffective data sources (e.g., AWS Athena or BigQuery). Usually, they will be renewed asyncronously by a dedicated refresh worker instance. It's an acceptable solution, however, the pre-aggregation database becomes a bottleneck and limits the scalability of the analytical API.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2Fac22387e-7c1c-4c70-9a35-430f789a7cfb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2Fac22387e-7c1c-4c70-9a35-430f789a7cfb.png" alt="cube store-04-2-03.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pre-aggregations in traditional databases often won't allow for high concurrency and low latency of the analytical API.&lt;/strong&gt; They also were the source of frustration due to poor performance in important use cases (e.g., with high cardinality of pre-aggregated data, with partitioned data, with joins across several databases), unsupported features (e.g., approximate distinct counts via HyperLogLog algorithm), or implementation details (e.g., support for long table names or support for various SQL types).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cube Store is designed to resolve these issues and provide a performant pre-aggregation storage layer for Cube.js.&lt;/strong&gt; It's a drop-in replacement for any currently used pre-aggregation storage and the go-to solution for new Cube.js apps. Being a distributed data store itself, Cube Store can be scaled horizontally with ease and predictability. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2F4f6d753e-77d2-4d46-964f-8d2512ff9a18.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2F4f6d753e-77d2-4d46-964f-8d2512ff9a18.png" alt="cube store-04-1-03.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;From now on, Cube Store is the recommended pre-aggregation storage layer for production use.&lt;/strong&gt; It doesn't have the limitations of other databases used to store pre-aggregations. On top of that, Cube Store provides guaranteed high concurrency and sub-second latency, performance optimizations for analytical queries, and additional features such as data federation via cross-database joins. You'll get these advantages of Cube Store regardless of your data source and the data volume.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2F67ac0250-4bda-4028-ae56-ec23324bcebf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2F67ac0250-4bda-4028-ae56-ec23324bcebf.png" alt="cube store-01-03.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How Cube Store works
&lt;/h2&gt;

&lt;p&gt;Cube Store uses a well-known and proven distributed query engine architecture. In every Cube Store cluster:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a single &lt;em&gt;router&lt;/em&gt; node handles incoming connections, manages database metadata, builds query plans, and orchestrates their execution&lt;/li&gt;
&lt;li&gt;multiple &lt;em&gt;worker&lt;/em&gt; nodes ingest warmed up data and execute queries in parallel&lt;/li&gt;
&lt;li&gt;a local or cloud-based blob storage keeps pre-aggregated data in columnar format&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2Fdb0e1aeb-3101-4280-b4a4-902e21bcd9a0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2Fdb0e1aeb-3101-4280-b4a4-902e21bcd9a0.png" alt="cube store-03-03.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cube Store was designed with performance in mind.&lt;/strong&gt; It's written entirely in Rust to leverage low-level optimizations, effectively making Rust the top-1 language by lines of code in Cube.js codebase. Cube Store allows for almost unlimited horizontal scalability by increasing the number of worker nodes. It stores pre-aggregated data in Parquet format which was deliberately chosen for its efficiency and performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cube Store is based on well-known open-source building blocks.&lt;/strong&gt; Together with &lt;a href="https://en.wikipedia.org/wiki/Apache_Parquet" rel="noopener noreferrer"&gt;Parquet&lt;/a&gt;, Cube Store uses &lt;a href="https://github.com/apache/arrow" rel="noopener noreferrer"&gt;Apache Arrow&lt;/a&gt; for its efficient in-memory data structures and &lt;a href="https://github.com/apache/arrow/tree/master/rust/datafusion" rel="noopener noreferrer"&gt;DataFusion&lt;/a&gt; as a query execution framework.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cube Store has been used in &lt;a href="https://cube.dev/cloud/?ref=introducing-cubestore" rel="noopener noreferrer"&gt;Cube Cloud&lt;/a&gt; for more than 6 months.&lt;/strong&gt; A few select early access users have been exposed to Cube Store in Cube Cloud and experienced substantial performance improvements thanks to Cube Store.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to use Cube Store
&lt;/h2&gt;

&lt;p&gt;Cube Store is ready to be used while developing new Cube.js applications and also as a drop-in replacement for the pre-aggregation storage layer in existing Cube.js apps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Development.&lt;/strong&gt; When Cube.js is run in &lt;a href="https://cube.dev/docs/configuration/overview?ref=introducing-cubestore#development-mode" rel="noopener noreferrer"&gt;development mode&lt;/a&gt;, a single-node in-process instance of Cube Store is enabled. It requires zero configuration and allows to instantly use the pre-aggregations. All you need is to mark them as &lt;em&gt;external&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;cube&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Cube`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`select * from table`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;preAggregations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;main&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`rollup`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

      &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cube.js will build pre-aggregations and store them in Cube Store. You can review the console output of Cube.js to see if queries are served using pre-aggregations. You can also see matched rollups on the Cache tab in Cube.js Developer Playground available at &lt;a href="http://localhost:4000" rel="noopener noreferrer"&gt;localhost:4000&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Production.&lt;/strong&gt; A Cube Store cluster should be run in production and consist of a single router instance and several worker instances. There's an official &lt;code&gt;cubejs/cubestore&lt;/code&gt; &lt;a href="https://hub.docker.com/r/cubejs/cubestore" rel="noopener noreferrer"&gt;Docker image&lt;/a&gt; as well as a &lt;a href="https://cube.dev/docs/caching/using-pre-aggregations?ref=introducing-cubestore#running-in-production" rel="noopener noreferrer"&gt;Docker Compose template&lt;/a&gt; you can use as a starting point.&lt;/p&gt;

&lt;p&gt;Just like Cube.js, Cube Store can be configured via &lt;a href="https://cube.dev/docs/reference/environment-variables?ref=introducing-cubestore#cube-store" rel="noopener noreferrer"&gt;environment variables&lt;/a&gt;, e.g., Cube Store nodes assume the worker role when &lt;code&gt;CUBESTORE_WORKER_PORT&lt;/code&gt; is set and ingest pre-aggregated data from the blob storage specified with &lt;code&gt;CUBESTORE_GCS_BUCKET&lt;/code&gt; or &lt;code&gt;CUBESTORE_S3_BUCKET&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Benchmarks.&lt;/strong&gt; Depending on the currently used pre-aggregation storage and the data volume, after switching to Cube Store, you can expect your API performance to stay at least at the same level or increase substantially. You can review and use a &lt;a href="https://github.com/cube-js/cube.js/tree/master/examples/cubestore-benchmark" rel="noopener noreferrer"&gt;benchmark template&lt;/a&gt; leveraging &lt;a href="https://k6.io" rel="noopener noreferrer"&gt;k6&lt;/a&gt;, an open-source load testing tool, to do your own measurements prior to switching to Cube Store.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2F13a9bde1-5ddb-4b09-a7b1-d683acaaa42e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcubedev-blog-images.s3.us-east-2.amazonaws.com%2F13a9bde1-5ddb-4b09-a7b1-d683acaaa42e.png" alt="cube store-02-03.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Here at the Cube.js team, we wholeheartedly encourage you to use Cube Store in development and consider migrating your production deployments to Cube Store as well.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Get started with &lt;a href="https://cube.dev?ref=introducing-cubestore" rel="noopener noreferrer"&gt;Cube.js&lt;/a&gt; today. Review the &lt;a href="https://forum.cube.dev/t/v0-27-release-notes/128" rel="noopener noreferrer"&gt;release notes&lt;/a&gt; and &lt;a href="https://cube.dev/docs/caching/using-pre-aggregations?ref=introducing-cubestore#pre-aggregations-storage" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;, share your experience in the &lt;em&gt;#cube-store&lt;/em&gt; channel in &lt;a href="https://slack.cube.dev?ref=introducing-cubestore" rel="noopener noreferrer"&gt;Slack&lt;/a&gt; or post to &lt;a href="https://forum.cube.dev" rel="noopener noreferrer"&gt;Discourse&lt;/a&gt;, and don't hesitate to file Cube Store-related issues on &lt;a href="https://github.com/cube-js/cube.js/issues" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Also, feel free to sign up for the waiting list of &lt;a href="https://cube.dev/cloud/?ref=introducing-cubestore" rel="noopener noreferrer"&gt;Cube Cloud&lt;/a&gt; which provides a fully managed Cube Store cluster for all deployments. Cube Cloud is currently in early access but will be generally available later this year.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>database</category>
      <category>webdev</category>
      <category>rust</category>
    </item>
    <item>
      <title>React Pivot Table with AG Grid and Cube.js 🔢</title>
      <dc:creator>Igor Lukanin</dc:creator>
      <pubDate>Tue, 23 Mar 2021 16:04:27 +0000</pubDate>
      <link>https://forem.com/cubejs/react-pivot-table-with-ag-grid-and-cube-js-2b8o</link>
      <guid>https://forem.com/cubejs/react-pivot-table-with-ag-grid-and-cube-js-2b8o</guid>
      <description>&lt;p&gt;&lt;em&gt;TL;DR: In this tutorial, we'll learn how to add a pivot table to a React app using AG Grid, the self-proclaimed "best JavaScript grid in the world", on the front-end and Cube.js, an analytical API platform, on the back-end. We'll build a &lt;a href="https://react-pivot-table-demo.cube.dev" rel="noopener noreferrer"&gt;pivot table data visualization&lt;/a&gt;, explore the features of AG Grid, and learn why Cube.js is a great fit for AG Grid.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  What is a pivot table?
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Pivot_table" rel="noopener noreferrer"&gt;Pivot tables&lt;/a&gt;, also known as multi-dimensional tables or cross-tables, are tables that display the statistical summary of the data in usual, flat tables. Often, such tables come from databases, but it's not always easy to make sense of the data in large tables. Pivot tables summarize the data in a meaningful way by aggregating it with sums, averages, or other statistics.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here's how a pivot table is explained in Wikipedia.&lt;/strong&gt; Consider you have a flat table like this with e-commerce T-shirt inventory data: regions, ship dates, units, prices, etc.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fupcvyrhxaft6eb9fsyp1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fupcvyrhxaft6eb9fsyp1.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An inventory might be overwhelmingly lengthy, but we can easily explore the data with a pivot table. Let's say we want to know &lt;code&gt;how many items&lt;/code&gt; were shipped to &lt;code&gt;each region&lt;/code&gt; on &lt;code&gt;each date&lt;/code&gt;. Here's the pivot table that answers exactly to this question:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg29u48y40h6kftyo1q99.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg29u48y40h6kftyo1q99.PNG" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analytics 101.&lt;/strong&gt; Note that &lt;code&gt;how many items&lt;/code&gt; is an aggregated, numerical value — a sum of items that were shipped. In analytical applications, such aggregated values are called "measures". Also note that &lt;code&gt;each region&lt;/code&gt; and &lt;code&gt;each date&lt;/code&gt; are categorial, textual values that be can enumerate. In analytical apps, such categorial values are called "dimensions".&lt;/p&gt;

&lt;p&gt;Actually, that's everything one should know about data analytics to work with pivot tables. We'll use this knowledge later.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why AG Grid?
&lt;/h1&gt;

&lt;p&gt;AG Grid is a feature-rich implementation of a JavaScript data table. It supports React, Angular, and Vue as well as vanilla JavaScript. Honestly, it's no exaggeration to say that it contains every feature possible (for a data table): &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frn02rqsxwcquhfuq03gw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frn02rqsxwcquhfuq03gw.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AG Grid's authors emphasize that it's particularly useful for building enterprise applications. So, it's understandable that it comes in two versions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;free and open-source, MIT-licensed Community version&lt;/li&gt;
&lt;li&gt;free-to-evaluate but paid and non-OSS Enterprise version&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Almost all features are included in the Community version, but a few are available only as a part of the Enterprise version: server-side row model, Excel export, various tool panels, and — oh, my! — &lt;em&gt;pivoting&lt;/em&gt; and grouping.&lt;/p&gt;

&lt;p&gt;It's totally okay for the purpose of this tutorial, but make sure to purchase the license if you decide to develop a production app with an AG Grid pivot table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here's what our end result will look like:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpq0xxdnziks2copbfy3r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpq0xxdnziks2copbfy3r.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Want to try it? Here's the &lt;a href="https://react-pivot-table-demo.cube.dev" rel="noopener noreferrer"&gt;live demo&lt;/a&gt; you can use right away.&lt;/strong&gt; Also, the full source code is on &lt;a href="https://github.com/cube-js/cube.js/tree/master/examples/react-pivot-table/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now we're all set, so let's pivot! 🔀&lt;/p&gt;

&lt;h1&gt;
  
  
  How to Create an Analytical API
&lt;/h1&gt;

&lt;p&gt;Pivot tables are useless without the data, and the API is where the data comes from in a real-world app. And the more data we have, the better it is.&lt;/p&gt;

&lt;p&gt;So, what are we going to do? We'll use Cube.js:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/cube-js" rel="noopener noreferrer"&gt;
        cube-js
      &lt;/a&gt; / &lt;a href="https://github.com/cube-js/cube" rel="noopener noreferrer"&gt;
        cube
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      📊  Cube — The Semantic Layer for Building Data Applications
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://cube.dev?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=react-pivot-table" rel="noopener noreferrer"&gt;Cube.js&lt;/a&gt; is an open-source analytical API platform. It allows you to create an API over any database and use that API in any front-end app. In this tutorial, we'll connect Cube.js to a database and we'll use the API in our React app.&lt;/p&gt;

&lt;p&gt;Cube.js provides an abstraction called a "semantic layer," or a "data schema," which encapsulates database-specific things, generates SQL queries for you, and lets you use high-level, domain-specific identifiers to work with data.&lt;/p&gt;

&lt;p&gt;Also, Cube.js has a built-in caching layer that provides predictable, low-latency response query times. It means that, regardless of your data volume and database, an API built with Cube.js will serve data to your app in a performant way and help create a great user experience.&lt;/p&gt;

&lt;p&gt;Let's try it in action.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The first step is to create a new Cube.js project.&lt;/strong&gt; Here, I assume that you already have &lt;a href="https://nodejs.org/en/" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; installed on your machine. Note that you can also &lt;a href="https://cube.dev/docs/getting-started-docker?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=react-pivot-table" rel="noopener noreferrer"&gt;use Docker&lt;/a&gt; to run Cube.js. Run in your console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx cubejs-cli create react-pivot-table &lt;span class="nt"&gt;-d&lt;/span&gt; postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you have your new Cube.js project in the &lt;code&gt;react-pivot-table&lt;/code&gt; folder containing a few files. Let's navigate to this folder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The second step is to add database credentials to the &lt;code&gt;.env&lt;/code&gt; file.&lt;/strong&gt; Cube.js will pick up its configuration options from this file. Let's put the credentials from a publicly available Postgres database there. Make sure your &lt;code&gt;.env&lt;/code&gt; file looks like this, or specify your own credentials:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# Cube.js environment variables: https://cube.dev/docs/reference/environment-variables
&lt;/span&gt;
&lt;span class="py"&gt;CUBEJS_DB_TYPE&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;postgres&lt;/span&gt;
&lt;span class="py"&gt;CUBEJS_DB_HOST&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;demo-db.cube.dev&lt;/span&gt;
&lt;span class="py"&gt;CUBEJS_DB_PORT&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;5432&lt;/span&gt;
&lt;span class="py"&gt;CUBEJS_DB_SSL&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;CUBEJS_DB_USER&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;cube&lt;/span&gt;
&lt;span class="py"&gt;CUBEJS_DB_PASS&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;12345&lt;/span&gt;
&lt;span class="py"&gt;CUBEJS_DB_NAME&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;ecom&lt;/span&gt;

&lt;span class="py"&gt;CUBEJS_DEV_MODE&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;CUBEJS_WEB_SOCKETS&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;CUBEJS_API_SECRET&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;SECRET&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's what all these options mean:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Obviously, &lt;code&gt;CUBEJS_DB_TYPE&lt;/code&gt; says we'll be connecting to Postgres.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CUBEJS_DB_HOST&lt;/code&gt; and &lt;code&gt;CUBEJS_DB_PORT&lt;/code&gt; specify where our Postgres instance is running, and &lt;code&gt;CUBEJS_DB_SSL&lt;/code&gt; turns on secure communications over TLS.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CUBEJS_DB_USER&lt;/code&gt; and &lt;code&gt;CUBEJS_DB_PASS&lt;/code&gt; are used to authenticate the user to Postgres.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CUBEJS_DB_NAME&lt;/code&gt; is the database name where all data schemas and data tables are kept together.&lt;/li&gt;
&lt;li&gt;The rest of the options configure Cube.js and have nothing to do with the database.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The third step is to start Cube.js.&lt;/strong&gt; Run in your console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it! Here's what you should see:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftxdf4jgqudcm5cv1t30s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftxdf4jgqudcm5cv1t30s.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Great, the API is up and running. Let's move on! 🔀&lt;/p&gt;

&lt;h1&gt;
  
  
  How to Define a Data Schema
&lt;/h1&gt;

&lt;p&gt;Before we can tinker with the data, we need to describe it with a data schema. The &lt;a href="https://cube.dev/docs/getting-started-cubejs-schema?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=react-pivot-table" rel="noopener noreferrer"&gt;data schema&lt;/a&gt; is a high-level domain-specific description of your data. It allows you to skip writing SQL queries and rely on Cube.js to generate them for you.&lt;/p&gt;

&lt;p&gt;As the console output suggests, please navigate to &lt;a href="http://localhost:4000" rel="noopener noreferrer"&gt;localhost:4000&lt;/a&gt; — this application is Cube.js Developer Playground. It's able to generate an initial version of the data schema automatically. Go to the "Schema" tab, select all tables under "public", and click the "Generate Schema" button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fppnwb0pr2gjmlernv2u5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fppnwb0pr2gjmlernv2u5.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's all. You can check that in the &lt;code&gt;schema&lt;/code&gt; folder there's a number of files containing the data schema files: &lt;code&gt;Orders.js&lt;/code&gt;, &lt;code&gt;Products.js&lt;/code&gt;, &lt;code&gt;Users.js&lt;/code&gt;, etc. &lt;/p&gt;

&lt;p&gt;Now we have the data schema in place. Let's explore the data! 🔀&lt;/p&gt;

&lt;h1&gt;
  
  
  How to Explore the Data
&lt;/h1&gt;

&lt;p&gt;Go to the "Build" tab, click "+ Dimension" or "+ Measure," and select any number of dimensions and measures. For example, let's select these measures and dimensions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Orders Count&lt;/code&gt; measure&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Line Items Price&lt;/code&gt;  measure&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Line Items Quantity&lt;/code&gt;  measure&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Products Name&lt;/code&gt; dimension&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Orders Status&lt;/code&gt; dimension&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Users City&lt;/code&gt; dimension&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As the result, you should get a complex, lengthy table with the data about our e-commerce enterprise:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flqwki78rj4vnr5kmrb4a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flqwki78rj4vnr5kmrb4a.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Looks interesting, right? Definitely feel free to experiment and try your own queries, measures, dimensions, time dimensions, granularities, and filters.&lt;/p&gt;

&lt;p&gt;Take note that, at any time, you can click the "JSON Query" button and see the query being sent to Cube.js API in JSON format which, essentially, lists the measures and dimensions you were selecting in the UI.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6gqza5fniv7uqaxwk9pb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6gqza5fniv7uqaxwk9pb.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Later, we'll use this query to fill our upcoming pivot table with data. So, let's move on and build a pivot table! 🔀&lt;/p&gt;

&lt;h1&gt;
  
  
  How to Build an Analytical App
&lt;/h1&gt;

&lt;p&gt;Okay, I'll be honest, Cube.js Developer Playground has one more feature to be explored and used for the greater good. &lt;/p&gt;

&lt;p&gt;Let's go to the "Dashboard App" tab where you can generate the code for a front-end application with a dashboard. There's a variety of templates for different frameworks (React and Angular included) and charting libraries but you can always choose to "create your own".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F43ljijihw21cpknz4i22.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F43ljijihw21cpknz4i22.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's choose "React", "React Antd Dynamic", "Bizcharts", and click "OK". Just in a few seconds you'll have a newly created front-end app in the &lt;code&gt;dashboard-app&lt;/code&gt; folder. Click "Start dashboard app" to run it, or do the same by navigating to &lt;code&gt;dashboard-app&lt;/code&gt; and running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Believe it or not, this dashboard app will allow you to run the same queries you've already run the Developer Playground. On the "Explore" tab, you can create a query, tailor the chart, and then click "Add to dashboard". On the "Dashboard" tab, you'll see the result.&lt;/p&gt;

&lt;p&gt;Impressive? We'll go further than that, and replace the dashboard with the pivot table right now. 🔀&lt;/p&gt;

&lt;h1&gt;
  
  
  How to Add a Pivot Table
&lt;/h1&gt;

&lt;p&gt;We'll need to follow a series of simple steps to add AG Grid, tune it, review the result, and understand how everything works. I should say that AG Grid has excellent documentation with versions for &lt;a href="https://www.ag-grid.com/javascript-grid/" rel="noopener noreferrer"&gt;vanilla JavaScript&lt;/a&gt;, &lt;a href="https://www.ag-grid.com/react-grid/" rel="noopener noreferrer"&gt;React&lt;/a&gt;, &lt;a href="https://www.ag-grid.com/angular-grid/" rel="noopener noreferrer"&gt;Angular&lt;/a&gt;, and &lt;a href="https://www.ag-grid.com/vue-grid/" rel="noopener noreferrer"&gt;Vue&lt;/a&gt;. However, here's an even more condensed version of the steps you need to follow to set up AG Grid.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First, let's install the AG Grid packages.&lt;/strong&gt; Make sure to switch to the &lt;code&gt;dashboard-app&lt;/code&gt; folder now. AG Grid can be installed via &lt;a href="https://www.ag-grid.com/javascript-grid/packages-modules/" rel="noopener noreferrer"&gt;packages or modules&lt;/a&gt;, but the former way is simpler. Let's run in the console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save&lt;/span&gt; ag-grid-enterprise ag-grid-react
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that we're installing &lt;code&gt;ag-grid-enterprise&lt;/code&gt; version. There's also &lt;code&gt;ag-grid-community&lt;/code&gt; that contains a subset of the enterprise features but the &lt;a href="https://www.ag-grid.com/react-grid/pivoting/" rel="noopener noreferrer"&gt;pivot table feature&lt;/a&gt; is included in the enterprise version only. It's going to work but it will print a giant warning in the console until you obtain a license:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzbsrmkyr5oky570tey7w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzbsrmkyr5oky570tey7w.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second, let's create a pivot table component.&lt;/strong&gt; Add a new file at the &lt;code&gt;src/components/Grid.js&lt;/code&gt; location with the following contents. Basically, it sets AG Grid up, adds data from Cube.js API, and does the pivoting. It's not very lengthy, and we'll break this code down in a few minutes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useCubeQuery&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@cubejs-client/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Space&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Layout&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;antd&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AgGridColumn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;AgGridReact&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ag-grid-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ag-grid-enterprise&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ag-grid-community/dist/styles/ag-grid.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ag-grid-community/dist/styles/ag-theme-alpine.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;order&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Orders.count&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;desc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;measures&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Orders.count&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;LineItems.price&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;LineItems.quantity&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dimensions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Products.name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Orders.status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Users.city&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Grid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;rowData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setRowData&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;resultSet&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCubeQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resultSet&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setRowData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resultSet&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tablePivot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)]:&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;resultSet&lt;/span&gt; &lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;columnDefs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;measures&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;headerName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;}));&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Layout&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Header&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#43436B&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Space&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;large&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://cube.dev&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;_blank&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;noreferrer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;img&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://cubejs.s3-us-west-2.amazonaws.com/downloads/logo-full.svg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="nx"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cube.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/a&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Space&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://github.com/cube-js/cube.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;_blank&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="nx"&gt;ghost&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;GitHub&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://slack.cube.dev&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;_blank&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="nx"&gt;ghost&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Slack&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Space&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Space&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Layout.Header&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ag-theme-alpine&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AgGridReact&lt;/span&gt;
          &lt;span class="nx"&gt;defaultColDef&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;
            &lt;span class="na"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;minWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;sortable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;resizable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;}}&lt;/span&gt;
          &lt;span class="nx"&gt;aggFuncs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;min&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;max&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sum&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;avg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="p"&gt;}}&lt;/span&gt;
          &lt;span class="nx"&gt;autoGroupColumnDef&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;minWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
          &lt;span class="nx"&gt;pivotMode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="nx"&gt;sideBar&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;columns&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="nx"&gt;rowData&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;rowData&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;columnDefs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;column&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;column&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isDimension&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isMeasure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;measures&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AgGridColumn&lt;/span&gt;
                &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="nx"&gt;headerName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;column&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headerName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;column&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="nx"&gt;enablePivot&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="nx"&gt;enableRowGroup&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isDimension&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="nx"&gt;enableValue&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isMeasure&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="nx"&gt;pivot&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;column&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headerName&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="nx"&gt;rowGroup&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;column&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headerName&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="nx"&gt;allowedAggFuncs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{[&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sum&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;max&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;avg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;min&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;]}&lt;/span&gt;
                &lt;span class="nx"&gt;aggFunc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isMeasure&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sum&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
              &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="p"&gt;})}&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/AgGridReact&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Layout&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make everything work, now go to &lt;code&gt;src/App.js&lt;/code&gt; and change a few lines there to add this new &lt;code&gt;Grid&lt;/code&gt; component to the view:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gi"&gt;+ import Grid from './components/Grid';
&lt;/span&gt;  import './body.css';
  import 'antd/dist/antd.css';
&lt;span class="err"&gt;
&lt;/span&gt;  // ...
&lt;span class="err"&gt;
&lt;/span&gt;  const AppLayout = ({
    children
  }) =&amp;gt; &amp;lt;Layout style={{
    height: '100%'
  }}&amp;gt;
&lt;span class="gd"&gt;-   &amp;lt;Header /&amp;gt;
-   &amp;lt;Layout.Content&amp;gt;{children}&amp;lt;/Layout.Content&amp;gt;
&lt;/span&gt;&lt;span class="gi"&gt;+   &amp;lt;Grid /&amp;gt;
&lt;/span&gt;  &amp;lt;/Layout&amp;gt;;
&lt;span class="err"&gt;
&lt;/span&gt;  // ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Believe it or not, we're all set! 🎉&lt;/strong&gt; Feel free to start your &lt;code&gt;dashboard-app&lt;/code&gt; again with &lt;code&gt;npm run start&lt;/code&gt; and prepare to be amused. Here's our data grid:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpq0xxdnziks2copbfy3r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpq0xxdnziks2copbfy3r.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can even turn "Pivot Mode" off with the knob in the top right corner, remove all measures and dimensions from "Row Groups" and "Values", and behold the raw ungrouped and unpivoted data as fetched from Cube.js API:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu7bj4b8jg8r44m6w586w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu7bj4b8jg8r44m6w586w.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Amazing! Let's break the code down and review the features of AG Grid! 🔀&lt;/p&gt;

&lt;h1&gt;
  
  
  How Everything Works
&lt;/h1&gt;

&lt;p&gt;All relevant code resides inside the &lt;code&gt;src/components/Grid.js&lt;/code&gt; component. We'll explore it from the top to the bottom.&lt;/p&gt;

&lt;p&gt;In the imports, you can see this React hook imported from the Cube.js client React package. We'll use it later to send a query to Cube.js API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Cube.js React hook&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useCubeQuery&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@cubejs-client/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, AG Grid imports go. It has a convenient &lt;code&gt;AgGridReact&lt;/code&gt; component that we'll use. However, in complex scenarios, you'll need to use the &lt;a href="https://www.ag-grid.com/react-grid/grid-interface/#access-the-grid--column-api-1" rel="noopener noreferrer"&gt;onGridReady&lt;/a&gt; callback to get access to the Grid API and tinker with it directly. Also, note that AG Grid provides style definitions and a few &lt;a href="https://www.ag-grid.com/react-grid/themes-provided/" rel="noopener noreferrer"&gt;themes&lt;/a&gt; you can import and use.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// AG Grid React components &amp;amp; library&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AgGridColumn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;AgGridReact&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ag-grid-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ag-grid-enterprise&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// AG Grid styles&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ag-grid-community/dist/styles/ag-grid.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ag-grid-community/dist/styles/ag-theme-alpine.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, meet the Cube.js query in JSON format. I hope you remember this query from Developer Playground where it was available on the "JSON Query" tab:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;order&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Orders.count&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;desc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;measures&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Orders.count&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;LineItems.price&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;LineItems.quantity&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dimensions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Products.name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Orders.status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Users.city&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we jump into the functional &lt;code&gt;Grid&lt;/code&gt; component. Time for React stuff! Here we define a state variable where we'll store the rows to be displayed in our table. Also, we use the &lt;code&gt;useCubeQuery&lt;/code&gt; hook to send the request to Cube.js API. Then, in &lt;code&gt;useEffect&lt;/code&gt;, we get the result, transform it into tabular format with the convenient &lt;code&gt;tablePivot&lt;/code&gt; method, and assign it to the state. (Remapping is needed because Cube.js returns column names in the &lt;code&gt;Cube.measure&lt;/code&gt; and &lt;code&gt;Cube.dimension&lt;/code&gt; format but AG Grid doesn't work with dots in the names.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;rowData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setRowData&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;resultSet&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCubeQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resultSet&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setRowData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resultSet&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tablePivot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)]:&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;resultSet&lt;/span&gt; &lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we extract the column names from the dataset. We'll use them later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;columnDefs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;measures&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;headerName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Time for JSX! Note that the &lt;code&gt;AgGridReact&lt;/code&gt; component is wrapped with a &lt;code&gt;div.ag-theme-alpine&lt;/code&gt; to apply the custom Ag Grid styles. Also, note how default column styles and properties are set.&lt;/p&gt;

&lt;p&gt;The last three lines are the most important ones because they activate the pivot table, enable a convenient sidebar you might know from Excel or similar software, and also wire the row data into the component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ag-theme-alpine&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AgGridReact&lt;/span&gt;
    &lt;span class="nx"&gt;defaultColDef&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;
      &lt;span class="na"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;minWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;sortable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;resizable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}}&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="nx"&gt;autoGroupColumnDef&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;minWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
    &lt;span class="nx"&gt;pivotMode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;    &lt;span class="c1"&gt;// !!!&lt;/span&gt;
    &lt;span class="nx"&gt;sideBar&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;columns&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// !!!&lt;/span&gt;
    &lt;span class="nx"&gt;rowData&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;rowData&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;// !!!&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the most complex part. To transform the row data into a pivot table, we need to specify the column or columns used on the left side and on the top side of the table. With the &lt;code&gt;pivot&lt;/code&gt; option we specify that data is pivoted (the top side of the table) by the "status" column. With the &lt;code&gt;rowGroup&lt;/code&gt; option we specify that the data is grouped by the "name" column.&lt;/p&gt;

&lt;p&gt;Also, we use &lt;code&gt;aggFunc&lt;/code&gt; to specify the default aggregation function used to queeze the pivoted values into one as &lt;code&gt;sum&lt;/code&gt;. Then, we list all allowed aggregation functions under &lt;code&gt;allowedAggFuncs&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;columnDefs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;column&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AgGridColumn&lt;/span&gt;
      &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;headerName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;column&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headerName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;column&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;enablePivot&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;enableRowGroup&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isDimension&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;enableValue&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isMeasure&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;pivot&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;column&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headerName&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;rowGroup&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;column&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headerName&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;allowedAggFuncs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{[&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sum&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;max&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;avg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;min&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;]}&lt;/span&gt;
      &lt;span class="nx"&gt;aggFunc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isMeasure&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sum&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's how these functions are implemented. Nothing fancy, just a little bit of JavaScript functional code for minimum, maximum, sum, and average:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;aggFuncs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;min&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;max&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sum&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;avg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can click on "Values" to change the aggregation function used for every column, or set it programmatically as specified above:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnz0lkw3h4qikgwtm363i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnz0lkw3h4qikgwtm363i.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And that's all, folks! 🎉&lt;/strong&gt; Thanks to AG Grid and Cube.js, we had to write only a few tiny bits of code to create a pivot table.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpq0xxdnziks2copbfy3r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpq0xxdnziks2copbfy3r.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I strongly encourage you to &lt;a href="https://react-pivot-table-demo.cube.dev" rel="noopener noreferrer"&gt;spend some time&lt;/a&gt; with this pivot table and explore what AG Grid is capable of. You'll find column sorting, a context menu with CSV export, drag-and-drop in the sidebar, and much more. Don't hesitate to check AG Grid &lt;a href="https://www.ag-grid.com/react-grid/" rel="noopener noreferrer"&gt;docs&lt;/a&gt; to learn more about these features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thank you for following this tutorial, learning more about &lt;a href="https://cube.dev?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=react-pivot-table" rel="noopener noreferrer"&gt;Cube.js&lt;/a&gt;, building a pivot table, and exploring how to work with AG Grid. I wholeheartedly hope that you enjoyed it 😇&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Please don't hesitate to like and bookmark this post, write a comment, and give a star to &lt;a href="https://github.com/cube-js/cube.js" rel="noopener noreferrer"&gt;Cube.js&lt;/a&gt; or &lt;a href="https://github.com/ag-grid/ag-grid/" rel="noopener noreferrer"&gt;AG Grid&lt;/a&gt; on GitHub. I hope that you'll try Cube.js and AG Grid in your next production gig or your next pet project.&lt;/p&gt;

&lt;p&gt;Good luck and have fun!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>DEV Survey ✅ — Front-end Data Visualization Tools</title>
      <dc:creator>Igor Lukanin</dc:creator>
      <pubDate>Mon, 22 Mar 2021 21:48:08 +0000</pubDate>
      <link>https://forem.com/cubejs/dev-survey-front-end-data-visualization-tools-1f37</link>
      <guid>https://forem.com/cubejs/dev-survey-front-end-data-visualization-tools-1f37</guid>
      <description>&lt;p&gt;&lt;em&gt;TL;DR: PLease complete &lt;a href="https://forms.gle/mY89MpSf7HRnHevs5" rel="noopener noreferrer"&gt;this survey&lt;/a&gt;. Thank you! 🙏&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Hey friends 👋 I guess that if you've ever been building a web application in your life, chances are you were using a charting library, a data grid, or any other data visualization tool to present your data in a meaningful and comprehensible way.&lt;/p&gt;

&lt;p&gt;The overwhelming success with thousands of views and hundreds of bookmarks under this post in &lt;a href="https://dev.to/cubejs"&gt;Cube.js blog&lt;/a&gt; proves my point 🙂&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag__link"&gt;
  &lt;a href="/cubejs" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__org__pic"&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F553%2F6ca9ad7d-1de7-42a2-a671-2f8f2cf79749.png" alt="Cube"&gt;
      &lt;div class="ltag__link__user__pic"&gt;
        &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F370563%2F72c5ca46-0b5e-4129-b832-4de4ef5cbfe5.jpeg" alt=""&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/cubejs/understanding-front-end-data-visualization-tools-ecosystem-in-2021-2nog" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Understanding front-end data visualization tools ecosystem in 2021 📊📈&lt;/h2&gt;
      &lt;h3&gt;Nikita Kakuev for Cube ・ Feb 5 '21&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#javascript&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#frontend&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#dataviz&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://cube.dev?ref=dataviz-survey" rel="noopener noreferrer"&gt;Cube.js&lt;/a&gt; an open-source project that provides developers with an easy way to create an analytical API over their data stored in any database. More importantly, that API can be paired with any front-end frameworks and data visualization libraries.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/cube-js" rel="noopener noreferrer"&gt;
        cube-js
      &lt;/a&gt; / &lt;a href="https://github.com/cube-js/cube" rel="noopener noreferrer"&gt;
        cube
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      📊  Cube — The Semantic Layer for Building Data Applications
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://slack.cube.dev?ref=dataviz-survey" rel="noopener noreferrer"&gt;In our Slack&lt;/a&gt; and in private conversations, the Cube.js team is often asked for a recommendation of a charting library, or a data grid, or any other data visualization tool that works great with Cube.js. &lt;/p&gt;

&lt;p&gt;All of them do, but we decided to create a website with a list of data visualization tools that can be used by developers to choose the best tool for their projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;As the first step, we want to understand how you, as developers, approach this choice and which criteria are the most relevant. That’s why we’re kindly asking you to take part in this short survey:&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://forms.gle/mY89MpSf7HRnHevs5" rel="noopener noreferrer"&gt;DEV Survey ✅ — Front-end Data Visualization Tools&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Thank you in advance! 😇🙏&lt;/p&gt;

&lt;p&gt;Surely, we’ll make the results public in the &lt;a href="https://dev.to/cubejs"&gt;Cube.js blog&lt;/a&gt;, so feel free to subscribe to it and stay tuned!&lt;/p&gt;

&lt;p&gt;Also, after you fill the survey, feel free to drop a few lines in the comments. What are the best data visualization on public websites that you know? What are the best tools to make such data visualizations? Let's discuss!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Impact of COVID-19 on people's habits worldwide</title>
      <dc:creator>Igor Lukanin</dc:creator>
      <pubDate>Thu, 04 Mar 2021 18:55:18 +0000</pubDate>
      <link>https://forem.com/igorlukanin/impact-of-covid-19-on-people-s-habits-worldwide-2lki</link>
      <guid>https://forem.com/igorlukanin/impact-of-covid-19-on-people-s-habits-worldwide-2lki</guid>
      <description>&lt;p&gt;&lt;em&gt;TL;DR. I've built a &lt;a href="https://bigquery-public-datasets-demo.cube.dev" rel="noopener noreferrer"&gt;web application&lt;/a&gt; that reveals the impact of the COVID-19 pandemic on people's lives and habits. Also, I tell how I built it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here's how this application looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bigquery-public-datasets-demo.cube.dev" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2kaxomm1ace7vb2hjqhb.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Surely, you can select any country — &lt;a href="https://bigquery-public-datasets-demo.cube.dev" rel="noopener noreferrer"&gt;definitely try your own&lt;/a&gt;.&lt;/strong&gt; You will explore how COVID-19 affected social mobility or how "stay at home" requirements were followed (or not).&lt;/p&gt;

&lt;h1&gt;
  
  
  Insights
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Let's take Israel.&lt;/strong&gt; You can clearly see three waves and the positive effect of "stay at home" requirements — after they are introduced, every wave spreads with lesser speed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwts71vfluxnwvuhza7u9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwts71vfluxnwvuhza7u9.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Let's take Germany.&lt;/strong&gt; You can see how Germans interact with the rules: after the first "stay at home" requirements are lifted, park activity grows, and after the second "stay at home" requirements are introduced, parks instantly become deserted.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe63mtd6ocea1f3u7rq0q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe63mtd6ocea1f3u7rq0q.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Let's take Singapore.&lt;/strong&gt; Obviously enough, you can see Singapore doing a great job containing the virus. The third wave is nearly unexistent.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcn7fc0ww08xraetp9ikn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcn7fc0ww08xraetp9ikn.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Tutorial
&lt;/h1&gt;

&lt;p&gt;You can build this application from scratch using BigQuery public datasets, JavaScript, React, and &lt;a href="https://cube.dev/?utm_source=dev-to&amp;amp;utm_medium=post&amp;amp;utm_campaign=bigquery-public-datasets" rel="noopener noreferrer"&gt;Cube.js&lt;/a&gt;. Read on:&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/cubejs" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__org__pic"&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F553%2F6ca9ad7d-1de7-42a2-a671-2f8f2cf79749.png" alt="Cube"&gt;
      &lt;div class="ltag__link__user__pic"&gt;
        &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F374270%2F8d416f5c-7685-4c7b-9b66-5d40e5af117a.jpg" alt=""&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/cubejs/using-bigquery-public-datasets-to-research-the-impact-of-covid-19-30fp" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Using BigQuery Public Datasets to research the impact of COVID-19 🦠&lt;/h2&gt;
      &lt;h3&gt;Igor Lukanin for Cube ・ Mar 4 '21&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#javascript&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#database&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#tutorial&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;Also, don't hesitate to share your insights in the comments 🙏&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>bigdata</category>
    </item>
  </channel>
</rss>
