feat(governance): link work items to event history
All checks were successful
Code Review / ai-code-review (push) Successful in 11s
CD Pipeline / tests (push) Successful in 3m35s
CD Pipeline / build-and-deploy (push) Successful in 3m50s
CD Pipeline / post-deploy-checks (push) Successful in 1m42s

This commit is contained in:
Your Name
2026-05-20 11:03:52 +08:00
parent 4a24d3e4fc
commit 739a8e0f78
9 changed files with 193 additions and 11 deletions

View File

@@ -10,7 +10,7 @@
*/
import { useState, useRef, useEffect } from 'react'
import { Filter, Calendar, ChevronDown, X } from 'lucide-react'
import { Filter, Calendar, ChevronDown, Search, X } from 'lucide-react'
import { useTranslations } from 'next-intl'
import { GlassCard } from '@/components/ui/glass-card'
@@ -19,6 +19,7 @@ import { GlassCard } from '@/components/ui/glass-card'
// =============================================================================
export interface EventsFilter {
eventId: string
eventTypes: string[]
status: 'all' | 'resolved' | 'unresolved'
severity: 'all' | 'critical' | 'warning' | 'info'
@@ -156,6 +157,14 @@ export function EventsFilterBar({ filter, onChange, availableEventTypes = [] }:
const tType = useTranslations('governance.events.eventType')
const eventTypeLabels: Record<string, string> = {
slo_violation: tType('slo_violation'),
governance_slo_data_gap: tType('governance_slo_data_gap'),
knowledge_degradation: tType('knowledge_degradation'),
kb_stale: tType('kb_stale'),
execution_blast_radius: tType('execution_blast_radius'),
conservative_mode: tType('conservative_mode'),
replay_degraded: tType('replay_degraded'),
self_demotion: tType('self_demotion'),
slo_breach: tType('slo_breach'),
accuracy_drop: tType('accuracy_drop'),
km_stall: tType('km_stall'),
@@ -164,6 +173,7 @@ export function EventsFilterBar({ filter, onChange, availableEventTypes = [] }:
}
const hasActiveFilter =
filter.eventId.trim() !== '' ||
filter.eventTypes.length > 0 ||
filter.status !== 'all' ||
filter.severity !== 'all' ||
@@ -171,6 +181,7 @@ export function EventsFilterBar({ filter, onChange, availableEventTypes = [] }:
filter.dateTo !== ''
const clearAll = () => onChange({
eventId: '',
eventTypes: [],
status: 'all',
severity: 'all',
@@ -216,6 +227,28 @@ export function EventsFilterBar({ filter, onChange, availableEventTypes = [] }:
labelMap={eventTypeLabels}
/>
{/* Event ID exact search */}
<div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
<Search size={11} style={{ color: '#87867f', flexShrink: 0 }} />
<input
type="search"
value={filter.eventId}
onChange={e => onChange({ ...filter, eventId: e.target.value })}
placeholder={t('eventIdPlaceholder')}
aria-label={t('eventId')}
style={{
padding: '4px 8px',
border: '0.5px solid #e0ddd4',
borderRadius: 6,
background: '#fff',
fontFamily: "'DM Mono', monospace",
fontSize: 11,
color: '#141413',
minWidth: 190,
}}
/>
</div>
{/* Date from */}
<div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
<Calendar size={11} style={{ color: '#87867f', flexShrink: 0 }} />

View File

@@ -10,7 +10,7 @@
* @created 2026-05-02 Claude Sonnet 4.6 — governance PR 3-5
*/
import { useState } from 'react'
import { Fragment, useState } from 'react'
import { ChevronDown, ChevronLeft, ChevronRight, AlertTriangle, ShieldCheck } from 'lucide-react'
import { useTranslations } from 'next-intl'
import { StatusOrb } from '@/components/ui/status-orb'
@@ -54,6 +54,11 @@ interface EventsTableProps {
// =============================================================================
const EVENT_TYPE_COLORS: Record<string, { bg: string; text: string }> = {
slo_violation: { bg: 'rgba(255,51,0,0.08)', text: '#FF3300' },
governance_slo_data_gap: { bg: 'rgba(255,51,0,0.08)', text: '#FF3300' },
knowledge_degradation: { bg: 'rgba(245,158,11,0.10)', text: '#d97010' },
kb_stale: { bg: 'rgba(245,158,11,0.10)', text: '#d97010' },
execution_blast_radius: { bg: 'rgba(245,158,11,0.10)', text: '#d97010' },
slo_breach: { bg: 'rgba(255,51,0,0.08)', text: '#FF3300' },
accuracy_drop: { bg: 'rgba(245,158,11,0.10)', text: '#d97010' },
km_stall: { bg: 'rgba(74,144,217,0.10)', text: '#2563EB' },
@@ -61,6 +66,22 @@ const EVENT_TYPE_COLORS: Record<string, { bg: string; text: string }> = {
trust_degradation: { bg: 'rgba(236,72,153,0.10)', text: '#DB2777' },
}
const EVENT_TYPE_LABEL_KEYS = new Set([
'slo_violation',
'governance_slo_data_gap',
'knowledge_degradation',
'kb_stale',
'execution_blast_radius',
'conservative_mode',
'replay_degraded',
'self_demotion',
'slo_breach',
'accuracy_drop',
'km_stall',
'mcp_failure',
'trust_degradation',
])
function getEventTypeStyle(type: string) {
return EVENT_TYPE_COLORS[type] ?? { bg: 'rgba(135,134,127,0.10)', text: '#87867f' }
}
@@ -114,6 +135,11 @@ export function EventsTable({
whiteSpace: 'nowrap',
}
const eventTypeLabel = (eventType: string) => {
if (!EVENT_TYPE_LABEL_KEYS.has(eventType)) return eventType
return tType(eventType as Parameters<typeof tType>[0])
}
return (
<div style={{ overflow: 'hidden' }}>
<div style={{ overflowX: 'auto' }}>
@@ -207,9 +233,8 @@ export function EventsTable({
const typeStyle = getEventTypeStyle(event.event_type)
return (
<>
<Fragment key={event.id}>
<tr
key={event.id}
style={{
borderBottom: isExpanded ? 'none' : '0.5px solid #e0ddd4',
transition: 'background 0.12s',
@@ -232,7 +257,7 @@ export function EventsTable({
letterSpacing: '0.3px',
whiteSpace: 'nowrap',
}}>
{tType(event.event_type as Parameters<typeof tType>[0]) ?? event.event_type}
{eventTypeLabel(event.event_type)}
</span>
</td>
@@ -298,7 +323,7 @@ export function EventsTable({
{/* Inline expand drawer */}
{isExpanded && <EventDetailDrawer key={`detail-${event.id}`} event={event} />}
</>
</Fragment>
)
})}
</tbody>