Metabase Embedding SDK 中的 SdkQuestionId 类型详解:数值 ID、实体 ID 与新建问题模式
【免费下载链接】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
SdkQuestionId 是 Metabase Embedding SDK 中用于标识 Question(问题)的统一类型,它同时覆盖"渲染已有问题"与"创建新问题"两种使用场景。本文以 SdkQuestionId.md 为骨架,结合InteractiveQuestion、StaticQuestion等组件的 Props 定义与前端源码实现,系统讲解该类型的四种取值来源、在 SDK 组件中的实际传参方式,以及"new"与"new-native"两种新建模式在源码中的处理逻辑,帮助你在嵌入应用里正确、灵活地指定要展示的问题。
一、类型定义:一个联合类型的四种身份
SdkQuestionId 的定义非常简洁,是一个 TypeScript 联合类型:
type SdkQuestionId = number | "new" | "new-native" | SdkEntityId;其中SdkEntityId是 Metabase SDK 定义的"品牌字符串"(branded string)类型,用于让普通字符串与实体 ID 在类型层面区分开:
type SdkEntityId = string & {};从类型结构可以看出,一个合法的SdkQuestionId只能是以下四种形态之一:
| 取值形态 | 含义 | 来源 |
|---|---|---|
number | 问题的数值 ID | 问题链接 URL,如http://localhost:3000/question/1-my-question中的1 |
string(实体 ID) | 问题的全局唯一字符串 ID | 问题对象上的entity_id字段,可通过 API 或 SDK 的 Collection Browser 获得 |
"new" | 打开 Notebook 编辑器,创建新的汇总型(notebook-style)问题 | SDK 保留字面量 |
"new-native" | 打开 SQL 编辑器,创建新的原生 SQL 问题 | SDK 保留字面量 |
官方示例
原文给出了四段覆盖全部取值形态的示例代码:
// Numerical ID from question URL const questionId: SdkQuestionId = 123; // Entity ID string const questionId: SdkQuestionId = "abc123def456"; // Create new notebook-style question const questionId: SdkQuestionId = "new"; // Create new native SQL question const questionId: SdkQuestionId = "new-native";二、数值 ID:从问题 URL 中提取
数值 ID 是最直观、最常见的问题标识方式。在 Metabase 中打开任意一个问题时,浏览器地址栏的 URL 形如:
http://localhost:3000/question/1-my-question其中 URL 路径中紧跟/question/的数字1就是该问题的数值 ID(后面的my-question只是便于阅读的 slug,不参与标识)。将questionId设为该数字,即可在嵌入组件中渲染这个已保存的问题。
在 InteractiveQuestionProps.md 与 StaticQuestionProps.md 中,questionId属性的类型均为SdkQuestionId | null,其说明原文确认了这种获取方式:
the numerical ID when accessing a question link, i.e.
http://localhost:3000/question/1-my-questionwhere the ID is1
三、实体 ID:字符串形式的稳定标识
除数值 ID 外,Metabase 的每个问题还拥有一个全局唯一的字符串实体 ID(entity ID)。获取它的途径有两种:
- 直接调用 Metabase REST API:问题对象的响应体中含有
entity_id键; - 使用 SDK 的 Collection Browser:通过
CollectionBrowser组件浏览集合并选择数据时,返回的问题数据中同样带有entity_id字段。
实体 ID 的好处是与数据库自增主键解耦,在跨环境迁移、序列化/反序列化(参考仓库中的 serialization.md 所描述的能力)等场景下更加稳定。使用时直接将实体 ID 字符串传给questionId即可,例如上文的"abc123def456"。
四、新建模式:"new"与"new-native"
"new"与"new-native"是 SDK 提供的两个特殊字面量,用于在嵌入应用中"从零开始创建问题",分别对应两种编辑器:
"new":展示 Notebook 编辑器,引导用户通过可视化的步骤(选表、汇总、筛选、分组等)构建查询问题;"new-native":展示原生 SQL 编辑器,让用户直接编写 SQL 查询。
当questionId为这两个值之一时,SDK 内部会把组件切换到"新建问题"的工作模式。这一点在源码中有明确的判据,见下文源码分析。
五、在 SDK 组件中如何使用:Props 一览
SdkQuestionId并非独立使用的 API,而是作为多个公开组件questionId属性的类型。目前仓库中直接引用它的组件 Props 包括:
- InteractiveQuestionProps.md:交互式问题组件(可编辑、可下钻、可保存),
questionId?: SdkQuestionId | null; - StaticQuestionProps.md:静态问题组件(轻量只读展示),
questionId?: SdkQuestionId | null; - SdkQuestionProps.md:交互式问题组件的基础 Props 类型,
questionId?: SdkQuestionId | null。
一个典型用法是配合InteractiveQuestion渲染一个已保存的问题:
import { InteractiveQuestion } from "@metabase/embedding-sdk-react"; export function RevenueQuestion() { return <InteractiveQuestion questionId={123} />; }而要在嵌入应用内"新建"问题,则只需把questionId换成"new"或"new-native":
// Notebook 编辑器 <InteractiveQuestion questionId="new" /> // 原生 SQL 编辑器 <InteractiveQuestion questionId="new-native" />与card/query/token的互斥关系
在 SdkQuestionEntityPublicProps.md 中可以看到,questionId与另外三个属性构成互斥联合(discriminated union):一次只能提供card、query、questionId、token四者之一(其余必须为never)。这意味着:
- 传
questionId时,不能再同时传card(临时问题定义)或query(由useMetabaseQueryObject创建的临时查询对象); - 反过来,
token形态(客座嵌入的 JWT 令牌模式)则由 SDK 内部解析出资源 ID,调用方无需传questionId。
这种设计让"渲染已保存问题(questionId)""渲染临时问题(card/query)""受令牌约束的问题(token)"三种模式在类型层面就被严格区分,避免调用方混淆。
六、源码级验证:"new"/"new-native"是如何被处理的
1. InteractiveQuestion 的入口判断
在 InteractiveQuestion.tsx 中,InteractiveQuestionInner会对questionId做归一化处理:当通过query属性渲染(例如 Metabot 的navigate_to跳转)时,没有传入questionId,此时会从反序列化后的 card 中推导出问题 ID,以保证原生查询能打开 SQL 编辑器。随后组件用如下代码判定"是否处于新建模式":
const isNewQuestion = resolvedQuestionId === "new" || resolvedQuestionId === "new-native";该布尔值进一步用于 SDK 组件挂载埋点(useTrackSdkComponentMount),区分id_new与id_new_native两种埋点场景,说明 SDK 在分析侧也将两种新建模式分开统计。
2. SdkQuestionProvider 的上下文处理
在 SdkQuestionProvider.tsx 中,传入的原始questionId会先经过useExtractResourceIdFromJwtToken处理:
const { resourceId: questionId, token, tokenError, } = useExtractResourceIdFromJwtToken({ isGuestEmbed, resourceId: rawQuestionId, ... });即:在客座嵌入(guest embed)场景下,若传入了 JWT 令牌,问题资源 ID 会从令牌中提取;否则直接使用调用方传入的rawQuestionId。提取之后,同一套判据再次出现:
const isNewQuestion = questionId === "new" || questionId === "new-native";后续的"创建问题"(useCreateQuestion)、"保存问题"(useSaveQuestion)等内部 hooks 都会依据这个标志走新建流程,从而让questionId="new"/"new-native"真正打开对应的编辑器并支持把新问题保存回 Metabase。
从上述两处源码可以看出,"new"与"new-native"不是 UI 层的魔法字符串,而是贯穿组件挂载、埋点、资源解析与创建/保存流程的核心分支条件。
七、实践建议与注意事项
- 两种 ID 的取舍:临时嵌入、ID 不会跨环境迁移时,直接使用 URL 中的数值 ID 最简单;需要长期稳定引用、或通过 API/Collection Browser 获取数据时,优先使用
entity_id字符串。 - 新建模式与保存能力配合:使用
"new"/"new-native"进入新建模式后,建议配合isSaveEnabled(控制是否显示保存按钮)与targetCollection(保存目标集合)等 Props 一起使用,才能形成"新建 → 编辑 → 保存"的完整闭环,相关属性说明见 SdkQuestionProps.md。 - 不要混用互斥属性:
questionId与card、query互斥,同时传入会被 TypeScript 的联合类型直接拦截;这也是SdkQuestionEntityPublicProps将对应字段声明为never的原因。 - 客座嵌入注意:在通过
token(JWT 客座嵌入)渲染问题时,资源 ID 由令牌决定,通常不需要再显式传questionId。
八、相关 API 索引
SdkQuestionId 属于 Embedding SDK 公开 API 类型体系的一部分。在 API 索引 中,与它直接相关的类型与组件包括:
- SdkEntityId:实体 ID 的品牌字符串类型;
- SdkQuestionEntityPublicProps:
questionId/card/query/token互斥联合; - InteractiveQuestionProps 与 StaticQuestionProps:消费
SdkQuestionId的组件 Props; - SdkQuestionProps:交互式问题组件完整 Props;
- 前端实现参考:InteractiveQuestion.tsx、SdkQuestionProvider.tsx。
理解SdkQuestionId的四种形态,是正确使用InteractiveQuestion、StaticQuestion等 SDK 问题组件的第一步——无论是展示已有分析、还是把"新建问题"的能力直接嵌入到你的产品中。
【免费下载链接】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),仅供参考