Skip to content

fix(web): prevent duplicate system setting notifications#6076

Open
worryzyy wants to merge 1 commit into
QuantumNous:mainfrom
worryzyy:fix/default-ui-duplicate-save-toasts
Open

fix(web): prevent duplicate system setting notifications#6076
worryzyy wants to merge 1 commit into
QuantumNous:mainfrom
worryzyy:fix/default-ui-duplicate-save-toasts

Conversation

@worryzyy

@worryzyy worryzyy commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

⚠️ 提交说明 / PR Notice

Important

  • 请提供人工撰写的简洁摘要,避免直接粘贴未经整理的 AI 输出。

📝 变更描述 / Description

修复新版系统设置页面保存配置时重复显示提示的问题。

系统设置表单会逐项提交发生变化的配置。此前每次更新都会独立显示成功提示;部分页面还会额外显示自己的提示,错误响应也可能同时经过 Axios 拦截器和 mutation 处理,因此一次保存可能产生多条重复通知。

本次调整包括:

  • 同一个设置表单实例使用稳定的 toast ID,多项配置更新只保留一条提示。
  • 系统配置更新由共享 mutation 统一处理错误,避免与 Axios 全局拦截器重复提示。
  • 业务失败转换为 rejection,防止保存失败后继续重置表单状态。
  • 页面可以覆盖或抑制共享提示,避免组合保存流程产生额外通知。
  • 增加通知去重和 API 错误处理的回归测试。

AI-assisted disclosure: 本变更由 AI 辅助实现,并已完成本地代码检查和测试。

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix) - 请关联对应 Issue,避免将设计取舍、理解偏差或预期不一致直接归类为 bug
  • ✨ 新功能 (New feature) - 重大特性建议先通过 Issue 沟通
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

✅ 提交前检查项 / Checklist

  • 人工确认: 我已亲自整理并撰写此描述,没有直接粘贴未经处理的 AI 输出。
  • 非重复提交: 已存在同类 PR fix(settings): 修复保存系统设置时重复弹出成功提示及页面数据状态错乱的问题 #5866,本 PR 提供不同且覆盖范围更完整的实现。
  • Bug fix 说明: 当前没有单独关联 Issue。
  • 变更理解: 我已理解这些更改的工作原理及可能影响。
  • 范围聚焦: 本 PR 未包含任何与当前任务无关的代码改动。
  • 本地验证: 已在本地运行并通过测试或手动验证,维护者可以据此复核结果。
  • 安全合规: 代码中无敏感凭据,且符合项目代码规范。

📸 运行证明 / Proof of Work

  • bun test: 37 passed, 0 failed
  • bun run typecheck: passed
  • 变更文件 oxlint: passed
  • 变更文件 oxfmt --check: passed
  • bun run build: passed

仓库全量 lint、format 和 copyright 检查仍受 main 上已有的无关问题影响,本 PR 涉及文件均已通过对应检查。

Summary by CodeRabbit

  • Bug Fixes
    • Improved system settings update feedback with consistent success and error notifications.
    • Prevented duplicate notifications when saving multiple settings together.
    • Ensured backend rejection messages are shown when an update fails.
    • Reduced unnecessary success messages during composite save actions.
    • Updated announcements, API information, FAQs, uptime monitoring, channel affinity, and payment settings to use the unified notification behavior.

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: deduplicating system setting notifications in the web app.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@worryzyy worryzyy marked this pull request as ready for review July 10, 2026 08:58
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
putComment timed out

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
web/default/src/features/system-settings/hooks/update-option-notification.ts (1)

19-20: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer import { t } from 'i18next' per coding guidelines.

The coding guidelines recommend import { t } from 'i18next' in non-React code. Using the named import is more concise and aligns with the project convention.

As per coding guidelines: "In non-React code (utility functions, constants, class methods), use import { t } from 'i18next' only when the text does not need reactive language switching."

♻️ Optional import refactor
-import i18next from 'i18next'
+import { t } from 'i18next'

Then replace i18next.t(...) calls with t(...):

