Skip to main content

Embed a visualization in a web app

You can share visualizations from Imply Polaris with users outside Polaris by creating a link to the data cube or dashboard. You can then share the link directly or embed the visualization in a third-party app.

Embedding links can be public (unrestricted) or restricted (secure).

This guide walks you through the process to create a restricted link to a dashboard using the Embedding links v1 API, then create a simple web app to embed the dashboard.

The process in this guide involves the following steps:

  1. Create a restricted embedding link in Polaris, which generates a secret key.
  2. Create a client-side app to display the link.
  3. Create a server-side app with the ability to sign the link.
  4. Generate a signed link.
  5. Fetch the link when a user attempts to access it.
  6. Apply data cube and access filters to the link.

The following diagram shows the components both inside and outside of Polaris.

Prerequisites

Before you complete the steps in this guide, ensure that you have the following:

  • A Polaris API key with the AdministerEmbedLinks permission.

    In the API requests in this guide, the API key is stored in the variable named POLARIS_API_KEY. For information on how to obtain an API key and assign permissions, see Authenticate with API keys. For information on permissions, visit Permissions reference.

  • The ID of a data cube or dashboard in Polaris that you want to embed in the sample web app. Retrieve the ID from the Polaris UI, or by sending a GET request to the Data cubes v1 API or Dashboards v1 API.

About the sample dashboard

The sample dashboard in this guide displays US Geological Survey earthquake summary data.

The data contains details of seismic activity including the place where the activity occurred, geographical coordinates, the type of activity, and details including depth, intensity, and alert status.

The Guide for analyzing data walks through the process to ingest a day's earthquake summary data into Polaris and use the Dashboards v1 API to create the following dashboard:

Polaris dashboard

Follow the guide if you want to create the same dashboard to display in this guide's sample web app. Otherwise you can display any dashboard of your choice.

The following diagram summarizes the process followed in this guide. The first step is to create an embedding link in Polaris.

A link to an embedded visualization can be public or restricted. A public link is simple to set up, but insecure. Once you create a public link and share or embed it in a third-party app, anyone can access it and view all of its underlying data.

This guide shows how to generate a restricted embedding link. Restricted links can only be accessed when a signature created with a secret key is attached to the URL, for a configurable time to live (ttl).

Submit a POST request to the Embedding links v1 API with "restricted": true and pass the visualization configuration as a payload to the request.

Sample request

The following example creates a restricted embedding link to the dashboard with ID 7dfe. Replace the dashboard ID with your own if you copy the request.

The request body contains the following properties:

  • layout hides the dashboard's navigation panel and header bar.
  • restricted creates a restricted link and generates a secret key in the response.
  • nonFilterableDimensions prevents users from changing the Latitude and Longitude settings on the map.
  • nonShowableDimensions excludes Url from the show bar of the embedded dashboard.
  • ttl is the link's time to live and determines how long the signature is valid for after it’s generatedit controls the maximum length of a session. After the signature is first exchanged for a cookie, the signature becomes unusable for 10 hours.

See the Embedding links v1 API documentation for a description of all API properties.

info

Use the verbose option -v to include the header information in the response. Polaris returns the embedding link in the location header.

Show sample request
curl --location -v --request POST "https://ORGANIZATION_NAME.REGION.CLOUD_PROVIDER.api.imply.io/v1/projects/PROJECT_ID/embedding-links" \
--header "Authorization: Basic $POLARIS_API_KEY" \
--header "Content-Type: application/json" \
--data-raw '{
"name": "Quake dashboard API",
"description": "1 day quake data dashboard including map",
"layout": "visualization-only",
"restricted": true,
"ttl": 3600000,
"nonFilterableDimensions": [
"Latitude",
"Longitude"
],
"nonShowableDimensions": [
"Url"
],
"viewDescription": {
"dashboard": "7dfe"
}
}'

Sample response

The following example shows a response to successful embedding link creation. When you run your request, make a note of the following. You'll need them later in the process.

  • The embedding link contained in the Location header.
  • The SECRET_KEY in the embeddingSecret property.
