Metabase 嵌入 SDK 全局插件配置:MetabaseGlobalPluginsConfig 类型详解与实战
【免费下载链接】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
导读
MetabaseGlobalPluginsConfig是 Metabase 模块化嵌入 SDK(Embedded Analytics SDK)中定义全局插件配置的 TypeScript 类型,它决定了嵌入应用在「链接点击处理」「无数据/无对象占位插图」两个维度上如何被宿主应用定制。本篇以该类型为骨架,结合仓库内的类型定义、运行时实现与官方示例,讲解pluginsConfig的挂载位置、三个可配置函数签名与返回约定、作用域限制,并给出可直接复制运行的 React 代码。读完本文,你将能独立完成嵌入应用中链接拦截与空状态品牌化定制。
一、类型定义总览:从源码到文档
在文档 MetabaseGlobalPluginsConfig.md 中,该类型的完整声明为:
type MetabaseGlobalPluginsConfig = MetabasePluginsConfig & { getNoDataIllustration?: () => string | null | undefined; getNoObjectIllustration?: () => string | null | undefined; handleLink?: (url: string) => { handled: boolean; }; };其底层源码位于 frontend/src/embedding-sdk-bundle/types/plugins.ts,与文档完全一致,并补充了两条关键 JSDoc 注释:
getNoDataIllustration与getNoObjectIllustration的返回值是base64 编码的图片字符串,返回null则回退到默认插图;handleLink接收 URL 字符串,返回{ handled: boolean }。
继承的基类型 MetabasePluginsConfig
全局插件类型通过&继承了组件级插件的基类型MetabasePluginsConfig(见 MetabasePluginsConfig.md 与 plugins.ts):
type MetabasePluginsConfig = { dashboard?: MetabaseDashboardPluginsConfig; mapQuestionClickActions?: MetabaseClickActionPluginsConfig; };| 属性 | 类型 | 用途 |
|---|---|---|
dashboard? | MetabaseDashboardPluginsConfig | 自定义仪表盘卡片菜单(dashboardCardMenu) |
mapQuestionClickActions? | MetabaseClickActionPluginsConfig | 定制图表/仪表盘数据点点击行为 |
因此MetabaseGlobalPluginsConfig实际可配置五个字段:上述两个继承字段,加上本文重点的三个全局字段。两者的作用域差异见下文「插件作用域」一节。
二、挂载位置:pluginsConfig 与 MetabaseProvider
全局插件必须挂载在<MetabaseProvider>的pluginsConfigprop 上。该 prop 的类型即MetabaseGlobalPluginsConfig,这可以从 SDK API 文档 MetabaseProviderProps.md 中确认:
pluginsConfig?:MetabaseGlobalPluginsConfig— See Plugins.
一个最小挂载示例(源码示例见 global-plugins.tsx):
import type { PropsWithChildren } from "react"; import { type MetabaseAuthConfig, MetabaseProvider, type MetabaseTheme, } from "@metabase/embedding-sdk-react"; const authConfig = {} as MetabaseAuthConfig; const theme = {} as MetabaseTheme; const Example = ({ children }: PropsWithChildren) => ( <MetabaseProvider authConfig={authConfig} theme={theme} pluginsConfig={{ mapQuestionClickActions: () => [], // 在此添加自定义点击行为 }} > {children} </MetabaseProvider> );运行时如何读取全局插件
从源码结构看,MetabaseProvider与 SDK 的大部分代码分属不同的 bundle(npm 包与 SDK bundle),无法直接共享一个导出对象作为单例。因此仓库在 sdk-global-plugins.ts 中通过ensureMetabaseProviderPropsStore建立了一个挂在window上的单例 store(key 为METABASE_PROVIDER_PROPS_STORE,见 ensure-metabase-provider-props-store.ts):
export const getSdkGlobalPlugins = (): SdkGlobalPlugins => { return ( ensureMetabaseProviderPropsStore<SdkGlobalPluginsProps>().getState().props ?.pluginsConfig || {} ); };即:<MetabaseProvider>收到的pluginsConfig会被写入全局单例 store,SDK 各组件通过getSdkGlobalPlugins()跨 bundle 读取。值得注意的是,SDK 对外要求同步函数,而内部实际处理链路是异步的(类型定义同时兼容两种签名,见 sdk-global-plugins.ts 中的HandleLinkFn)。
三、handleLink:拦截嵌入内容中的链接点击
签名与返回值约定
handleLink?: (url: string) => { handled: boolean; };- 入参
url:用户点击的链接字符串; - 返回值
{ handled: true }:宿主应用接管了该链接,阻止 SDK 默认导航行为; - 返回值
{ handled: false }:不接管,恢复 SDK 默认行为(默认在新标签页打开链接)。
完整示例:内部链接交给路由,外部链接保持默认
仓库官方示例 handlelink.tsx 展示了最典型的使用场景——把内部链接交给宿主应用自己的路由器处理:
import { InteractiveDashboard, type MetabaseAuthConfig, MetabaseProvider, } from "@metabase/embedding-sdk-react"; const authConfig = {} as MetabaseAuthConfig; export default function App() { const plugins = { handleLink: (urlString: string) => { const url = new URL(urlString, window.location.origin); const isInternal = url.origin === window.location.origin; if (isInternal) { // 处理内部导航(例如交给你的 router) console.log("Navigate to:", url.pathname + url.search + url.hash); return { handled: true }; // 阻止默认导航 } return { handled: false }; // 让 SDK 执行默认行为 }, }; return ( <MetabaseProvider authConfig={authConfig} pluginsConfig={plugins}> <InteractiveDashboard dashboardId={1} /> </MetabaseProvider> ); }该示例的实践意义:嵌入场景中,点击内部链接默认会在新标签页打开,体验割裂;通过handleLink结合window.location.origin判断同源,可将内部链接改走宿主应用的客户端路由(例如 React Router),同时放行外部链接走默认新标签页。
底层调用链:openUrl 如何遵守插件约定
handleLink并非孤立 API,它被接入了 Metabase 全局的 URL 打开工具链。在 frontend/src/metabase/urls/open-url.ts 中:
/** * Opens a URL using the most appropriate strategy: in the current window, * a new tab, or via client-side navigation when it's an in-app Metabase URL. * Honours the embedding SDK's `handleLink` plugin if installed. */ export async function openUrl(url: string, {...}): Promise<void> { url = ignoreSiteUrl ? url : getWithSiteUrl(url); // In the sdk, allow the host app to override how to open links if (isEmbeddingSdk()) { const result = await handleLinkSdkPlugin(url); if (result.handled) { // Plugin handled the link, don't continue with default behavior return; } } // ...否则按默认策略打开(新窗口 / 同源客户端导航 / 当前窗口) }调用链可概括为:openUrl→handleLinkSdkPlugin(见 sdk-global-plugins.ts,内部调用默认的MODULAR_EMBEDDING_HANDLE_LINK_PLUGIN,其默认实现为(_url) => Promise.resolve({ handled: false }))→ 读取宿主传入的handleLink。因此默认情况下链接行为不受影响,只有宿主显式配置了插件才会被拦截。handleLinkSdkPlugin也是该插件模块的核心导出,在 SDK 的链接渲染组件(如表单中的链接渲染)中被复用。
作用域限制与 Modular Embedding 的等价 API
根据 plugins.md:
handleLink只能全局使用(provider 级别),不能在单个组件上通过pluginsprop 使用;- 在 Modular Embedding(无 React 的模块化嵌入)中,
handleLink同样可用,通过defineMetabaseConfig的pluginsConfig传入,API 完全一致(可参考 MetabaseProvider.ts 的 API 文档); - 若想在表格列中产生可点击链接,需将列的格式设置为「以链接形式显示」(display as link)。
四、getNoDataIllustration 与 getNoObjectIllustration:定制空状态插图
签名与返回值约定
getNoDataIllustration?: () => string | null | undefined; getNoObjectIllustration?: () => string | null | undefined;- 返回base64 编码的图片字符串,作为空状态占位插图;
- 返回
null或undefined时回退到默认插图。
两者语义区别
根据 loading-and-errors.md 的官方说明:
getNoDataIllustration:覆盖「查询返回零行」的场景,即图表/表格无数据时的提示图;getNoObjectIllustration:覆盖「搜索无结果」的场景,例如搜索页面、实体选择器(entity picker)中找不到任何匹配项,或没有仪表盘、没有集合等对象为空的情况。
默认情况下,Metabase 在这两种场景展示一张帆船(sailboat)插图。
完整示例
官方示例 custom-images.tsx:
import { InteractiveDashboard, type MetabaseAuthConfig, MetabaseProvider, } from "@metabase/embedding-sdk-react"; const authConfig = {} as MetabaseAuthConfig; export default function App() { const img_base64 = "..."; // base64-encoded image const plugins = { getNoDataIllustration: () => img_base64, getNoObjectIllustration: () => img_base64, }; return ( <MetabaseProvider authConfig={authConfig} pluginsConfig={plugins}> <InteractiveDashboard dashboardId={1} /> </MetabaseProvider> ); }与loaderComponent、errorComponent这类「组件 prop」不同,这两个配置项属于插件(plugins),必须放进pluginsConfig,且返回的是 base64 图片字符串而不是 React 组件。两者都只能全局设置(provider 级别)。
渲染链路:从插件到 UI
从源码看,这两个插件的消费链路是「选择器 → 错误组件 → 渲染」:
- 选择器定义于 frontend/src/metabase/selectors/whitelabel/index.ts:
export function getNoDataIllustration(state: State) { return PLUGIN_SELECTORS.getNoDataIllustration(state); } export function getNoObjectIllustration(state: State) { return PLUGIN_SELECTORS.getNoObjectIllustration(state); }- 默认实现位于 frontend/src/metabase/plugins/oss/core.ts,两者默认都返回
noResultsSource(即默认的帆船插图资源):
getNoDataIllustration: (_state: State): string | null => { return noResultsSource; }, getNoObjectIllustration: (_state: State): string | null => { return noResultsSource; },- 消费组件分别是 NoDataError.tsx 与 NoObjectError.tsx。两者结构一致:通过
useSelector(getNoDataIllustration / getNoObjectIllustration)读取插件返回值,非空时渲染为 120×120 的<Image>(alt 文本为「No results」),为空则渲染null。
export function NoDataError(props: ImageProps) { const noDataIllustration = useSelector(getNoDataIllustration); return noDataIllustration ? ( <Image alt={t`No results`} w={120} h={120} src={noDataIllustration} {...props} /> ) : null; }- 在嵌入 SDK 侧,InteractiveDashboard 的 props 校验 schema 也显式允许这两个字段(见 InteractiveDashboard.schema.ts):
plugins: Yup.object({ mapQuestionClickActions: Yup.mixed().optional(), dashboard: Yup.mixed().optional(), getNoDataIllustration: Yup.mixed().optional(), getNoObjectIllustration: Yup.mixed().optional(), }) .optional() .noUnknown(),这也验证了:这两个插图插件在 SDK 中被视为与mapQuestionClickActions、dashboard同级的全局插件配置项。
五、插件作用域总结:全局 vs 组件级
根据 plugins.md,Metabase SDK 插件分两种挂载方式:
| 插件 | 全局(providerpluginsConfig) | 组件级(组件pluginsprop) |
|---|---|---|
handleLink | ✅ 仅限全局 | ❌ |
getNoDataIllustration | ✅ 仅限全局 | ❌ |
getNoObjectIllustration | ✅ 仅限全局 | ❌ |
mapQuestionClickActions | ✅ | ✅ |
dashboard(卡片菜单) | ✅ | ✅(按组件) |
组件级插件的挂载示例(见 component-plugins.tsx):
import { InteractiveQuestion } from "@metabase/embedding-sdk-react"; const Example = () => ( <InteractiveQuestion questionId={1} plugins={{ mapQuestionClickActions: () => [], }} /> );因此,本文三个核心字段的共同特征是「全局生效、provider 挂载」——这正是MetabaseGlobalPluginsConfig中「Global」一词的含义:它们影响的是整个嵌入应用的行为与观感,而不是单个组件。
六、小结与进一步阅读
MetabaseGlobalPluginsConfig是嵌入 SDK 全局定制的类型入口:
handleLink让宿主应用接管嵌入内容中的链接导航,实现同源路由跳转、弹窗打开等自定义策略,底层由openUrl调用链统一遵守(open-url.ts);getNoDataIllustration/getNoObjectIllustration用 base64 图片替换默认帆船插图,覆盖「查询无数据」与「搜索无对象」两种空状态,渲染链路经过白标选择器(whitelabel/index.ts)与错误组件(NoDataError.tsx、NoObjectError.tsx)。
若需继续深入,可阅读仓库内以下文档:
- 插件总览与作用域:docs/embedding/sdk/plugins.md
- 空状态与加载、错误定制:docs/embedding/sdk/loading-and-errors.md
- 基类型
MetabasePluginsConfig:docs/embedding/sdk/api/snippets/MetabasePluginsConfig.md - 图表点击行为定制(
mapQuestionClickActions):docs/embedding/sdk/chart.md - 仪表盘卡片菜单定制(
dashboard.dashboardCardMenu):docs/embedding/sdk/dashboard.md
【免费下载链接】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),仅供参考