169 lines
5.2 KiB
TypeScript
169 lines
5.2 KiB
TypeScript
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<AlertDto['state']>;
|
|
type AlertSeverity = NonNullable<AlertDto['severity']>;
|
|
|
|
export interface AlertsFilter {
|
|
state?: AlertState | AlertState[];
|
|
severity?: AlertSeverity | AlertSeverity[];
|
|
ruleId?: string;
|
|
limit?: number;
|
|
}
|
|
|
|
function toArray<T>(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'] });
|
|
},
|
|
});
|
|
}
|