Embedding Cubes — rb-cube-renderer

Drop a DataPallas cube into any web application with the rb-cube-renderer web component. Users pick fields; the cube renders the data.


Table of Contents

What rb-cube-renderer Does

<rb-cube-renderer> is a custom web element that:

  1. Shows the cube's fields as a tree: measures first, then the main table with its joined tables collapsed, then filters and drill paths
  2. Lets the viewer tick fields, filter them and pick a time grain
  3. Asks the DataPallas backend for the rows and draws them in place — a table, a chart or a single value, as the host asks or as the selection implies

Because the component talks to the DataPallas backend (not the database directly), the credentials and the schema validation stay server-side. The browser only ever sees the cube's shape, never the connection's password. The component never writes DSL either: it sends the ticks and the name of the cube they belong to, and the SQL is generated on the server from the cube's own file.

📷 Screenshot placeholder: 200_25_cube-embed-snippet.png — The Usage tab inside the Cube modal showing the copy-paste <rb-cube-renderer> HTML snippet.

The Three Modes

The component works one of three ways, and which one it is depends only on which attributes are set. They differ in where the fields come from, which endpoint answers the rows, and what credential the page needs.

ModeSetFields come fromRows come fromCredential
Field pickerneither cube-id nor report-idthe parsed cube the host hands over as the cubeConfig propertynowhere — the host runs its own querynone
Author modecube-id/api/cubes/{id} and parse-dsl, by the component itself/api/cubes/{id}/queryan author's login or API key
Live cubereport-id + component-id/api/reports/{reportId}/cube/{componentId}/metathe matching /querythe viewer's session, or an embed-token

Field picker is the cube editor's own preview: the host has the parsed DSL already, passes it in, listens for selectionChanged and calls generate-sql itself.

Author mode is for a page whose viewer is an author of DataPallas. The component loads the cube by name and answers for itself. It needs an author's credential, so it is not the mode for a page your customers see.

Live cube is the viewer's mode, and the only one to embed in a customer-facing page. The dashboard's widget file says which cube may be asked about, under which name in its file, and on which connection; none of that is sent from the page, so what the embed can ask is exactly what the dashboard declares. An embed-token unlocks that one report and nothing else.

Minimal Embed

Drop this in any HTML page:

<!-- 1. Load the DataPallas web-components bundle -->
<script type="module"
        src="http://localhost:9090/api/web-components/rb-components.esm.js"></script>
 
<!-- 2. Embed a dashboard's live cube -->
<rb-cube-renderer
  report-id="sales-board"
  component-id="cube1"
  api-base-url="http://localhost:9090/api"
  embed-token="<the token your server minted>">
</rb-cube-renderer>

The component loads the widget's cube, ticks whatever the widget starts with, and asks for the rows as the viewer changes the selection.

📷 Screenshot placeholder: 200_30_cube-embed-running.png — A simple host page with <rb-cube-renderer> running, showing dimension/measure checkboxes on the left and a result table on the right.

Attributes

