Skip to content

feat: 重构 Webhook Onboarding 引导系统#506

Merged
ding113 merged 2 commits intodevfrom
feat/webhook-onboarding-refactor
Jan 2, 2026
Merged

feat: 重构 Webhook Onboarding 引导系统#506
ding113 merged 2 commits intodevfrom
feat/webhook-onboarding-refactor

Conversation

@ding113
Copy link
Owner

@ding113 ding113 commented Jan 2, 2026

Summary

实现版本化 Onboarding 系统与 Webhook 迁移引导,帮助用户从旧版单 Webhook 模式平滑过渡到新版多目标通知系统。

Related Issues:


Problem

PR #505 实现了强大的多目标 Webhook 通知系统,但缺少用户引导机制:

  1. 无迁移提示 - 用户不知道如何从旧版单 Webhook 配置迁移到新系统
  2. 默认值冲突 - 数据库 schema 默认 useLegacyMode: true,但代码期望 false
  3. 平台识别困难 - 用户需要手动选择 Webhook 平台类型(企业微信/飞书/钉钉/Telegram)
  4. 迁移脚本不幂等 - 重复运行 migration 0043 会导致错误
  5. 旧 Onboarding 过时 - user-onboarding-tour 组件已不再适用

Solution

1. 版本化 Onboarding 系统

新架构 (src/lib/onboarding/index.ts):

  • 使用 localStorage 键 cch-onboarding-state 存储已完成的引导
  • 每个引导功能关联 APP_VERSION,支持多版本管理
  • 例如:webhookMigration: "0.3.41" 表示该功能在 0.3.41 引入

API:

// 检查用户是否需要看引导
shouldShowOnboarding("webhookMigration") // true/false

// 标记引导已完成
markOnboardingCompleted("webhookMigration")

// 重置引导(用于测试)
resetOnboarding()

2. Webhook 迁移对话框

智能检测 (src/lib/webhook/migration.ts):

  • 自动识别平台类型(企业微信/飞书/钉钉/Telegram)
    • qyapi.weixin.qq.com → 企业微信
    • open.feishu.cn / open.larksuite.com → 飞书
    • oapi.dingtalk.com → 钉钉
    • api.telegram.org → Telegram
  • 未识别的 URL 允许用户手动选择平台
  • 收集旧配置中的所有 Webhook URL(熔断/成本/排行榜)

迁移流程:

  1. 用户登录 Dashboard 后检测 useLegacyMode === true
  2. 如有旧 Webhook 配置,显示迁移对话框
  3. 显示所有待迁移的 Webhook(自动识别平台 + 允许手动调整)
  4. 一键迁移:创建 Webhook Targets + 绑定到对应通知类型
  5. 自动跳转到通知设置页面查看结果

迁移对话框组件 (src/app/[locale]/dashboard/_components/webhook-migration-dialog.tsx):

  • 489 行代码,完整的迁移向导
  • 平台选择器(自动检测 + 手动覆盖)
  • 迁移进度显示
  • 错误处理与回退

3. 数据库 Schema 修复

问题:

  • src/drizzle/schema.tsuseLegacyMode 默认值为 true
  • src/repository/notifications.ts 中默认值也是 true
  • 这导致新部署默认工作在旧模式,与新系统设计不符

修复:

- useLegacyMode: boolean('use_legacy_mode').notNull().default(true),
+ useLegacyMode: boolean('use_legacy_mode').notNull().default(false),

向后兼容:

  • 现有用户数据库中已有行保持 useLegacyMode = true
  • 只有新创建的记录默认为 false

4. 迁移脚本幂等性

问题 (drizzle/0043_faithful_mother_askani.sql):

  • 原脚本重复运行会报错 "type already exists" / "column already exists"

修复:

DO $$ BEGIN
  CREATE TYPE "notification_type" AS ENUM(...);
EXCEPTION
  WHEN duplicate_object THEN NULL;
END $$;

DO $$ BEGIN
  ALTER TABLE "notification_settings" ADD COLUMN "use_legacy_mode" boolean DEFAULT false NOT NULL;
EXCEPTION
  WHEN duplicate_column THEN NULL;
END $$;

