Keep 告警详情侧边栏(AlertSidebar)集成指南:统一 Incident Alerts 页面告警交互体验
【免费下载链接】keepThe open-source AIOps and alert management platform项目地址: https://gitcode.com/GitHub_Trending/kee/keep
Keep(开源 AIOps 与告警管理平台)在前端keep-ui中通过将事件(Incident)详情页的告警表格从ViewAlertModal弹窗模式切换为AlertSidebar侧边抽屉模式,实现了全应用统一的告警详情查看与操作体验。本文以该集成实现为线索,结合源码讲解组件替换、状态管理、交互入口、侧边栏功能与测试覆盖,帮助你在 Keep 前端中理解、复现并扩展这套侧边栏方案。
集成背景:为什么用 AlertSidebar 替换 ViewAlertModal
在 Keep 的告警工作流中,告警详情通常有两种查看形态:弹窗(Modal)与侧边抽屉(Sidebar)。ViewAlertModal作为独立弹窗组件存在,而主告警表格(alert-table.tsx)早已使用AlertSidebar。本次集成把事件(Incident)详情页的告警列表也切换到同一侧边栏组件,核心动机是:
- 一致性:无论从告警流(Alert Feed)还是事件(Incident)上下文进入,看到的都是同一套侧边栏交互;
- 功能对齐:侧边栏自带告警菜单(AlertMenu)、时间线、相关服务拓扑等能力,事件上下文同样受益;
- 可维护性:只维护一个侧边栏组件,替代多个弹窗组件,降低 UI 代码的重复与分叉。
该决策记录于 ALERT_SIDEBAR_INTEGRATION.md/incidents/[id]/alerts/ALERT_SIDEBAR_INTEGRATION.md),实现集中在事件告警页组件incident-alerts.tsx中。
组件集成实现剖析(incident-alerts.tsx)
核心改动位于 incident-alerts.tsx/incidents/[id]/alerts/incident-alerts.tsx)。该文件是事件详情页“Alerts”标签页的入口组件,接收incident: IncidentDto作为 props,负责拉取该事件关联的告警列表、渲染表格并处理行级交互。
导入变更:移除弹窗、引入侧边栏
原实现仅依赖ViewAlertModal,集成后同时引入两个组件:
import { AlertSidebar } from "@/features/alerts/alert-detail-sidebar"; import { ViewAlertModal } from "@/features/alerts/view-raw-alert";其中AlertSidebar通过 alert-detail-sidebar/index.ts 统一导出,实际实现在 ui/alert-sidebar.tsx。保留ViewAlertModal是因为“查看按钮”路径仍走弹窗(详见下文交互入口),两类组件按需共存。
状态管理重构
集成将原先单一的viewAlertModal状态拆分为三个独立状态,职责更清晰:
// State for ViewAlertModal (opened by view button) const [viewAlertModal, setViewAlertModal] = useState<AlertDto | null>(null); // State for AlertSidebar (opened by row click) const [selectedAlert, setSelectedAlert] = useState<AlertDto | null>(null); const [isSidebarOpen, setIsSidebarOpen] = useState(false); // Add state for incident selector modal (needed by AlertSidebar) const [isIncidentSelectorOpen, setIsIncidentSelectorOpen] = useState(false);selectedAlert:记录当前在侧边栏中展示的告警对象;isSidebarOpen:控制侧边栏开关;isIncidentSelectorOpen:供AlertSidebar内部“关联事件(Correlate Incident)”动作使用——侧边栏内部菜单通过该状态打开事件选择器弹窗。
侧边栏关闭统一走handleSidebarClose,同时复位两个状态,避免关闭后再操作残留旧数据:
const handleSidebarClose = () => { setIsSidebarOpen(false); setSelectedAlert(null); };侧边栏挂载与可选 props
在 JSX 中,侧边栏通过 Headless UI 的Dialog渲染为右侧抽屉,挂载于表格之后:
<AlertSidebar isOpen={isSidebarOpen} toggle={handleSidebarClose} alert={selectedAlert} // These optional props are passed to maintain feature parity with the main alerts table setRunWorkflowModalAlert={undefined} setDismissModalAlert={undefined} setChangeStatusAlert={undefined} setIsIncidentSelectorOpen={setIsIncidentSelectorOpen} />对照AlertSidebar的完整 props 定义(alert-sidebar.tsx):
| Prop | 类型 | 必填 | 作用 |
|---|---|---|---|
isOpen | boolean | 是 | 控制侧边栏显示 |
toggle | VoidFunction | 是 | 关闭回调(点击遮罩/关闭按钮触发) |
alert | AlertDto \| null | 是 | 当前展示的告警 |
setRunWorkflowModalAlert | (alert) => void | 可选 | 打开“运行工作流”弹窗 |
setDismissModalAlert | (alerts[]) => void | 可选 | 打开“忽略(Dismiss)”弹窗 |
setChangeStatusAlert | (alert) => void | 可选 | 打开“变更状态”弹窗 |
setIsIncidentSelectorOpen | (open: boolean) => void | 必填 | 打开“关联事件”选择器 |
从源码结构看,事件页目前只接通了setIsIncidentSelectorOpen,其余三个动作 handler 传undefined,侧边栏菜单对应入口即按条件隐藏;而在主告警表格(alert-table.tsx)中,这些 props 全部接通,这也是下文“功能对齐”设计的关键——同一组件在不同页面通过 props 开放不同能力。
两种用户交互入口
文档描述了侧边栏的两种打开方式,结合源码与测试(incident-alerts-sidebar.test.tsx/incidents/[id]/alerts/tests/incident-alerts-sidebar.test.tsx))核验后的真实行为如下:
行点击(打开 AlertSidebar):点击告警表格中的任意一行,触发
AlertsTableBody的onRowClick回调,设置selectedAlert并打开侧边栏:<AlertsTableBody table={table} showSkeleton={false} theme={theme} onRowClick={(alert) => { setSelectedAlert(alert); setIsSidebarOpen(true); }} lastViewedAlert={null} presetName={"incident-alerts"} />表格由 TanStack Table 构建,行 ID 使用
alert.fingerprint,且对 API 偶发返回重复 ID 的情况做了“追加行下标”的去重兜底处理。View Details 按钮(打开 ViewAlertModal):行尾操作托盘
IncidentAlertActionTray中的“查看详情”按钮实际打开的是ViewAlertModal(源码注释// Open the ViewAlertModal when clicking the view button),而非侧边栏。测试用例should open ViewAlertModal when clicking view button in action tray也明确断言了这一点:点击 view 按钮后view-alert-modal可见、alert-sidebar不可见。因此严格来说,事件页的侧边栏唯一入口是行点击,两个组件按“行点击 vs 视图按钮”分工并存。关闭方式:点击右上角关闭按钮、点击遮罩区域(
Dialog onClose={toggle})都会调用handleSidebarClose。测试专门覆盖了关闭后isOpen=false且alert=null,防止出现 “Cannot read properties of null (reading 'fingerprint')” 之类的空指针回归。
AlertSidebar 核心能力与源码级讲解
侧边栏本体是一个固定右侧、宽度 2/4 的抽屉面板(Dialog.Panel,带 300ms 滑入/淡出过渡),自上而下包含:标题区(严重级别徽标 + 告警名)、告警菜单(AlertMenu)、可配置字段区、关联事件列表、告警时间线、相关服务拓扑。
告警菜单(AlertMenu)与动作体系
侧边栏头部内嵌AlertMenu(alert-menu.tsx),isInSidebar={true}时展示完整动作集合,动作项通过item.show条件按 props 是否传入决定显隐:
- Run Workflow:回调
setRunWorkflowModalAlert?.(alert)打开工作流运行弹窗; - Dismiss / Restore:
onDismiss回调setDismissModalAlert?.([alert]); - Change Status:
setChangeStatusAlert?.(alert); - Correlate Incident:
setIsIncidentSelectorOpen?.(true)(仅当传入该回调时显示); - 查看原始 Payload:
openAlertPayloadModal; - Provider 方法调用:
openMethodModal(method),可对已安装 provider 执行自定义方法; - Assign 分派:
callAssignEndpoint()。
这解释了事件页集成时为何必须新增isIncidentSelectorOpen状态——事件上下文里“关联事件”是高频操作,但运行工作流/忽略/改状态等弹窗入口在事件页暂以undefined关闭,形成与主告警流有差异、面向事件场景的能力子集。
可配置字段渲染(ALERT_SIDEBAR_FIELDS)
侧边栏主体按config.ALERT_SIDEBAR_FIELDS(来自 useConfig)渲染字段,字段注册表定义在 alertSidebarFields.tsx:
export type AlertSidebarFieldName = | "service" | "source" | "description" | "message" | "fingerprint" | "url" | "incidents" | "timeline" | "relatedServices";- 标准字段:
service、source、description、message、fingerprint、url等,每个字段都带shouldRender(alert)守卫(如无 URL 时不渲染 URL 行),fingerprint与url支持一键复制(handleCopyFingerprint/handleCopyUrl,失败时通过showErrorToast提示并给出文档链接); - 特殊字段:
incidents、timeline、relatedServices在字段循环外单独渲染(见下文); - 自定义字段:
getCustomFields解析ALERT_SIDEBAR_FIELDS中非标准字段,按点分路径取值,支持labels.alertname、annotations.description,甚至数组下标incident_dto.0.assignee(见getNestedValue实现),字段名自动由 snake_case/camelCase 转成 Title Case。
关联事件、时间线与相关服务拓扑
- Incidents:当配置包含
incidents且alert.incident_dto存在时,渲染可折叠的CollapsibleIncidentsList,展示该告警所属的事件列表; - Alert Timeline:通过
useAlerts().useAlertAudit(alert.fingerprint)拉取审计历史,展示状态变更与操作记录,key随审计数据长度变化以便刷新后重挂载,支持手动刷新(handleRefresh调用mutate()); - Related Services:当配置包含
relatedServices时,渲染TopologyMap(复用 拓扑地图/topology/ui/map) 组件),以alert.providerId与alert.service定位相关服务拓扑,帮助在事件排障时快速看清告警影响的上下游服务。
测试覆盖与运行方式
集成配套的测试文件为 incident-alerts-sidebar.test.tsx/incidents/[id]/alerts/tests/incident-alerts-sidebar.test.tsx),采用 Jest + Testing Library,通过 mock 掉next/navigation、数据 hooks(useIncidentAlerts、usePollIncidentAlerts)、provider/config hooks 及AlertSidebar本身来隔离测试目标组件。覆盖点:
| 用例 | 断言内容 |
|---|---|
| 渲染 | 告警列表正确展示(名称、严重级别),初始侧边栏关闭 |
| 行点击打开 | 点击alert-row-*后侧边栏出现且内容为对应告警 |
| 关闭 | 点击关闭按钮后侧边栏消失、状态复位 |
| 无异常关闭 | 关闭后isOpen=false且alert=null,无空指针异常 |
| 告警切换 | 从告警 A 切到告警 B,侧边栏内容随selectedAlert更新 |
| 空状态 | 无告警时渲染EmptyStateCard(“No alerts yet”) |
| 加载状态 | 数据拉取中渲染IncidentAlertsTableBodySkeleton骨架屏 |
| 双组件共存 | View 按钮打开ViewAlertModal,行点击打开AlertSidebar,两者可同时存在 |
运行测试(文档给出的命令,需在keep-ui目录下):
cd keep-ui npm test -- --testPathPattern="incident-alerts-sidebar.test.tsx"若只跑事件告警相关的全部用例,可将 pattern 替换为incident-alerts.*。
收益与未来扩展
收益总结:全应用统一的侧边栏交互;事件上下文中告警动作能力与主告警流对齐;单组件多页面复用,显著降低 UI 维护成本;对用户而言,熟悉的行点击-抽屉模式降低了学习成本。
未来扩展方向:文档指出侧边栏已具备工作流执行、状态变更等能力,当前事件页以undefined关闭这些入口;后续如需在事件上下文中启用,只需把对应 handler 从父组件传入:
<AlertSidebar isOpen={isSidebarOpen} toggle={handleSidebarClose} alert={selectedAlert} setRunWorkflowModalAlert={setRunWorkflowModalAlert} // 传入后菜单项自动显示 setDismissModalAlert={setDismissModalAlert} setChangeStatusAlert={setChangeStatusAlert} setIsIncidentSelectorOpen={setIsIncidentSelectorOpen} />由于AlertMenu依据 props 是否存在控制菜单项显隐,接入无需改动侧边栏本体,体现了该组件面向需求演进的扩展性设计。若需进一步定制展示字段,可通过后端配置ALERT_SIDEBAR_FIELDS(含标准字段与点分路径自定义字段)动态调整侧边栏内容,实现“一套组件、多处配置”的灵活布局。
【免费下载链接】keepThe open-source AIOps and alert management platform项目地址: https://gitcode.com/GitHub_Trending/kee/keep
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考