AttributeTypeRequiredDescription
api-base-urlstringYesDataPallas backend URL (e.g. http://localhost:9090/api). The same value the rest of the page's rb-* components use.
report-idstringLive cubeThe published dashboard the widget belongs to. Set it together with component-id.
component-idstringLive cubeThe widget inside that dashboard. The pair is what makes this a live cube.
cube-idstringAuthor modeThe saved cube to load, by name. It needs an author's login or API key — a page your customers see wants report-id + component-id instead.
connection-idstringNoThe connection to read through, in author mode. A live cube's connection is the dashboard's own and cannot be changed from the page.
embed-tokenstringNoA short-lived token minted by your server, sent as X-Embed-Token. It unlocks the one report it was minted for.
api-keystringNoAn author's API key, sent as X-API-Key. Author mode only; a live cube ignores it.
cube-namestringNoWhich cube of a file holding several starts selected.
default-fieldsstringNoComma-separated field names to tick on first render, e.g. "Revenue,CategoryName". They replace the ticks a saved selection makes, never its filters. A name the cube does not offer is ignored and said so in the console.
displaystringNoWhat to draw the answer as: table, chart or value. A live cube's own /meta wins over it, and leaving it out lets the selection decide.
read-onlybooleanNoThe answer without the asking: no field tree, no filter icons, no grain selects, and the chips without their ×. What is being looked at is still shown; only changing it is taken away.
show-hiddenbooleanNoList a public false cube, tagged "hidden", instead of leaving it out. The cube editor sets it.
cube-configstringNoThe parsed cube as JSON, for the field-picker mode, when a string attribute is easier than a property. Usually set as the cubeConfig property instead.

Properties

Three inputs are objects or functions, so they are set on the element rather than written as attributes:

PropertyTypeDescription
cubeConfigobjectThe parsed cube (what POST /api/cubes/parse-dsl returns). Setting it is what puts the component in the field-picker mode.
initialFiltersarrayThe filters the tree starts with, in the query's own shape: [{ member, operator, values }]. A published dashboard's saved filters arrive this way, so the embed opens filtered and the chips say by what.
fetchFilterOptionsfunction(dimension, search) => Promise<{ values: [[value, label], …], truncated }>. Where a filter's list of values comes from in the field-picker mode. Without it a dimension has no filter icon at all — the other two modes have their own endpoint and need nothing here.
const cube = document.querySelector('rb-cube-renderer');
cube.cubeConfig = parsed;                 // from POST /api/cubes/parse-dsl
cube.initialFilters = [{ member: 'Country', operator: 'equals', values: ['Germany'] }];

Events

The component dispatches standard DOM events you can hook with addEventListener:

EventDetail payloadFires when
selectionChanged{ cubeName, dimensions, measures, segments, filters, granularities, selectedDimensions, selectedMeasures, selectedSegments, selectedFilters, selectedOrder, selectedLimit }The viewer ticks, unticks, filters, reorders or changes a grain.
dataLoaded{ rows, columns, sql? }A query completes.
error{ message, code }A query is refused or fails. code is the HTTP status, so a host can tell a refusal from a failure.

selectionChanged says the same selection under two spellings, so no host has to be rewritten for the other: dimensions / measures / segments / filters, and the selected* keys every host already reads. granularities maps a time dimension to the grain it is shown at, and cubeName is the cube inside the file the ticks belong to — '' for a file's unnamed cube.

dataLoaded.sql is the generated statement, and it is there only in author mode: a viewer's dashboard never hands the page its SQL.

<rb-cube-renderer id="cube" report-id="sales-board" component-id="cube1"
                  api-base-url="http://localhost:9090/api"></rb-cube-renderer>
 
<script>
  const cube = document.getElementById('cube');
  cube.addEventListener('selectionChanged', (e) => {
    console.log('Picked:', e.detail.dimensions, e.detail.measures);
  });
  cube.addEventListener('dataLoaded', (e) => {
    console.log(e.detail.rows.length + ' rows');
  });
  cube.addEventListener('error', (e) => {
    console.warn(e.detail.code, e.detail.message);
  });
</script>

Composing with Other rb-* Components

A cube provides the data; you can choose the visualization separately. <rb-chart> takes Chart.js's own shape — { labels, datasets } — so the rows are turned into it first:

<rb-cube-renderer
  id="cube"
  report-id="sales-board"
  component-id="cube1"
  api-base-url="http://localhost:9090/api"
  default-fields="OrderDate,Revenue"
  read-only>
</rb-cube-renderer>
 
<rb-chart id="chart" type="line"></rb-chart>
 
<script>
  const cube = document.getElementById('cube');
  const chart = document.getElementById('chart');
 
  cube.addEventListener('dataLoaded', (e) => {
    const { rows, columns } = e.detail;
    // `columns` is the dimensions asked for, then the measures: the first is the axis, the
    // second is the line.
    const [axis, measure] = columns;
    chart.data = {
      labels: rows.map((row) => row[axis]),
      datasets: [{ label: measure, data: rows.map((row) => Number(row[measure])) }]
    };
  });
</script>

This pattern — an interactive cube renderer feeding a fixed chart — is the foundation of self-service dashboards. End users change the cube selection; the chart updates without a page reload.

See the related component pages for full attribute references:

Who the Viewer Is: attrs, tz and locale

A cube can carry an access_filter — a condition DataPallas adds to every query it generates for that cube, written with the built-in ${dp_…} variables (see DSL reference). For a viewer who signs in to DataPallas those variables are filled from their account. An embedded page usually has no such viewer: the host application has its own signed-in customer, and it is the host application that says who they are, when it mints the token.

POST /api/embed/token takes three optional fields for this:

FieldTypeWhat it does
attrsobjectName/value pairs describing the viewer. Each one is readable from SQL and from an access_filter as ${dp_attr_<name>} — {"customer_id": "4711"} becomes ${dp_attr_customer_id}. Names are lower-case letters, digits and _, starting with a letter; each value is a single value, not a list or an object. A bad name is refused when the token is minted, not silently ignored on every render.
tzstringAn IANA time zone, e.g. Europe/Berlin. ${dp_today} and ${dp_now} are computed in it, and ${dp_user_timezone} reads it. Without it the installation's zone is used.
localestringA BCP 47 tag, e.g. de-DE, read as ${dp_user_locale}.
// On your server, for the customer your page is being rendered for:
const res = await fetch('http://localhost:9090/api/embed/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    reportId: 'sales-board',
    ttlSeconds: 3600,
    attrs: { customer_id: '4711', plan: 'gold' },
    tz: 'Europe/Berlin',
    locale: 'de-DE'
  })
});
const { token } = await res.json();