5. 移除旧 Onboarding

删除文件:

  • src/app/[locale]/dashboard/_components/user/user-onboarding-tour.tsx (110 行)
  • 该组件用于用户页面的首次访问引导,已不再需要

Changes

New Files

文件 行数 用途
src/lib/onboarding/index.ts 151 版本化 Onboarding 工具(localStorage 管理)
src/lib/webhook/migration.ts 312 Webhook 迁移逻辑(平台检测 + 数据转换)
src/app/[locale]/dashboard/_components/webhook-migration-dialog.tsx 489 迁移对话框 UI
tests/unit/lib/onboarding.test.ts 96 Onboarding 系统单元测试

Modified Files

文件 变更 用途
src/drizzle/schema.ts 1 line useLegacyMode 默认值 truefalse
src/repository/notifications.ts 1 line 同步默认值更新
src/app/[locale]/settings/notifications/page.tsx 移除 Legacy UI 删除旧版单 Webhook 编辑表单
drizzle/0043_faithful_mother_askani.sql +53 添加 DO $$ BEGIN...EXCEPTION 幂等性处理
messages/*/dashboard.json 5 语言 迁移对话框文案(en/zh-CN/zh-TW/ja/ru)

Deleted Files

文件 行数 原因
src/app/[locale]/dashboard/_components/user/user-onboarding-tour.tsx 110 旧引导系统已废弃

Migration Flow Diagram

用户登录 Dashboard
    ↓
检查 useLegacyMode === true ?
    ↓ Yes
收集旧 Webhook 配置
    ↓
有旧配置? → Yes → 显示迁移对话框
    ↓                   ↓
   No                自动识别平台类型
    ↓                   ↓
  正常使用          允许用户调整
                        ↓
                   一键迁移
                        ↓
                   创建 Webhook Targets
                        ↓
                   创建 Bindings
                        ↓
                   设置 useLegacyMode = false
                        ↓
                   跳转到通知设置页面

Platform Detection Logic

Webhook URL 域名 自动识别为 参考文档
qyapi.weixin.qq.com 企业微信 https://developer.work.weixin.qq.com/document/path/91770
open.feishu.cn / open.larksuite.com 飞书 https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot
oapi.dingtalk.com 钉钉 https://open.dingtalk.com/document/robots/custom-robot-access
api.telegram.org Telegram https://core.telegram.org/bots/api
其他 允许用户手动选择 -

Breaking Changes

无破坏性变更

潜在影响点 缓解措施
useLegacyMode 默认值变更 仅影响新创建的 notification_settings 行,现有数据不变
删除 user-onboarding-tour.tsx 该组件在用户页面已不再使用
迁移脚本幂等性 使用 IF NOT EXISTS / DO $$ EXCEPTION 确保安全重复运行

Test Plan

Automated Tests

  • 单元测试 (tests/unit/lib/onboarding.test.ts)

    • shouldShowOnboarding() 逻辑
    • markOnboardingCompleted() localStorage 更新
    • resetOnboarding() 清理
    • 多版本管理场景
  • TypeScript typecheck 通过

  • Biome lint 检查通过

Manual Testing

场景 1: 新用户(无旧配置)

  • 登录 Dashboard,不显示迁移对话框
  • Onboarding 系统标记 webhookMigration 为已完成
  • 再次登录,仍不显示对话框

场景 2: 旧用户(有旧 Webhook 配置)

  • 登录 Dashboard,显示迁移对话框
  • 对话框列出所有旧 Webhook URL
  • 自动识别的平台类型正确(企业微信/飞书/钉钉/Telegram)
  • 未识别的 URL 允许手动选择平台
  • 点击"一键迁移"后:
    • 创建对应的 Webhook Targets
    • 创建 Notification Bindings
    • useLegacyMode 设置为 false
    • 自动跳转到 /settings/notifications
  • 在通知设置页面验证迁移结果

场景 3: 迁移脚本幂等性

  • 运行 bun run db:migrate 两次
  • 第二次运行不报错
  • 数据库 schema 正确

场景 4: 跳过迁移

  • 在迁移对话框点击"稍后处理"
  • 对话框关闭
  • webhookMigration onboarding 未标记为已完成
  • 下次登录仍显示对话框

I18n Coverage

更新了所有 5 种语言的文案:

语言 文件 新增键
英语 messages/en/dashboard.json webhookMigration.*
日语 messages/ja/dashboard.json webhookMigration.*
俄语 messages/ru/dashboard.json webhookMigration.*
简中 messages/zh-CN/dashboard.json webhookMigration.*
繁中 messages/zh-TW/dashboard.json webhookMigration.*

新增翻译键示例:

  • webhookMigration.title - 迁移对话框标题
  • webhookMigration.description - 迁移说明
  • webhookMigration.platformDetected - 自动识别提示
  • webhookMigration.manualSelect - 手动选择提示
  • webhookMigration.migrateButton - 迁移按钮
  • webhookMigration.skipButton - 跳过按钮

Validation

所有检查项已通过:

✅ bun run lint          # Biome 检查通过
✅ bun run typecheck     # TypeScript 类型检查通过
✅ bun run test          # 单元测试通过
✅ bun run build         # 生产构建成功

Checklist

  • 代码遵循项目规范
  • 已完成自我评审
  • 测试通过
  • 5 种语言的 i18n 文案已更新
  • 数据库迁移脚本幂等
  • 无破坏性变更

🤖 Generated with Claude Code

Co-Authored-By: Claude Sonnet 4.5 noreply@anthropic.com

- 实现版本化 Onboarding 系统,支持 localStorage 多版本管理
- 新增 Webhook 迁移对话框,自动检测旧配置并引导一键迁移
- 支持自动识别平台类型(企业微信/飞书/钉钉/Telegram)
- 移除遗留模式 UI,强制使用新 Webhook 体验
- 更新数据库 schema 默认值(useLegacyMode: false)
- 修复数据库迁移脚本幂等性问题
- 移除旧的 User Onboarding Tour 组件
- 更新 5 种语言的 i18n 文案
- 添加 Onboarding 系统单元测试

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @ding113, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

此拉取请求对 Webhook 通知系统进行了全面的重构和升级,旨在提供更灵活、更可靠的通知管理体验。通过引入版本化 Onboarding 系统和自动化迁移流程,确保现有用户能够平滑过渡到新的多目标 Webhook 架构。同时,优化了数据库连接和写入性能,并增强了国际化支持,为未来的功能扩展奠定了基础。

Highlights

  • 重构 Webhook 通知系统: 引入了全新的版本化 Webhook 通知系统,支持多目标推送、独立配置和更高的可靠性。旧的单 URL 配置将被引导迁移到新系统。
  • 自动化 Webhook 迁移: 新增了 Webhook 迁移对话框,用户登录 Dashboard 后会自动检测旧配置并引导一键迁移。系统能自动识别平台类型(企业微信/飞书/钉钉/Telegram),对于无法识别的 URL 允许用户手动选择。
  • 移除旧版 UI 和数据库字段: 移除了遗留的模式 UI 开关和旧的 User Onboarding Tour 组件,强制使用新的 Webhook 体验。数据库 notification_settings 表新增 useLegacyMode 字段,默认值为 false
  • 数据库优化与幂等性修复: 更新了 .env.exampleREADME.md,增加了 PostgreSQL 连接池配置和消息请求异步批量写入模式的选项。修复了数据库迁移脚本的幂等性问题,确保多次执行不会出错。
  • 多语言支持与测试: 更新了 5 种语言(en/zh-CN/zh-TW/ja/ru)的 i18n 文案,以支持新的 Webhook 迁移和配置界面。为新的 Onboarding 系统添加了单元测试,并新增了 Webhook 目标与绑定的 E2E 和集成测试。
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions github-actions bot added the size/XL Extra Large PR (> 1000 lines) label Jan 2, 2026
Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

85 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines 1 to 20
/**
* Versioned Onboarding System
*
* Uses APP_VERSION from the application to track which onboarding features
* users have completed. Each onboarding feature records the version when completed.
*/