Show sample response
> Content-Type: application/json
> Content-Length: 404
>
< HTTP/1.1 201 Created
< Access-Control-Allow-Credentials: true
< Content-Type: application/json; charset=utf-8
< Date: Wed, 16 Apr 2024 13:13:46 GMT
< ETag: W/"20e-aWHxc74IO20n+Ylj2AWPTg+C3kA"
< Location: https://ORGANIZATION_NAME.REGION.CLOUD_PROVIDER.api.imply.io/embedding-links/66151f5a68c7a3f002
< Vary: Accept-Encoding
< vary: Origin
< Content-Length: 526
< Connection: keep-alive

{
"createdAt": "2024-04-16T13:13:46.932Z",
"createdBy": "f35c9fe5-8ec4-4d9a-8222-c03a06fb3552",
"updatedAt": "2024-04-16T13:13:46.932Z",
"updatedBy": "f35c9fe5-8ec4-4d9a-8222-c03a06fb3552",
"name": "Quake dashboard",
"description": "1 day quake data dashboard including map",
"layout": "visualization-only",
"apiCreated": true,
"restricted": true,
"ttl": 3600000,
"nonFilterableDimensions": [
"Latitude",
"Longitude"
],
"nonShowableDimensions": [
"Url"
],
"viewDescription": {
"dashboard": "7dfe"
},
"id": "acf22f9751be622ad1",
"embeddingSecret": "SECRET_KEY"
}

This guide uses the Embedding Links v1 API to create a restricted link to a dashboard.

If you create a link to a data cube, you'll need to include measure and dimension definitions. See the Data cubes v1 API for details of the required parameters.

The JSON in API requests can be complex. To make things easier, you can create an embedding link in the Polaris UI, then send a GET request to the API to retrieve the JSON and adapt it if necessary. For example:

  1. Create an embedding link in the Polaris UI.
  2. To embed the link via API without modifying the JSON, click API call in the embedding link's edit view and copy the API request. Complete the rest of the steps if you want to modify the JSON first.
  3. Locate your link id: you can see it when viewing the link in the UI, or send a GET request to /v1/projects/PROJECT_ID/embedding-links to list all embedding links.
  4. Send a GET request to /v1/projects/PROJECT_ID/embedding-links/LINK_ID to retrieve the JSON that defines the link.
  5. Use the API documentation to modify the JSON as needed.
  6. Send a POST request to /v1/projects/PROJECT_ID/embedding-links with your JSON in the request body to create a new embedding link.

Create a client-side app

First, you'll create a simple JavaScript web application to display the dashboard visualization. You'll create a React application using the following:

  • TypeScript: An open-source language that adds syntax to JavaScript.
  • Blueprint: A React-based UI toolkit.

Create a web app

To create the app, run the following commands:

  1. Create a React app with a typescript template:

    npx create-react-app polaris-embedding-example --template typescript
  2. Navigate to the app directory:

    cd polaris-embedding-example
  3. Install Blueprint:

    npm install --save @blueprintjs/core
  4. Start the app:

    npm start
  5. Open your browser and navigate to http://localhost:3000 to see the app running: React web app

  6. Stop the app.

Create a users component

Now you'll create a component to allow us to simulate switching between users. In this example you'll hardcode the users. In a real world situation you'd likely map to another system to handle user authentication, using roles instead of users.

Note that the payload to create the signed link must include the cubeAccessFilter and linkAccessFilter properties. To keep the example simple for now, you'll set these properties to undefined. You'll apply filters later.

In polaris-embedding-example/src, create a new file named UsersMenu.tsx with the following content:

import '@blueprintjs/core/lib/css/blueprint.css';
import { Button, Menu, MenuItem, Popover } from "@blueprintjs/core";

// Export the user details
export interface User {
userName: string;
cubeAccessFilter?: string;
linkAccessFilter?: string;
}

interface UserMenuProps {
userId: number;
onUserIdChange: (user: number) => void;
}

