Bytebase 前端 React 迁移实战:项目数据库详情 Catalog 面板的 Vue Bridge 移除与 React 原生实现解析

Bytebase 前端 React 迁移实战:项目数据库详情 Catalog 面板的 Vue Bridge 移除与 React 原生实现解析

【免费下载链接】bytebase Database governance built for humans and agents — controlling changes and access across every major database. 【免费下载链接】bytebase 项目地址: https://gitcode.com/GitHub_Trending/by/bytebase

本指南以 Bytebase 仓库内的 React 迁移实现计划(docs/superpowers/plans/2026-04-13-project-database-detail-catalog-react-parity.md)为主体,深入解析"项目数据库详情页 → Catalog(敏感数据目录)标签页"从 Vue 桥接(bridge)迁移到 React 原生实现的全过程。读者将掌握该面板的组件架构、Catalog 数据扁平化逻辑、变更与授权写入语义、权限与订阅功能门控,以及对应的测试与验证命令,并可直接对照仓库中已落地的源码继续深挖。


一、迁移背景:为什么要移除 Vue Bridge

Bytebase 前端正在经历从 Vue 向 React 的渐进式迁移。在迁移中间态,部分页面通过 createApp 临时挂载 Vue 组件(如 NConfigProviderOverlayStackManager.vueDatabaseSensitiveDataPanel.vue)实现"React 壳 + Vue 芯"的桥接。这种方式虽然能让旧功能继续运行,但带来两个问题:

  1. 双框架渲染成本:每个页面多出一套 Vue 应用实例的创建、配置与叠加层管理,包体、性能与维护复杂度都随之上升;
  2. Parity 回归风险:桥接层里的 UI 细节(表格、抽屉、下拉、空态)与 React 侧组件体系(shadcn 风格 + @base-ui/react + Tailwind CSS v4)不一致,容易出现交互差异。

本计划的目标非常明确:从项目数据库详情页的 Catalog 标签页中移除 Vue 桥接,用仓库自有的 shadcn 风格 React 基础组件重建一个 React 原生实现,恢复完整的 Catalog 行为一致性(parity)。该计划对应文件 2026-04-13-project-database-detail-catalog-react-parity.md 中以 - [ ] 复选框组织的 4 个任务、共 17 个步骤,本文逐一展开并结合仓库现状给出实现事实。

需要说明:计划文档中预期的目标路径为 frontend/src/react/pages/project/database-detail/...,而当前仓库中该功能已实际落地于 frontend/src/routes/project/database-detail/... 目录下(与仓库整体 React 路由目录结构一致),以下源码引用均以仓库实际路径为准。


二、总体架构:面板壳 + 纯展示表格 + 聚焦式授权对话框

计划文档给出了明确的架构分层,这也是理解整个迁移的关键:

Reintroduce a React-owned DatabaseCatalogPanel that orchestrates catalog flattening, permissions, feature gating, row selection, inline edits, delete cleanup, and grant-access dialog state. Keep the UI split into a page-specific panel shell, a presentational SensitiveColumnTable, and a focused GrantAccessDialog, all backed by existing Vue/Pinia stores through useVueState.

对应到当前仓库,三个组件分别落地为:

职责层文件职责
面板壳(orchestrator)DatabaseCatalogPanel.tsx数据拉取、扁平化、权限与功能门控、行选择、内联编辑、删除清理、对话框状态
纯展示表格SensitiveColumnTable.tsx行渲染、复选框选择、内联下拉编辑、删除操作、空态,无任何 store 直连
授权对话框GrantAccessDialog.tsx资源预览、原因/过期时间/成员采集、CEL 表达式生成与策略追加

三者之外的配套组件还包括 MarkSensitiveDataSheet.tsx(批量标记敏感数据)。

当前仓库实现中,面板不再依赖 createApp / NConfigProvider / OverlayStackManager / DatabaseSensitiveDataPanel.vue / Naive UI,而是通过 useDatabaseCataloguseSemanticTypes hooks 与 useAppStore(Zustand store,后端为 Pinia 数据层的 React 入口)读取数据。例如 useDatabaseCatalog.ts 注释明确写道它是 "React port of the legacy Pinia useDatabaseCatalog composable":先按 bb.databaseCatalogs.get 权限门控地拉取 catalog,再响应式返回缓存条目,未加载时回退到稳定的空 catalog(emptyDatabaseCatalog),避免 selector 死循环。


三、Task 1:恢复 React Catalog 面板壳