// localStorage key for onboarding state
const ONBOARDING_STATE_KEY = "cch-onboarding-state";
// Legacy key from old onboarding system (users page)
const LEGACY_KEY = "cch-users-onboarding-seen";

/**
* Onboarding features and the version they were introduced.
* Users will see the onboarding if they haven't completed it.
*/
export const ONBOARDING_INTRODUCED_IN = {
webhookMigration: "0.3.41",
} as const;

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: duplicate onboarding implementation exists

Two separate onboarding files exist:

  • src/lib/onboarding.ts (used by migration dialog)
  • src/lib/onboarding/index.ts (this file, appears unused)

These have different implementations. The migration dialog imports from @/lib/onboarding which resolves to onboarding.ts, not this file. Consider removing this duplicate file to avoid confusion.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/onboarding/index.ts
Line: 1:20

Comment:
**logic:** duplicate onboarding implementation exists

Two separate onboarding files exist:
- `src/lib/onboarding.ts` (used by migration dialog)
- `src/lib/onboarding/index.ts` (this file, appears unused)

These have different implementations. The migration dialog imports from `@/lib/onboarding` which resolves to `onboarding.ts`, not this file. Consider removing this duplicate file to avoid confusion.

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

本次 PR 对 Webhook 和 Onboarding 系统进行了大规模重构,引入了版本化的引导系统、多目标 Webhook 推送、以及旧配置的自动迁移流程,整体架构设计出色,极大提升了系统的灵活性和可维护性。同时,通过引入数据库连接池和异步日志写入,显著优化了性能和可靠性。代码质量很高,包含了幂等的数据库迁移脚本和必要的安全防护措施。