export const USERS = [
// Set the username, cube access filter, and link access filter
{
userName: "Research user 1",
cubeAccessFilter: undefined,
linkAccessFilter: undefined,
},

// Set the username, cube access filter, and link access filter
{
userName: "Research user 2",
cubeAccessFilter: undefined,
linkAccessFilter: undefined,
},

// Set the username, cube access filter, and link access filter
{
userName: "Research user 3",
cubeAccessFilter: undefined,
linkAccessFilter: undefined,
},
];
// Set up the users drop-down list
function UsersMenu({ onUserIdChange, userId }: UserMenuProps) {
const usersMenuItems = USERS.map((user, i) => (
<MenuItem
key={user.userName}
text={user.userName}
onClick={() => onUserIdChange(i)}
/>
));
return (
<Popover content={<Menu>{usersMenuItems}</Menu>}>
<Button
alignText="left"
icon="user"
rightIcon="caret-down"
text={USERS[userId].userName}
/>
</Popover>
);
}

export default UsersMenu;

Add an iFrame for the embedded dashboard

The next step is to add an iFrame for the visualization, import UsersMenu for switching between users, and add a component state for storing the current user and the embedding link URL.

  1. Open the file polaris-embedding-example/src/App.tsx

  2. Replace the contents of the file with the following, and save:

    import { useState } from "react";
    import "./App.css";
    import UsersMenu from "./UsersMenu";

    // Add a component state for the current user and embedding link
    function App() {
    const [link, setLink] = useState("");
    const [userId, setUserId] = useState(0);

    // Set up the iFrame for the visualization
    return (
    <div className="App">
    <iframe
    width="80%"
    height={450}
    src={link}
    title="Embedded visualization"
    />
    <div>
    <UsersMenu onUserIdChange={setUserId} userId={userId} />
    </div>
    </div>
    );
    }

    export default App;
  3. Restart the app. In your browser at http://localhost:3000 you should now see a blank iFrame and a drop-down selection of users: React web app blank

Generating the link involves three steps:

  • Importing the secret key
  • Creating the payload
  • Signing the payload

Now you'll create a router file that performs these steps.

In polaris-embedding-example/src/routes, create a new file named embed.ts with the following content.
Replace SECRET_KEY and MY_LINK with the key and link you saved from your embedding link creation response.

import { Router } from "express";
import { Buffer } from "buffer";
// Import webcrypto for generate signature
// See: https://nodejs.org/api/webcrypto.html
import { webcrypto as crypto } from "crypto";

const embed = Router();
const KEY = 'SECRET_KEY';
const LINK = 'MY_LINK';

// Set up the URL
embed.post("/", async (request, response) => {
const { cubeAccessFilter, linkAccessFilter } = request.body;
const url = new URL(LINK);

// Import secret key using ECDSA and P-256
const privateKey = await crypto.subtle.importKey(
"pkcs8",
Buffer.from(KEY, "base64"),
{ name: "ECDSA", namedCurve: "P-256" },
true,
["sign"],
);

// String value of the time the link was created, in milliseconds
const date = Date.now().toString();
const data = Buffer.from(
JSON.stringify({
cubeAccessFilter: cubeAccessFilter,
linkAccessFilter: linkAccessFilter,
created: date,
}),
"utf8",
);
// Generate a signature format using ECDSA P-256
const signature = await crypto.subtle.sign(
{ name: "ECDSA", hash: { name: "SHA-256" } },
privateKey,
data,
);

// Build the restricted link from the signed parameters,
// in the order cubeAccessFilter, linkAccessFilter, created
const searchParams = new URLSearchParams();
searchParams.append("signature", Buffer.from(signature).toString("base64"));
if (cubeAccessFilter) {
searchParams.append("cubeAccessFilter", cubeAccessFilter);
}
if (linkAccessFilter) {
searchParams.append("linkAccessFilter", linkAccessFilter);
}
searchParams.append("created", date);

url.search = searchParams.toString();

response.status(200).send({ signedLink: url.toString() });
});

export default embed;

Create a server-side app

Now you'll create a server-side app containing an endpoint to sign the link. The server will run on your localhost port 3001.

  1. Run the following commands to install Express and Cross-origin resources sharing (CORS) to allow your localhost sites to communicate with each other:

    npm install --save express cors @types/express @types/cors
    npm install --save-dev ts-node
  2. In polaris-embedding-example/src, create a file named server.ts with the following content:

    import express, { Application, Request, Response } from "express";
    import cors from "cors";
    import embed from "./routes/embed";
    import bodyParser from "body-parser";

    const app = express();

    // Allow communication from the app on port 3000
    const allowedOrigins = ["http://localhost:3000"];

    const options: cors.CorsOptions = {
    origin: allowedOrigins,
    };
    // Set port 3001 for the server
    const PORT: number = 3001;
    app.use(cors(options));

    app.use("/embed", bodyParser.json(), embed);

    // Print the server status and port
    app.listen(PORT, (): void => {
    console.log("Server is up on port:", PORT);
    });

