Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python Flask example #1

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
python/.venv
python/__pycache__
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Metabase static embedding sample apps

This repo contains code samples demonstrating how embed Metabase dashboards in an app using [static embedding](https://www.metabase.com/docs/latest/embedding/static-embedding).

For [interactive embedding](https://www.metabase.com/docs/latest/embedding/interactive-embedding), check out the following resources:

* [Interactive embedding quick start](https://www.metabase.com/learn/customer-facing-analytics/interactive-embedding-quick-start)
* [Sample repo for the quick start](https://github.com/metabase/metabase-nodejs-express-interactive-embedding-sample)
* [Sample with React](https://github.com/metabase/sso-examples/tree/master/app-embed-example)

If you are unsure about what embedding feature is best for you, check out our [live demos](https://www.metabase.com/embedding-demo).

## Free static embedding

Embedding Metabase charts will always be free, but we include a "Powered by Metabase" graphic when using the open source version.

If you'd like to remove the "Powered by Metabase" attribution, check out our [paid plans](https://www.metabase.com/pricing/). In addition to removing the banner, you can also customize the fonts and hide the download buttons for questions.

## Samples
* [Python Flask](/python)
52 changes: 52 additions & 0 deletions python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Metabase Python static embedding sample

This sample code demonstrates using Metabase static embedding in Python using Flask.

## Pre-requisites
* Have a running instance of Metabase. If you don't have one
* [download the free open source version](https://www.metabase.com/start/oss/) or
* [sign up for a free trial of Metabase Cloud](https://www.metabase.com/pricing/).
* A dashboard to embed. If you don't have one, use X-Rays to let Metabase create one for you. Note down the dashboard ID.

## Preparing the embed
1. Sign in to your Metabase instance as an admin.
2. Go to admin settings and enabling embedding.
3. Under admin settings/embedding, click on static embedding and copy the embedding secret key.

## Configure the app

1. Paste the secret key into an env var:
```
export METABASE_EMBEDDING_SECRET="PASTE_SECRET_HERE"
```
2. Create an env var pointing to your Metabase site URL, if it's not on http://localhost:3000
```
export METABASE_SITE_URL="http://localhost:4000"
```
3. Create an env var with the ID of the dashboard to embed, if it's not 1:
```
export METABASE_EMBED_DASHBOARD_ID="8"
```

## Embed the dashboard
1. Go to your dashboard, click on the share/embed button at the top
2. Click on the "Publish" button

# Running the sample
1. Create the virtual environment:
```
python3 -m venv .venv
```
2. Activate the virtual environment:
```
source .venv/bin/activate
```
3. Install the dependencies:
```
pip install -r requirements.txt
```
4. Start the app:
```
flask run -p 9090
```
5. Open the app at http://localhost:9090
26 changes: 26 additions & 0 deletions python/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import time
import os
from urllib.parse import urljoin

from flask import Flask
albertoperdomo marked this conversation as resolved.
Show resolved Hide resolved
import jwt

app = Flask(__name__)

METABASE_SITE_URL = os.environ.get('METABASE_SITE_URL', 'http://localhost:3000')
METABASE_EMBEDDING_SECRET = os.environ.get('METABASE_EMBEDDING_SECRET')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a default value here. I ran the sample without reading the readme first. And it failed to run.

Suggested change
METABASE_EMBEDDING_SECRET = os.environ.get('METABASE_EMBEDDING_SECRET')
METABASE_EMBEDDING_SECRET = os.environ.get('METABASE_EMBEDDING_SECRET', 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff')

this key is used in most of our example keys, so it should be safe.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it work with that sample key? If not, it might be better to check and raise an error that the env key needs to be set before running, otherwise it will still be confusing. If we don't need to be fancy, changing get to os.environ['METABASE_EMBEDDING_SECRET'] would work as it will automatically raise a key error when not yet

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think as @maxzheng says, adding a default value is just going to cover up the fact that the secret is missing. I prefer an explicit error.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool. I'm on board with you guys. My gripe was that the error was quite cryptic. If os.environ['METABASE_EMBEDDING_SECRET'] throws better error that you need to set that key then let's do that.

METABASE_EMBED_DASHBOARD_ID = int(os.environ.get('METABASE_EMBED_DASHBOARD_ID', '1'))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for here as well. Default of 1 is ok if it should work, otherwise make it required instead like secret.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are now going to be seeding instances with an example dashboard, which I expect to have ID 1, although I don't know if it's 100% guaranteed.


@app.route("/")
def static_embed():
payload = {
"resource": {"dashboard": METABASE_EMBED_DASHBOARD_ID},
"params": {
},
"exp": round(time.time()) + (60 * 10) # 10 minute expiration
}
token = jwt.encode(payload, METABASE_EMBEDDING_SECRET, algorithm="HS256")
iframeUrl = urljoin(METABASE_SITE_URL, "/embed/dashboard/" + token + "#bordered=true&titled=true")
return f'''<script src="{METABASE_SITE_URL}/app/iframeResizer.js"></script>
<iframe src="{iframeUrl}" frameborder="0" width="800" height="600" onload="iFrameResize({{}}, this)" allowtransparency></iframe>
'''
2 changes: 2 additions & 0 deletions python/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Flask==3.0.0
albertoperdomo marked this conversation as resolved.
Show resolved Hide resolved
PyJWT==2.8.0