我在评审中发现了一些可以改进的地方,主要集中在代码一致性和类型安全方面:

  1. 存在两套 Onboarding 系统实现,建议统一为功能更强的版本化系统。
  2. 部分组件的属性类型定义不一致,导致使用了 as any 类型断言。
  3. Webhook URL 的平台检测逻辑在两个不同文件中存在重复。

修复这些问题将进一步提升代码的健壮性和可维护性。总体而言,这是一次非常出色的重构工作。

SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { setOnboardingCompleted, shouldShowOnboarding } from "@/lib/onboarding";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

src/lib/onboarding.tssrc/lib/onboarding/index.ts 中存在两套 Onboarding 系统的实现。当前组件使用的是 src/lib/onboarding.ts 中的简单实现,但这与 PR 描述中提到的、功能更强大的版本化系统(位于 src/lib/onboarding/index.ts)不符。版本化系统支持按应用版本管理引导流程,更有利于未来的功能迭代。

建议统一使用 src/lib/onboarding/index.ts 中的版本化实现,并移除多余的 src/lib/onboarding.ts 文件,以避免混淆和维护成本。

这需要将此处的导入从:

import { setOnboardingCompleted, shouldShowOnboarding } from "@/lib/onboarding";

修改为:

import { setOnboardingCompleted, shouldShowOnboarding } from "@/lib/onboarding/index";
// 同时需要从 package.json 中获取 APP_VERSION
import { version as appVersion } from "../../../../../../package.json";

并相应地调整 setOnboardingCompleted 的调用方式,传入当前的应用版本号,例如:

setOnboardingCompleted("webhookMigration", appVersion);

onCreate={createTarget}
onUpdate={updateTarget}
onDelete={deleteTarget}
onTest={testTarget as any}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

此处使用了 as any 类型断言,这通常是为了绕过类型检查,可能隐藏了潜在的类型不匹配问题。检查发现 onTest 属性在 WebhookTargetsSectionWebhookTargetCardWebhookTargetDialog 等组件中的类型定义不一致。

建议统一 onTest 的函数签名,确保所有组件中该属性的类型定义(包括参数和返回值)完全一致,从而移除 as any,增强代码的类型安全和可维护性。

