十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

PostHog Dashboard Widget 配置契约与代码生成:从 Pydantic 单一事实源到前端 Zod 的全链路指南

PostHog Dashboard Widget 配置契约与代码生成:从 Pydantic 单一事实源到前端 Zod 的全链路指南 PostHog Dashboard Widget 配置契约与代码生成从 Pydantic 单一事实源到前端 Zod 的全链路指南【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthogPostHog 仪表盘 WidgetDashboard Widget的widget.config形状由一套单一事实源Single Source of TruthSSOT机制统一驱动后端 Pydantic 模型负责运行时校验并通过 OpenAPI 桥接 REST 接口与 MCP 工具最终自动生成前端 Zod schema。本文基于仓库内 config-and-codegen.md 展开结合products/dashboards/backend/widget_specs/下的真实实现讲清楚整条契约链路配置模型写在哪里、如何注入 OpenAPI、如何生成前端类型、CI 如何防漂移以及新增widget_type时必须绕开的坑。读完本文你将掌握widget 配置模型的正确改动流程改 Pydantic → 跑hogli build:openapi→ 提交生成产物、多态 OpenAPI 与 Zod codegen 的底层原理、枚举名冲突的诊断与修复以及 8 个高频踩坑-修复对照。何时需要关心这份契约这份契约是 widget 配置体系的核心枢纽以下任意场景都会触发对它的一次完整消费Pydantic 配置变更修改了widget_specs/configs.py中某个*WidgetConfig的字段hogli build:openapi需要重新生成 OpenAPI 与前端类型Zod/OpenAPI 漂移CI 的 schema 一致性测试失败ENUM_NAME_OVERRIDES新增widget_type后枚举名冲突MCPconfig_schema更新Agent 工具侧拿到的配置形状需要刷新。平台整体文件地图见 architecture.md新类型上线清单见 checklist-new-widget-type.md 第 1–4 节存量类型迁移路径见 managing-existing-widgets.md 的 Config schema migration 一节。Config contractwidget_specs/是唯一的契约源头后端 Pydantic 模型是整个契约链的起点它同时驱动运行时校验、REST/MCP 的 OpenAPI 文档以及前端 Zod 代码生成。整个链路如下widget_specs/configs.py Pydantic *WidgetConfig per type ( shared common.py) │ ├─► pydantic_openapi.py injects model_json_schema() into OpenAPI components (no DRF bridge) ├─► openapi.py polymorphic batch-add / PATCH / catalog OpenAPI (auto from WIDGET_SPECS) ├─► registry.py WIDGET_SPECS manifest validate_widget_config() (config, catalog labels, run_*) └─► widget_catalog.py config_schema model_json_schema() (for agents) bin/build-dashboard-widget-types.py (hogli build:widget-types — step 1) ├─► widget-date-from-options.json date preset values labels (from constants.py) └─► widget-form-fields.json modal .pick() fields (from WidgetSpec.form_fields) generate-widget-config-zod.mjs (hogli build:widget-types — step 2) ├─► widget-config-property-keys.json per-type keys/trees via discoverCatalogEntryConfigPropertyKeys() └─► Orval generateReusableSchemas (catalog slice → widget-config-schemas/*.zod.ts) hogli build:openapi ├─► frontend/generated/api.schemas.ts ├─► products/dashboards/frontend/generated/widget-configs.zod.ts (schemas, types, form picks) └─► services/mcp/...后端各文件职责文件职责configs.py每种 widget 类型的 Pydantic 配置模型 ——字段变更优先在这里改common.py共享的dateRange、widgetFilters、filterTestAccounts等基础模型registry.pyWIDGET_SPECS清单 validate_widget_config()—— 每种类型的 manifestPydantic 模型、run_*查询函数、scopes、RBAC、Agent 目录标签与可用性widgets/config.py仅查询期使用 ——resolve_filter_test_accounts(config, team)校验逻辑在 Pydantic 侧openapi.py批量添加、目录config_schema、dashboard PATCH 的多态 OpenAPI —— 完全由WIDGET_SPECS自动构建无需按类型手写api/widget_openapi_serializers.py供dashboard.api导入的稳定再导出层实现位于widget_specs/openapi.py前端配置分层禁止手工复制整份 schema文件职责generated/widget-config-schemas/*.zod.ts每个组件一个的 Orval Zod如ErrorTrackingListWidgetConfig、共享的WidgetDateRange等generated/widget-configs.zod.ts友好的再导出、推断类型、表单.pick()schema由hogli build:widget-types生成generated/widget-config-property-keys.json每种类型顶层配置键清单取自目录 OpenAPI 切片由generate-widget-config-zod.mjs生成generated/widget-date-from-options.json来自constants.py的日期预设 value label 对由build-dashboard-widget-types.py生成generated/widget-form-fields.json每种 widget 的弹窗字段清单取自WidgetSpec.form_fields由build-dashboard-widget-types.py生成widgets/widgetConfigValidation.ts共享的 HogQL 过滤器辅助函数 parseWidgetConfigApiError——不是按类型的 schemawidget_types/widgetConfigShared.ts从生成的 JSON 再导出日期选择选项 resolveWidgetFilterTestAccountswidgets/*/*WidgetConfigValidation.ts导入生成的表单 schema仅做 API 错误解析与校验逻辑同目录widget_types/catalog.ts手写标签、布局、经由生成 Zod 的defaultConfig预览见widgets/previews/dashboardWidgetPreviews.ts新增类型的默认值参考widget-intake.md 的 Defaults 一节。源码视角manifest 的真实形状后端清单定义在 products/dashboards/backend/widget_specs/registry.py。WidgetSpec是一个 frozen dataclass包含widget_type、config_modelPydantic 模型类、query_fn懒加载的run_*函数、required_scopes、group_id/group_label、label/descriptionAgent 目录文案、required_product_accessRBAC、availability_requirements前置条件 flag、form_fields弹窗字段、filter_fields参与widget 过滤器变更埋点的字段等dataclass(frozenTrue) class WidgetSpec: widget_type: str config_model: type[BaseModel] query_fn: Callable[..., dict[str, Any]] required_scopes: tuple[str, ...] group_id: str group_label: str label: str description: str required_product_access: str | None product_access_denied_message: str | None availability_requirements: tuple[str, ...] form_fields: tuple[str, ...] filter_fields: tuple[str, ...] is_live: bool False # 实时 widget一次性 SEED客户端自刷新禁止 dateRange/filterTestAccounts creation_flag: str | None None # 仅新增的灰度 gate两个值得注意的实现细节实时 widgetlive的强约束__post_init__会检查is_liveTrue的类型是否在配置模型里引入了dateRange或filterTestAccounts见_LIVE_FORBIDDEN_CONFIG_FIELDS一旦出现就抛ValueError—— 因为实时流无法应用测试账号过滤窗口固定为实时。校验是纯 Pydanticvalidate_widget_config()先查WIDGET_SPECS未注册类型直接抛 DRFValidationError然后config_model.model_validate(config)失败时把每个loc与msg拼成一条人类可读的config错误成功则model_dump(modejson, exclude_noneTrue)归一化输出。EXPECTED_WIDGET_TYPES直接由WIDGET_SPECS.keys()派生frozenset因此类型清单永远和注册表一致不需要手工维护第二份列表。共享基础模型common.pyproducts/dashboards/backend/widget_specs/common.py 定义了跨类型复用的模型WidgetDateRange仅含date_from取值必须是预设相对区间extraforbid拒绝未知字段。WidgetFilterEntry单个属性过滤项包含filterId、propertyName、optionId、operator取自posthog.schema.PropertyOperator、value字符串/字符串数组/空并要求列表值全为字符串。WidgetListConfigBase列表类 widget 的公共基类 ——filterTestAccounts布尔、widgetFiltersdict[str, WidgetFilterEntry]key 必须与filterId一致。三个带边界的 limit 类型WidgetLimit1–25、ActivityWidgetLimit1–50、LogsWidgetLimit1–100上限常量定义在 products/dashboards/backend/constants.py。日期预设的可选值WIDGET_DATE_FROM_VALUES_ORDERED同样在constants.py-1M1 分钟、-30M、-1h、-3h、-24h、-7d、-14d、-30d、-90d—— 注意注释里专门提醒M是分钟、m才是月。这些常量同时是widget-date-from-options.json的输入保证前后端选项完全一致。每种类型的配置模型configs.pyproducts/dashboards/backend/widget_specs/configs.py 按类型定义具体模型目前包含 8 种类型对应DashboardWidgetTypeLiteralwidget_type配置模型关键字段默认值activity_events_listActivityEventsListWidgetConfiglimit默认 25、eventName、properties最多 20 个过滤含 key/label 长度与 value 长度约束error_tracking_listErrorTrackingListWidgetConfiglimit默认 10、orderByoccurrences、orderDirectionDESC、statusactive、assigneesession_replay_listSessionReplayListWidgetConfiglimit、orderBystart_time、savedFilterId、collectionId引用已保存过滤器/合集的short_idexperiments_listExperimentsListWidgetConfiglimit、orderBycreated_at、statusall、createdByexperiment_resultsExperimentResultsWidgetConfigexperimentId空直到用户在设置里选择survey_resultsSurveyResultsWidgetConfigsurveyId、dateRange空 全部时间、limitlogs_listLogsListWidgetConfiglimit默认 50、orderBylatest、severityLevels、serviceNames、wrapLines、timezoneUTC/local、savedViewIdconversations_recent_ticketsConversationsRecentTicketsWidgetConfiglimit、statusall、priorities、channel、assignees支持me/unassigned/{id,type}、search≤200 字符、savedViewId模型都开启extraforbid非法字段会被拒绝。枚举值如ErrorTrackingOrderBy、LogSeverityLevel、WidgetOrderDirection的ASC/DESC都用 Literal 表达因此会直接出现在 OpenAPI 的enum与 Zod 联合类型里让 Agent 拿到的是边界与选项而非裸默认值。Codegen 与 CI一条命令串起全部生成没有独立的 widget codegen 步骤—— 全部由一条命令完成hogli build:openapi # openapi-schema → build:widget-types → openapi-types → MCPWidget 配置的 Zod 是产品级作用域的products/dashboards/frontend/bin/generate-widget-config-zod.mjs用filterSchemaByOperationIds从目录 OpenAPI 操作dashboards_widget_catalog_retrieveincludeResponseSchemas: true里切出 catalog 片段再调用tools/openapi-codegen中的 Orval要求 8.14并开启generateReusableSchemas: true产物落到generated/widget-config-schemas/再在widget-configs.zod.ts里聚合友好导出 —— 这与frontend/bin/generate-openapi-types.mjs全量 API 类型生成是两条独立流水线。关键约束OpenAPI 必须暴露一个非空的DashboardWidgetConfigoneOf由 pydantic_openapi.py 注入否则 Orval 会生成一个空的 TS union 类型前端直接失去类型保障。Pydantic → OpenAPI 的注入原理pydantic_openapi.py 是整个桥接的无 DRF 中间层实现pydantic_model_to_openapi_components()调用model.model_json_schema(modeserialization)把 Pydantic 的$defs提升为具名 OpenAPI 组件并把#/$defs/引用重写为#/components/schemas/pydantic_stub_serializer()生成一个空的 serializer 外壳schema 内容由后处理注入DRF 本身不参与pydantic_config_field()返回一个 OpenAPI 形状为$ref的JSONFieldinject_widget_spec_pydantic_components()是 drf-spectacular 的POSTPROCESSING_HOOKS入口遍历WIDGET_SPECS注入每个config_model的组件并用所有配置模型的$ref组装DashboardWidgetConfig {oneOf: [...]}。若注入时发现同名组件已存在且内容不同如PropertyOperator与/queryPydantic 路径撞名会通过spectacular_warn告警 —— 该告警计入GENERATOR_STATS在--fail-on-warn下会直接打断构建提示你重命名模型。多态序列化层在 openapi.py_build_openapi_serializers()为每个类型动态构造三类 serializer —— 配置序列化器*OpenApiSerializer、批量添加请求{prefix}AddRequestOpenApiSerializer含单值widget_typeChoiceField config、目录条目{prefix}CatalogEntryOpenApiSerializer含config_schema与live标志再组合成AddDashboardWidgetRequestOpenApi、UpdateDashboardWidgetRequestOpenApi、WidgetCatalogEntryOpenApi等PolymorphicProxySerializer。PatchedDashboardOpenApiSerializer则定义了 dashboard PATCH 的 OpenAPI-only body含嵌套的tiles[].widget.config。这些全部由WIDGET_SPECS自动生成没有任何按类型的手写接线。本地开发与 CI 流程本地开发Vite 读取的是products/dashboards/frontend/generated/下已提交的文件 ——hogli up或保存时不会自动重新生成。改动widget_specs/或序列化器之后需要手动跑hogli build:openapi并提交生成差异。没有 pre-commit hook 兜底。CIci-backend.yml中的check-openapi-types执行同样的hogli build:openapi然后 diff 生成产物。同仓库 PR 可能自动提交漂移fork PR 和未推送的修复会以runhogli build:openapilocally失败。触发器覆盖products/**/backend/**含widget_specs/和products/*/frontend/generated/**。新增widget_type在widget_specs/configs.py中按*ListWidgetConfig→*WidgetConfig的命名约定新增 Pydantic 模型即可 ——build:widget-types会自动推导 Orval 导出名如果 OpenAPI 切片里缺了该模型就会失败。Schema 生成阻塞点枚举名冲突build:openapi-schema启用了--fail-on-warn。多态按类型序列化器各自使用单例ChoiceField表示widget_type而dashboard.py使用完整的EXPECTED_WIDGET_TYPES列表 —— 这会让 drf-spectacular 的枚举名发生碰撞。发新类型时必须给posthog/settings/web.py中的ENUM_NAME_OVERRIDES加上{YourWidgetTypeEnum: [your_widget_type]}该配置位于 posthog/settings/web.py 第 554 行附近注释里同样指引用find_enum_collisions诊断。双保险验证hogli build:widget-types和test_widget_openapi_enums.py会在注册表类型缺少 override 时失败spectacular 碰撞测试在 override 哈希错误时失败。诊断命令python manage.py find_enum_collisions # 逻辑在 posthog/openapi/enum_collisions.pySchema 一致性测试便宜的漂移守卫改动widget_specs/时至少跑这两条hogli test products/dashboards/backend/api/test/test_widget_config_schema_parity.py hogli test products/dashboards/frontend/widgets/widgetConfigSchemaParity.test.ts后端侧校验目录config_schema与 Pydantic JSON schema 一致前端侧校验 Zod 配置顶层键与后端属性映射widget-config-property-keys.json一致。更多 CI 类型安全网注册表 ↔ catalog ↔ 序列化器数量、dashboard PATCH OpenAPI ⊆ 运行时可写字段、前端DASHBOARD_WIDGET_REGISTRY satisfies Record…等见 architecture.md 的 CI 一节。Footguns配置与代码生成的常见陷阱常见错误修复方法加 widget 字段时瘦身PatchedDashboardOpenApiSerializerextend_schema(request...)会整体替换PATCH schema —— 应当扩展类绝不重写。CItest_dashboard_openapi.py会把运行时DashboardSerializer可写字段扣除api/test/dashboard_openapi_test_helpers.py中的排除项与 serializer spectacular 输出对比MCP 测试把dashboard-updateschema 链到DashboardsPartialUpdateBody在 dashboard PATCH 上放嵌套的按类型widget 配置序列化器运行时序列化器上保持 tileconfig为JSONField—— 类型化 OpenAPI 只存在于widget_specs/openapi.pyPatchedDashboardOpenApiSerializer手写前端 Zod 配置 schema统一走 codegen 生成widget-configs.zod.ts在registry.py的WidgetSpec上加 Pydantic*WidgetConfigform_fields在 widget 配置里导入共享的posthog.schema模型优先在configs.py用本地 Pydantic 模型如WidgetAssigneeFilter—— 避免 spectacular 组件名冲突手工重复 catalogconfig_schema后端目录用config_model.model_json_schema()—— Agent 拿到的应是边界/选项/描述而不只是默认值改生成的 Zod/TS 但不重新生成跑hogli build:openapi提交products/dashboards/frontend/generated/*—— CIcheck-openapi-types会 diff失败或自动提交hogli build:openapi-schema因警告失败--fail-on-warn所致 —— 用find_enum_collisionsENUM_NAME_OVERRIDES修复详见上文 Codegen 与 CI 一节推荐的新类型落地路径在 configs.py 定义YourWidgetConfig继承WidgetListConfigBase或WidgetDateRangeConfigBaseextraforbid用带边界的 Annotated limit在 registry.py 的WIDGET_SPECS注册WidgetSpec含query_fn懒导入、scopes、RBAC、catalog 文案、form_fields/filter_fields在posthog/settings/web.py的ENUM_NAME_OVERRIDES补上枚举映射跑hogli build:openapi生成 OpenAPI widget-configs.zod.ts MCP schema提交全部生成产物跑两条 schema parity 测试确认无漂移前端在widgets/registry.tsx注册Component/EditModal并在widget_types/catalog.ts补目录条目布局、默认值、预览见 architecture.md 的 reference implementation。全程遵循后端 Pydantic 是唯一事实源、codegen 是唯一写入路径、CI 是最后一道防线三条铁律widget 配置体系就能始终在运行时校验、REST/MCP OpenAPI 与前端 Zod 之间保持严格一致。【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthog创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表