将数据可视化发布到 Web

与互联网上的优秀人士分享独立的图表和仪表盘。

在本教程中,我们将向您展示发布 Metabase 图表和仪表盘的几种选项,从最低到最高难度。

我们将从无需代码的公共链接开始,逐步升级到只需要单个代码片段的公共嵌入,最后提供示例代码,供那些想要启动自己的 Web 应用程序的用户使用。

如果您想查看何时以及为何选择一种发布方式而不是另一种发布方式的一些示例,请查看Metabase 谜团

为了快速共享问题和仪表盘,您可以简单地发送一个公共链接,或者在您的网站(或任何渲染 HTML 的内容)中放置一个 iframe。这是一种动态共享图表和仪表盘的好方法。

共享选项

假设我们要共享一个仪表盘。我们将单击共享图标,然后选择共享和嵌入选项。

An example dashboard using the Sample Database included with Metabase. To access the sharing options, click on the arrow in the upper right and select Sharing and embedding.

这将调出我们的共享选项

Sharing options include: Public link, Public embed, and Embed this dashboard in an Application (a static embed).

公共链接是共享仪表盘的最简单方法。公共链接甚至不是嵌入;它们只是指向单个问题或仪表盘的链接,尽管这些公共项目与它们的原始版本略有不同。

An example of using a public link to share a dashboard, with the Powered by Metabase footer.

这些公共链接将包含一个 Powered by Metabase 页脚,如果您有 Pro 版或企业版计划,您可以将其删除。图表也将禁用钻取功能,并且我们将无法自定义仪表盘上的点击行为

如果我们设置了默认过滤器值,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,并呈现来自该地址的响应 - 在本例中,是我们想要呈现的图表或仪表盘。与公共链接一样,该图表将包含 Powered by Metabase 页脚

我们可以调整宽度和高度以适合我们的图表或仪表盘。如果我们嵌入的是仪表盘,并且 iframe 的宽度不足以容纳仪表盘布局,Metabase 会按照问题在仪表盘上从左到右出现的顺序堆叠问题。

在其他应用程序中启用嵌入

如果我们想限制谁可以查看我们的图表或仪表盘,或者锁定过滤器,我们需要使用静态嵌入。此功能与公共共享选项分开,并且仅管理员可以访问。我们还需要启用 在其他应用程序中嵌入 设置才能使其工作。在管理面板设置选项卡中,我们将单击 在其他应用程序中嵌入 并切换到 已启用

让我们回到我们想要共享的仪表盘。启用嵌入后,我们获得一个密钥。

With embedding enabled, you

要设置静态嵌入,我们需要在我们的服务器上插入一些代码,以为我们的用户签署 JSON Web 令牌 (JWT)。Metabase 将为 Clojure、Python、Ruby 和 JavaScript (Node.js) 生成代码,但您应该也能够为以其他语言编写的服务器翻译该代码。

Metabase will supply the code you

在我们点击发布按钮之前,让我们回顾一下我们的一些选项。

隐藏或锁定参数以限制显示的数据

如果我们的问题或仪表盘有过滤器,我们可以禁用过滤器,或锁定参数以设置固定的过滤器值。

假设我们想要向某人展示一个仪表盘,但我们只想让他们看到 Gadget 类别中的订单。

Locking a filter with the value 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 令牌:我们的用户不会——而且不应该——看到该密钥。

以下是它的工作方式。

  1. 在 Metabase 中,我们发布我们想要嵌入到应用程序中的仪表盘。
  2. 我们将 iframe 插入到我们应用程序的页面中。
  3. 我们将用于签署 JSON Web 令牌的代码放置在我们的服务器中。
  4. 我们的用户登录到我们的应用程序。
  5. 用户在我们的应用程序中请求包含嵌入式仪表盘的页面。
  6. 当服务器处理该页面的请求时,它将签署用户的令牌并将该令牌作为 iframe 的源 URL 插入。
  7. 当页面加载时,页面使用已签名的令牌从我们的 Metabase 实例请求仪表盘。
  8. 仪表盘在我们的应用程序中的 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 = 'https://127.0.0.1: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.encode 函数(来自 jwt 库)使用我们从 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/,则会按顺序发生以下事情

  1. 浏览器向侦听端口 8000 的 Django 应用程序发送对 /(网站的根目录)的 HTTP 请求。

  2. 该应用程序将请求中的 URL 与 home 函数匹配。

  3. home 生成一个新的令牌,其过期时间为未来 10 秒。

  4. 然后,它读取 index.html 并将 {{title}} 替换为 “Embedding Metabase”,并将 {{iframeUrl}} 替换为包含新生成的令牌的 URL。

  5. home 然后将该 HTML 发回浏览器。

  6. 当浏览器显示该 HTML 时,它会遇到 iframe。iframe 标记中的 src 属性告诉它向 Metabase 发送请求。

  7. 当 Metabase 收到 URL 以 /embed/question 开头的请求时,它会从 URL 中提取路径的其余部分并对其进行解密。

  8. 由于 URL 是使用 Metabase 生成的密钥进行编码的,因此解密成功,这告诉 Metabase 发送者被允许查看问题。嵌入在令牌中的值告诉 Metabase 正在请求哪个问题。

  9. Metabase 运行问题并生成它将在其自己的界面中显示的 HTML,然后将该 HTML 发回发出请求的浏览器。

  10. 浏览器将该 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,并包含两个元素:包含页面标题的 1 级标题和一个包含我们的 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)

然后,我们使用 jwt_claim 函数(来自 jose 库)来构造令牌。此函数的名字来源于我们声明我们被允许做某事,并且它接受任意数量的命名参数作为输入

  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 运行此程序,并将浏览器指向 https://127.0.0.1:8001,我们将看到我们嵌入的问题。

交互式嵌入

如果您想解锁 Metabase 在嵌入方面的全部潜力,这将允许人们深入查看数据,或将他们发送到自定义目标(如其他仪表板或外部 URL),您将需要交互式嵌入。要了解更多信息,请参阅向您的客户交付分析在您的应用中嵌入 Metabase 以交付多租户、自助式分析

延伸阅读

下一步:Metabase 谜题

公共链接、公共嵌入和签名嵌入之间有什么区别?

下一篇文章