Comment on lines 62 to 66
private static detectProvider(webhookUrl: string): ProviderType {
const url = new URL(webhookUrl);
if (url.hostname === "qyapi.weixin.qq.com") return "wechat";
if (url.hostname === "open.feishu.cn") return "feishu";
throw new Error(`Unsupported webhook hostname: ${url.hostname}`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

此处的 detectProvider 静态方法只支持 wechatfeishu,对于其他类型的 URL 会抛出错误。而在 src/lib/webhook/migration.ts 中存在一个功能更完整的 detectProviderType 函数,它额外支持了 dingtalktelegram

为了提高代码的健壮性和一致性,建议将这两处逻辑合并。可以考虑将 migration.ts 中的函数提取到公共位置(如 src/lib/webhook/utils),并在此处及其他地方复用,以避免逻辑重复和潜在的不一致问题。

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

This PR implements a versioned onboarding system for webhook migration with significant architectural improvements. However, CRITICAL bugs were found that will cause TypeScript compilation failures and runtime errors.

PR Size: XL

  • Lines changed: 11,393 (10,374 additions + 1,019 deletions)
  • Files changed: 85

⚠️ Recommendation: This PR is too large (XL). Consider splitting into:

  1. Core infrastructure (onboarding system + migration utilities)
  2. UI components (migration dialog)
  3. Database schema changes
  4. I18n updates

Issues Found

Category Critical High Medium Low
Logic/Bugs 2 0 0 0
Error Handling 0 1 0 0
Types 0 0 0 0
Tests 0 1 0 0
Total 2 2 0 0

Critical Issues (Must Fix)

1. Missing Required Parameter in setOnboardingCompleted() Calls

Location: src/app/[locale]/dashboard/_components/webhook-migration-dialog.tsx:129,133,147,172
Severity: CRITICAL
Category: [LOGIC-BUG]

Problem: The function setOnboardingCompleted(feature, version) requires 2 parameters, but all 4 calls only pass 1 parameter.

Evidence:

  • Function signature (src/lib/onboarding/index.ts:87): export function setOnboardingCompleted(feature: OnboardingFeature, version: string): void
  • All calls: setOnboardingCompleted("webhookMigration")

Impact: TypeScript compilation will fail. If bypassed, runtime version will be undefined, breaking the versioned onboarding system's core functionality.

Fix:

import { ONBOARDING_INTRODUCED_IN } from "@/lib/onboarding";

// Change all 4 occurrences from:
setOnboardingCompleted("webhookMigration");

// To:
setOnboarding Completed("webhookMigration", ONBOARDING_INTRODUCED_IN.webhookMigration);

2. Test Uses Non-Existent OnboardingFeature Types

Location: tests/unit/lib/onboarding.test.ts:57-72
Severity: CRITICAL
Category: [TEST-BROKEN]

Problem: Test uses feature names "userManagement", "providerSetup", "quotaManagement" that don't exist in the OnboardingFeature type.

Evidence:

  • Type definition (src/lib/onboarding/index.ts:17-19):
export const ONBOARDING_INTRODUCED_IN = {
  webhookMigration: "0.3.41",
} as const;
export type OnboardingFeature = keyof typeof ONBOARDING_INTRODUCED_IN; // Only "webhookMigration"
  • Test uses: setOnboardingCompleted("userManagement")

Impact: Test will fail TypeScript compilation. This violates the type safety contract.

Fix:

// Option 1: Use only existing feature
it("resetAllOnboarding 清空所有类型的状态", () => {
  setOnboardingCompleted("webhookMigration", "0.3.41");
  expect(shouldShowOnboarding("webhookMigration")).toBe(false);
  
  resetAllOnboarding();
  
  expect(shouldShowOnboarding("webhookMigration")).toBe(true);
});

// Option 2: Add the features to ONBOARDING_INTRODUCED_IN if they're planned
export const ONBOARDING_INTRODUCED_IN = {
  webhookMigration: "0.3.41",
  userManagement: "0.3.42", // If planned
  providerSetup: "0.3.42",
  quotaManagement: "0.3.42",
} as const;

High Priority Issues (Should Fix)

3. Silent Failure in Migration Check

Location: src/app/[locale]/dashboard/_components/webhook-migration-dialog.tsx:135-137
Severity: HIGH
Category: [ERROR-SILENT]

Problem: Empty catch block swallows all errors when fetching notification settings, providing no visibility into failures.

Code:

try {
  const settings = await getNotificationSettingsAction();
  // ... processing
} catch {
  // Silently fail - don't block user if settings fetch fails  ❌
}

Why this is a problem:

  • Network errors, permission issues, or API failures go unreported
  • Users won't know why migration dialog doesn't appear
  • Debugging production issues becomes impossible

Fix:

} catch (error) {
  logger.error("[WebhookMigrationDialog] Failed to fetch notification settings", {
    error: error instanceof Error ? error.message : String(error),
  });
  // Still don't block user, but log for debugging
}

4. Test Missing Version Parameter

Location: tests/unit/lib/onboarding.test.ts:44,57-60
Severity: HIGH
Category: [TEST-INCOMPLETE]

Problem: Test calls setOnboardingCompleted() without the required version parameter (same issue as #1).

Fix: Add version parameter to all test calls:

setOnboardingCompleted("webhookMigration", "0.3.41");

Review Coverage

  • Logic and correctness - 2 critical bugs found
  • Security (OWASP Top 10) - Clean
  • Error handling - 1 high-priority issue (silent failure)
  • Type safety - Issues detected via missing parameters
  • Documentation accuracy - Clean
  • Test coverage - 1 high-priority issue (broken test)
  • Code clarity - Good overall

Validation Summary

Blocking Issues: 2 Critical + 2 High
Action Required: Fix critical bugs before merge. These will cause compilation and test failures.

Positive Notes:

  • Database migration uses proper idempotency with DO 20310 BEGIN...EXCEPTION blocks ✅
  • Cleanup pattern with cancelled flag prevents race conditions ✅
  • Platform detection logic is comprehensive ✅
  • I18n coverage complete for all 5 languages ✅

Automated review by Claude AI

const created = await createWebhookTarget(normalized);

// 数据迁移策略:当创建第一个 webhook_target 时,自动切换到新模式
const settings = await getNotificationSettings();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[CRITICAL] [LOGIC-BUG] useLegacyMode flips to false before bindings exist, can disable notifications

Evidence (src/actions/webhook-targets.ts:366-370):

// 数据迁移策略:当创建第一个 webhook_target 时,自动切换到新模式
const settings = await getNotificationSettings();
if (settings.useLegacyMode) {
  await updateNotificationSettings({ useLegacyMode: false });
}

Why this is a problem: useLegacyMode is global. This switch happens during target creation, but bindings are created later in the migration flow (src/lib/webhook/migration.ts:172-274) and also later in the manual UI flow. If migration/setup fails mid-way, the app ends up in new-mode with no bindings, so enabled notifications will be skipped (e.g. "no_bindings").

Suggested fix: Delay the mode switch until after bindings exist for all enabled notification types.

One concrete approach:

  1. Remove the auto-switch from createWebhookTargetAction.
  2. In updateBindingsAction, after upsertBindings, check whether all enabled types now have at least 1 enabled binding, then flip once:
// src/actions/notification-bindings.ts
import { getNotificationSettings, updateNotificationSettings } from "@/repository/notifications";
import { getEnabledBindingsByType } from "@/repository/notification-bindings";

// ...after upsertBindings(...)
const settings = await getNotificationSettings();
if (settings.useLegacyMode) {
  const required: NotificationType[] = [];
  if (settings.circuitBreakerEnabled) required.push("circuit_breaker");
  if (settings.dailyLeaderboardEnabled) required.push("daily_leaderboard");
  if (settings.costAlertEnabled) required.push("cost_alert");

  const hasAllBindings = (
    await Promise.all(required.map((t) => getEnabledBindingsByType(t)))
  ).every((bindings) => bindings.length > 0);

  if (hasAllBindings) {
    await updateNotificationSettings({ useLegacyMode: false });
  }
}

latencyMs,
});
} catch (_e) {
// 忽略写回失败
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[HIGH] [ERROR-SWALLOWED] updateTestResult write-back failures are silently ignored

Evidence (src/actions/webhook-targets.ts:463-471):

try {
  await updateTestResult(id, {
    success: false,
    error: error instanceof Error ? error.message : "测试失败",
    latencyMs,
  });
} catch (_e) {
  // 忽略写回失败
}

Why this is a problem: If persisting the test result fails (DB connectivity, schema mismatch, permission), the action returns but you lose the signal for the secondary failure. This makes debugging flaky test results much harder.

Suggested fix:

} catch (writeBackError) {
  logger.warn("[WebhookTargets] Failed to persist test result", {
    targetId: id,
    error: writeBackError instanceof Error ? writeBackError.message : String(writeBackError),
  });
}

