diff --git a/cameleer-server-app/src/test/java/com/cameleer/server/app/alerting/storage/V17MigrationIT.java b/cameleer-server-app/src/test/java/com/cameleer/server/app/alerting/storage/V17MigrationIT.java index cfd0965e..4854a9ce 100644 --- a/cameleer-server-app/src/test/java/com/cameleer/server/app/alerting/storage/V17MigrationIT.java +++ b/cameleer-server-app/src/test/java/com/cameleer/server/app/alerting/storage/V17MigrationIT.java @@ -35,14 +35,24 @@ class V17MigrationIT extends AbstractPostgresIT { } @Test - void open_rule_index_predicate_is_reworked() { - String def = jdbcTemplate.queryForObject(""" - SELECT pg_get_indexdef(indexrelid) - FROM pg_index + void open_rule_index_exists_and_is_unique() { + // Structural check only — the pg_get_indexdef pretty-printer varies across + // Postgres versions. Predicate semantics (ack doesn't close; soft-delete + // frees the slot; RESOLVED excluded) are covered behaviorally by + // PostgresAlertInstanceRepositoryIT#findOpenForRule_* and + // #save_rejectsSecondOpenInstanceForSameRuleAndExchange. + Integer count = jdbcTemplate.queryForObject(""" + SELECT COUNT(*)::int FROM pg_indexes + WHERE indexname = 'alert_instances_open_rule_uq' + AND tablename = 'alert_instances' + """, Integer.class); + assertThat(count).isEqualTo(1); + + Boolean isUnique = jdbcTemplate.queryForObject(""" + SELECT indisunique FROM pg_index JOIN pg_class ON pg_class.oid = pg_index.indexrelid WHERE pg_class.relname = 'alert_instances_open_rule_uq' - """, String.class); - assertThat(def).contains("state = ANY (ARRAY['PENDING'::alert_state_enum, 'FIRING'::alert_state_enum])"); - assertThat(def).contains("deleted_at IS NULL"); + """, Boolean.class); + assertThat(isUnique).isTrue(); } } diff --git a/ui/src/components/AlertStateChip.test.tsx b/ui/src/components/AlertStateChip.test.tsx index dbdc0785..336c5a75 100644 --- a/ui/src/components/AlertStateChip.test.tsx +++ b/ui/src/components/AlertStateChip.test.tsx @@ -11,7 +11,6 @@ describe('AlertStateChip', () => { it.each([ ['PENDING', /pending/i], ['FIRING', /firing/i], - ['ACKNOWLEDGED', /acknowledged/i], ['RESOLVED', /resolved/i], ] as const)('renders %s label', (state, pattern) => { renderWithTheme(); diff --git a/ui/src/components/AlertStateChip.tsx b/ui/src/components/AlertStateChip.tsx index a81548f0..4d1a47ee 100644 --- a/ui/src/components/AlertStateChip.tsx +++ b/ui/src/components/AlertStateChip.tsx @@ -6,14 +6,12 @@ type State = NonNullable; const LABELS: Record = { PENDING: 'Pending', FIRING: 'Firing', - ACKNOWLEDGED: 'Acknowledged', RESOLVED: 'Resolved', }; const COLORS: Record = { PENDING: 'warning', FIRING: 'error', - ACKNOWLEDGED: 'warning', RESOLVED: 'success', }; diff --git a/ui/src/components/LayoutShell.tsx b/ui/src/components/LayoutShell.tsx index f2c49ea7..2775f6d1 100644 --- a/ui/src/components/LayoutShell.tsx +++ b/ui/src/components/LayoutShell.tsx @@ -368,7 +368,7 @@ function LayoutContent() { const { data: envRecords = [] } = useEnvironments(); // Open alerts + rules for CMD-K (env-scoped). - const { data: cmdkAlerts } = useAlerts({ state: ['FIRING', 'ACKNOWLEDGED'], limit: 100 }); + const { data: cmdkAlerts } = useAlerts({ state: ['FIRING'], acked: false, limit: 100 }); const { data: cmdkRules } = useAlertRules(); // Merge environments from both the environments table and agent heartbeats