-    notification?.success || i18next.t('Setting updated successfully'),
+    notification?.success || t('Setting updated successfully'),
-      i18next.t('Failed to update setting'),
+      t('Failed to update setting'),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/system-settings/hooks/update-option-notification.ts`
around lines 19 - 20, Replace the default i18next import with the named t import
in the update-option notification module, then update every i18next.t(...) call
to use t(...), preserving the existing translation keys and behavior.

Source: Coding guidelines

web/default/src/features/system-settings/api.test.ts (1)

1-54: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use Vitest instead of node:test per coding guidelines.

The coding guidelines for web/default/**/*.test.ts require Vitest for unit tests. This file uses node:test and node:assert/strict, and monkey-patches api.put manually. Migrating to Vitest with vi.spyOn would align with the guideline, eliminate the manual restore logic, and match the style used in update-option-notification.test.ts.

As per coding guidelines: "Write unit tests for utility functions and pure logic with Vitest."

♻️ Proposed migration to Vitest
-import assert from 'node:assert/strict'
-import { test } from 'node:test'
-
-import { api } from '`@/lib/api`'
-
-import { updateSystemOption } from './api'
-
-test('system option updates use one notification owner', async () => {
-  const originalPut = api.put
-  let requestConfig: Parameters<typeof api.put>[2]
-
-  try {
-    api.put = (async (_url, _request, config) => {
-      requestConfig = config
-      return {
-        data: { success: true, message: '' },
-      }
-    }) as typeof api.put
-
-    await updateSystemOption({ key: 'SystemName', value: 'Example' })
-
-    assert.equal(requestConfig?.skipBusinessError, true)
-    assert.equal(requestConfig?.skipErrorHandler, true)
-
-    api.put = (async () => ({
-      data: { success: false, message: 'Update rejected' },
-    })) as typeof api.put
-
-    await assert.rejects(
-      updateSystemOption({ key: 'SystemName', value: 'Example' }),
-      /Update rejected/
-    )
-  } finally {
-    api.put = originalPut
-  }
-})
+import { expect, test, vi } from 'vitest'
+
+import { api } from '`@/lib/api`'
+
+import { updateSystemOption } from './api'
+
+test('updateSystemOption bypasses shared error handlers and throws on business failure', async () => {
+  const putSpy = vi.spyOn(api, 'put')
+
+  putSpy.mockResolvedValue({ data: { success: true, message: '' } } as never)
+  await updateSystemOption({ key: 'SystemName', value: 'Example' })
+
+  expect(putSpy).toHaveBeenCalledWith(
+    '/api/option/',
+    { key: 'SystemName', value: 'Example' },
+    expect.objectContaining({
+      skipBusinessError: true,
+      skipErrorHandler: true,
+    })
+  )
+
+  putSpy.mockResolvedValue({
+    data: { success: false, message: 'Update rejected' },
+  } as never)
+
+  await expect(
+    updateSystemOption({ key: 'SystemName', value: 'Example' })
+  ).rejects.toThrow('Update rejected')
+})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/system-settings/api.test.ts` around lines 1 - 54,
Replace the node:test and node:assert/strict imports in the system option update
test with Vitest APIs, using vi.spyOn(api, 'put') to mock requests and restore
the spy through Vitest cleanup rather than manual reassignment and try/finally.
Preserve the existing success and rejection assertions, following the style of
update-option-notification.test.ts.

Source: Coding guidelines

web/default/src/features/system-settings/hooks/update-option-notification.test.ts (1)

19-20: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use Vitest instead of node:test for unit tests.

The coding guidelines require *.test.ts files under web/default/ to use Vitest. This file imports describe/test from node:test and assert from node:assert/strict instead. Additionally, toast.getHistory() appears to be an internal sonner API — with Vitest you could mock the sonner module via vi.mock('sonner') and assert on toast.success/toast.error call arguments, which would be more robust than relying on sonner's internal history.

As per coding guidelines: "web/default/**/*.test.ts: Write unit tests for utility functions and pure logic with Vitest."

♻️ Migrate to Vitest with mocked sonner
-import assert from 'node:assert/strict'
-import { describe, test } from 'node:test'
-
+import { beforeEach, describe, expect, it, vi } from 'vitest'
+
 import { toast } from 'sonner'
+
+vi.mock('sonner', () => ({
+  toast: {
+    success: vi.fn(),
+    error: vi.fn(),
+  },
+}))
+
 import {
   showUpdateOptionError,
   showUpdateOptionSuccess,
 } from './update-option-notification'