涉及文件:

  • 修改:DatabaseCatalogPanel.tsx
  • 参考(旧 Vue 实现):frontend/src/components/Database/DatabaseSensitiveDataPanel.vue
  • 参考(共享类型与工具):frontend/src/components/SensitiveData/types.tsfrontend/src/components/SensitiveData/utils.ts

3.1 面板需要拥有的状态与行为

计划要求 DatabaseCatalogPanel 全面接管以下职责,当前仓库实现逐条对应:

  • catalog 读取useDatabaseCatalog(database.name, false)(第二个参数 skipCache = false,即优先走缓存);
  • store 响应式读取:通过 useAppStore 选择器订阅 catalog、语义类型、数据分类配置、实例订阅功能;
  • 扁平化:把关系型列、对象型(NoSQL)schema 字段、表级分类统一拍平成 MaskData[]
  • 本地 UI 状态:搜索文本、勾选行、功能(feature)对话框、授权对话框、待删除项等 useState
  • 权限检查bb.databaseCatalogs.update(更新 catalog)、以及授权所需的三个权限;
  • 设置拉取:语义类型(SEMANTIC_TYPES)与数据分类(DATA_CLASSIFICATION)两类 workspace 设置;
  • 变更处理:语义类型编辑、分类编辑、删除清理、授权流程打开。

当前实现中,设置拉取通过 useEffect 调用 getOrFetchSettingByName(Setting_SettingName.SEMANTIC_TYPES, true)getOrFetchSettingByName(Setting_SettingName.DATA_CLASSIFICATION, true) 完成;数据库切换(database.name 变化)时通过另一个 useEffect 重置所有本地状态(搜索词、勾选行、各对话框、待删除项)。

3.2 保持既有变更语义(mutation semantics)

计划明确要求复用 Vue 面板的写入路径,当前实现逐一对应:

操作行为
语义类型变更就地修改行目标(item.target.semanticType = semanticTypeId)后调用 updateDatabaseCatalog(catalog) 持久化,并弹出 SUCCESS 通知
分类变更就地修改 item.target.classification 后同样 updateDatabaseCatalog 持久化
删除先从本地勾选集移除该行 → 清空目标上的 semanticType/classification(按目标类型判断)→ 持久化 catalog → 再调用 removeMaskingExceptions 清理项目 masking 豁免策略中匹配该列的豁免
授权仅在存在勾选行且权限/功能门控放行时打开 React 对话框

删除路径是其中最有代表性的"清理级联":handleDelete 先清本地勾选,再用 isCurrentColumnException(定义于 utils.ts)逐条过滤项目 MASKING_EXEMPTION 策略中的豁免表达式,匹配的豁免会被剔除后通过 upsertPolicy 写回。也就是说,删除敏感标记不只是清 catalog,还会顺带收回此前授予的访问豁免,避免残留授权形成数据泄漏面。

3.3 NoSQL 分支与功能门控(parity 的关键)

计划要求原样继承 Vue 面板的 NoSQL 分支行为:

  • 对象 schema 扁平化仍然要呈现嵌套字段flattenObjectSchema 递归遍历 ObjectSchemaOBJECT 类型沿 structKind.properties 逐层下钻,ARRAY 类型取其元素类型继续递归),最终把叶子节点拍平成 schema.table.column 形式的 MaskData,并在扁平化结果上标记 disableClassification: true(对象字段不支持分类);
  • 行选择禁用:当 instanceV1MaskingForNoSQL(instance) 返回 true(即该实例的 NoSQL 类型不支持批量授权)时,表格的勾选列不再渲染,Mark Sensitive Data 按钮也被隐藏;
  • 只读目标:语义类型目标不支持分类(如表级分类行 disableSemanticType: true),对象字段不支持分类(disableClassification: true),这些行在下拉编辑时保持禁用;
  • 订阅缺失:缺少 FEATURE_DATA_MASKING 功能时,"Grant Access"与"Mark Sensitive Data"点击后打开的是 feature 对话框(FeatureAttention 付费引导)而非实际操作。

四、Task 2:用 shadcn 风格基础组件重建 UI 组件

涉及文件:

  • 新建:SensitiveColumnTable.tsxGrantAccessDialog.tsx
  • 参考:frontend/src/components/SensitiveData/components/SensitiveColumnTable.vuefrontend/src/components/SensitiveData/exemptionDataUtils.tsfrontend/src/components/SensitiveData/utils.ts

4.1 SensitiveColumnTable:保持纯展示

