import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import type { components } from '../schema'; import { apiClient, useSelectedEnv } from './alertMeta'; export type AlertDto = components['schemas']['AlertDto']; export type UnreadCountResponse = components['schemas']['UnreadCountResponse']; type AlertState = NonNullable; type AlertSeverity = NonNullable; export interface AlertsFilter { state?: AlertState | AlertState[]; severity?: AlertSeverity | AlertSeverity[]; ruleId?: string; limit?: number; } function toArray(v: T | T[] | undefined): T[] | undefined { if (v === undefined) return undefined; return Array.isArray(v) ? v : [v]; } // NOTE ON TYPES: the generated OpenAPI schema for env-scoped alert endpoints // emits `path?: never` plus a `query.env: Environment` parameter because the // server resolves the env via the `@EnvPath` argument resolver, which the // OpenAPI scanner does not recognise as a path variable. At runtime the URL // template `{envSlug}` is substituted from `params.path.envSlug` by // openapi-fetch regardless of what the TS types say; we therefore cast the // call options to `any` to bypass the generated type oddity. /** List alert instances in the current env. Polls every 30s (pauses in background). */ export function useAlerts(filter: AlertsFilter = {}) { const env = useSelectedEnv(); return useQuery({ queryKey: ['alerts', env, filter], enabled: !!env, refetchInterval: 30_000, refetchIntervalInBackground: false, queryFn: async () => { if (!env) throw new Error('no env'); const { data, error } = await apiClient.GET( '/environments/{envSlug}/alerts', { params: { path: { envSlug: env }, query: { state: toArray(filter.state), severity: toArray(filter.severity), ruleId: filter.ruleId, limit: filter.limit ?? 100, }, }, } as any, ); if (error) throw error; return data as AlertDto[]; }, }); } /** Fetch a single alert instance by id. */ export function useAlert(id: string | undefined) { const env = useSelectedEnv(); return useQuery({ queryKey: ['alerts', env, 'detail', id], enabled: !!env && !!id, queryFn: async () => { if (!env || !id) throw new Error('no env/id'); const { data, error } = await apiClient.GET( '/environments/{envSlug}/alerts/{id}', { params: { path: { envSlug: env, id } }, } as any, ); if (error) throw error; return data as AlertDto; }, }); } /** Unread alert count for the current env. Polls every 30s (pauses in background). */ export function useUnreadCount() { const env = useSelectedEnv(); return useQuery({ queryKey: ['alerts', env, 'unread-count'], enabled: !!env, refetchInterval: 30_000, refetchIntervalInBackground: false, queryFn: async () => { if (!env) throw new Error('no env'); const { data, error } = await apiClient.GET( '/environments/{envSlug}/alerts/unread-count', { params: { path: { envSlug: env } }, } as any, ); if (error) throw error; return data as UnreadCountResponse; }, }); } /** Acknowledge a single alert instance. */ export function useAckAlert() { const env = useSelectedEnv(); const qc = useQueryClient(); return useMutation({ mutationFn: async (id: string) => { if (!env) throw new Error('no env'); const { data, error } = await apiClient.POST( '/environments/{envSlug}/alerts/{id}/ack', { params: { path: { envSlug: env, id } }, } as any, ); if (error) throw error; return data as AlertDto; }, onSuccess: () => { qc.invalidateQueries({ queryKey: ['alerts', env] }); }, }); } /** Mark a single alert instance as read (inbox semantics). */ export function useMarkAlertRead() { const env = useSelectedEnv(); const qc = useQueryClient(); return useMutation({ mutationFn: async (id: string) => { if (!env) throw new Error('no env'); const { error } = await apiClient.POST( '/environments/{envSlug}/alerts/{id}/read', { params: { path: { envSlug: env, id } }, } as any, ); if (error) throw error; }, onSuccess: () => { qc.invalidateQueries({ queryKey: ['alerts', env] }); qc.invalidateQueries({ queryKey: ['alerts', env, 'unread-count'] }); }, }); } /** Mark a batch of alert instances as read. */ export function useBulkReadAlerts() { const env = useSelectedEnv(); const qc = useQueryClient(); return useMutation({ mutationFn: async (ids: string[]) => { if (!env) throw new Error('no env'); const { error } = await apiClient.POST( '/environments/{envSlug}/alerts/bulk-read', { params: { path: { envSlug: env } }, body: { instanceIds: ids }, } as any, ); if (error) throw error; }, onSuccess: () => { qc.invalidateQueries({ queryKey: ['alerts', env] }); qc.invalidateQueries({ queryKey: ['alerts', env, 'unread-count'] }); }, }); }