Now you'll create a function to fetch the link for the appropriate users, and call the function from App.tsx.

  1. In polaris-embedding-example/src, create a new file named getSignedLink.ts with the following content:

    const EMBED_API_URL = "http://localhost:3001/embed";

    // Assemble the signed link
    export async function getSignedLink(
    cubeAccessFilter?: string,
    linkAccessFilter?: string,
    ): Promise<string> {
    const response = await fetch(EMBED_API_URL, {
    method: "POST",
    headers: {
    "Content-Type": "application/json",
    // Add any other headers if needed
    },
    body: JSON.stringify({ cubeAccessFilter, linkAccessFilter }),
    });
    if (!response.ok) {
    throw new Error(`Response not ok: ${response.status}`);
    }
    const data = await response.json();
    // Return the signed link
    return data.signedLink;
    }
  2. Edit the file polaris-embedding-example/src/App.tsx and replace the contents with the following:

    import "./App.css";
    import { useEffect, useState } from "react";
    import { getSignedLink } from "./getSignedLink";
    import UsersMenu, { User, USERS } from "./UsersMenu";

    function App() {
    const [link, setLink] = useState("");
    const [userId, setUserId] = useState(0);

    // Generated the signed link for the user
    useEffect(() => {
    async function generateLink() {
    const user: User = USERS[userId];
    try {
    setLink(
    await getSignedLink(user.cubeAccessFilter, user.linkAccessFilter),
    );
    } catch (error) {
    console.error("Error generating signed link:", error);
    }
    }
    generateLink();
    }, [userId]);

    // Set up the iFrame for the visualization
    return (
    <div className="App">
    <iframe
    width="80%"
    height={450}
    src={link}
    title="Embedded visualization"
    />
    <div>
    <UsersMenu onUserIdChange={setUserId} userId={userId} />
    </div>
    </div>
    );
    }

    export default App;

Start the server and app

To start up the app and view the embedded dashboard:

  1. Start the server:

    npx ts-node -O '{"module":"commonjs"}' src/server.ts
  2. Start the app:

    npm start
  3. View the app in your browser at http://localhost:3000:

    Embedded dashboard

Note that changing the user in the drop-down displays exactly the same dashboard for each. In the final steps you'll apply data cube and access filters to change the display for each user.

You can apply two types of filter to an embedding link:

  • Data cube access filters allow you to apply a SQL expression to a specific Polaris data cube's underlying data. You then assign the access filter to a user group, so that members of the group view the filtered data.
  • Link filters are SQL filter expressions applied to a link that can be used on any data cube.

Apply data cube access filters

