@@ -2,35 +2,37 @@ import * as React from 'react';
22import Head from 'next/head' ;
33import Link from 'next/link' ;
44import { useRouter } from 'next/router' ;
5- import { AlertInterval , AlertSource } from '@hyperdx/common-utils/dist/types' ;
5+ import { AlertInterval } from '@hyperdx/common-utils/dist/types' ;
66import {
7+ ActionIcon ,
78 Anchor ,
89 Breadcrumbs ,
9- Button ,
1010 Container ,
1111 Group ,
1212 Skeleton ,
1313 Stack ,
1414 Text ,
15+ Tooltip ,
1516} from '@mantine/core' ;
16- import { notifications } from '@mantine/notifications' ;
17- import { IconExternalLink , IconPencil } from '@tabler/icons-react' ;
18- import { useQueryClient } from '@tanstack/react-query' ;
17+ import { IconExternalLink } from '@tabler/icons-react' ;
1918
2019import { AckAlert } from '@/components/alerts/AckAlert' ;
2120import { AlertDetailChart } from '@/components/alerts/AlertDetailChart' ;
2221import { AlertDetailProperties } from '@/components/alerts/AlertDetailProperties' ;
2322import { AlertNote } from '@/components/alerts/AlertDetails' ;
2423import { AlertEvaluationsTable } from '@/components/alerts/AlertEvaluationsTable' ;
2524import { AlertHistoryCardList } from '@/components/alerts/AlertHistoryCards' ;
25+ import { AlertRowMenu } from '@/components/alerts/AlertRowMenu' ;
2626import { AlertStateBadge } from '@/components/alerts/AlertStateBadge' ;
27- import { EditAlertModal } from '@/components/alerts/EditAlertModal' ;
28- import ConfirmDeleteMenu from '@/components/ConfirmDeleteMenu' ;
2927import EmptyState from '@/components/EmptyState' ;
3028import { PageHeader } from '@/components/PageHeader' ;
3129import { TimePicker } from '@/components/TimePicker' ;
3230import { IS_ALERT_DETAILS_ENABLED } from '@/config' ;
33- import { getAlertDisplayName , getAlertSourceUrl } from '@/utils/alerts' ;
31+ import {
32+ getAlertDisplayName ,
33+ getAlertSourceLabel ,
34+ getAlertSourceUrl ,
35+ } from '@/utils/alerts' ;
3436
3537import { useBrandDisplayName } from './theme/ThemeProvider' ;
3638import api from './api' ;
@@ -80,35 +82,7 @@ function AlertProperties({ alert }: { alert: AlertsPageItem }) {
8082
8183function AlertDetailBody ( { alert } : { alert : AlertsPageItem } ) {
8284 const alertUrl = getAlertSourceUrl ( alert ) ;
83- const brandName = useBrandDisplayName ( ) ;
8485 const router = useRouter ( ) ;
85- const queryClient = useQueryClient ( ) ;
86- const deleteAlert = api . useDeleteAlert ( ) ;
87- const [ isEditModalOpen , setIsEditModalOpen ] = React . useState ( false ) ;
88-
89- const onDeleteAlert = React . useCallback ( async ( ) => {
90- try {
91- await deleteAlert . mutateAsync ( alert . _id ) ;
92- notifications . show ( {
93- color : 'green' ,
94- message : 'Alert deleted!' ,
95- autoClose : 5000 ,
96- } ) ;
97- // The alerts list and the source-bound edit surfaces (saved search
98- // modal / dashboard tile editor) all render this alert.
99- queryClient . invalidateQueries ( { queryKey : api . getAlertsQueryKey ( ) } ) ;
100- queryClient . invalidateQueries ( { queryKey : [ 'saved-search' ] } ) ;
101- queryClient . invalidateQueries ( { queryKey : [ 'dashboards' ] } ) ;
102- router . push ( '/alerts' ) ;
103- } catch ( error ) {
104- console . error ( 'Failed to delete alert:' , error ) ;
105- notifications . show ( {
106- color : 'red' ,
107- message : `Something went wrong. Please contact ${ brandName } team.` ,
108- autoClose : 5000 ,
109- } ) ;
110- }
111- } , [ alert . _id , brandName , deleteAlert , queryClient , router ] ) ;
11286
11387 // Interval-dependent, but fixed for the page lifetime: the body only
11488 // mounts once the alert has loaded, and useNewTimeQuery reads the initial
@@ -169,34 +143,41 @@ function AlertDetailBody({ alert }: { alert: AlertsPageItem }) {
169143 < Group gap = "sm" >
170144 { alert . state != null && < AlertStateBadge state = { alert . state } /> }
171145 < Text fw = { 500 } > { getAlertDisplayName ( alert ) } </ Text >
146+ { alertUrl && (
147+ /* Next to the name rather than in the actions row: it navigates
148+ to what the alert watches, so it belongs with the identity,
149+ not with the verbs acting on the alert. */
150+ < Tooltip
151+ label = { `Open ${ getAlertSourceLabel ( alert ) . toLowerCase ( ) } ` }
152+ withArrow
153+ >
154+ < ActionIcon
155+ component = { Link }
156+ href = { alertUrl }
157+ variant = "subtle"
158+ size = "md"
159+ aria-label = { `Open ${ getAlertSourceLabel ( alert ) . toLowerCase ( ) } ` }
160+ data-testid = "open-alert-source"
161+ >
162+ < IconExternalLink size = { 16 } />
163+ </ ActionIcon >
164+ </ Tooltip >
165+ ) }
172166 </ Group >
173167 }
174168 actions = {
175169 < Group gap = "sm" wrap = "nowrap" >
176170 < AckAlert alert = { alert } />
177- < Button
178- data-testid = "edit-alert-button"
179- variant = "secondary"
180- size = "compact-sm"
181- leftSection = { < IconPencil size = { 14 } /> }
182- onClick = { ( ) => setIsEditModalOpen ( true ) }
183- >
184- Edit alert
185- </ Button >
186- < ConfirmDeleteMenu onDelete = { onDeleteAlert } />
187- { alertUrl && (
188- < Button
189- component = { Link }
190- href = { alertUrl }
191- variant = "secondary"
192- size = "compact-sm"
193- rightSection = { < IconExternalLink size = { 14 } /> }
194- >
195- { alert . source === AlertSource . TILE
196- ? 'Open dashboard tile'
197- : 'Open saved search' }
198- </ Button >
199- ) }
171+ { /* Edit, Terraform export and Delete live behind one overflow
172+ control, shared with the alerts list so both surfaces offer the
173+ same actions. The menu's own source-link item is suppressed:
174+ this page carries that link next to the title instead. */ }
175+ < AlertRowMenu
176+ alert = { alert }
177+ alertName = { getAlertDisplayName ( alert ) }
178+ dateRange = { searchedTimeRange }
179+ onDeleted = { ( ) => router . push ( '/alerts' ) }
180+ />
200181 < TimePicker
201182 inputValue = { displayedTimeInputValue }
202183 setInputValue = { setDisplayedTimeInputValue }
@@ -205,12 +186,6 @@ function AlertDetailBody({ alert }: { alert: AlertsPageItem }) {
205186 </ Group >
206187 }
207188 />
208- < EditAlertModal
209- alert = { alert }
210- opened = { isEditModalOpen }
211- onClose = { ( ) => setIsEditModalOpen ( false ) }
212- dateRange = { searchedTimeRange }
213- />
214189 < div style = { { overflow : 'auto' , flexGrow : 1 } } >
215190 < Container size = "xl" py = "md" >
216191 < Stack gap = "lg" >
0 commit comments