setOnboardingCompleted("webhookMigration");
}
} catch {
// Silently fail - don't block user if settings fetch fails
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] [ERROR-SILENT] Settings fetch errors are swallowed, hiding migration detection failures

Evidence (src/app/[locale]/dashboard/_components/webhook-migration-dialog.tsx:135-137):

} catch {
  // Silently fail - don't block user if settings fetch fails
}

Why this is a problem: If getNotificationSettingsAction() fails (network/session/server error), users with legacy webhooks won't see the migration dialog and there is no signal for debugging. This failure will also keep retrying silently on future mounts.

Suggested fix:

} catch (error) {
  console.warn("[WebhookMigrationDialog] Failed to load notification settings", error);
}

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

This PR is XL and spans DB migrations/pooling, webhook notifications, onboarding UI, and i18n, which increases integration risk. I focused on NEW webhook onboarding/migration paths and found one critical mode-switching bug that can disable notifications, plus two error-handling gaps that hide failures.

PR Size: XL

  • Lines changed: 11393
  • Files changed: 85
  • Split suggestion (required for XL): split into (1) DB pool + message_request write-buffer changes, (2) webhook targets/bindings backend + migrations, (3) onboarding/migration dialog UI + i18n copy, (4) tests/config changes.

Issues Found

Category Critical High Medium Low
Logic/Bugs 1 0 0 0
Security 0 0 0 0
Error Handling 0 1 1 0
Types 0 0 0 0
Comments/Docs 0 0 0 0
Tests 0 0 0 0
Simplification 0 0 0 0

