资讯动态

Metabase Data App 从零搭建实战:Scaffold、沙箱内开发验证与 Remote-Sync 发布全流程

发布时间:2026/9/13 5:05:49 来源:尧图企业网站定制
Metabase Data App 从零搭建实战Scaffold、沙箱内开发验证与 Remote-Sync 发布全流程【免费下载链接】metabaseThe easy-to-use open source Business Intelligence and Embedded Analytics tool that lets everyone work with data :bar_chart:项目地址: https://gitcode.com/GitHub_Trending/me/metabase本文基于 Metabase 仓库中随 Agent 技能一起分发的>import { dataAppConfig } from metabase/embedding-sdk-react/data-app-dev/config; export default dataAppConfig();src/index.tsx 默认导出DataAppFactory返回{ component, providerProps }import type { DataAppFactory } from metabase/embedding-sdk-react/data-app; import App from ./App; import { sdkTheme } from ./theme; const factory: DataAppFactory () ({ component: App, providerProps: { theme: sdkTheme }, }); export default factory;package.json 中的脚本与依赖也说明了构建链路dev固定跑在 5174 端口build先执行embedding-sdk-react>APP_DIRrepo/data_apps/slug # skill-dir 本 SKILL.md 所在目录 # 如 .claude/skills/metabase-data-app-setup模板是其 template/ 子目录。 cp -R skill-dir/template/. $APP_DIR/data app 是 remote-sync 仓库的子目录而不是自己的仓库——所以这是一次普通复制绝不做嵌套的git clone/git init。此后所有命令都在$APP_DIR内执行。6. Step 4 — 定制脚手架模板就位后以下都在repo/data_apps/slug/目录内运行改package.json的name为 slug模板默认是data-app-template见 package.json。把metabase/embedding-sdk-react固定到已发布的>npm install metabase/embedding-sdk-react64-alpha该 tag 解析为当前内部测试 SDK 构建提供两个入口点metabase/embedding-sdk-react/data-app应用 API与metabase/embedding-sdk-react/data-app-dev/configvite.config.ts使用的 dev/build 预设负责提供沙箱入口。不要使用latest或64-stable——data apps 尚未正式发布。确保仓库根.gitignore忽略.env.local——必须在创建任何凭据文件之前做这样密钥永远不可能被提交。仓库没有.gitignore就先创建缺条目就补ROOT$(git rev-parse --show-toplevel 2/dev/null) if [ -z $ROOT ]; then echo MISSING (run this from inside the connected git repo) else GITIGNORE$ROOT/.gitignore # 不存在则创建并确保 .env.local 被忽略 [ -f $GITIGNORE ] || : $GITIGNORE grep -qxF .env.local $GITIGNORE || echo .env.local $GITIGNORE fi在仓库根repo/.env.local通常比应用目录高两级而不是应用目录里配置 Metabase 凭据——仓库里一份.env.local服务所有 data app。若不存在从示例文件复制然后不打印文件内容地校验两个变量文件里可能还有别的密钥——source之后只回显成败信号# 先解析仓库根不加保护的 $(git ...) 在仓库外会展开成 /.env.local 触碰系统级文件 ROOT$(git rev-parse --show-toplevel 2/dev/null) if [ -z $ROOT ]; then echo MISSING (run this from inside the connected git repo) else ENV_FILE$ROOT/.env.local [ -f $ENV_FILE ] || cp .env.local.example $ENV_FILE # 在子 shell 里 source变量不会泄漏到当前环境 ( source $ENV_FILE 2/dev/null [ -n $DATA_APP_MB_URL ] [ $DATA_APP_MB_URL ! mb_replace_me ] [ -n $DATA_APP_MB_API_KEY ] [ $DATA_APP_MB_API_KEY ! mb_replace_me ] ) echo creds present || echo MISSING fi若输出MISSING让用户自己在repo/.env.local填DATA_APP_MB_URL正在运行的 Metabase 实例地址和DATA_APP_MB_API_KEYAdmin → Authentication → API keys 生成——事前一次性配好。安全红线绝不要求用户把 API key 粘贴到对话里也绝不cat/echo/ 打印.env.local或其变量。该文件被 git 忽略且可能含其他密钥——其内容和密钥本身永远不得进入对话或上下文。每个需要 key 的命令都source该文件让 shell 直接使用值你只能看到creds present/MISSING信号。creds present只表示两个变量已填且不是默认占位符mb_replace_me不代表URL 或 key 有效错误的 key 会在后续请求失败时才暴露。npm install或用户偏好的包管理器——模板不随附 lockfilenpm/yarn/pnpm/bun都可以clone 后若出现既有 lockfile 则沿用。修正应用的.gitignore让 lockfile 和构建产物都被提交。remote-sync 仓库必须 track 两样东西lockfile——删掉忽略 lockfile 的整段# Lockfiles —到bun.lockb之间覆盖package-lock.json/yarn.lock/pnpm-lock.yaml/bun.lock/bun.lockb让项目提交 lockfile 以保证可复现安装构建产物——Metabase 直接从提交的 Git 树中读取data_app.yaml里path声明的文件模板构建到dist/index.js即默认path来服务它所以该文件必须被提交。若模板的.gitignore忽略了dist/或你的构建输出目录删掉该行。用git status验证——npm install 构建之后生成的 lockfile 与构建产物path指向的文件都必须以可提交文件出现。任何一个没有就说明对应的.gitignore行还在删掉再查。不要跳过这一步——Agent 曾多次交付没有 lockfile 或 bundle 未同步的项目。npm run dev确认 http://localhost:5174 的预览渲染出起步的 Hello, data app 消息。若预览遇到 CORS把http://localhost:5174加入 Admin → Embedding → Embedded analytics SDK → CORS。编辑data_app.yaml随模板附在应用目录模板见 data_app.yaml。这是 Metabase 在 sync 时读取的每应用配置——每个应用一个文件。为这个应用填好各字段name: Sales App # admin UI 中显示的展示名 description: Pipeline health and quota attainment by region # 可选见下 path: ./dist/index.js # bundle 路径相对本应用目录——不改构建输出就保持原样 # allowed_hosts: # 可选——应用可 fetch/XHR 的外部源见下 # - https://api.example.com # - https://*.internal.acme.com把它与构建产物path指向的文件一起提交。description——可选一句话说明这个应用做什么显示在 admin UI 中应用名下方方便管理员一眼区分。Sync 会把连续空白折叠为单空格超过 255 字符会被拒绝admin 列表对剩余文字做换行而非截断所以一句话效果好、段落会挤爆周围行。用一句关于本应用的真实句子替换模板占位文案或整行删除。allowed_hosts——仅当应用要用fetch/XHR直接调用外部API 时才需要。沙箱默认阻断所有网络出站在此列出源精确匹配或*.子域通配会在npm run devdev-server CSP和 Metabaseiframe CSP 沙箱两处同时放行。不要把 Metabase 实例列进去——Metabase 数据读走useMetabaseQuerydata hooks、写走useActionSDK 处理鉴权永远不要裸fetch。应用只与 Metabase 对话时allowed_hosts整段省略。原生form action…提交与iframe src…/导航同样受该白名单约束。优先用客户端form onSubmitpreventDefault后通过useAction/fetch写入原生提交会把被沙箱化的 iframe 导航走。如果确实要用目标 host 必须在allowed_hosts中提交对应form-action嵌入/导航对应frame-src被导航或嵌入的 host 还必须允许被 frameX-Frame-Options/frame-ancestors——很多公开站点不允许。7. Step 5 — 验证起步应用到这一步data app 已经存在。保持该工作流聚焦于创建脚手架 证明起步 bundle 能跑运行npm run typecheck运行npm run build确认git status显示应用源码、lockfile、data_app.yaml与构建产物默认dist/index.js均为可提交文件如果用户要求活预览运行npm run dev并确认起步 Hello, data app 界面经沙箱预览正常渲染预览打开时查一次诊断流——它是你无浏览器者看到运行时失败的唯一地方见第 10 节curl -s http://localhost:5174/__data-app/diagnostics?startEventId0期望clients: 1且没有任何alert: true的条目。clients: 0意味着没有预览标签页开着空 feed 证明不了任何东西。用户只要求创建/搭建时到此为止。如果下一个任务是构建或迭代真实 UI——尤其涉及既有 data app、Metabase 数据、生成的 schema 文件、已保存 question、table、metric、action、过滤器、语义层实体或 data hooks——那是另一个编辑既有 data app任务走 Agent 常规技能发现流程不要把数据层编写规则并入本 scaffold 技能。8. 构建契约为什么vite.config.ts只有一行除非改动确有必要不要修改src/index.tsx或tsconfig.json。整个构建/开发设置都藏在 SDK 的dataAppConfig()背后连 dev HTML shell 也是它提供的——没有index.html可编辑所以vite.config.ts就是import { dataAppConfig } from metabase/embedding-sdk-react/data-app-dev/config; export default dataAppConfig();dataAppConfig只暴露一个受控覆写集合目前只有port。整个契约——工厂形状、externals/globals、dev 沙箱入口、CSS 内联、SVG 作为组件支持——都是烤死的、不可覆写这是有意为之保证 data app 不会偏离 Metabase 实际加载的东西。没有本地构建配置可动也没有index.html而随意改动src/index.tsx有破坏工厂形状的风险会静默搞坏 drill popup 与路由。没有逃生舱给额外的 Vite 插件、alias 或define——port是唯一的旋钮。如果以为需要更多几乎肯定不需要去src/里解决。每完成一轮有意义的编辑运行npm run typecheck。它跑tsc --noEmit对应模板 package.json 中的typecheck: tsc --noEmit覆盖src/与vite.config.ts——能抓到与 SDK 类型不符的 prop 形状、坏的重构、缺失导入。Vite dev server不做类型检查只转译所以本会让生产 CI 失败的错误可能在一趟看起来正常的npm run dev会话里悄悄存在。宣告任务完成前先跑。交付前复查包卫生metabase/embedding-sdk-react应使用目标环境预期的>// src/components/CustomerCard.tsx import { StaticQuestion } from metabase/embedding-sdk-react; type Customer { name: string; questionId: number }; export default function CustomerCard({ customer }: { customer: Customer }) { return ( article h3{customer.name}/h3 StaticQuestion questionId{customer.questionId} height{300} width100% / /article ); }9.2 从第一天就分层起步应用扩展后的默认布局src/ ├── index.tsx (模板——工厂别动) ├── App.tsx (只做路由 组合) ├── theme.ts ├── pages/ (一屏一文件) │ ├── Overview.tsx │ └── CustomerDetail.tsx ├── components/ (共享 UI) │ └── Card.tsx ├── hooks/ (数据获取包装、自定义 hooks) │ └── useCustomers.ts ├── lib/ (纯工具/派生逻辑) │ └── format.ts └── types/ (共享 TS 类型) └── customer.tsVite 会把从src/index.tsx可达的一切打进单个dist/index.jsIIFE——目录结构纯粹为自己可读性服务。多 tab 应用最左/首个 tab 必须在初始加载时选中。应用绝不应启动在空白页、空壳或等待点击状态上。本地状态 tab 直接初始化为第一个const [active, setActive] useState(TABS[0].id); // 默认 最左 tab若 tab 由 URL 路由支撑同样规则通过 router 实现基础路径/需解析到默认 tab参见 routing 技能。无论哪种都要重新加载应用验证最左 tab 的内容立即可见且呈现选中态。构建产物是一个自包含的.js文件——别无其他。后端只服务单个 bundle没有 sidecar 文件CSS 内联进 JS所有导入资产图片、字体、以 URL 形式引入的 SVGbase64 内联为 data URI。import logo from ./logo.png/import iconUrl from ./icon.svg直接得到可用的>// ✅ 正确 import { StaticQuestion } from metabase/embedding-sdk-react; import { DataAppRouter, DataAppLink, useMetabaseQuery, } from metabase/embedding-sdk-react/data-app; // ❌ 错误——没有 globalThis 模式那样读到的是空 const { MetabaseProvider, StaticQuestion } globalThis;不要在App.tsx里渲染MetabaseProvider。dev 入口SDK dev 预设提供与生产宿主各自在自己的 realm 里给你的树包上 provider——在 bundle 内再包一层会把 SDK 的经监听器setState路径送进 Near Membrane 沙箱静默搞坏 drill popup、插件初始化等。9.4 React 也按常规导入构建把react与react-dom外部化普通导入在两种模式下解析方式相同——生产宿主与 dev 沙箱都以沙箱 globals 形式赋予它们import { useState, useEffect, useMemo } from react;TSX 文件不需要import React from react——模板使用自动 JSX runtimetsconfig.json的jsx: react-jsxdataAppConfig()内置 React 插件。编译器自动注入所需 JSX-runtime 导入生产react/jsx-runtime、devreact/jsx-dev-runtime二者均被外部化并由沙箱赋予。写 JSX 加命名导入即可。React类型ComponentType、ReactNode、RefObject等用命名类型导入不用React.命名空间import type { ComponentType, ReactNode } from react;10. 读取诊断流Reading the diagnostics feednpm run dev把工具栏显示的一切以 JSON 提供——这是你看到运行时失败的唯一途径因为沙箱阻断、CSP 拒绝、失败的查询与未捕获错误都不会出现在终端也不会被npm run typecheck抓到。任何无法通过读代码验证的改动之后都要查。循环编辑前记下nextEventId→ 做改动自动重建→ 重读。startEventId含边界、且跨页面刷新存活。curl -s http://localhost:5174/__data-app/diagnostics?startEventId0{ entries: [{ eventId: 31, kind: blocked-network, alert: true, summary: Blocked fetch to api.example.com (not in allowed_hosts), detail: null, // 有时的 stack frames hint: Add https://api.example.com to allowed_hosts in data_app.yaml …, buildId: 7 // 报告该事件的 bundle 代次 }], clients: 1, // 已连接的预览标签页——0 表示什么都没跑 buildId: 7, // 当前运行中的代次 staleEntries: 164, // 被扣住、由旧构建报告的条目见下 nextEventId: 32 // 下次作为 ?startEventId 传回 }clients: 0不代表健康——它意味着没有预览标签页开着空entries证明不了什么。先打开http://localhost:5174。feed 回答的是正在运行的那个 bundle而非一切历史。每次保存都会重建并重挂载多步编辑会经过编译不过或渲染不了的构建——那些错误描述的是预览已替换掉的代码它们被扣住、计入staleEntries编辑中途读只看到当前构建的失败而不是一堆旧账。没有任何丢失重建会从头重跑应用仍坏的东西会在新buildId下再报告一次对比构建时才用?includeStaletrue取回被扣住的条目。构建失败不会推进buildId——预览继续跑最后一个好 bundle其条目保持最新。按kind分诊永远先读hint——它指明精确修法汇报时不得弱化summarykind修法blocked-network/csp-violation把源加入data_app.yaml的allowed_hosts然后重启npm run dev白名单 CSP 在启动时读取blocked-api生产同样被阻断——改用 SDK API 或删掉调用不要绕过sdk-callalert: true一个 Metabase 请求失败修查询——summary含端点与状态码error真 bugdetail里有 stack文本会被截断缓冲区保留最近 200 个事件。curl -X DELETE .../__data-app/diagnostics为所有读者清空它。11. 主题规则Theme rulesMetabaseProvider的theme定义在src/theme.ts模板见 src/theme.ts是改变 SDK 组件外观的唯一途径——它不是给 bundle 自身 chrome 用的样式表。字段用途备注colors.brandSDK 控件强调色用应用主色colors.brand-hover、colors.brand-hover-lightSDK 控件 hover/强调背景用与 hover 文字对比的微表面色不要用与brand同饱和度的颜色colors.charts图表调色板字符串数组。只用鲜艳的品牌色阶绝不用浅色淡彩——调色板 index 1 可能渲染文字标签白底上浅色不可见colors.positive/colors.negative语义指示色colors.background、colors.background-secondarySDK 组件表面必须与 SDK 组件的直接父容器一致图表在白色卡片里就用white在有底色页面上就用该底色colors.text-primary、text-secondary、text-tertiarySDK 表面上的文字必须与background对比白底用#1f2937一类深色。SDK 默认text-primary解析为近白——白表面下不设就是白色隐形文字。覆写background时务必成对设置text-primaryfontFamilySDK 控件字体hover 色是对比对若设了colors.text-hover在打开的菜单、图表类型选择器、可视化设置下拉中验证它与colors.brand-hover/colors.brand-hover-light的对比。蓝色brand用#EAF4FF这类浅色 hover 表面而非品牌蓝本身。主题只样式化 SDK 控件——绝不用于你自己 UI 的样式。页面背景、页头、卡片包装器用内联style{{ background: #f5f5f7 }}或 CSS modules 处理自己的元素。不要指望按子树主题化。页面上多个MetabaseProvider会争夺唯一的 CSS 变量槽。外观需要随状态变化时在 App 层重算sdkTheme再整树重渲染——全树重新主题化。12. 自定义错误 UI可选当某个内嵌 question/dashboard 加载不了questionId被删、用户无权限、请求失败宿主会在该组件位置渲染一个中性的内置错误态弱化图标 SDK 消息。零配置除非应用有强烈理由重样式化否则保持默认。要匹配应用外观就在工厂的providerProps里与theme同一个对象返回自己的errorComponent。它是普通的providerProps键不是工厂形状变更——安全可加// src/components/AppError.tsx import type { SdkErrorComponentProps } from metabase/embedding-sdk-react; export default function AppError({ message }: SdkErrorComponentProps) { return ( div style{{ padding: 16, textAlign: center, color: #6b7280 }} {message} /div ); }// src/index.tsx —— 与 theme 并排加一个键 import AppError from ./components/AppError; const factory: DataAppFactory () ({ component: App, providerProps: { theme: sdkTheme, errorComponent: AppError }, });规则它替换的是应用中所有SDK 组件的错误 UI不只是 not-found——文案保持通用。渲染message或你自己的措辞不要预设是哪类错误message是ReactNode——原样渲染不要.toString()或下标访问保持为小型展示型组件。它渲染在宿主的 provider 内部但按叶子 UI 对待——不用 data hooks、不用useMetabaseQuery想保留中性默认就整个省略errorComponent。13. SDK 能力面与被禁 APIbundle 从react导入 hooks/JSX从metabase/embedding-sdk-react导入 SDK 组件从metabase/embedding-sdk-react/data-app导入>div style{{ height: 360 }} StaticQuestion questionId{1} height100% width100% withChartTypeSelector{false} / /div不要用 hover 时裁剪或位移的容器包裹InteractiveQuestion/StaticQuestion。避开overflow: hidden、hover transform 与 hover 驱动布局位移——popover、菜单与图表 tooltip 需要稳定几何与可见溢出。16. 同步到 Metabase提交即发布Data apps 经 Git 交付而非上传——你提交应用目录Metabase 在下次 remote-sync 导入时拉取。npm run build→ 产出data_app.yamlpath处的 bundle模板构建到dist/index.js在仓库根提交应用目录——data_app.yaml、构建产物path指向的文件、源码与 lockfile——并pushgit add data_apps/slug git commit -m Add slug data app git push应用在下次 remote-sync 导入后出现在 Metabase——手动Pull changesAdmin → Data apps / Remote sync、自动导入轮询或重启——地址/apps/slug。不要主动部署应用也不要问 bundle 怎么到 staging 环境——没有独立部署步骤这个问题只会让用户困惑Metabase 在下次 sync 时从连接的仓库直接导入已提交的 bundle。变更一旦到达 Metabase 同步的分支合并 PR 或直接 push由用户决定只需告诉他们拉取并在 Metabase 打开/apps/slug。更新提交新构建再次拉取删除从仓库删除应用目录并 push——下次 sync 即移除。UI 上删不掉 repo 管理的应用Data apps admin 页的Remove动作只在断开仓库连接后才出现用于清理遗留应用。17. 常见坑速查表症状修法Failed to fetch the user, the session might be invalid.API key 或 CORS 错误——用仓库根.env.local里的凭据请求$DATA_APP_MB_URL/api/user/current验证source 文件、x-api-key头、只回显成败并在 SDK CORS origins 加http://localhost:5174图表标签不可见主题里设text-primary见第 11 节图表与实例其他图表风格不一、无视主题、无 tooltip/格式化/drill-through它是 React 手搓的——改用StaticQuestion/InteractiveQuestionvisualization见 13.2图表溢出容器给 SDK 组件传height/width见第 15 节应用背景中途结束、短内容下方裸白根元素给minHeight: 100vh见第 14 节运行时 Invalid hook call两份 React。dataAppConfig()外部化react——确认react/react-dom已安装且没有第二份或版本不匹配bundle 达数 MBReact/SDK 本应被契约插件外部化——确认vite.config.ts仍用dataAppConfig()且装了固定 tag 的 contenteditable="false">【免费下载链接】metabaseThe easy-to-use open source Business Intelligence and Embedded Analytics tool that lets everyone work with data :bar_chart:项目地址: https://gitcode.com/GitHub_Trending/me/metabase创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价