将数据可视化发布到网络
与互联网上的好人分享独立的图表和仪表板。
加入我们的关于Metabase中面向客户的分析的网络研讨会。
在这个教程中,我们将向您展示发布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将按照从左到右的顺序堆叠仪表板上的问题。
在其他应用程序中启用嵌入
如果我们想限制谁可以看到我们的图表或仪表板,或锁定筛选,我们需要使用静态嵌入。此功能与公共共享选项分开,并且仅管理员可以访问。我们还需要在它生效之前启用在其他应用程序中嵌入
设置。在管理员面板的设置选项卡中,我们将点击在其他应用程序中嵌入
并将启用
切换打开。
让我们回到我们想要分享的仪表板。启用嵌入后,我们将获得一个密钥。
为了设置静态嵌入,我们需要在我们的服务器上插入一些代码来为我们的用户签名JSON Web Tokens (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 Tokens的代码。
- 用户登录我们的应用程序。
- 用户请求包含嵌入式仪表板的应用程序中的页面。
- 当服务器处理该页面的请求时,它将签署用户的令牌并将该令牌作为iframe的源URL插入。
- 当页面加载时,页面将使用签名的令牌从我们的Metabase实例请求仪表板。
- 仪表板在我们应用程序的iframe中加载,参数和过期时间由我们的应用程序服务器设置。
如果令牌未签名,或以任何方式更改,则仪表板将无法加载。
在payload
映射中,我们可以指定签名的JWT何时过期(在上面的代码中,令牌在10分钟后过期)。
:params
字段是我们可以锁定参数以设置仪表板或问题的默认、固定过滤器的地方。例如,我们可以创建一个具有用户ID过滤器的仪表板,然后——在我们的服务器上——以编程方式插入用户的ID作为参数。签名的令牌将锁定该过滤器,因此任何查看该仪表板的人只能看到经过他们ID过滤的数据。
示例
我们在一个公开的Git仓库中维护了几个语言的嵌入示例。下面的子部分将详细介绍Django(一个流行的Python网络框架)和Shiny(最受欢迎的R网络框架)的最小示例。
使用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的URLiframeUrl
。标题只是一个字符串,但如上所述,我们需要做一些工作来构建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 = 'https://127.0.0.1:3000'
METABASE_SECRET_KEY = '40e0106db5156325d600c37a5e077f44a49be1db9d02c96271e7bd67cc9529fa'
我们需要计算我们的签名令牌的过期时间,并使用jwt
库来签名令牌。METABASE_SITE_URL
告诉程序在哪里找到我们的Metabase实例——在本例中我们是在本地运行它,而METABASE_SECRET_KEY
是Metabase生成的值,我们将其发送回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
然后我们将浏览器指向https://127.0.0.1:8000/
,以下事情将按顺序发生
-
浏览器向监听8000端口的Django应用程序发送HTTP请求,请求
/
(网站的根目录)。 -
该应用程序将请求中的URL与
home
函数匹配。 -
home
生成一个新的令牌,其过期时间为10秒后。 -
然后它读取
index.html
,将“Embedding Metabase
”替换为{{title}}
,将包含新生成令牌的URL替换为{{iframeUrl}}
。 -
home
然后将该HTML发送回浏览器。 -
当浏览器显示该HTML时,它遇到了iframe。在
iframe
标签的src
属性告诉它向Metabase发送请求。 -
当Metabase收到以
/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 <- 'https://127.0.0.1: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
生成所需的空映射{}
;为了确保这将起作用,请确保您有0.3或更高版本的jose
。
一旦我们有了声明,我们可以用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
并将我们的浏览器指向 https://127.0.0.1:8001
,我们将看到我们的嵌入式问题。
交互式嵌入
如果您想解锁在嵌入时Metabase的全部潜力,这将允许人们深入数据或发送他们到自定义目的地(如其他仪表板或外部URL),您将需要交互式嵌入。了解更多信息,请参阅向您的客户交付分析和在您的应用程序中嵌入Metabase以提供多租户、自助式分析。
进一步阅读
下一步:Metabase之谜
公共链接、公共嵌入和签名嵌入之间有什么区别?