Critical Issues (Must Fix)

  • src/actions/webhook-targets.ts:366 [LOGIC-BUG] Prematurely flips useLegacyMode before bindings exist (Confidence: 98)

High Priority Issues (Should Fix)

  • src/actions/webhook-targets.ts:463 [ERROR-SWALLOWED] Swallows updateTestResult write-back failures (Confidence: 85)
  • src/app/[locale]/dashboard/_components/webhook-migration-dialog.tsx:135 [ERROR-SILENT] Swallows settings fetch errors, hiding migration detection failures (Confidence: 80)

Review Coverage

  • Logic and correctness
  • Security (OWASP Top 10)
  • Error handling
  • Type safety
  • Documentation accuracy
  • Test coverage
  • Code clarity

Automated review by Codex AI

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Reviewed PR #506 and applied label size/XL.
  • Left 3 inline review comments (diff-only):
    • src/actions/webhook-targets.ts:366 [CRITICAL] [LOGIC-BUG] useLegacyMode flips to false before bindings exist → can disable notifications during/after partial migration or manual setup.
    • src/actions/webhook-targets.ts:463 [HIGH] [ERROR-SWALLOWED] updateTestResult write-back failures are silently ignored.
    • src/app/[locale]/dashboard/_components/webhook-migration-dialog.tsx:135 [MEDIUM] [ERROR-SILENT] settings fetch errors are swallowed, hiding migration detection failures.
  • Submitted the required PR review summary via gh pr review.
  • XL split suggestion: separate PRs for (1) DB pool + message_request write-buffer, (2) webhook targets/bindings backend + migrations, (3) onboarding/migration dialog UI + i18n, (4) tests/config.

- 删除冗余的 onboarding/index.ts 文件
- 统一 onTest 类型定义,移除 as any 类型断言
- 为静默失败添加日志(webhook-migration-dialog, webhook-targets)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@ding113 ding113 changed the base branch from main to dev January 2, 2026 14:58
@ding113 ding113 merged commit d188719 into dev Jan 2, 2026
6 of 7 checks passed
@github-project-automation github-project-automation bot moved this from Backlog to Done in Claude Code Hub Roadmap Jan 2, 2026
@ding113 ding113 deleted the feat/webhook-onboarding-refactor branch January 2, 2026 14:59
This was referenced Jan 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:docs area:i18n area:UI enhancement New feature or request size/XL Extra Large PR (> 1000 lines)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant