mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-24 12:31:14 -05:00
Implement pipeline monitoring alerts
Approve and implement the Supabase migration for the pipeline monitoring alert system. This includes expanding alert types, adding new monitoring functions, and updating existing ones with escalating thresholds.
This commit is contained in:
124
src/components/admin/PipelineHealthAlerts.tsx
Normal file
124
src/components/admin/PipelineHealthAlerts.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* Pipeline Health Alerts Component
|
||||
*
|
||||
* Displays critical pipeline alerts on the admin error monitoring dashboard.
|
||||
* Shows top 10 active alerts with severity-based styling and resolution actions.
|
||||
*/
|
||||
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { useSystemAlerts } from '@/hooks/useSystemHealth';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { AlertTriangle, CheckCircle, XCircle, AlertCircle } from 'lucide-react';
|
||||
import { format } from 'date-fns';
|
||||
import { supabase } from '@/lib/supabaseClient';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
const SEVERITY_CONFIG = {
|
||||
critical: { color: 'destructive', icon: XCircle },
|
||||
high: { color: 'destructive', icon: AlertCircle },
|
||||
medium: { color: 'default', icon: AlertTriangle },
|
||||
low: { color: 'secondary', icon: CheckCircle },
|
||||
} as const;
|
||||
|
||||
const ALERT_TYPE_LABELS: Record<string, string> = {
|
||||
failed_submissions: 'Failed Submissions',
|
||||
high_ban_rate: 'High Ban Attempt Rate',
|
||||
temp_ref_error: 'Temp Reference Error',
|
||||
orphaned_images: 'Orphaned Images',
|
||||
slow_approval: 'Slow Approvals',
|
||||
submission_queue_backlog: 'Queue Backlog',
|
||||
ban_attempt: 'Ban Attempt',
|
||||
upload_timeout: 'Upload Timeout',
|
||||
high_error_rate: 'High Error Rate',
|
||||
validation_error: 'Validation Error',
|
||||
stale_submissions: 'Stale Submissions',
|
||||
circular_dependency: 'Circular Dependency',
|
||||
};
|
||||
|
||||
export function PipelineHealthAlerts() {
|
||||
const { data: criticalAlerts } = useSystemAlerts('critical');
|
||||
const { data: highAlerts } = useSystemAlerts('high');
|
||||
const { data: mediumAlerts } = useSystemAlerts('medium');
|
||||
|
||||
const allAlerts = [
|
||||
...(criticalAlerts || []),
|
||||
...(highAlerts || []),
|
||||
...(mediumAlerts || [])
|
||||
].slice(0, 10);
|
||||
|
||||
const resolveAlert = async (alertId: string) => {
|
||||
const { error } = await supabase
|
||||
.from('system_alerts')
|
||||
.update({ resolved_at: new Date().toISOString() })
|
||||
.eq('id', alertId);
|
||||
|
||||
if (error) {
|
||||
toast.error('Failed to resolve alert');
|
||||
} else {
|
||||
toast.success('Alert resolved');
|
||||
}
|
||||
};
|
||||
|
||||
if (!allAlerts.length) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<CheckCircle className="w-5 h-5 text-green-500" />
|
||||
Pipeline Health: All Systems Operational
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm text-muted-foreground">No active alerts. The sacred pipeline is flowing smoothly.</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>🚨 Active Pipeline Alerts</CardTitle>
|
||||
<CardDescription>
|
||||
Critical issues requiring attention ({allAlerts.length} active)
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
{allAlerts.map((alert) => {
|
||||
const config = SEVERITY_CONFIG[alert.severity];
|
||||
const Icon = config.icon;
|
||||
const label = ALERT_TYPE_LABELS[alert.alert_type] || alert.alert_type;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={alert.id}
|
||||
className="flex items-start justify-between p-3 border rounded-lg hover:bg-accent transition-colors"
|
||||
>
|
||||
<div className="flex items-start gap-3 flex-1">
|
||||
<Icon className="w-5 h-5 mt-0.5 flex-shrink-0" />
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<Badge variant={config.color as any}>{alert.severity.toUpperCase()}</Badge>
|
||||
<span className="text-sm font-medium">{label}</span>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">{alert.message}</p>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
{format(new Date(alert.created_at), 'PPp')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => resolveAlert(alert.id)}
|
||||
>
|
||||
Resolve
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -6311,9 +6311,19 @@ export type Database = {
|
||||
}
|
||||
Returns: undefined
|
||||
}
|
||||
mark_orphaned_images: { Args: never; Returns: undefined }
|
||||
mark_orphaned_images: {
|
||||
Args: never
|
||||
Returns: {
|
||||
details: Json
|
||||
status: string
|
||||
task: string
|
||||
}[]
|
||||
}
|
||||
migrate_ride_technical_data: { Args: never; Returns: undefined }
|
||||
migrate_user_list_items: { Args: never; Returns: undefined }
|
||||
monitor_ban_attempts: { Args: never; Returns: undefined }
|
||||
monitor_failed_submissions: { Args: never; Returns: undefined }
|
||||
monitor_slow_approvals: { Args: never; Returns: undefined }
|
||||
process_approval_transaction: {
|
||||
Args: {
|
||||
p_idempotency_key?: string
|
||||
@@ -6349,6 +6359,14 @@ export type Database = {
|
||||
}
|
||||
Returns: string
|
||||
}
|
||||
run_pipeline_monitoring: {
|
||||
Args: never
|
||||
Returns: {
|
||||
check_name: string
|
||||
details: Json
|
||||
status: string
|
||||
}[]
|
||||
}
|
||||
run_system_maintenance: {
|
||||
Args: never
|
||||
Returns: {
|
||||
|
||||
@@ -438,6 +438,18 @@ async function submitCompositeCreation(
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
// Report to system alerts (non-blocking)
|
||||
import('./pipelineAlerts').then(async ({ reportTempRefError }) => {
|
||||
try {
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
if (user) {
|
||||
await reportTempRefError(uploadedPrimary.type, errors, user.id);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to report temp ref error:', e);
|
||||
}
|
||||
});
|
||||
|
||||
throw new Error(`Temp reference validation failed: ${errors.join(', ')}`);
|
||||
}
|
||||
};
|
||||
|
||||
82
src/lib/pipelineAlerts.ts
Normal file
82
src/lib/pipelineAlerts.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* Pipeline Alert Reporting
|
||||
*
|
||||
* Client-side utilities for reporting critical pipeline issues to system alerts.
|
||||
* Non-blocking operations that enhance monitoring without disrupting user flows.
|
||||
*/
|
||||
|
||||
import { supabase } from '@/lib/supabaseClient';
|
||||
import { handleNonCriticalError } from '@/lib/errorHandler';
|
||||
|
||||
/**
|
||||
* Report temp ref validation errors to system alerts
|
||||
* Called when validateTempRefs() fails in entitySubmissionHelpers
|
||||
*/
|
||||
export async function reportTempRefError(
|
||||
entityType: 'park' | 'ride',
|
||||
errors: string[],
|
||||
userId: string
|
||||
): Promise<void> {
|
||||
try {
|
||||
await supabase.rpc('create_system_alert', {
|
||||
p_alert_type: 'temp_ref_error',
|
||||
p_severity: 'high',
|
||||
p_message: `Temp reference validation failed for ${entityType}: ${errors.join(', ')}`,
|
||||
p_metadata: {
|
||||
entity_type: entityType,
|
||||
errors,
|
||||
user_id: userId,
|
||||
timestamp: new Date().toISOString()
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Report temp ref error to alerts'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Report submission queue backlog
|
||||
* Called when IndexedDB queue exceeds threshold
|
||||
*/
|
||||
export async function reportQueueBacklog(
|
||||
pendingCount: number,
|
||||
userId?: string
|
||||
): Promise<void> {
|
||||
// Only report if backlog > 10
|
||||
if (pendingCount <= 10) return;
|
||||
|
||||
try {
|
||||
await supabase.rpc('create_system_alert', {
|
||||
p_alert_type: 'submission_queue_backlog',
|
||||
p_severity: pendingCount > 50 ? 'high' : 'medium',
|
||||
p_message: `Submission queue backlog: ${pendingCount} pending submissions`,
|
||||
p_metadata: {
|
||||
pending_count: pendingCount,
|
||||
user_id: userId,
|
||||
timestamp: new Date().toISOString()
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Report queue backlog to alerts'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check queue status and report if needed
|
||||
* Called on app startup and periodically
|
||||
*/
|
||||
export async function checkAndReportQueueStatus(userId?: string): Promise<void> {
|
||||
try {
|
||||
const { getPendingCount } = await import('./submissionQueue');
|
||||
const pendingCount = await getPendingCount();
|
||||
await reportQueueBacklog(pendingCount, userId);
|
||||
} catch (error) {
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Check queue status'
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import { RefreshButton } from '@/components/ui/refresh-button';
|
||||
import { ErrorDetailsModal } from '@/components/admin/ErrorDetailsModal';
|
||||
import { ApprovalFailureModal } from '@/components/admin/ApprovalFailureModal';
|
||||
import { ErrorAnalytics } from '@/components/admin/ErrorAnalytics';
|
||||
import { PipelineHealthAlerts } from '@/components/admin/PipelineHealthAlerts';
|
||||
import { format } from 'date-fns';
|
||||
|
||||
// Helper to calculate date threshold for filtering
|
||||
@@ -180,6 +181,9 @@ export default function ErrorMonitoring() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Pipeline Health Alerts */}
|
||||
<PipelineHealthAlerts />
|
||||
|
||||
{/* Analytics Section */}
|
||||
<ErrorAnalytics errorSummary={errorSummary} approvalMetrics={approvalMetrics} />
|
||||
|
||||
|
||||
Reference in New Issue
Block a user