A cube whose access_filter reads ${CUBE}."CustomerID" = ${dp_attr_customer_id} then answers that page with customer 4711's rows and nobody else's — the same statement for every customer, with the value bound per request.

Three things worth knowing:

  • The values travel inside the signed token, never in the request. A viewer who edits the token breaks its signature, and a viewer who adds ?dp_attr_customer_id=1 to the URL is overwritten by the server's value.
  • An attribute the token does not carry is empty, not missing. A filter that names it matches no row, rather than every row.
  • Share links say the same thing, as attributes on POST /api/embed/share-link. It is fixed for the link's life: to change what a recipient may see, create a new link and revoke the old one.

Authentication and CORS

<rb-cube-renderer> calls the DataPallas REST API directly from the browser, so the page needs a credential the backend accepts and an origin the backend allows.

The credential.

  • A live cube is read with the viewer's own DataPallas session, or, where the page has no such viewer, with an embed-token your server mints (POST /api/embed/token). The component sends it as the X-Embed-Token header. It unlocks the one report it was minted for, for as long as its ttlSeconds says, and nothing else.
  • Author mode is read with an author's credential: their signed-in session, or an API key in the api-key attribute, which the component sends as the X-API-Key header. An API key is an author's key, so it belongs on a page only authors see — never in a page you ship to customers.
  • The field-picker mode calls nothing, so it needs no credential at all.

CORS. The origins a browser may call DataPallas from are a fixed list in the server, not a setting:

http://localhost:[*]    http://127.0.0.1:[*]    https://localhost:[*]    file://

Credentials are allowed, every method the UI sends is allowed, and headers are left open. What this means in practice:

  • Local dev — DataPallas on localhost:9090 and your app on localhost:3000 both match the list. Nothing to configure.
  • Production — serve the host page and DataPallas from the same origin, normally by putting DataPallas behind the same reverse proxy as your application, under a path such as /datapallas. A same-origin call is not a CORS call at all, so nothing has to be allowed. A page served from a different origin is refused by the browser before the request reaches DataPallas, and there is no setting today that changes that.

📷 Screenshot placeholder: 200_35_cube-feeding-dashboard.png (optional) — A dashboard page showing one <rb-cube-renderer> driving a <rb-chart> and a <rb-pivot-table> simultaneously.