Our embedded dashboard is built on a Polaris data cube named Earthquake sample day. For this example you'll simulate access for three users with three different data cube access filters.

  1. In the UI, create access filters on the data cube underlying the embedded dashboard, for example:

    • Intensity 2: Only show events with an intensity of 2.
    • Quarry blasts: Only show events with type 'quarry blast'.
    • Reviewed events: Only show events with review status 'reviewed'.

    Data cube access filters

  2. Make a note of each access filter's ID. You can view this in the Polaris UI (hover over the ID to see it in full and double-click it to copy) or by sending a GET request to the Data cubes v1 API.

  3. Edit the UsersMenu.tsx file in polaris-embedding-example/src to include the access filter IDs in the cubeAccessFilter variables. Replace the contents of the file with the following, then replace DATA_CUBE_ACCESS_FILTER_ID1, DATA_CUBE_ACCESS_FILTER_ID2, and DATA_CUBE_ACCESS_FILTER_ID3 with each of your access filter IDs:

    import '@blueprintjs/core/lib/css/blueprint.css';
    import { Button, Menu, MenuItem, Popover } from "@blueprintjs/core";

    // Export the user details
    export interface User {
    userName: string;
    cubeAccessFilter?: string;
    linkAccessFilter?: string;
    }

    interface UserMenuProps {
    userId: number;
    onUserIdChange: (user: number) => void;
    }

    export const USERS = [
    // Set the username, cube access filter, and link access filter
    {
    userName: "Research user 1",
    cubeAccessFilter: "DATA_CUBE_ACCESS_FILTER_ID1",
    linkAccessFilter: undefined,
    },

    // Set the username, cube access filter, and link access filter
    {
    userName: "Research user 2",
    cubeAccessFilter: "DATA_CUBE_ACCESS_FILTER_ID2",
    linkAccessFilter: undefined,
    },

    // Set the username, cube access filter, and link access filter
    {
    userName: "Research user 3",
    cubeAccessFilter: "DATA_CUBE_ACCESS_FILTER_ID3",
    linkAccessFilter: undefined,
    },
    ];
    // Set up the users drop-down list
    function UsersMenu({ onUserIdChange, userId }: UserMenuProps) {
    const usersMenuItems = USERS.map((user, i) => (
    <MenuItem
    key={user.userName}
    text={user.userName}
    onClick={() => onUserIdChange(i)}
    />
    ));
    return (
    <Popover content={<Menu>{usersMenuItems}</Menu>}>
    <Button
    alignText="left"
    icon="user"
    rightIcon="caret-down"
    text={USERS[userId].userName}
    />
    </Popover>
    );
    }

    export default UsersMenu;
  4. Restart the server and the app.

  5. View the app at http://localhost:3000.

As you change the user selection in the drop-down, the dashboard view now applies the appropriate data cube access filter to each user.

Embedded dashboard change on user

Now you'll apply a link filter to one user, in addition to the data cube access filter.

  1. Edit the UsersMenu.tsx file in polaris-embedding-example/src to define a link filter for Research user 1. In the example below, the filter is t."Place"='13 km S of Tres Pinos, CA'. Make sure you surround the filter with backticks.

    import '@blueprintjs/core/lib/css/blueprint.css';
    import { Button, Menu, MenuItem, Popover } from "@blueprintjs/core";

    export interface User {
    userName: string;
    cubeAccessFilter?: string;
    linkAccessFilter?: string;
    }

    interface UserMenuProps {
    userId: number;
    onUserIdChange: (user: number) => void;
    }

    export const USERS = [
    // Set the username, cube access filter, and link access filter
    {
    userName: "Research user 1",
    cubeAccessFilter: "DATA_CUBE_ACCESS_FILTER_ID",
    linkAccessFilter: `t."Place"='13 km S of Tres Pinos, CA'`,
    },

    // Set the username, cube access filter, and link access filter
    {
    userName: "Research user 2",
    cubeAccessFilter: "DATA_CUBE_ACCESS_FILTER_ID",
    linkAccessFilter: undefined,
    },

    // Set the username, cube access filter, and link access filter
    {
    userName: "Research user 3",
    cubeAccessFilter: "DATA_CUBE_ACCESS_FILTER_ID",
    linkAccessFilter: undefined,
    },
    ];
    // Set up the users drop-down list
    function UsersMenu({ onUserIdChange, userId }: UserMenuProps) {
    const usersMenuItems = USERS.map((user, i) => (
    <MenuItem
    key={user.userName}
    text={user.userName}
    onClick={() => onUserIdChange(i)}
    />
    ));
    return (
    <Popover content={<Menu>{usersMenuItems}</Menu>}>
    <Button
    alignText="left"
    icon="user"
    rightIcon="caret-down"
    text={USERS[userId].userName}
    />
    </Popover>
    );
    }

    export default UsersMenu;
  2. Restart the server and the app.

  3. View the app at http://localhost:3000.

Research user 1 already had a data cube access filter applied, that limited the data to events with an intensity of 2. Adding the link filter has restricted the data furtherthe dashboard now shows events that occurred at place 13 km S of Tres Pinos, CA with an intensity of 2.

Embedded dashboard with cube and link filters

Note that you can use either filter method to apply these restrictions. The difference is that data cube access filters are assigned to a user group. Link filters apply to anyone viewing the link.

Learn more

See the following topics to learn more about the Polaris features explored in this guide: