feat(notifications): match routing rules by labels and categories (#776)

- Add label_ids and categories columns to notification_routes via idempotent migration
- Matcher logic always evaluates routes (AND semantics across all non-empty matchers)
- getStackLabelIds skips DB call when no enabled route uses label filtering
- Extract ALL_NOTIFICATION_CATEGORIES array from NotificationService as single source of truth
- Derive VALID_CATEGORIES set from the array in the route handler
- Extract validateLabelIds and validateCategories helpers to remove POST/PUT duplication
- Extract tryAddColumn as a private DatabaseService class method (removes 5 local re-declarations)
- Extract CATEGORY_LABELS to frontend/src/lib/notificationCategories.ts (shared by NotificationPanel and NotificationRoutingSection)
- Frontend form adds label and category multiselects with AND-filter hint
- Route cards show label and category badges; empty-matcher routes show 'Matches all alerts'
- Add tests for category-only, label-only, and combined AND-semantics routing
This commit is contained in:
Anso
2026-04-25 14:58:50 -04:00
committed by GitHub
parent fcbdd59ec2
commit e0034132b4
8 changed files with 370 additions and 96 deletions
+1 -14
View File
@@ -22,6 +22,7 @@ import {
import { cn } from '@/lib/utils';
import type { NotificationCategory, NotificationItem } from './dashboard/types';
import type { Node } from '@/context/NodeContext';
import { CATEGORY_LABELS } from '@/lib/notificationCategories';
const NODE_FILTER_ALL = 'all' as const;
const CATEGORY_FILTER_ALL = 'all' as const;
@@ -29,20 +30,6 @@ type NotifFilter = 'all' | 'unread' | 'alerts';
type NodeFilter = typeof NODE_FILTER_ALL | number;
type CategoryFilter = typeof CATEGORY_FILTER_ALL | NotificationCategory;
const CATEGORY_LABELS: Record<NotificationCategory, string> = {
deploy_success: 'Deploy success',
deploy_failure: 'Deploy failure',
stack_started: 'Stack started',
stack_stopped: 'Stack stopped',
stack_restarted: 'Stack restarted',
image_update_available: 'Update available',
image_update_applied: 'Update applied',
autoheal_triggered: 'Auto-heal',
monitor_alert: 'Monitor alert',
scan_finding: 'Scan finding',
system: 'System',
};
type LevelConfig = {
icon: LucideIcon;
iconClass: string;