将数据可视化发布到网络
与互联网上的广大用户分享独立的图表和仪表盘。
在本教程中,我们将向您展示发布 Metabase 图表和仪表盘的几种选项,从最省力到最费力。
我们将从无需代码的公共链接开始,逐步介绍只需单个代码片段的公共嵌入,最后为希望启动自己的 Web 应用程序的人提供示例代码。
如果您想了解何时以及为何选择一种发布方式而非另一种的示例,请查看一个 Metabase 谜团。
公共链接和嵌入
要快速共享问题和仪表盘,您只需发送一个公共链接,或将 iframe 嵌入到您的网站(或任何可渲染 HTML 的地方)。这是即时共享图表和仪表盘的绝佳方式。
分享选项
假设我们要共享一个仪表盘。我们将点击共享图标并选择共享和嵌入选项。
这将显示我们的共享选项
公共链接
公共链接是共享仪表盘最简单的方式。公共链接甚至不是嵌入;它们只是指向单个问题或仪表盘的链接,尽管这些公共项目与它们的原始版本略有不同。
这些公共链接将包含一个“由 Metabase 提供支持”的页脚,如果您有专业版或企业版计划,可以将其删除。图表的钻取功能也将被禁用,并且我们无法自定义仪表盘上的点击行为。
如果我们设置了默认筛选器值,Metabase 会将该筛选器应用于问题或仪表盘。人们可以更改筛选器,因此我们不能依赖筛选器来限制人们可以看到的数据。要锁定(或隐藏)筛选器,我们需要使用静态嵌入。
我们还可以格式化 URL 以将值分配给筛选器,或者完全隐藏筛选器——但请记住,接收者可以简单地编辑 URL。
虽然这些公共链接很难被猜到,但任何拥有该链接的人都可以查看我们的仪表盘,所以它不是共享敏感数据的最佳解决方案。不过,我们可以快速共享一个仪表盘的公共链接(例如,与客户共享),然后一旦他们看过就禁用它。如果我们再次共享仪表盘,Metabase 将生成一个新链接(因此我们无需担心旧链接的访问权限)。如果您不小心共享了链接,您可以随时禁用它;只需将共享开关关闭即可。管理员可以从管理面板查看和禁用所有公共链接。
公共嵌入
我们可以使用 iframe 将问题或仪表盘嵌入到我们的网站中。这就像从 Metabase 复制粘贴代码并将其放入网页的源代码中一样简单。我们甚至可以在无代码网站构建器中使用它——任何可以放置 HTML 的地方。例如,您可以在博客中嵌入仪表盘以帮助用数据讲述故事,或者简单地用仪表盘填充整个页面。
这是一个用于显示仪表盘的 iframe
<iframe
src="http://your-website.com/public/dashboard/f54f5ae5-39a4-4662-85d5-a02e78165279"
frameborder="0"
width="800"
height="600"
allowtransparency
></iframe>
iframe 在当前浏览器窗口中创建一个嵌套的浏览器窗口。iframe 窗口指向自己的 URL,并显示该地址的响应——在这种情况下,是我们想要呈现的图表或仪表盘。与公共链接一样,图表将显示“由 Metabase 提供支持”的页脚。
我们可以调整宽度和高度以适应我们的图表或仪表盘。如果我们要嵌入仪表盘且 iframe 的宽度不足以容纳仪表盘布局,Metabase 将按照它们在仪表盘上从左到右的顺序堆叠问题。
在其他应用程序中启用嵌入
如果我们想限制谁可以查看我们的图表或仪表盘,或者锁定筛选器,我们需要使用静态嵌入。此功能独立于公共共享选项,并且仅供管理员访问。我们还必须启用 Embedding in other Applications
设置才能使其工作。在管理面板的设置选项卡中,我们将点击 Embedding in other Applications
并切换到 Enabled
。
让我们回到我们要共享的仪表盘。启用嵌入后,我们将获得一个秘密密钥。
要设置静态嵌入,我们需要在服务器上插入一些代码来为我们的用户签署 JSON Web 令牌 (JWT)。Metabase 将为 Clojure、Python、Ruby 和 JavaScript (Node.js) 生成代码,但您也应该能够将该代码翻译成其他语言的服务器代码。
在点击发布按钮之前,让我们回顾一些选项。
隐藏或锁定参数以限制显示数据
如果我们的问题或仪表盘有筛选器,我们可以禁用筛选器,或者锁定参数以设置固定筛选器值。
假设我们想向某人展示一个仪表盘,但我们只想让他们看到“Gadget
”类别的订单。
在我们的示例中,参数设置了仪表盘上的筛选器。这里我们将“类别”筛选器设置为Gadget
。
以下是一些用 Clojure 编写的服务器示例代码
(require '[buddy.sign.jwt :as jwt])
(def metabase-site-url "MY-DOMAIN-HERE")
(def metabase-secret-key "SECRET-KEY-HERE")
(def payload
{:resource {:dashboard 3}
:params {"category" ["Gadget"]}
:exp (+ (int (/ (System/currentTimeMillis) 1000)) (* 60 10))}) ; 10 minute expiration
(def token (jwt/sign payload metabase-secret-key))
(def iframe-url (str metabase-site-url "/embed/dashboard/" token "#bordered=true&titled=true"))
此代码将使用 Metabase 提供给我们的密钥来签署 JWT 令牌:我们的用户将不会——也不应该——看到该密钥。
流程如下:
- 在 Metabase 中,我们发布要在应用程序中嵌入的仪表盘。
- 我们将 iframe 插入到应用程序的页面中。
- 我们在服务器中放置用于签署 JSON Web 令牌的代码。
- 我们的用户登录到我们的应用程序。
- 用户请求应用程序中包含嵌入式仪表盘的页面。
- 当服务器处理该页面的请求时,它将签署用户的令牌并将其插入为 iframe 的源 URL。
- 页面加载时,页面使用已签名的令牌从我们的 Metabase 实例请求仪表盘。
- 仪表盘在我们的应用程序中的 iframe 中加载,其中包含由应用程序服务器设置的参数和过期时间。
如果令牌未签名或以任何方式被更改,仪表盘将无法加载。
在 payload
映射中,我们可以指定已签名 JWT 的过期时间(在上面的代码中,令牌在 10 分钟后过期)。
的 :params
字段用于锁定参数,以便在您的仪表板或问题上设置默认的固定过滤器。例如,我们可以创建一个仪表板,其中包含用户 ID 的过滤器,然后——在我们的服务器上——以编程方式将用户的 ID 作为参数插入。签名令牌会将过滤器锁定到该 ID,因此任何查看该仪表板的用户将只能看到已按其 ID 过滤的数据。
示例
我们在一个公共 Git 存储库中维护了多种语言的嵌入示例。以下小节将介绍 Django(一个流行的 Python Web 框架)和 Shiny(最流行的 R Web 框架)中的最小示例。
使用 Django 的示例
我们的最小 Django 应用程序只包含两个文件:index.html
中的 HTML 模板和 index.py
中的简短 Python 程序。模板简短精炼
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<h1>Embed {{ title }}</h1>
<iframe
src="{{iframeUrl}}"
frameborder="1"
width="800"
height="600"
></iframe>
</body>
</html>
它需要两个值:title
中的页面标题和包含静态嵌入的 iframe 的 URL iframeUrl
。标题只是一个字符串,但如上所述,我们需要做一些工作来构建 URL。程序以 Django 所需的一些库和设置开始
# Required by Django
import os
from django.conf.urls import url
from django.http import HttpResponse
from django.template.loader import render_to_string
DEBUG = True
SECRET_KEY = '4l0ngs3cr3tstr1ngw3lln0ts0l0ngw41tn0w1tsl0ng3n0ugh'
ROOT_URLCONF = __name__
TEMPLATES = [{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.getcwd()]
}]
然后,我们包含几个库并为嵌入定义几个值
# Required for Metabase embedding
import time
import jwt
METABASE_SITE_URL = 'http://localhost:3000'
METABASE_SECRET_KEY = '40e0106db5156325d600c37a5e077f44a49be1db9d02c96271e7bd67cc9529fa'
我们需要 time
来计算签名令牌的过期时间,以及 jwt
库来签名令牌。METABASE_SITE_URL
告诉程序在哪里可以找到我们的 Metabase 实例——在这个例子中,我们在本地运行它——而 METABASE_SECRET_KEY
是 Metabase 生成的值,我们将其发送回去以证明我们有权访问问题。在生产环境中我们不会将其放在源代码中;相反,我们会将其存储在环境变量或单独的配置文件中。
我们必须在发送到 Metabase 的令牌中包含三个值。由于其中一个是令牌过期时间,它会随每次请求而变化,因此我们将构建令牌的代码放在处理页面请求的函数中。为了演示目的,我们需要问题 #1,并且我们要求令牌从现在起十分钟内有效
# Handle requests for '/'.
def home(request):
payload = {
'resource': {'question': 1},
'params': {},
'exp': round(time.time()) + (60 * 10)
}
token = jwt.encode(payload, METABASE_SECRET_KEY, algorithm='HS256')
iframeUrl = METABASE_SITE_URL + '/embed/question/' + token + '#bordered=true&titled=true'
html = render_to_string('index.html', {
'title': 'Embedding Metabase',
'iframeUrl': iframeUrl
})
return HttpResponse(html)
我们使用 jwt
库中的 jwt.encode
函数,利用从 metabase 获取的密钥来加密我们的令牌参数。(参数 algorithm='HS256'
告诉 jwt.encode
使用哪种哈希算法——我们必须始终使用该算法。)然后我们将该加密令牌插入到 URL 中,渲染 HTML 模板,并将其返回给应用程序。最后,我们的程序通过告诉 Django 如何将传入请求与渲染函数匹配来结束
urlpatterns = [
url(r'^$', home, name='homepage')
]
如果我们从命令行运行我们的应用程序
$ django-admin runserver --pythonpath=. --settings=index
然后将浏览器指向 http://localhost:8000/
,会依次发生以下事情
-
浏览器向监听端口 8000 的 Django 应用程序发送
/
(网站根目录)的 HTTP 请求。 -
该应用程序将请求中的 URL 与
home
函数匹配。 -
home
生成一个新令牌,其过期时间为未来 10 秒。 -
然后,它读取
index.html
并将{{title}}
替换为“Embedding Metabase
”,将{{iframeUrl}}
替换为包含新生成的令牌的 URL。 -
home
然后将该 HTML 返回给浏览器。 -
当浏览器显示该 HTML 时,它遇到 iframe。
iframe
标签中的src
属性告诉它向 Metabase 发送请求。 -
当 Metabase 收到一个 URL 以
/embed/question
开头的请求时,它会从 URL 中提取路径的其余部分并对其进行解密。 -
由于 URL 是使用 Metabase 生成的密钥编码的,解密成功,这告诉 Metabase 发送者被允许查看该问题。令牌中嵌入的值告诉 Metabase 请求的是哪个问题。
-
Metabase 运行该问题并生成它将在自己的界面中显示的 HTML,然后将该 HTML 发送回发出请求的浏览器。
-
浏览器将该 HTML 插入 iframe 并显示给用户。
使用 Shiny 的示例
我们的最小 Shiny 示例比上面所示的 Django 示例稍微简单一些,因为 Shiny 需要的样板代码更少。首先,我们加载 Shiny 本身、管理 web 令牌以及连接字符串所需的库
library(shiny)
library(jose)
library(glue)
然后我们定义两个值,它们指定 Metabase 的运行位置(我们将使用本地实例)以及 Metabase 为身份验证提供的秘密密钥
METABASE_SITE_URL <- 'http://localhost:3000'
METABASE_SECRET_KEY <- '40e0106db5156325d600c37a5e077f44a49be1db9d02c96271e7bd67cc9529fa'
用户界面基于流行的 CSS 框架 Bootstrap,包含两个元素:包含页面标题的一级标题和一个包含 iframe 的 div
。UI 不会自己构建 iframe;相反,它使用 uiOutput
函数来渲染一个名为 container
的内容
ui <- bootstrapPage(
h1('Page title'),
uiOutput('container')
)
container
从何而来?在 Shiny 中,答案是“来自服务器”。如下所示,server
构建一个 JWT 声明并将其加密以构造 iframe 的 URL。然后它调用 renderUI
来获取 iframe 的 HTML 内容,并将其分配给 output$container
。当发生此分配时,Shiny 会自动通知 UI 需要重新绘制页面
server <- function(input, output) {
# Token expires 10 minutes in the future.
expiry <- as.integer(unclass(Sys.time())) + (60 * 10)
# Construct params in two steps so that JSON conversion knows it's a list.
params <- list()
names(params) <- character(0)
# Create the JWT claim.
claim <- jwt_claim(exp = expiry, resource = list(question = 1), params = params)
# Encode token and use it to construct iframe URL.
token <- jwt_encode_hmac(claim, secret = METABASE_SECRET_KEY)
url <- glue("{METABASE_SITE_URL}/embed/question/{token}#bordered=true&titled=true")
output$container <- renderUI(tags$iframe(width = "600", height = "600", src = url))
}
我们来详细了解 server
函数。首先,我们希望令牌在十分钟内有效,因此我们获取当前时间作为整数并加上 600 秒
expiry <- as.integer(unclass(Sys.time())) + (60 * 10)
然后我们使用 jose
库中的 jwt_claim
函数来构建令牌。这个函数的名字来源于我们声明我们被允许做某事的事实,它接受任意数量的命名参数作为输入
params <- list()
names(params) <- character(0)
claim <- jwt_claim(exp = expiry, resource = list(question = 1), params = list())
上面代码的前两行是必需的,因为 jose
依赖于另一个名为 jsonlite
的库将结构转换为 JSON 字符串,默认情况下该库将空列表转换为空数组 []
而不是空映射 {}
。这是一个很小的区别,但当 Metabase 解码结构时,它要求 params
是一个带有键的映射。解决方案是给 params
一个空的名称列表,以便 jsonlite
按照要求生成 {}
;为了确保这能正常工作,请确保您的 jose
版本为 0.3 或更高。
一旦我们有了声明,就可以使用 jwt_encode_hmac
对其进行编码,并构造 iframe URL。jwt_encode_hmac
默认使用 HS256 编码算法,因此我们无需显式指定它
token <- jwt_encode_hmac(claim, secret = METABASE_SECRET_KEY)
url <- glue("{METABASE_SITE_URL}/embed/question/{token}#bordered=true&titled=true")
最后,我们使用 renderUI
来获取 iframe 的 HTML 并将其赋值给 output$container
,这会自动触发浏览器页面的更新
output$container <- renderUI(tags$iframe(width = "600", height = "600", src = url))
文件的最后一行将我们的应用程序运行在 8001 端口
shinyApp(ui = ui, server = server, options = list(port = 8001))
如果我们使用 Rscript app.R
从命令行运行此程序,并将浏览器指向 http://localhost:8001
,我们将看到嵌入的问题。
交互式嵌入
如果您想在嵌入时充分发挥 Metabase 的潜力,允许用户钻取数据,或将其发送到自定义目标(如其他仪表盘或外部 URL),则需要交互式嵌入。要了解更多信息,请参阅向客户提供分析和在您的应用程序中嵌入 Metabase 以提供多租户自助分析。
延伸阅读
下一篇:一个 Metabase 谜团
公共链接、公共嵌入和签名嵌入之间有什么区别?