fix(web): 標示 AIOps 範例資料模式
All checks were successful
CD Pipeline / tests (push) Successful in 1m23s
Code Review / ai-code-review (push) Successful in 13s
CD Pipeline / build-and-deploy (push) Successful in 3m41s
CD Pipeline / post-deploy-checks (push) Successful in 2m7s

This commit is contained in:
Your Name
2026-06-05 01:28:31 +08:00
parent 96df223100
commit d5ce17c72d
10 changed files with 21 additions and 20 deletions

View File

@@ -2239,7 +2239,7 @@
"aiopsTimeline": { "aiopsTimeline": {
"title": "AIOps 全景時序", "title": "AIOps 全景時序",
"subtitle": "告警→感官調查→AI決策→自動執行→驗證→學習 完整鏈路", "subtitle": "告警→感官調查→AI決策→自動執行→驗證→學習 完整鏈路",
"mockBadge": "MOCK 模式", "sampleBadge": "範例資料",
"stages": { "stages": {
"alert": "告警觸發", "alert": "告警觸發",
"diagnose": "感官調查", "diagnose": "感官調查",

View File

@@ -2239,7 +2239,7 @@
"aiopsTimeline": { "aiopsTimeline": {
"title": "AIOps 全景時序", "title": "AIOps 全景時序",
"subtitle": "告警→感官調查→AI決策→自動執行→驗證→學習 完整鏈路", "subtitle": "告警→感官調查→AI決策→自動執行→驗證→學習 完整鏈路",
"mockBadge": "MOCK 模式", "sampleBadge": "範例資料",
"stages": { "stages": {
"alert": "告警觸發", "alert": "告警觸發",
"diagnose": "感官調查", "diagnose": "感官調查",

View File

@@ -5,7 +5,7 @@
// AIOps 全景時序頁面 // AIOps 全景時序頁面
// 告警→感官調查→AI決策→自動執行→驗證→學習 完整鏈路視覺化 // 告警→感官調查→AI決策→自動執行→驗證→學習 完整鏈路視覺化
// //
// Mock 模式NEXT_PUBLIC_AIOPS_TIMELINE_MOCK=true // 範例資料模式NEXT_PUBLIC_AIOPS_TIMELINE_MOCK=true
// 真實 API: GET /api/v1/aiops/timeline?incident_id=&limit=20 // 真實 API: GET /api/v1/aiops/timeline?incident_id=&limit=20
// ============================================================ // ============================================================

View File

@@ -12,7 +12,7 @@
* - ApprovalCard 滑入動畫 * - ApprovalCard 滑入動畫
* - 記憶體安全清理 * - 記憶體安全清理
* *
* 真實性條款: 禁止任何 Mock Data * 真實性條款:禁止以範例資料掩蓋 API 狀態
* i18n: 100% next-intl * i18n: 100% next-intl
*/ */

View File

@@ -20,7 +20,7 @@ import {
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { TimelineFilter } from './TimelineFilter' import { TimelineFilter } from './TimelineFilter'
import { TimelineStage } from './TimelineStage' import { TimelineStage } from './TimelineStage'
import { MOCK_INCIDENTS } from './mock-data' import { SAMPLE_TIMELINE_INCIDENTS } from './sample-incidents'
import type { import type {
TimelineIncident, TimelineIncident,
TimelineFilterState, TimelineFilterState,
@@ -31,7 +31,8 @@ import type {
// Constants // Constants
// ============================================================ // ============================================================
const IS_MOCK = process.env.NEXT_PUBLIC_AIOPS_TIMELINE_MOCK === 'true' // 保留既有環境變數相容UI 以「範例資料」呈現,避免誤讀為正式證據。
const USE_SAMPLE_TIMELINE = process.env.NEXT_PUBLIC_AIOPS_TIMELINE_MOCK === 'true'
const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? '' const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? ''
const STAGE_ORDER: StageType[] = ['alert', 'diagnose', 'decide', 'execute', 'verify', 'learn'] const STAGE_ORDER: StageType[] = ['alert', 'diagnose', 'decide', 'execute', 'verify', 'learn']
@@ -280,14 +281,14 @@ export default function AiopsTimelinePanel() {
} = useQuery({ } = useQuery({
queryKey: ['aiops-timeline', filter.incident_id, filter.time_range], queryKey: ['aiops-timeline', filter.incident_id, filter.time_range],
queryFn: () => fetchTimeline(filter.incident_id), queryFn: () => fetchTimeline(filter.incident_id),
enabled: !IS_MOCK, enabled: !USE_SAMPLE_TIMELINE,
staleTime: 30_000, staleTime: 30_000,
refetchInterval: 60_000, refetchInterval: 60_000,
}) })
// 客戶端篩選 — rawIncidents 內聯進 useMemo 避免 conditional 依賴警告 // 客戶端篩選 — rawIncidents 內聯進 useMemo 避免 conditional 依賴警告
const incidents = useMemo(() => { const incidents = useMemo(() => {
const rawIncidents: TimelineIncident[] = IS_MOCK ? MOCK_INCIDENTS : (apiData ?? []) const rawIncidents: TimelineIncident[] = USE_SAMPLE_TIMELINE ? SAMPLE_TIMELINE_INCIDENTS : (apiData ?? [])
let list = rawIncidents let list = rawIncidents
if (filter.incident_id.trim()) { if (filter.incident_id.trim()) {
@@ -325,9 +326,9 @@ export default function AiopsTimelinePanel() {
<h1 className="font-heading text-2xl font-bold text-nothing-black flex items-center gap-2.5"> <h1 className="font-heading text-2xl font-bold text-nothing-black flex items-center gap-2.5">
<GitBranch className="w-6 h-6 text-claw-blue" aria-hidden="true" /> <GitBranch className="w-6 h-6 text-claw-blue" aria-hidden="true" />
{t('title')} {t('title')}
{IS_MOCK && ( {USE_SAMPLE_TIMELINE && (
<span className="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-body font-bold uppercase tracking-widest bg-status-warning/10 border border-status-warning/20 text-status-warning"> <span className="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-body font-bold uppercase tracking-widest bg-status-warning/10 border border-status-warning/20 text-status-warning">
{t('mockBadge')} {t('sampleBadge')}
</span> </span>
)} )}
</h1> </h1>
@@ -337,7 +338,7 @@ export default function AiopsTimelinePanel() {
</div> </div>
{/* Refresh button (僅真實模式顯示) */} {/* Refresh button (僅真實模式顯示) */}
{!IS_MOCK && ( {!USE_SAMPLE_TIMELINE && (
<button <button
onClick={() => refetch()} onClick={() => refetch()}
disabled={isLoading} disabled={isLoading}
@@ -362,7 +363,7 @@ export default function AiopsTimelinePanel() {
/> />
{/* Loading state (真實 API) */} {/* Loading state (真實 API) */}
{!IS_MOCK && isLoading && ( {!USE_SAMPLE_TIMELINE && isLoading && (
<div className="flex items-center justify-center py-20" role="status" aria-live="polite"> <div className="flex items-center justify-center py-20" role="status" aria-live="polite">
<RefreshCw className="w-5 h-5 animate-spin text-nothing-gray-400" /> <RefreshCw className="w-5 h-5 animate-spin text-nothing-gray-400" />
<span className="ml-2 font-body text-sm text-nothing-gray-400">{t('loading')}</span> <span className="ml-2 font-body text-sm text-nothing-gray-400">{t('loading')}</span>
@@ -370,7 +371,7 @@ export default function AiopsTimelinePanel() {
)} )}
{/* Error state */} {/* Error state */}
{!IS_MOCK && error && !isLoading && ( {!USE_SAMPLE_TIMELINE && error && !isLoading && (
<div <div
className="flex flex-col items-center justify-center py-20 gap-3" className="flex flex-col items-center justify-center py-20 gap-3"
role="alert" role="alert"

View File

@@ -3,5 +3,5 @@ export { TimelineStage } from './TimelineStage'
export { TimelineStageDetails } from './TimelineStageDetails' export { TimelineStageDetails } from './TimelineStageDetails'
export { TimelineFilter } from './TimelineFilter' export { TimelineFilter } from './TimelineFilter'
export { EvidenceViewer } from './EvidenceViewer' export { EvidenceViewer } from './EvidenceViewer'
export { MOCK_INCIDENTS } from './mock-data' export { SAMPLE_TIMELINE_INCIDENTS } from './sample-incidents'
export type * from './types' export type * from './types'

View File

@@ -1,12 +1,12 @@
// 2026-04-26 P2.5 by Claude — AIOps Timeline // 2026-04-26 P2.5 by Claude — AIOps Timeline
// ============================================================ // ============================================================
// Mock Data — 3 範例 incident完整 6 階段 + metadata // Sample incidents — 3 範例 incident完整 6 階段 + metadata
// NEXT_PUBLIC_AIOPS_TIMELINE_MOCK=true 時使用 // NEXT_PUBLIC_AIOPS_TIMELINE_MOCK=true 時使用
// ============================================================ // ============================================================
import type { TimelineIncident } from './types' import type { TimelineIncident } from './types'
export const MOCK_INCIDENTS: TimelineIncident[] = [ export const SAMPLE_TIMELINE_INCIDENTS: TimelineIncident[] = [
{ {
incident_id: 'INC-2026-0425-001', incident_id: 'INC-2026-0425-001',
title: 'API Server CPU 100% — k8s-188', title: 'API Server CPU 100% — k8s-188',

View File

@@ -9,7 +9,7 @@
* 3. getTerminalComponents — 只返回 allowInTerminal=true 的組件 * 3. getTerminalComponents — 只返回 allowInTerminal=true 的組件
* 4. validateProps — 錯誤碼分類 (UNKNOWN_COMPONENT / ZOD_VALIDATION_FAILED) * 4. validateProps — 錯誤碼分類 (UNKNOWN_COMPONENT / ZOD_VALIDATION_FAILED)
* *
* 注意: registry.ts 使用 React.lazy在 Node 環境需要 Mock * 注意registry.ts 使用 React.lazy在 Node 環境需以測試替身隔離 lazy 載入
* *
* @see ADR-032 GenUI Dynamic Rendering * @see ADR-032 GenUI Dynamic Rendering
* Phase 19.6 ogt 2026-03-31 (台北時間) * Phase 19.6 ogt 2026-03-31 (台北時間)
@@ -17,7 +17,7 @@
import { vi, describe, it, expect, beforeAll } from 'vitest' import { vi, describe, it, expect, beforeAll } from 'vitest'
// ===== Mock React (lazy Node 環境無法執行) ===== // ===== React lazy 測試替身(Node 環境無法執行 lazy import =====
vi.mock('react', () => ({ vi.mock('react', () => ({
lazy: vi.fn((factory: () => Promise<unknown>) => factory), lazy: vi.fn((factory: () => Promise<unknown>) => factory),
})) }))

View File

@@ -5,7 +5,7 @@
* ===================================== * =====================================
* 顯示真實待審核的操作,整合 NuclearKeyButton 長按確認 * 顯示真實待審核的操作,整合 NuclearKeyButton 長按確認
* *
* 2026-04-07 Claude Code: Sprint F 打假行動 — 移除 MOCK_PENDING * 2026-04-07 Claude Code: Sprint F 打假行動 — 移除舊待審範例常數
* 專案鐵律: 接真實 /api/v1/approvals/pending無資料顯示 EmptyState * 專案鐵律: 接真實 /api/v1/approvals/pending無資料顯示 EmptyState
*/ */

View File

@@ -5,7 +5,7 @@
* ================================ * ================================
* 5 KPI + 執行路徑分佈 + Playbook 排名 + 時間軸 * 5 KPI + 執行路徑分佈 + Playbook 排名 + 時間軸
* *
* 2026-04-07 Claude Code: Sprint F 打假行動 — 移除所有 MOCK 常數 * 2026-04-07 Claude Code: Sprint F 打假行動 — 移除所有假資料常數
* 專案鐵律: 所有數據從 stats/playbooks/history props 計算,禁止寫死! * 專案鐵律: 所有數據從 stats/playbooks/history props 計算,禁止寫死!
*/ */