diff --git a/.changeset/tall-lizards-care.md b/.changeset/tall-lizards-care.md new file mode 100644 index 00000000..c776b2e9 --- /dev/null +++ b/.changeset/tall-lizards-care.md @@ -0,0 +1,5 @@ +--- +'@hyperdx/app': patch +--- + +Refactor: Extract shared alert logic into a separate component diff --git a/packages/app/src/Alert.tsx b/packages/app/src/Alert.tsx new file mode 100644 index 00000000..0224739b --- /dev/null +++ b/packages/app/src/Alert.tsx @@ -0,0 +1,93 @@ +import { sub } from 'date-fns'; +import { Form, FormSelectProps } from 'react-bootstrap'; +import api from './api'; +import type { AlertInterval, AlertChannelType } from './types'; + +export function intervalToGranularity(interval: AlertInterval) { + if (interval === '1m') return '1 minute' as const; + if (interval === '5m') return '5 minute' as const; + if (interval === '15m') return '15 minute' as const; + if (interval === '30m') return '30 minute' as const; + if (interval === '1h') return '1 hour' as const; + if (interval === '6h') return '6 hour' as const; + if (interval === '12h') return '12 hour' as const; + if (interval === '1d') return '1 day' as const; + return '1 day'; +} + +export function intervalToDateRange(interval: AlertInterval): [Date, Date] { + const now = new Date(); + if (interval === '1m') return [sub(now, { minutes: 15 }), now]; + if (interval === '5m') return [sub(now, { hours: 1 }), now]; + if (interval === '15m') return [sub(now, { hours: 4 }), now]; + if (interval === '30m') return [sub(now, { hours: 8 }), now]; + if (interval === '1h') return [sub(now, { hours: 16 }), now]; + if (interval === '6h') return [sub(now, { days: 4 }), now]; + if (interval === '12h') return [sub(now, { days: 7 }), now]; + if (interval === '1d') return [sub(now, { days: 7 }), now]; + return [now, now]; +} + +export const ALERT_INTERVAL_OPTIONS: Record = { + '1m': '1 minute', + '5m': '5 minute', + '15m': '15 minute', + '30m': '30 minute', + '1h': '1 hour', + '6h': '6 hour', + '12h': '12 hour', + '1d': '1 day', +}; + +export const ALERT_CHANNEL_OPTIONS: Record = { + webhook: 'Slack Webhook', +}; + +export const SlackChannelForm = ({ + webhookSelectProps, +}: { + webhookSelectProps: FormSelectProps; +}) => { + const { data: slackWebhooks } = api.useWebhooks('slack'); + + const hasSlackWebhooks = + Array.isArray(slackWebhooks?.data) && slackWebhooks.data.length > 0; + + return ( + <> + {hasSlackWebhooks && ( +
+ Slack Webhook + + {/* Ensure user selects a slack webhook before submitting form */} + + {slackWebhooks.data.map((sw: any) => ( + + ))} + +
+ )} + +
+ + + Add New Slack Incoming Webhook + +
+ + ); +}; diff --git a/packages/app/src/CreateLogAlertModal.tsx b/packages/app/src/CreateLogAlertModal.tsx index 6938400a..73b4c858 100644 --- a/packages/app/src/CreateLogAlertModal.tsx +++ b/packages/app/src/CreateLogAlertModal.tsx @@ -1,6 +1,5 @@ import { Button, Form, Modal } from 'react-bootstrap'; import { Controller, useForm } from 'react-hook-form'; -import { sub } from 'date-fns'; import { toast } from 'react-toastify'; import { useEffect, useMemo, useState } from 'react'; @@ -11,32 +10,19 @@ import { FieldSelect } from './ChartUtils'; import { capitalizeFirstLetter } from './utils'; import { genEnglishExplanation } from './queryv2'; -import type { AlertChannelType, AlertInterval, LogView } from './types'; - -function intervalToGranularity(interval: AlertInterval) { - if (interval === '1m') return '1 minute' as const; - if (interval === '5m') return '5 minute' as const; - if (interval === '15m') return '15 minute' as const; - if (interval === '30m') return '30 minute' as const; - if (interval === '1h') return '1 hour' as const; - if (interval === '6h') return '6 hour' as const; - if (interval === '12h') return '12 hour' as const; - if (interval === '1d') return '1 day' as const; - return '1 day'; -} - -function intervalToDateRange(interval: AlertInterval): [Date, Date] { - const now = new Date(); - if (interval === '1m') return [sub(now, { minutes: 15 }), now]; - if (interval === '5m') return [sub(now, { hours: 1 }), now]; - if (interval === '15m') return [sub(now, { hours: 4 }), now]; - if (interval === '30m') return [sub(now, { hours: 8 }), now]; - if (interval === '1h') return [sub(now, { hours: 16 }), now]; - if (interval === '6h') return [sub(now, { days: 4 }), now]; - if (interval === '12h') return [sub(now, { days: 7 }), now]; - if (interval === '1d') return [sub(now, { days: 7 }), now]; - return [now, now]; -} +import type { + AlertChannelType, + AlertInterval, + AlertType, + LogView, +} from './types'; +import { + intervalToGranularity, + intervalToDateRange, + ALERT_INTERVAL_OPTIONS, + ALERT_CHANNEL_OPTIONS, + SlackChannelForm, +} from './Alert'; function AlertForm({ alertId, @@ -47,11 +33,11 @@ function AlertForm({ }: { defaultValues: | { - channelType: AlertChannelType; groupBy: string | undefined; interval: AlertInterval; threshold: number; - type: string; + type: AlertType; + channelType: AlertChannelType; webhookId: string | undefined; } | undefined; @@ -67,6 +53,8 @@ function AlertForm({ onDeleteClick: () => void; query: string; }) { + const { data: team } = api.useTeam(); + const { register, handleSubmit, @@ -86,8 +74,6 @@ function AlertForm({ } : undefined, }); - const { data: slackWebhooks } = api.useWebhooks('slack'); - const { data: team } = api.useTeam(); const channel = watch('channelType'); const interval = watch('interval'); @@ -113,8 +99,12 @@ function AlertForm({
Alert when
- - + +
lines appear within
- - - - - - - - + {Object.entries(ALERT_INTERVAL_OPTIONS).map(([value, text]) => ( + + ))}
@@ -160,39 +147,21 @@ function AlertForm({
via
- + {Object.entries(ALERT_CHANNEL_OPTIONS).map(([value, text]) => ( + + ))}
- {channel === 'webhook' && - Array.isArray(slackWebhooks?.data) && - slackWebhooks.data.length > 0 && ( -
- Slack Webhook - - {slackWebhooks.data.map((sw: any) => ( - - ))} - -
- )} + {channel === 'webhook' && ( -
- - - Add New Slack Incoming Webhook - -
+ )} +
+
Alert Threshold Preview
@@ -287,7 +257,7 @@ export default function CreateLogAlertModal({ size="xl" > -
+
Alerts for
{savedSearch == null ? (