ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

OpenMetadata Conversation V2 端到端可追溯性矩阵:从旧版 Feed 行为迁移到新对话与活动体系

2026/9/14 13:12:56 拓冰建站 浏览量
OpenMetadata Conversation V2 端到端可追溯性矩阵:从旧版 Feed 行为迁移到新对话与活动体系 OpenMetadata Conversation V2 端到端可追溯性矩阵从旧版 Feed 行为迁移到新对话与活动体系【免费下载链接】OpenMetadataThe Open Context Layer for Data and AI , OpenMetadata is the open platform for building trusted data context and business semantics for humans, AI assistants, and agents.项目地址: https://gitcode.com/GitHub_Trending/op/OpenMetadata导读本文以 OpenMetadata 仓库中的 CONVERSATION_V2_TRACEABILITY.md 为主线系统梳理 Conversation V2 端点切换endpoint cutover后旧的 legacy feed 行为如何在新的/conversations与/activity路由体系中找到等价替代测试。你将看到一张完整的旧行为 → 新测试映射表理解 keyset 分页cursor 分页、有界回复水合bounded hydration、首条/后续回复去重no synthetic root / no duplicate POST等关键机制的落地方式并学会如何阅读与扩展这套可追溯性矩阵防止测试随套件改名而悄悄消失。为什么需要一份 Conversation V2 可追溯性矩阵在 OpenMetadata 的 UI 演进过程中活动流Activity Feed长期依赖旧的 feed 端点而 Conversation V2 将用户会话user conversation、活动activity与公告announcement拆分为独立的路由体系。端点切换后一个最现实的风险是旧的测试套件被重命名或删除测试覆盖率在无声无息中丢失。为此仓库在 CONVERSATION_V2_TRACEABILITY.md 中维护了一张逐行映射矩阵明确记录每一个可观察的 legacy feed 行为由哪个新测试承担哪些旧场景被移除以及被哪些确定性场景替代。这份矩阵的本质是一份行为契约即使测试文件名变了行为必须仍然存在且被某个明确的位置覆盖。它同时回答了三个问题谁在测、测什么、旧行为去哪了。从源码结构看新体系分为两大路由族/conversations用户会话的根root、回复reply、反应reaction、解决resolve、编辑edit、删除delete与 keyset 分页见 ConversationResourceIT.java服务端集成测试与 conversationsAPI.test.ts前端 REST 客户端单测/activity活动事件activity event的展示、首条/后续回复、编辑/反应/删除以及同about隔离见 ActivityAPI.spec.tsPlaywright E2E与 activityAPI.test.ts前端 REST 客户端单测。公告announcement场景则继续沿用/announcements路由不受本次切换影响。完整映射矩阵每个旧行为对应的 Conversation V2 替代测试下表完整继承自原文档逐行说明旧行为/旧测试与Conversation V2 替代测试的对应关系。阅读时建议配合后文的分层验证体系理解每个单元格的实际含义。Legacy behavior or test旧行为或测试Conversation V2 replacementConversation V2 替代测试Selected root, bounded replies, and reply count选中根节点、有界回复、回复计数ActivityThreadPanelBody.test.tsxrenders the selected conversation with its hydrated repliesConversationResourceITtestCompleteConversationCrudAndBoundedHydrationEmpty/create/select conversation states空态/创建/选中会话ActivityThreadPanelBody.test.tsxlists and selects conversations using Conversation V2和creates a conversation and updates the bounded listConversation keyset pagination会话 keyset 分页Features/ContextCenterArticles.spec.ts等待携带 cursor 的第二次请求及全部 11 条种子根ActivityThreadPanelBody.test.tsx与ConversationResourceIT覆盖 cursor 转发与 root/reply cursor 正确性Landing-page widget rendering, navigation, filters, footer, and card structure落地页组件渲染、导航、过滤器、页脚、卡片结构Features/ActivityFeed.spec.tsActivity Feed Widget场景Features/ActivityAPI.spec.tsHomepage Widget场景User-conversation root reaction and tooltip identity用户会话根反应与 tooltip 身份Features/ContextCenterArticles.spec.tsRelated assets, activity feed, user mentions, and article mentions workUser-conversation resolve, edit, and delete用户会话解决/编辑/删除同一个 Context Center 场景通过/conversations/{id}执行并逐一校验每次变更User-conversation drawer reply creation抽屉回复创建Features/ActivityFeed.spec.tsthread drawer opens from reply count and allows posting a replyMention notification identity and navigation提及通知身份与导航Features/ActivityFeed.spec.tsMention notification shows correct user details in Notification boxChinese mention encoding中文提及编码Features/ActivityFeed.spec.tsShould encode the chinese character while mentioning api endpointHomepage and entity All/My Data/Following filters首页与实体的全部/我的数据/关注过滤器Features/Tasks/ActivityFeed.spec.ts使用 activity 专用路由Task filters, badge, drawer, and navigation任务过滤器、徽标、抽屉、导航Features/Tasks/ActivityFeed.spec.ts使用/tasks路由Context Center conversation entry point and permissionsContext Center 会话入口与权限Features/ContextCenterArticles.spec.ts与Features/ContextCenterPermission.spec.ts使用/conversations路由Announcement scenarios公告场景现有公告套件继续通过/announcementsFirst and subsequent activity replies; no synthetic root or duplicate POST首条与后续活动回复无合成根、无重复 POSTFeatures/ActivityAPI.spec.tscreates exactly one reply and isolates activities with the same aboutActivity reply edit, reaction tooltip identity, and delete活动回复编辑、反应 tooltip 身份、删除同一个确定性 Activity API 场景执行 PATCH、PUT reaction、DELETE 路由Two activities with the sameabout两个同about的活动同一个 Activity API 场景验证以 ActivityEvent ID 为键的独立容器Removed GlossaryAF-05reply smoke已移除的术语表回复冒烟测试由确定性的用户会话抽屉回复、活动首条/后续回复场景替代Removed GlossaryAF-06/AF-07vacuous edit/delete checks已移除的空转编辑/删除检查由确定性的 Context Center 根变更与 Activity API 回复变更场景替代原文档还特别指出运行时 REST 客户端路由覆盖另外由 conversationsAPI.test.ts 与 activityAPI.test.ts 维护而 author/non-author/admin 动作可见性、解析分派resolution dispatch、活动回复状态替换activity-reply state replacement与抽屉根动作组合drawer root-action composition则由对应的组件/Provider 单元测试覆盖。分层验证体系三层测试如何共同兜住行为矩阵中的替代测试分布在三个层次理解分层有助于按需定位与排查服务端集成测试Java ITConversationResourceIT.java以真实服务端为对象验证/v1/conversations的 CRUD、有界回复水合与 keyset 分页语义是行为契约的源头事实前端 REST 客户端单测JestconversationsAPI.test.ts与activityAPI.test.tsmock 掉APIClient断言前端每次调用命中正确的 HTTP 方法与路径GET/POST/PATCH/PUT/DELETE防止路由漂移端到端测试PlaywrightActivityFeed.spec.ts、ActivityAPI.spec.ts、ContextCenterArticles.spec.ts、ContextCenterPermission.spec.ts、Features/Tasks/ActivityFeed.spec.ts等在真实浏览器中驱动 UI验证可观察行为渲染、导航、通知、编码、权限。当某个新需求改动路由时正确做法是从第 1、2 层确认语义与路径再到第 3 层确认 UI 行为最后回到本矩阵更新映射行。服务端语义有界回复水合与回复计数有界回复水合bounded hydration是 Conversation V2 最核心的语义之一。在 ConversationResourceIT.java 的testCompleteConversationCrudAndBoundedHydration中测试按以下步骤钉死该语义// 创建根会话断言初始状态 Conversation conversation createConversation(about, Root message); assertEquals(0, conversation.getReplyCount()); // 初始 replyCount 0 assertEquals(ConversationSource.User, conversation.getSource()); // 连续追加 4 条回复 for (int i 0; i 4; i) { createdReplies.add(addReply(conversation.getId(), Reply i)); } // 关键断言hydrated 对象只内嵌最近的 3 条回复 Conversation hydrated getConversation(conversation.getId()); assertEquals(4, hydrated.getReplyCount()); // 计数完整 assertEquals(3, hydrated.getReplies().size()); // 内嵌回复有界 // 独立分页接口可以取回全部 4 条 ConversationReplyList replies listReplies(conversation.getId(), 100, null, null); assertEquals(4, replies.getPaging().getTotal());这段代码揭示了两个要点计数与内容分离replyCount是权威总数而replies列表按设计只携带最近 N 条此处为 3避免一次请求把整棵回复树全部拉回完整内容走独立分页需要历史回复时通过/conversations/{id}/replies分页接口按需获取这正是矩阵中 root/reply cursor correctness 的服务端依据。同样的语义在前端 ActivityThreadPanelBody.test.tsx 的renders the selected conversation with its hydrated replies用例中得到 UI 级印证选中会话后FeedPanelBodyV1New与ActivityFeedcardNew.component以feed.id渲染水合后的回复。Keyset 分页cursor 分页的端到端链路Conversation V2 使用 keysetcursor分页而非 offset 分页矩阵对此有三处覆盖点服务端ConversationResourceIT的testListingFiltersAndKeysetPagination先请求limit2的第一页断言paging.after非空再用该 cursor 请求第二页断言两页数据不重叠前端组件ActivityThreadPanelBody.test.tsx的loads the next conversation page with the keyset cursor模拟paging.after: next-conversation-cursor滚动触发observer-element后断言listConversations以after: next-conversation-cursor再次调用并把两页结果合并刷新进列表E2EContextCenterArticles.spec.ts等待携带 cursor 的第二次请求并验证全部 11 条种子根均被渲染。前端 REST 层对应的契约在 conversationsAPI.test.ts 中it(lists conversations with filters and cursors, async () { const params { entityLink: #E::table::service.table, after: next }; await listConversations(params); expect(APIClient.get).toHaveBeenCalledWith(/conversations, { params }); });从组件单测可见listConversations的参数形态为{ after, entityLink }其中after即来自上一页paging的 cursor回复分页则使用独立 cursor见listConversationReplies单测中的{ before: previous, limit: 50 }。首条/后续回复无合成根、无重复 POST旧 feed 体系下回复行为容易出现合成根synthetic root或重复 POST 的隐患。新的活动路由用确定性断言锁死这一行为。在 ActivityAPI.spec.ts 的creates exactly one reply and isolates activities with the same about中测试先为同一张表播种两条独立的 activity eventfirstActivityText与secondActivityText然后page.on(request, (request) { if (request.method() ! POST) return; if (request.url().includes(/api/v1/activity/${firstActivityId}/replies)) { activityReplyRequests.push(request.url()); // 统计 activity 回复 POST } if (/\/api\/v1\/conversations(?:\?|$)/.test(request.url())) { conversationCreateRequests.push(request.url()); // 统计 conversation 创建 POST } }); // 发第一条回复 await postActivityComment(page, firstReply); expect(activityReplyRequests).toHaveLength(1); // 恰好一次回复 POST expect(conversationCreateRequests).toHaveLength(0); // 零次 conversation 创建 // 再发后续回复 await postActivityComment(page, subsequentReply); expect(activityReplyRequests).toHaveLength(2); expect(conversationCreateRequests).toHaveLength(0);两个断言分别回答无重复 POST每次回复只触发一次/activity/{id}/repliesPOST无合成根整个过程中/conversations的 POST 请求数为 0证明回复直接挂在 activity event 下而不是偷偷创建一棵假会话根。随后同一场景继续对首条回复执行编辑PATCH、反应PUT reaction、删除DELETE验证矩阵中 Activity reply edit, reaction tooltip identity, and delete 一行。同about活动的隔离以 ActivityEvent ID 为键矩阵中Two activities with the sameabout一行指向同一 Activity API 场景。其原理是即使两条 activity 拥有相同的about同一实体链接前端也以ActivityEvent ID作为独立容器的键而不是把回复塞进同一个根。这从 activityAPI.test.ts 中可以看到前端对活动数据的获取全部围绕活动 ID/实体 FQN 展开如getEntityActivityById、getActivityByEntityLink而组件渲染时按 event ID 定位卡片E2E 中则用feed-reply-card过滤hasText来逐一断言两条活动各自的回复互不串扰。用户会话的完整变更链路解决/编辑/删除/反应矩阵将 User-conversation root reaction and tooltip identity 与 resolve, edit, and delete 两行都映射到ContextCenterArticles.spec.ts的Related assets, activity feed, user mentions, and article mentions work场景——该场景通过/conversations/{id}执行并逐一校验每次变更。REST 层的完整路由在 conversationsAPI.test.ts 中被逐条钉死it(gets a conversation, async () { await getConversation(conversation-1); expect(APIClient.get).toHaveBeenCalledWith(/conversations/conversation-1); }); it(patches a conversation, async () { const patch: Operation[] [{ op: replace, path: /message, value: Updated }]; await patchConversation(conversation-1, patch); expect(APIClient.patch).toHaveBeenCalledWith(/conversations/conversation-1, patch); }); it(deletes a conversation, async () { await deleteConversation(conversation-1); expect(APIClient.delete).toHaveBeenCalledWith(/conversations/conversation-1); }); it(adds and removes a root reaction, async () { await addConversationReaction(conversation-1, ReactionType.Heart); await removeConversationReaction(conversation-1, ReactionType.Heart); const path /conversations/conversation-1/reaction/heart; expect(APIClient.put).toHaveBeenCalledWith(path); expect(APIClient.delete).toHaveBeenCalledWith(path); });与此对应回复子资源的完整 REST 契约同样被覆盖GET/POST /conversations/{id}/replies、PATCH/DELETE /conversations/{id}/replies/{replyId}、PUT/DELETE /conversations/{id}/replies/{replyId}/reaction/{type}见 conversationsAPI.test.ts 中lists replies with an independent cursor与adds and removes a reply reaction用例。注意 reaction 的增删分别使用PUT与DELETE命中同一路径ReactionType以小写形式进入 URL如heart、rocket。服务端对 resolve 语义的验证同样在ConversationResourceIT中patchConversation(conversation.getId(), patch(/resolved, true))后断言patchedRoot.getResolved()为真回复的 PATCH/message与 DELETE 也按序执行并断言replyCount随之从 4 变为 3形成一条完整的服务端 CRUD 闭环。通知、提及与中文编码矩阵中有两行与 提及mention直接相关均在 ActivityFeed.spec.ts 中Mention notification shows correct user details in Notification box验证被提及的用户在通知框中看到正确的用户详情身份与导航正确Should encode the chinese character while mentioning api endpoint验证调用提及 API 时中文字符被正确编码。该用例在仓库的 Discovery.md 中同样被登记为重要场景Mentions: Chinese character encoding in activity feed。这两个用例保护了多语言环境下的通知链路与 URL 编码细节是国际化场景中容易回归的部分。被移除场景的替代逻辑从冒烟/空转检查到确定性断言矩阵的最后两行明确标注了Removed已移除场景这是整张矩阵最具治理意义的部分旧术语表Glossary的AF-05回复冒烟测试被移除理由是它仅做能发出一条回复的冒烟验证缺少行为断言替代方案是确定性的用户会话抽屉回复ActivityFeed.spec.ts的thread drawer opens from reply count and allows posting a reply与活动首条/后续回复Activity API 场景——两者都有精确的请求计数与结果断言旧的AF-06/AF-07编辑/删除检查被移除因为它们属于空转检查vacuous checks即断言恒为真或未触及真实路由替代方案是确定性的Context Center 根变更resolve/edit/delete 逐一走/conversations/{id}与Activity API 回复变更PATCH/PUT/DELETE 逐条断言。矩阵的注释点明了设计意图被移除的场景必须显式点名called out explicitly这样测试才不会因为套件改名而在统计中悄悄消失。这也是整份文档最有价值的工程实践——它把删测试变成了一次必须留下书面痕迹的决策。如何阅读、维护与扩展这份矩阵结合 CONVERSATION_V2_TRACEABILITY.md 与上述源码推荐以下维护姿势变更路由时先改契约层若改动/conversations或/activity的路径、HTTP 方法或参数先同步更新 conversationsAPI.test.ts / activityAPI.test.ts 与 ConversationResourceIT.java它们锁死了前端叫对了后端的事实变更行为时补 E2E涉及用户可见行为渲染、导航、通知、编码、权限时在ActivityFeed.spec.ts、ActivityAPI.spec.ts、ContextCenterArticles.spec.ts、ContextCenterPermission.spec.ts、Features/Tasks/ActivityFeed.spec.ts中选择对应场景补充断言删除或重命名测试时必须更新矩阵在Legacy behavior or test列保留原行为描述在Replacement列写明承接方若是彻底移除要像AF-05/AF-06/AF-07那样说明被哪些确定性场景替代杜绝无声消失关注 keyset 分页的双 cursorroot 列表用aftercursor回复列表使用独立 cursorbefore/limit二者不要混用组件单测与 E2E 都已对此做出约束。小结Conversation V2 可追溯性矩阵不是一张静态表格而是一份行为不灭的治理契约旧 feed 的每一项可观察行为都能在新/conversations与/activity路由体系下找到明确的承接测试被删除的冒烟/空转用例也被更具断言力的确定性场景替换。配合 Java 集成测试、前端 REST 单测与 Playwright E2E 三层验证OpenMetadata 在端点切换后依然能保证活动流、用户会话、任务、公告与提及通知等核心体验的可回归性。任何改动端点的开发者都应以本矩阵为起点、以三层测试为护栏让每一次路由变更都留下可追溯的覆盖记录。【免费下载链接】OpenMetadataThe Open Context Layer for Data and AI , OpenMetadata is the open platform for building trusted data context and business semantics for humans, AI assistants, and agents.项目地址: https://gitcode.com/GitHub_Trending/op/OpenMetadata创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考