Skip to content

Commit 7a9882d

Browse files
authored
[HDX-3969] Add alerts page (Shift+A) with overview and recent history (#2093)
1 parent bfb455d commit 7a9882d

10 files changed

Lines changed: 630 additions & 4 deletions

File tree

.changeset/add-alerts-page.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@hyperdx/cli": patch
3+
---
4+
5+
Add alerts page (Shift+A) with overview and recent trigger history

packages/cli/AGENTS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ src/
4343
│ # buildTraceLogsSql (waterfall correlated logs)
4444
│ # buildFullRowSql (SELECT * for row detail)
4545
├── components/
46+
│ ├── AlertsPage.tsx # Alerts overview page — list + detail with recent history (Shift+A)
4647
│ ├── EventViewer.tsx # Main TUI view — table, search, detail panel with tabs
4748
│ ├── TraceWaterfall.tsx # Trace waterfall chart with j/k navigation + event details
4849
│ ├── RowOverview.tsx # Structured overview (top-level attrs, event attrs, resource attrs)
@@ -156,6 +157,7 @@ Key expression mappings from the web frontend's `getConfig()`:
156157
| `t` | Edit time range in $EDITOR |
157158
| `f` | Toggle follow mode (live tail) |
158159
| `w` | Toggle line wrap |
160+
| `A` (Shift+A) | Open alerts page |
159161
| `?` | Toggle help screen |
160162
| `q` | Quit |
161163

packages/cli/src/App.tsx

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@ import {
99
type SourceResponse,
1010
type SavedSearchResponse,
1111
} from '@/api/client';
12+
import AlertsPage from '@/components/AlertsPage';
1213
import ErrorDisplay from '@/components/ErrorDisplay';
1314
import LoginForm from '@/components/LoginForm';
1415
import SourcePicker from '@/components/SourcePicker';
1516
import EventViewer from '@/components/EventViewer';
1617

17-
type Screen = 'loading' | 'login' | 'pick-source' | 'events';
18+
type Screen = 'loading' | 'login' | 'pick-source' | 'events' | 'alerts';
1819

1920
interface AppProps {
2021
apiUrl: string;
@@ -122,6 +123,18 @@ export default function App({ apiUrl, query, sourceName, follow }: AppProps) {
122123
[eventSources],
123124
);
124125

126+
// Track the screen before alerts so we can return to it
127+
const [preAlertsScreen, setPreAlertsScreen] = useState<Screen>('events');
128+
129+
const handleOpenAlerts = useCallback(() => {
130+
setPreAlertsScreen(screen);
131+
setScreen('alerts');
132+
}, [screen]);
133+
134+
const handleCloseAlerts = useCallback(() => {
135+
setScreen(preAlertsScreen);
136+
}, [preAlertsScreen]);
137+
125138
if (error) {
126139
return (
127140
<Box paddingX={1}>
@@ -152,10 +165,17 @@ export default function App({ apiUrl, query, sourceName, follow }: AppProps) {
152165
</Text>
153166
<Text dimColor>Search and tail events from the terminal</Text>
154167
</Box>
155-
<SourcePicker sources={eventSources} onSelect={handleSourceSelect} />
168+
<SourcePicker
169+
sources={eventSources}
170+
onSelect={handleSourceSelect}
171+
onOpenAlerts={handleOpenAlerts}
172+
/>
156173
</Box>
157174
);
158175

176+
case 'alerts':
177+
return <AlertsPage client={client} onClose={handleCloseAlerts} />;
178+
159179
case 'events':
160180
if (!selectedSource) return null;
161181
return (
@@ -166,6 +186,7 @@ export default function App({ apiUrl, query, sourceName, follow }: AppProps) {
166186
sources={eventSources}
167187
savedSearches={savedSearches}
168188
onSavedSearchSelect={handleSavedSearchSelect}
189+
onOpenAlerts={handleOpenAlerts}
169190
initialQuery={activeQuery}
170191
follow={follow}
171192
/>

packages/cli/src/api/client.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,12 @@ export class ApiClient {
138138
return res.json() as Promise<DashboardResponse[]>;
139139
}
140140

141+
async getAlerts(): Promise<AlertsResponse> {
142+
const res = await this.get('/alerts');
143+
if (!res.ok) throw new Error(`GET /alerts failed: ${res.status}`);
144+
return res.json() as Promise<AlertsResponse>;
145+
}
146+
141147
// ---- ClickHouse client via proxy ---------------------------------
142148

143149
createClickHouseClient(
@@ -379,3 +385,59 @@ interface DashboardResponse {
379385
createdAt?: string;
380386
updatedAt?: string;
381387
}
388+
389+
// ---- Alerts --------------------------------------------------------
390+
391+
export interface AlertHistoryItem {
392+
counts: number;
393+
createdAt: string;
394+
lastValues: Array<{ startTime: string; count: number }>;
395+
state: 'ALERT' | 'OK' | 'INSUFFICIENT_DATA' | 'DISABLED';
396+
}
397+
398+
export interface AlertItem {
399+
_id: string;
400+
interval: string;
401+
scheduleOffsetMinutes?: number;
402+
scheduleStartAt?: string | null;
403+
threshold: number;
404+
thresholdType: 'above' | 'below';
405+
channel: { type?: string | null };
406+
state?: 'ALERT' | 'OK' | 'INSUFFICIENT_DATA' | 'DISABLED';
407+
source?: 'saved_search' | 'tile';
408+
dashboardId?: string;
409+
savedSearchId?: string;
410+
tileId?: string;
411+
name?: string | null;
412+
message?: string | null;
413+
createdAt: string;
414+
updatedAt: string;
415+
history: AlertHistoryItem[];
416+
dashboard?: {
417+
_id: string;
418+
name: string;
419+
updatedAt: string;
420+
tags: string[];
421+
tiles: Array<{ id: string; config: { name?: string } }>;
422+
};
423+
savedSearch?: {
424+
_id: string;
425+
createdAt: string;
426+
name: string;
427+
updatedAt: string;
428+
tags: string[];
429+
};
430+
createdBy?: {
431+
email: string;
432+
name?: string;
433+
};
434+
silenced?: {
435+
by: string;
436+
at: string;
437+
until: string;
438+
};
439+
}
440+
441+
interface AlertsResponse {
442+
data: AlertItem[];
443+
}

0 commit comments

Comments
 (0)