计划要求该组件保持 presentational:不直连 store,所有变更通过回调冒泡,勾选行由 props 受控。当前实现的 props 契约完整体现了这一点:

  • columnList / checkedColumnList:行数据与受控勾选集合(由父组件持有);
  • showSelection / canEdit / showOperation:分别控制勾选列、编辑态、操作列是否可用;
  • semanticTypeOptions / classificationOptions:下拉选项由父组件注入;
  • onCheckedColumnListChange / onSemanticTypeChange / onClassificationChange / onDelete:全部通过回调上抛。

实现细节与计划要求逐条对齐:

  • 共享 Table 原语:复用 frontend/src/components/ui/tableTable/TableBody/TableCell/TableHead/TableHeader/TableRow,以及 frontend/src/components/ui/checkboxfrontend/src/components/ui/selectfrontend/src/components/ui/button
  • 行选择:以 schema::table::column 拼出的 key 为唯一标识(itemKey),支持单选/全选,全选提供 indeterminate(半选)状态;
  • 内联编辑EditableSelect 封装空值语义——空值用哨兵值 __EMPTY__ 占位,选择该哨兵项即清空(写入空字符串),避免 Select 组件无法表达"无值";
  • schema 限定表名与 - 占位:表名单元格用 ${schema}.${table}(无 schema 时仅表名),列名为空时显示空占位符,行为与 Vue 面板一致;
  • 删除与空态:操作列提供垃圾桶按钮(带 aria-label 与 title),空数据显示居中的 "no-data" 占位。

4.2 GrantAccessDialog:保留策略写入逻辑

授权对话框是"数据治理"语义的核心载体,计划要求恢复其策略写入逻辑且不再引入 Vue drawer 或桥接包装。当前实现的关键流程:

  1. 资源预览:把选中的 SensitiveColumn[] 通过 convertSensitiveColumnToDatabaseResource 转换为 DatabaseResourceutils.ts 中的实现将 database 全名、schema、table、columns 组装为资源对象),对话框打开时据此初始化资源列表;
  2. 三种范围模式ALL(全部数据库,当前为禁用态)、EXPRESSION(CEL 表达式)、SELECT(手动选择),通过 RadioGroup 切换,且 SELECT ↔ EXPRESSION 之间切换时做双向转换(convertToConditionGroupExpr / convertToDatabaseResources),转换过程用 modeChangeRequestIdRef 做请求竞态防护;
  3. 表达式生成:核心工具是 maskingExemption.ts 中的 buildMaskingExemption——它将 CEL 表达式经 rewriteResourceDatabase(...) 重写为豁免条件,并将过期时间编码为 request.time < timestamp("...") 子句,最后组装成 MaskingExemptionPolicy_Exemption
  4. 追加而非替换:提交时先 getOrFetchPolicyByParentAndType 取项目现有 MASKING_EXEMPTION 策略,取其中已存在的 exemptions 数组,再 [...existed, exemption] 追加后 upsertPolicy 写回——这正是计划中强调的"append to existing exemptions instead of replacing them"语义;
  5. 表单要素:原因(description)、过期时间(ExpirationPicker,最小时间为当天 dayjs().startOf("day"),不选即永不过期)、成员(AccountMultiSelect,必填);成员为空或表达式无效时提交按钮禁用;
  6. 付费墙:选择 EXPRESSION/SELECT 模式而订阅缺少 FEATURE_DATA_MASKING 时,打开共享的 FeatureModal 引导升级,而不是阻塞操作。

4.3 面板接线

DatabaseCatalogPanel 将扁平化后的 filteredColumnList 与回调交给 SensitiveColumnTable;把勾选行映射为 { database, maskData }(即 SensitiveColumn 形状)传给 GrantAccessDialog;对话框关闭(closeGrantAccessDialog)时同步清空勾选行,与 Vue 行为保持一致。


五、Task 3:重建并扩展 React 测试覆盖

涉及文件:

计划把测试分为两层,并特别点名"旧版本最容易漏掉"的 parity 回归点:

面板级测试(第一层,必须覆盖):

  • 渲染扁平化后的关系型行;
  • 勾选行后启用 Grant Access 按钮;
  • 缺少 masking 功能时打开 feature 对话框;
  • 传给权限守卫(guard)的权限输入;
  • 删除选中行后从勾选集合中清除;
  • 内联语义类型与分类更新;
  • store 加载完成前 catalog 为 undefined 的渲染安全。