+
+beforeEach(() => {
+  vi.clearAllMocks()
+})

Then replace assertions, e.g.:

-    assert.equal(matchingToasts.length, 1)
-    assert.ok(matchingToast && 'title' in matchingToast)
-    assert.equal(matchingToast.title, 'Settings saved')
+    expect(toast.success).toHaveBeenCalledTimes(1)
+    expect(toast.success).toHaveBeenLastCalledWith('Settings saved', {
+      id: 'update-option-batch-success-test',
+    })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@web/default/src/features/system-settings/hooks/update-option-notification.test.ts`
around lines 19 - 20, Replace the node:test and node:assert imports in the
update-option notification tests with Vitest APIs, using describe, test, expect,
beforeEach, and vi as needed. Mock the sonner module with vi.mock('sonner'),
reset mocks between tests, and assert toast.success or toast.error call
arguments instead of relying on toast.getHistory().

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/default/src/features/system-settings/content/api-info-section.tsx`:
- Around line 247-249: Add a success value to the notification object used by
handleSaveAll in the API info save flow, using a specific translated success
message consistent with the announcements and FAQ sections, or set success:
false if no success toast is desired.

---

Nitpick comments:
In `@web/default/src/features/system-settings/api.test.ts`:
- Around line 1-54: Replace the node:test and node:assert/strict imports in the
system option update test with Vitest APIs, using vi.spyOn(api, 'put') to mock
requests and restore the spy through Vitest cleanup rather than manual
reassignment and try/finally. Preserve the existing success and rejection
assertions, following the style of update-option-notification.test.ts.

In
`@web/default/src/features/system-settings/hooks/update-option-notification.test.ts`:
- Around line 19-20: Replace the node:test and node:assert imports in the
update-option notification tests with Vitest APIs, using describe, test, expect,
beforeEach, and vi as needed. Mock the sonner module with vi.mock('sonner'),
reset mocks between tests, and assert toast.success or toast.error call
arguments instead of relying on toast.getHistory().

In
`@web/default/src/features/system-settings/hooks/update-option-notification.ts`:
- Around line 19-20: Replace the default i18next import with the named t import
in the update-option notification module, then update every i18next.t(...) call
to use t(...), preserving the existing translation keys and behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f193d15b-6a8a-4989-8214-0fe012e22fe2

📥 Commits

Reviewing files that changed from the base of the PR and between 4e57038 and 727c4fe.

📒 Files selected for processing (11)
  • web/default/src/features/system-settings/api.test.ts
  • web/default/src/features/system-settings/api.ts
  • web/default/src/features/system-settings/content/announcements-section.tsx
  • web/default/src/features/system-settings/content/api-info-section.tsx
  • web/default/src/features/system-settings/content/faq-section.tsx
  • web/default/src/features/system-settings/content/uptime-kuma-section.tsx
  • web/default/src/features/system-settings/general/channel-affinity/index.tsx
  • web/default/src/features/system-settings/hooks/update-option-notification.test.ts
  • web/default/src/features/system-settings/hooks/update-option-notification.ts
  • web/default/src/features/system-settings/hooks/use-update-option.ts
  • web/default/src/features/system-settings/integrations/payment-settings-section.tsx

Comment on lines +247 to +249
notification: {
error: t('Failed to save API info'),
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add a success notification to handleSaveAll for consistency.

The notification object only includes error — omitting success means showUpdateOptionSuccess will display the generic fallback "Setting updated successfully" toast, since undefined !== false. Both the announcements and FAQ sections provide specific success messages for their save flows. Add either a specific message or success: false if the toast should be suppressed.

Proposed fix: add specific success message
       notification: {
+        success: t('API info saved successfully'),
         error: t('Failed to save API info'),
       },

Or, to suppress the success toast entirely:

       notification: {
+        success: false,
         error: t('Failed to save API info'),
       },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
notification: {
error: t('Failed to save API info'),
},
notification: {
success: t('API info saved successfully'),
error: t('Failed to save API info'),
},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/system-settings/content/api-info-section.tsx` around
lines 247 - 249, Add a success value to the notification object used by
handleSaveAll in the API info save flow, using a specific translated success
message consistent with the announcements and FAQ sections, or set success:
false if no success toast is desired.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant