New alert service
This commit is contained in:
317
frontend/src/utils/alertManagement.ts
Normal file
317
frontend/src/utils/alertManagement.ts
Normal file
@@ -0,0 +1,317 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user