新增 parity 测试(第二层,旧版易漏):

  • NoSQL 模式下禁用行选择,但仍渲染扁平化的对象 schema 行;
  • 删除路径从策略 store 中移除匹配的 masking 豁免;
  • Grant Access 提交是追加而非替换已有豁免;
  • 缺少 bb.databaseCatalogs.update 权限时操作控件被禁用或隐藏。

页面级集成测试(第三层,保持最小化): ProjectDatabaseDetailPage.test.tsx 只聚焦标签路由、catalog 标签页的权限守卫行为、以及选中标签后的面板渲染,不把详细工作流断言塞进页面级文件。

当前仓库的 DatabaseCatalogPanel.test.tsx 中可以看到对应的测试基建:通过 makeColumn / makeObjectSchema / makeDatabaseCatalog 构造 fixture——其中 makeObjectSchema 构造了 contact.email / contact.phone 两层嵌套的 OBJECT 结构来验证递归扁平化,makeDatabase 使用 instances/inst1/databases/db1 这样的资源名,并通过 createRoot + act 在 React 测试环境下驱动交互。


六、Task 4:验证、清除桥接假设并收尾

6.1 确认无页面级 Vue 桥接依赖

最终实现必须确认不再导入或依赖:createAppNConfigProviderthemeOverridesOverlayStackManager.vueDatabaseSensitiveDataPanel.vueNaiveUI。Catalog 标签页从此"fully React-owned",即便其数据仍来自 Vue 背书的 stores——注意当前仓库中 frontend/src/components/header/no-vue-deps.test.ts 这类测试的存在,说明"React 组件不得携带 Vue 依赖"已经是一个被测试强约束的工程红线。

6.2 完整验证命令

计划给出的命令可以直接在仓库根目录执行(pnpm --dir frontend 指定工作目录为前端包):

# 自动修复(lint 等)
pnpm --dir frontend fix

# 全量检查
pnpm --dir frontend check

# 类型检查
pnpm --dir frontend type-check

# 定向运行 catalog 相关测试
pnpm --dir frontend test -- --run frontend/src/routes/project/database-detail/panels/DatabaseCatalogPanel.test.tsx frontend/src/routes/project/ProjectDatabaseDetailPage.test.tsx

(计划文档中写的是旧路径 frontend/src/react/pages/...,运行前请以仓库当前 frontend/src/routes/... 实际路径为准。)

6.3 Diff 自查

最后通过 git diff 检查改动范围是否严格限定在 catalog 相关五个文件内,确保没有意外的越界改动:

git diff -- frontend/src/routes/project/database-detail/panels/DatabaseCatalogPanel.tsx frontend/src/routes/project/database-detail/catalog/SensitiveColumnTable.tsx frontend/src/routes/project/database-detail/catalog/GrantAccessDialog.tsx frontend/src/routes/project/database-detail/panels/DatabaseCatalogPanel.test.tsx frontend/src/routes/project/ProjectDatabaseDetailPage.test.tsx

七、总结:一次可复用的"React 化"迁移样板

从本计划可以提炼出一套可复用的 React 迁移模式,适用于 Bytebase 中其他仍处于 Vue 桥接状态的面板:

  1. 壳层归 React:面板组件接管数据拉取、扁平化、权限、门控与全部对话框状态,移除 createApp 级桥接;
  2. 展示层归组件:表格与对话框保持 presentational,数据与回调全部由 props 注入,为测试和复用提供干净边界;
  3. 数据层复用:通过 hooks(useDatabaseCataloguseSemanticTypes)与 store 选择器(useAppStore)继续消费既有数据能力,迁移 UI 而不是重写业务;
  4. parity 靠测试锁定:专门为 NoSQL 行选择禁用、删除级联清理豁免、授权追加语义、权限缺失下的控件状态等"最易回归"点补充显式断言;
  5. 验证闭环fix → check → type-check → 定向测试 四步走,配合 git diff 范围自查收尾。

对想要深入研读实现细节的读者,推荐按以下顺序阅读源码:面板壳 DatabaseCatalogPanel.tsx → 数据模型 types.ts → 扁平化与豁免匹配工具 utils.ts → 表达式构建 maskingExemption.ts → 授权对话框 GrantAccessDialog.tsx,最后用测试文件 DatabaseCatalogPanel.test.tsx 验证自己对各行为的理解是否与仓库锁定的一致。

【免费下载链接】bytebase Database governance built for humans and agents — controlling changes and access across every major database. 【免费下载链接】bytebase 项目地址: https://gitcode.com/GitHub_Trending/by/bytebase

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值