/** * Unified Alert Management System * * Comprehensive system for handling all alert operations in the frontend * including API calls, SSE processing, and UI state management */ import { Alert, Event, AlertTypeClass, PriorityLevel, EventDomain } from '../api/types/events'; import { translateAlertTitle, translateAlertMessage } from '../utils/alertI18n'; // ============================================================ // Type Definitions // ============================================================ export interface AlertFilterOptions { type_class?: AlertTypeClass[]; priority_level?: PriorityLevel[]; domain?: EventDomain[]; status?: ('active' | 'acknowledged' | 'resolved' | 'dismissed' | 'in_progress')[]; search?: string; } export interface AlertProcessingResult { success: boolean; alert?: Alert | AlertResponse; error?: string; } // ============================================================ // Alert Processing Utilities // ============================================================ /** * Normalize alert to the unified structure (only for new Event structure) */ export function normalizeAlert(alert: any): Alert { // Only accept the new Event structure - no legacy support if (alert.event_class === 'alert') { return alert as Alert; } // If it's an SSE EventSource message with nested data if (alert.data && alert.data.event_class === 'alert') { return alert.data as Alert; } throw new Error('Only new Event structure is supported by normalizeAlert'); } /** * Apply filters to an array of alerts */ export function applyAlertFilters( alerts: Alert[], filters: AlertFilterOptions = {}, search: string = '' ): Alert[] { return alerts.filter(alert => { // Filter by type class if (filters.type_class && filters.type_class.length > 0) { if (!alert.type_class || !filters.type_class.includes(alert.type_class as AlertTypeClass)) { return false; } } // Filter by priority level if (filters.priority_level && filters.priority_level.length > 0) { if (!alert.priority_level || !filters.priority_level.includes(alert.priority_level as PriorityLevel)) { return false; } } // Filter by domain if (filters.domain && filters.domain.length > 0) { if (!alert.event_domain || !filters.domain.includes(alert.event_domain as EventDomain)) { return false; } } // Filter by status if (filters.status && filters.status.length > 0) { if (!alert.status || !filters.status.includes(alert.status as any)) { return false; } } // Search filter if (search) { const searchLower = search.toLowerCase(); const title = translateAlertTitle(alert, (key: string, params?: any) => key) || ''; const message = translateAlertMessage(alert, (key: string, params?: any) => key) || ''; if (!title.toLowerCase().includes(searchLower) && !message.toLowerCase().includes(searchLower) && !alert.id.toLowerCase().includes(searchLower)) { return false; } } return true; }); } // ============================================================ // Alert Filtering and Sorting // ============================================================ /** * Filter alerts based on provided criteria */ export function filterAlerts(alerts: Alert[], filters: AlertFilterOptions = {}): Alert[] { return alerts.filter(alert => { // Type class filter if (filters.type_class && !filters.type_class.includes(alert.type_class)) { return false; } // Priority level filter if (filters.priority_level && !filters.priority_level.includes(alert.priority_level)) { return false; } // Domain filter if (filters.domain && !filters.domain.includes(alert.event_domain)) { return false; } // Status filter if (filters.status && !filters.status.includes(alert.status as any)) { return false; } // Search filter if (filters.search) { const searchTerm = filters.search.toLowerCase(); const title = translateAlertTitle(alert, (key: string, params?: any) => key).toLowerCase(); const message = translateAlertMessage(alert, (key: string, params?: any) => key).toLowerCase(); if (!title.includes(searchTerm) && !message.includes(searchTerm)) { return false; } } return true; }); } /** * Sort alerts by priority, urgency, and creation time */ export function sortAlerts(alerts: Alert[]): Alert[] { return [...alerts].sort((a, b) => { // Sort by priority level first const priorityOrder: Record = { critical: 4, important: 3, standard: 2, info: 1 }; const priorityDiff = priorityOrder[b.priority_level] - priorityOrder[a.priority_level]; if (priorityDiff !== 0) return priorityDiff; // If same priority, sort by type class const typeClassOrder: Record = { escalation: 5, action_needed: 4, prevented_issue: 3, trend_warning: 2, information: 1 }; const typeDiff = typeClassOrder[b.type_class] - typeClassOrder[a.type_class]; if (typeDiff !== 0) return typeDiff; // If same type and priority, sort by creation time (newest first) return new Date(b.created_at).getTime() - new Date(a.created_at).getTime(); }); } // ============================================================ // Alert Utility Functions // ============================================================ /** * Get alert icon based on type and priority */ export function getAlertIcon(alert: Alert): string { switch (alert.type_class) { case 'action_needed': return alert.priority_level === 'critical' ? 'alert-triangle' : 'alert-circle'; case 'escalation': return 'alert-triangle'; case 'trend_warning': return 'trending-up'; case 'prevented_issue': return 'check-circle'; case 'information': default: return 'info'; } } /** * Get alert color based on priority level */ export function getAlertColor(alert: Alert): string { switch (alert.priority_level) { case 'critical': return 'var(--color-error)'; case 'important': return 'var(--color-warning)'; case 'standard': return 'var(--color-info)'; case 'info': default: return 'var(--color-success)'; } } /** * Check if alert requires immediate attention */ export function requiresImmediateAttention(alert: Alert): boolean { return alert.type_class === 'action_needed' && (alert.priority_level === 'critical' || alert.priority_level === 'important') && alert.status === 'active'; } /** * Check if alert is actionable (not already addressed) */ export function isActionable(alert: Alert): boolean { return alert.status === 'active' && !alert.orchestrator_context?.already_addressed; } // ============================================================ // SSE Processing // ============================================================ // ============================================================ // Alert State Management Utilities // ============================================================ /** * Merge new alerts with existing alerts, avoiding duplicates */ export function mergeAlerts(existingAlerts: Alert[], newAlerts: Alert[]): Alert[] { const existingIds = new Set(existingAlerts.map(alert => alert.id)); const uniqueNewAlerts = newAlerts.filter(alert => !existingIds.has(alert.id)); return [...existingAlerts, ...uniqueNewAlerts]; } /** * Update specific alert in array (for status changes, etc.) */ export function updateAlertInArray(alerts: Alert[], updatedAlert: Alert): Alert[] { return alerts.map(alert => alert.id === updatedAlert.id ? updatedAlert : alert ); } /** * Remove specific alert from array */ export function removeAlertFromArray(alerts: Alert[], alertId: string): Alert[] { return alerts.filter(alert => alert.id !== alertId); } /** * Get alert statistics */ export function getAlertStats(alerts: Alert[]) { const stats = { total: alerts.length, active: 0, acknowledged: 0, resolved: 0, critical: 0, important: 0, standard: 0, info: 0, actionNeeded: 0, preventedIssue: 0, trendWarning: 0, escalation: 0, information: 0 }; alerts.forEach(alert => { switch (alert.status) { case 'active': stats.active++; break; case 'acknowledged': stats.acknowledged++; break; case 'resolved': stats.resolved++; break; } switch (alert.priority_level) { case 'critical': stats.critical++; break; case 'important': stats.important++; break; case 'standard': stats.standard++; break; case 'info': stats.info++; break; } switch (alert.type_class) { case 'action_needed': stats.actionNeeded++; break; case 'prevented_issue': stats.preventedIssue++; break; case 'trend_warning': stats.trendWarning++; break; case 'escalation': stats.escalation++; break; case 'information': stats.information++; break; } }); return stats; }