将数据可视化发布到网络

与互联网上的广大用户分享独立的图表和仪表盘。

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

我们将从无需代码的公共链接开始,逐步介绍只需单个代码片段的公共嵌入,最后为希望启动自己的 Web 应用程序的人提供示例代码。

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

要快速共享问题和仪表盘,您只需发送一个公共链接,或将 iframe 嵌入到您的网站(或任何可渲染 HTML 的地方)。这是即时共享图表和仪表盘的绝佳方式。

分享选项

假设我们要共享一个仪表盘。我们将点击共享图标并选择共享和嵌入选项。

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.

这些公共链接将包含一个“由 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

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

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 = '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/,会依次发生以下事情

  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 <- '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 谜团

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

下一篇文章
© . All rights reserved.