Embedded Analytics
Not available on the Free plan (see Pricing)
Cluvio supports embedding of dashboards and reports within web applications at different levels of integration:
- Embedding a non-interactive dashboard into your web application (available on the
Pro
plan). - Embedding an interactive dashboard into your web application (available on the
Business
plan). - Embedding an interactive dashboard with constrained filters (available on the
Business
plan). - Embedding a dashboard with filters customized to a user of your web application (available on the
Business
plan).
In all of these cases, whether the embedded dashboard is interactive or not, it is always displaying the latest data as well as reflecting any changes made to the dashboard, reloading automatically. In other words, an embedded dashboard behaves just like a dashboard for a logged-in Cluvio viewer user account, refreshing the data in the background according to the configured dashboard and report refresh intervals (which is 24 hours by default).
An example of an embedded, interactive dashboard can be seen at https://tickit.cluvio.com.
Getting Started
The key to embedding Cluvio dashboards or reports are sharing links. Rather than sharing these links directly with other people, they can also be embedded in other web pages via an iframe.
By default, a sharing link is non-interactive, which means that the dashboard
or report shown on the sharing link do not allow changing the selected
filters. To embed a non-interactive sharing link in your
website, simply place an <iframe>
section at the desired location on your
site (see also Creating Sharing Links). The link URL
offers the same configuration options as regular sharing links, allowing to fine-tune
the appearance/behavior of the embed.
To create an interactive sharing link that allows viewers of the link to
change some or all of the Cluvio filter selection, you must create a sharing link that
has Requires Secret
enabled and generate
sharing secrets when rendering your website's HTML which includes the embedded
<iframe>
element.
Creating Sharing Secrets
A sharing secret is a (usually short-lived) JWT that is
added to a Cluvio sharing link as a sharingSecret
URL parameter when the
embedded sharing link is rendered in your website's HTML.
To create a sharing link with a sharing secret, your code needs to have the following information:
- The sharing link URL or the dashboard ID and sharing token, from which the sharing link can be constructed.
- Your Cluvio organization's account secret.
The payload of a Cluvio JWT sharing secret must contain the following required fields:
exp
: A JSON number representing the UNIX timestamp of the intended expiration.sharing_token
: A JSON string with the sharing token of the sharing link for which the sharing secret is created.
Additionally, the payload may include the following optional fields:
-
fixed_parameters
: A JSON object where the keys are filter variable names and the values are the allowed filter values for that filter. If a filter is included in the fixed parameters with a single value, that value is fixed for the sharing link. If a filter is included in the fixed parameters with multiple values, the sharing link allows only these values to be used with the filter. -
data_timestamp
: A UNIX timestamp that is a lower bound for the age of data of all reports on the dashboard. It can be used to dynamically shorten the regular refresh intervals based on conditions in your application. Note that Cluvio will never return data that is older than what the regular refresh interval of the report or dashboard dictates. This parameter can only be used to ensure newer data is returned than what may otherwise be returned according to the regular refresh intervals.Plan RestrictionThe
data_timestamp
parameter for sharing secrets is only available starting from the Business plan (see Pricing). -
keep_open_after_expiration
: By default, when a sharing secret expires, the sharing link will show an error. When this option is set totrue
, the sharing link will keep showing the last data but will no longer update. This can be a good option for web applications that are not single-page applications, i.e. where the user needs to refresh the page to see newer data. See Updating Sharing Secrets to keep the sharing link "alive" beyond the initial expiration of the sharing secret. -
hidden_reports
: An array of report IDs that should be hidden from the dashboard.
Below you can find sample code in various languages for generating sharing links with sharing secrets for the embedding of interactive dashboards. See also Common Embeddings for documentation on how to set up common Cluvio embedding scenarios.
- Ruby
- Node.js
- Go
- Python
- PHP
- Java
- Rust
# Uses jwt gem (https://github.com/jwt/ruby-jwt) to generate the JWT token.
# Install via 'gem install jwt' or add to your Gemfile.
require 'jwt'
# The sharing link that we are working with
base_url = 'https://dashboards.cluvio.com/dashboards/wm73-peg8-y0qv/shared?sharingToken=fc77dd26-1b9d-417d-9489-518bc4ab7431'
dashboard_id='wm73-peg8-y0qv' # the dashboard id from the URL
sharing_token='fc77dd26-1b9d-417d-9489-518bc4ab7431' # sharingToken query parameter from the URL
# validity (expiration) of the generated token as unix timestamp, should be short
expires_at = (Time.now + 60 * 60).to_i
# Value from Organization settings (https://app.cluvio.com/admin/organization).
# You want to keep this secure and pass e.g. via environment variable.
secret='d880b5e74cea82a4e8998ae0e8775176fb440a4569eb097505f7e08e555b17b5aad78b5fa1f47acd1fbfbd876631dfdd'
hash = {
sharing_token: sharing_token,
exp: expires_at
}
sharing_secret = JWT.encode(hash, secret)
final_url = base_url + "&sharingSecret=#{sharing_secret}"
puts final_url
// Uses jsonwebtoken library (https://github.com/auth0/node-jsonwebtoken) to generate the JWT token.
// Install via 'npm install jsonwebtoken' or add to your package.json
var jwt = require('jsonwebtoken');
// The sharing link that we are working with
const baseUrl = 'https://dashboards.cluvio.com/dashboards/wm73-peg8-y0qv/shared?sharingToken=fc77dd26-1b9d-417d-9489-518bc4ab7431';
const sharingToken='fc77dd26-1b9d-417d-9489-518bc4ab7431'; // sharingToken query parameter from the URL
// validity (expiration) of the generated token as unix timestamp, should be short
const expiresAt = Math.floor(Date.now() / 1000) + (60 * 60); // valid for 1 hour
// Value from Organization settings (https://app.cluvio.com/admin/organization).
// You want to keep this secure and pass e.g. via environment variable.
const secret='d880b5e74cea82a4e8998ae0e8775176fb440a4569eb097505f7e08e555b17b5aad78b5fa1f47acd1fbfbd876631dfdd';
const hash = {
sharing_token: sharingToken,
exp: expiresAt
};
const sharingSecret = jwt.sign(hash, secret);
const finalUrl = baseUrl + `&sharingSecret=${sharingSecret}`;
console.log(finalUrl);
// Uses golang-jwt (https://github.com/golang-jwt/jwt) to generate the JWT token.
// Run 'go get github.com/golang-jwt/jwt/v5' to install it first.
package main
import (
"github.com/golang-jwt/jwt/v5"
"time"
"fmt"
)
func main() {
// The sharing link that we are working with
baseUrl := "https://dashboards.cluvio.com/dashboards/wm73-peg8-y0qv/shared?sharingToken=fc77dd26-1b9d-417d-9489-518bc4ab7431"
sharingToken := "fc77dd26-1b9d-417d-9489-518bc4ab7431" // sharingToken query parameter from the URL
// validity (expiration) of the generated token as unix timestamp, should be short
expiresAt := time.Now().Add(24 * 60 * time.Minute)
// Value from Organization settings (https://app.cluvio.com/admin/organization).
// You want to keep this secure and pass e.g. via environment variable.
secret := "d880b5e74cea82a4e8998ae0e8775176fb440a4569eb097505f7e08e555b17b5aad78b5fa1f47acd1fbfbd876631dfdd"
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"sharing_token": sharingToken,
"exp": expiresAt.Unix(),
})
// Sign and get the sharingSecret value
sharingSecret, _ := token.SignedString([]byte(secret))
finalUrl := baseUrl + "&sharingSecret=" + sharingSecret
fmt.Println(finalUrl)
# Uses PyJWT (https://github.com/jpadilla/pyjwt) to generate the JWT token.
# Install via ` pip install PyJWT`.
import time
import jwt
# The sharing link that we are working with
base_url = 'https://dashboards.cluvio.com/dashboards/wm73-peg8-y0qv/shared?sharingToken=fc77dd26-1b9d-417d-9489-518bc4ab7431'
sharing_token='fc77dd26-1b9d-417d-9489-518bc4ab7431' # sharingToken query parameter from the URL
# validity (expiration) of the generated token as unix timestamp, should be short
expires_at = int(time.time()) + 60 * 60
# Value from Organization settings (https://app.cluvio.com/admin/organization).
# You want to keep this secure and pass e.g. via environment variable.
secret='d880b5e74cea82a4e8998ae0e8775176fb440a4569eb097505f7e08e555b17b5aad78b5fa1f47acd1fbfbd876631dfdd'
hash = {
'sharing_token': sharing_token,
'exp': expires_at
}
sharing_secret = jwt.encode(hash, secret, 'HS256')
final_url = base_url + '&sharingSecret=' + sharing_secret
print(final_url)
// Uses firebase/php-jwt (https://github.com/firebase/php-jwt) to generate the JWT token.
// Install via `composer require firebase/php-jwt`.
use Firebase\JWT\JWT;
require_once('vendor/autoload.php');
// The sharing link that we are working with
$base_url = 'https://dashboards.cluvio.com/dashboards/wm73-peg8-y0qv/shared?sharingToken=fc77dd26-1b9d-417d-9489-518bc4ab7431';
$sharing_token = 'fc77dd26-1b9d-417d-9489-518bc4ab7431'; // sharingToken query parameter from the URL
// validity (expiration) of the generated token as unix timestamp, should be short
$expires_at = (new DateTimeImmutable())->modify('+60 minutes')->getTimestamp(); // valid for 1 hour
// Value from Organization settings (https://app.cluvio.com/admin/organization).
// You want to keep this secure and pass e.g. via environment variable.
$secret = 'd880b5e74cea82a4e8998ae0e8775176fb440a4569eb097505f7e08e555b17b5aad78b5fa1f47acd1fbfbd876631dfdd';
$data = [
'sharing_token' => $sharing_token,
'exp' => $expires_at
];
$sharing_secret = JWT::encode(
$data,
$secret,
'HS512'
);
$final_url = $base_url.'&sharingSecret='.$sharing_secret;
echo $final_url;
// Uses java-jwt library (https://github.com/auth0/java-jwt) to generate the JWT token.
// Install e.g. via maven
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
public class JwtExample {
public static void main(String[] args) {
// The sharing link that we are working with
String baseUrl = "https://dashboards.cluvio.com/dashboards/wm73-peg8-y0qv/shared?sharingToken=fc77dd26-1b9d-417d-9489-518bc4ab7431";
String sharingToken = "fc77dd26-1b9d-417d-9489-518bc4ab7431"; // sharingToken query parameter from the URL
// validity (expiration) of the generated token as unix timestamp, should be short
Instant expiresAt = Instant.now().plus(1, ChronoUnit.HOURS);
// Value from Organization settings (https://app.cluvio.com/admin/organization).
// You want to keep this secure and pass e.g. via environment variable.
String secret = "d880b5e74cea82a4e8998ae0e8775176fb440a4569eb097505f7e08e555b17b5aad78b5fa1f47acd1fbfbd876631dfdd";
// Sign and get the sharingSecret value
String sharingSecret = JWT.create()
.withExpiresAt(expiresAt)
.withClaim("sharing_token", sharingToken)
.sign(Algorithm.HMAC256(secret));
String finalUrl = baseUrl + "&sharingSecret=" + sharingSecret;
System.out.println(finalUrl);
}
}
// Uses jsonwebtoken (https://github.com/Keats/jsonwebtoken) library to generate the JWT token.
// Install via cargo as usual.
use serde::{Deserialize, Serialize};
use std::time::{SystemTime, UNIX_EPOCH};
use jsonwebtoken::{encode, EncodingKey, Header};
#[derive(Debug, Serialize, Deserialize)]
struct Claims {
exp: usize,
sharing_token: String,
}
fn main() {
// The sharing link that we are working with
let base_url = "https://dashboards.cluvio.com/dashboards/wm73-peg8-y0qv/shared?sharingToken=fc77dd26-1b9d-417d-9489-518bc4ab7431";
let sharing_token = "fc77dd26-1b9d-417d-9489-518bc4ab7431"; // sharingToken query parameter from the URL
// validity (expiration) of the generated token as unix timestamp, should be short
let exp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() + 60 * 60; // 1 hour
// Value from Organization settings (https://app.cluvio.com/admin/organization).
// You want to keep this secure and pass e.g. via environment variable.
let secret = "d880b5e74cea82a4e8998ae0e8775176fb440a4569eb097505f7e08e555b17b5aad78b5fa1f47acd1fbfbd876631dfdd";
let my_claims = Claims { sharing_token: sharing_token.to_owned(), exp: exp as usize};
// Sign and get the sharingSecret value
let sharing_secret = encode(&Header::default(), &my_claims, &EncodingKey::from_secret(secret.as_ref())).unwrap();
let final_url = format!("{base_url}&sharingSecret={sharing_secret}");
println!("{}", final_url);
}
The final URL will look like this and can be used either as a normal link, or embedded as an iframe
:
<iframe src="https://dashboards.cluvio.com/dashboards/wm73-peg8-y0qv/shared?sharingToken=fc77dd26-1b9d-417d-9489-518bc4ab7431&sharingSecret=eyJhbGciOiJIUzI1NiJ9.eyJzaGFyaW5nX3Rva2VuIjoiZmM3N2RkMjYtMWI5ZC00MTdkLTk0ODktNTE4YmM0YWI3NDMxIiwiZXhwIjo0ODMzNDMwNjA0fQ.vbWY6QJNlAxjz0A--aANdCUZuWHWKRLTT5tES__GaqY"
style="width: 100%; height: 600px;"
frameborder="0"
allowfullscreen>
</iframe>
Here is a Cluvio sample dashboard embedded into this documentation:
Updating Sharing Secrets
It is good practice to create short-lived sharing secrets in order to minimize the risk of unauthorized access to sharing links. A validity between 10 minutes and 1 hour is typically sufficient. When the sharing secret expires, it needs to be renewed in order for the sharing link to continue showing the latest data.
To do so, your web application frontend must handle the secretExpired
event that is sent from the embedded Cluvio window to the outer window.
Your event handler then needs to make a request to your web application backend server
to generate a new sharing secret using your Cluvio organization's account
secret.
Your Cluvio organization's account secret should not be exposed to your web application frontend!
When your frontend receives the new sharing secret JWT, it can send the
updateSharingSecret
command to
the embedded Cluvio window to update the sharing secret. No page reload is
required.
The following diagram illustrates the process:
To avoid intermittent errors in the embedded dashboard while the sharing secret
is being updated, include keep_open_after_expiration: true
in the sharing
secret payload.
Common Embeddings
The following is a summary of common embedding approaches, in increasing level of complexity and functionality.
1. Non-interactive Dashboard
The configuration required for this type of embedding is not available on the Free
plan (see
Pricing).
Embedding a non-interactive dashboard or individual report is the simplest kind of embedding and is accomplished by creating a sharing link that is used in an embedded iframe.
<iframe
frameborder="0"
allowfullscreen
height="800"
width="100%"
src="https://dashboards.cluvio.com/dashboards/jvre-px0w-p1z2/shared?sharingToken=1c1d9827-bbd6-4d08-af81-2f68baacd81d"
/>
This iframe pulls the dashboard content identified by the sharingToken
from
the sharing link, using the fixed dashboard filters
saved with the link. However, you can use some additional URL
parameters to further refine the appearance of
the embedded dashboard. For example, to share a single report, you can use the
reportId
parameter:
<iframe
frameborder="0"
allowfullscreen
height="800"
width="100%"
src="https://dashboards.cluvio.com/dashboards/7wo6-vmdx-6x40/shared?sharingToken=524ea7d6-8957-4fe0-aad0-b7f3629c7654&reportId=govm-x00l-1m6q&reportOnly&noBorder=true&backgroundColor=FFF"
/>
By creating multiple links, each fixed to different dashboard filter parameters, the same dashboard can be embedded non-interactively in different contexts - for instance, creating a sharing link for the same dashboard for each customer, parameterised differently for each customer. However, while this approach is easy to set up, it can become hard to manage with more than a handful of links. See options #2 and #7 below for approaches that use a single sharing link but still allow the dashboard to display data customized for each user accessing it.
2. Interactive URL Parameters
The configuration required for this type of embedding is only available on the
Business
plan (see
Pricing).
It is possible to configure a sharing link such that it accepts dashboard
parameters as URL parameters, which allows your application to forward user
inputs to Cluvio. To do so, the link must have Requires Secret
and
Allow Parameters
enabled. The embedded dashboard need not be interactive for
the user via the UI, thus enabling non-interactive dashboards with different
dashboard parameters through a single link. Please note, however, that the user
who has access to the link can change the URL parameters - non-interactivity is
not enforced by default. To put constraints on the filter parameter selection,
you must encode fixed_parameters
into your sharingSecret
(see configuration
#6).
For details on how to encode URL parameters for dashboard filters, see Parameters in URLs.
3. Interactive Dashboard (Cluvio Toolbar)
The configuration required for this type of embedding is only available on the
Business
plan (see
Pricing).
By combining Requires Secret
and Allow Parameters
with Show Toolbar
,
you can configure a sharing link to be interactive, such that viewers can use
Cluvio's filter controls to affect what is shown on the dashboard.
Requires Secret
.If you do not wish to put any constraints on the interactivity of a sharing link and thus do not want to create a sharing secret, you may contact support@cluvio.com with your use-case to get this feature enabled.
4. Interactive Dashboard (Custom Controls)
The configuration required for this type of embedding is only available on the
Business
plan (see
Pricing).
Embedding interactive dashboards with custom input controls is very similar to
configuration #3, but instead of enabling Show Toolbar
, the embedded iframe
is updated via window.postMessage
from the parent frame as a result of actions being performed on your own UI
controls. Using postMessage
, you can:
- smoothly switch between full-screen reports (within the iframe) on the dashboard.
- change any dashboard/report parameters.
An example of an embedded dashboard with custom controls is the Tickit
application at https://tickit.cluvio.com, where changing a filter parameter is
achieved by calling a changeParam
function defined as follows:
<script>
function changeParam(param, value) {
var message = { action: 'applyFilters', params: {} };
message.params[param] = value;
document.getElementById("embed-1").contentWindow.postMessage(JSON.stringify(message), '*');
}
</script>
For example, the list of aggregation options invokes changeParam
from an
onclick
event handler:
<ul>
<li><a href="#" onclick="changeParam('aggregation', 'week')">Week</a></li>
<li><a href="#" onclick="changeParam('aggregation', 'month')">Month</a></li>
<li><a href="#" onclick="changeParam('aggregation', 'quarter')">Quarter</a></li>
<li><a href="#" onclick="changeParam('aggregation', 'year')">Year</a></li>
</ul>
See Actions & Events for the message format and available actions that can be sent to embedded dashboards.
5. Short-Lived Sharing Secrets
The configuration required for this type of embedding is only available on the
Business
plan (see
Pricing).
If you turn on Requires secret
for a sharing link, access to the link will now
require an additional URL parameter called sharingSecret
which is a
JWT. You need to generate this JWT from within your server
application which has access to the organization's account
secret.
By making your JWTs short-lived, you can significantly reduce the potential impact of unintentionally exposed links, thus enhancing the security of your embedded Cluvio dashboards. A Cluvio JWT sharing secret is cryptographically authenticated via your organization's account secret.
The payload of the Cluvio JWT sharing secret is a JSON object with the following structure:
{
"sharing_token": "<sharingToken of the sharing link>",
"exp": "<unix timestamp of expiration>"
}
Both sharing_token
and exp
iration are required.
A JWT library is available for most programming languages, but always make sure to generate the JWT in a location (typically your web application servers) where your Cluvio account secret is not easily exposed.
The following sample code in Ruby/Rails generates a sharingSecret
:
require 'jwt'
payload = {
sharing_token: 'fc77dd26-1b9d-417d-9489-518bc4ab7431', # value from sharing link
exp: (Time.now + 10.years).to_i # should be short in practice
}
secret='d880b5e74cea82a4e8998ae0e8775176fb440a4569eb097505f7e08e555b17b5aad78b5fa1f47acd1fbfbd876631dfdd' # account secret
JWT.encode(payload, secret)
At time of writing, this produced the following JWT:
eyJhbGciOiJIUzI1NiJ9.eyJzaGFyaW5nX3Rva2VuIjoiZmM3N2RkMjYtMWI5ZC00MTdkLTk0ODktNTE4YmM0YWI3NDMxIiwiZXhwIjoxODMzMDE5MDM5fQ.UrwXZo7w2Dxf9a_2wrr3_wGQ4umRZYTmATXfaQ7npQI
And when used in a sharing link, the full URL is:
https://dashboards.cluvio.com/dashboards/wm73-peg8-y0qv/shared?sharingToken=fc77dd26-1b9d-417d-9489-518bc4ab7431&sharingSecret=eyJhbGciOiJIUzI1NiJ9.eyJzaGFyaW5nX3Rva2VuIjoiZmM3N2RkMjYtMWI5ZC00MTdkLTk0ODktNTE4YmM0YWI3NDMxIiwiZXhwIjoxODMzMDE5MDM5fQ.UrwXZo7w2Dxf9a_2wrr3_wGQ4umRZYTmATXfaQ7npQI
6. Restricted Interactivity
The configuration required for this type of embedding is only available on the
Business
plan (see
Pricing).
The use of authenticated sharing secrets (ideally short-lived) also allows your application to put restrictions on the allowed parameters for your Cluvio filters. There are several use-cases where it is important to embed a dashboard such that the user is prevented from tampering with the filters or see data that he should not have access to:
- Secure non-interactive dashboards showing different data to different users/clients/etc.
- Secure interactive dashboards where only some of the filters are interactive/changeable by the user, depending on the users's level of authorization in your application.
Building on the short-lived sharing links from scenario #5, we can achieve both
of these goals by including fixed_parameters
in the JWT payload:
{
"sharing_token": "<sharingToken of the sharing link>",
"exp": "<unix timestamp of expiration>",
"fixed_parameters": { "param1": value1, "param2": [ "value2", "value 3" ] }
}
The value of fixed_parameters
must be a JSON object where each key refers to a
Cluvio filter variable name (see Filters) and the value
either a single string or number, or an array of strings or numbers, according
to the filter's configuration.
Cluvio accepts numbers to be given as strings in the fixed parameter values and normalizes them appropriately for numeric filters.
The following is a Cluvio JWT payload for creating a restricted sharing link to one of Cluvio's sample dashboards:
payload = {
sharing_token: 'fc77dd26-1b9d-417d-9489-518bc4ab7431', # value from sharing link
exp: (Time.now + 10.years).to_i, # should be short,
fixed_parameters: {
aggregation: 'month',
origin_airports: ['SFO']
}
}
After creating a sharing secret, we get a fully functional restricted sharing link:
Sharing Link: Flights from San Francisco International Airport
7. Context-Restricted Dashboards
The configuration required for this type of embedding is only available on the
Business
plan (see
Pricing).
Building on scenario #6, it is easy to embed a dashboard that is parameterised differently for different access contexts, e.g. different users of your application.
Ultimately, the report SQL queries need to filter the data accordingly. To do
so, choose a unique identifier in your application, like a user_id
,
project_id
, user_email
, or similar, which is used to determine what a user
of your application should see in the embedded Cluvio dashboard. Add this
identifier to the WHERE
clause of the report queries that you wish to use for
the context-sensitive embedded dashboard. For example, we could use the following query for
reporting on the sales of a particular sales representative:
SELECT SUM(price) from sales WHERE {salesrep_id=user_id}
When generating the Cluvio JWT sharing secret for the logged-in user, the
user_id
filter can then be fixed to the identifier of that user:
payload = {
sharing_token: '<token>',
exp: (Time.now + 1.hour).to_i,
fixed_parameters: {
user_id: [<logged-in user id>]
}
}
With a sharing secret generated from this payload, all of the queries with
{salesrep_id=user_id}
in the WHERE
clauses of the embedded dashboard will
show results restricted to that user.