Files
bakery-ia/frontend/src/utils/alertManagement.ts

317 lines
9.0 KiB
TypeScript
Raw Normal View History

2025-12-05 20:07:01 +01:00
/**
* 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<PriorityLevel, number> = {
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<AlertTypeClass, number> = {
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;
}