Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 21dcf30

Browse files
committedJan 21, 2025··
feat: Add chart preview to alert modal
1 parent ecd4530 commit 21dcf30

File tree

6 files changed

+145
-50
lines changed

6 files changed

+145
-50
lines changed
 

‎packages/app/src/DBSearchPageAlertModal.tsx

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { NativeSelect, NumberInput } from 'react-hook-form-mantine';
44
import { z } from 'zod';
55
import { zodResolver } from '@hookform/resolvers/zod';
66
import {
7+
Accordion,
78
Box,
89
Button,
910
Group,
@@ -16,8 +17,7 @@ import {
1617
} from '@mantine/core';
1718
import { notifications } from '@mantine/notifications';
1819

19-
import { AlertSchema } from '@/commonTypes';
20-
import { SQLInlineEditorControlled } from '@/components/SQLInlineEditor';
20+
import { AlertSchema, SavedSearch } from '@/commonTypes';
2121
import { useSavedSearch } from '@/savedSearch';
2222
import { useSource } from '@/source';
2323
import {
@@ -26,7 +26,9 @@ import {
2626
ALERT_THRESHOLD_TYPE_OPTIONS,
2727
} from '@/utils/alerts';
2828

29+
import { AlertPreviewChart } from './components/AlertPreviewChart';
2930
import { WebhookChannelForm } from './components/Alerts';
31+
import { SQLInlineEditorControlled } from './components/SQLInlineEditor';
3032
import api from './api';
3133

3234
const CHANNEL_ICONS = {
@@ -40,29 +42,29 @@ const zAlertForm = AlertSchema;
4042
type AlertForm = z.infer<typeof zAlertForm>;
4143

4244
const AlertForm = ({
43-
sourceId,
45+
savedSearch,
4446
defaultValues,
4547
loading,
4648
deleteLoading,
4749
onDelete,
4850
onSubmit,
4951
onClose,
5052
}: {
51-
sourceId?: string;
53+
savedSearch?: SavedSearch;
5254
defaultValues?: null | AlertForm;
5355
loading?: boolean;
5456
deleteLoading?: boolean;
5557
onDelete: (id: string) => void;
5658
onSubmit: (data: AlertForm) => void;
5759
onClose: () => void;
5860
}) => {
59-
const { data: source } = useSource({ id: sourceId });
61+
const { data: source } = useSource({ id: savedSearch?.source });
6062

6163
const databaseName = source?.from.databaseName;
6264
const tableName = source?.from.tableName;
6365
const connectionId = source?.connection;
6466

65-
const { control, handleSubmit } = useForm<AlertForm>({
67+
const { control, handleSubmit, watch } = useForm<AlertForm>({
6668
defaultValues: defaultValues || {
6769
interval: '5m',
6870
threshold: 1,
@@ -139,6 +141,26 @@ const AlertForm = ({
139141
<WebhookChannelForm control={control} name={`channel.webhookId`} />
140142
</Paper>
141143
</Stack>
144+
145+
{savedSearch && (
146+
<Accordion defaultValue={'chart'} mt="sm" mx={-16}>
147+
<Accordion.Item value="chart">
148+
<Accordion.Control icon={<i className="bi bi-chart"></i>}>
149+
<Text size="sm">Threshold chart</Text>
150+
</Accordion.Control>
151+
<Accordion.Panel>
152+
<AlertPreviewChart
153+
savedSearch={savedSearch}
154+
interval={watch('interval')}
155+
groupBy={watch('groupBy')}
156+
threshold={watch('threshold')}
157+
thresholdType={watch('thresholdType')}
158+
/>
159+
</Accordion.Panel>
160+
</Accordion.Item>
161+
</Accordion>
162+
)}
163+
142164
<Group mt="lg" justify="space-between" gap="xs">
143165
<div>
144166
{defaultValues && (
@@ -249,7 +271,13 @@ export const DBSearchPageAlertModal = ({
249271
};
250272

251273
return (
252-
<Modal opened={open} onClose={onClose} size="xl" withCloseButton={false}>
274+
<Modal
275+
opened={open}
276+
onClose={onClose}
277+
size="xl"
278+
withCloseButton={false}
279+
zIndex={9999}
280+
>
253281
<Box pos="relative">
254282
<LoadingOverlay
255283
visible={isLoading}
@@ -291,7 +319,7 @@ export const DBSearchPageAlertModal = ({
291319
</Tabs>
292320

293321
<AlertForm
294-
sourceId={savedSearch?.source}
322+
savedSearch={savedSearch}
295323
key={activeIndex}
296324
defaultValues={
297325
activeIndex === 'stage'

‎packages/app/src/HDXMultiSeriesTimeChart.tsx

Lines changed: 6 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,7 @@ export const MemoChart = memo(function MemoChart({
237237
groupKeys,
238238
lineNames,
239239
lineColors,
240-
alertThreshold,
241-
alertThresholdType,
240+
referenceLines,
242241
logReferenceTimestamp,
243242
displayType = DisplayType.Line,
244243
numberFormat,
@@ -254,8 +253,7 @@ export const MemoChart = memo(function MemoChart({
254253
groupKeys: string[];
255254
lineNames: string[];
256255
lineColors: Array<string | undefined>;
257-
alertThreshold?: number;
258-
alertThresholdType?: 'above' | 'below';
256+
referenceLines?: React.ReactNode;
259257
displayType?: DisplayType;
260258
numberFormat?: NumberFormat;
261259
logReferenceTimestamp?: number;
@@ -501,31 +499,7 @@ export const MemoChart = memo(function MemoChart({
501499
}}
502500
allowEscapeViewBox={{ y: true }}
503501
/>
504-
{alertThreshold != null && alertThresholdType === 'below' && (
505-
<ReferenceArea
506-
y1={0}
507-
y2={alertThreshold}
508-
ifOverflow="extendDomain"
509-
strokeWidth={0}
510-
fillOpacity={0.05}
511-
/>
512-
)}
513-
{alertThreshold != null && alertThresholdType === 'above' && (
514-
<ReferenceArea
515-
y1={alertThreshold}
516-
ifOverflow="extendDomain"
517-
strokeWidth={0}
518-
fillOpacity={0.05}
519-
/>
520-
)}
521-
{alertThreshold != null && (
522-
<ReferenceLine
523-
y={alertThreshold}
524-
label={<Label value="Alert Threshold" fill={'white'} />}
525-
stroke="red"
526-
strokeDasharray="3 3"
527-
/>
528-
)}
502+
{referenceLines}
529503
{highlightStart && highlightEnd ? (
530504
<ReferenceArea
531505
// yAxisId="1"
@@ -569,8 +543,7 @@ const HDXMultiSeriesTimeChart = memo(
569543
displayType: displayTypeProp = DisplayType.Line,
570544
},
571545
onSettled,
572-
alertThreshold,
573-
alertThresholdType,
546+
referenceLines,
574547
showDisplaySwitcher = true,
575548
setDisplayType,
576549
logReferenceTimestamp,
@@ -583,8 +556,7 @@ const HDXMultiSeriesTimeChart = memo(
583556
displayType?: DisplayType;
584557
};
585558
onSettled?: () => void;
586-
alertThreshold?: number;
587-
alertThresholdType?: 'above' | 'below';
559+
referenceLines?: React.ReactNode;
588560
showDisplaySwitcher?: boolean;
589561
setDisplayType?: (type: DisplayType) => void;
590562
logReferenceTimestamp?: number;
@@ -858,11 +830,10 @@ const HDXMultiSeriesTimeChart = memo(
858830
isClickActive={activeClickPayload}
859831
setIsClickActive={setActiveClickPayload}
860832
dateRange={dateRange}
861-
alertThreshold={alertThreshold}
862-
alertThresholdType={alertThresholdType}
863833
displayType={displayType}
864834
numberFormat={numberFormat}
865835
logReferenceTimestamp={logReferenceTimestamp}
836+
referenceLines={referenceLines}
866837
/>
867838
</div>
868839
</div>

‎packages/app/src/commonTypes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export const SavedSearchSchema = z.object({
3636
name: z.string(),
3737
select: z.string(),
3838
where: z.string(),
39-
whereLanguage: z.string().optional(),
39+
whereLanguage: z.union([z.literal('sql'), z.literal('lucene')]).optional(),
4040
source: z.string(),
4141
tags: z.array(z.string()),
4242
orderBy: z.string().optional(),
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import React from 'react';
2+
import { Label, ReferenceArea, ReferenceLine } from 'recharts';
3+
import { Paper } from '@mantine/core';
4+
5+
import { SavedSearch } from '@/commonTypes';
6+
import { DBTimeChart } from '@/components/DBTimeChart';
7+
import { useSource } from '@/source';
8+
import { AlertInterval } from '@/types';
9+
import { intervalToDateRange, intervalToGranularity } from '@/utils/alerts';
10+
11+
import { getAlertReferenceLines } from './Alerts';
12+
13+
export type AlertPreviewChartProps = {
14+
savedSearch?: SavedSearch;
15+
interval: AlertInterval;
16+
groupBy?: string;
17+
thresholdType: 'above' | 'below';
18+
threshold: number;
19+
};
20+
21+
export const AlertPreviewChart = ({
22+
savedSearch,
23+
interval,
24+
groupBy,
25+
threshold,
26+
thresholdType,
27+
}: AlertPreviewChartProps) => {
28+
const { data: source } = useSource({ id: savedSearch?.source });
29+
30+
if (!savedSearch || !source) {
31+
return null;
32+
}
33+
34+
return (
35+
<Paper w="100%" h={200}>
36+
<DBTimeChart
37+
sourceId={savedSearch.source}
38+
showDisplaySwitcher={false}
39+
referenceLines={getAlertReferenceLines({ threshold, thresholdType })}
40+
config={{
41+
where: savedSearch.where || '',
42+
whereLanguage: savedSearch.whereLanguage,
43+
dateRange: intervalToDateRange(interval),
44+
granularity: intervalToGranularity(interval),
45+
groupBy,
46+
select: [
47+
{
48+
aggFn: 'count' as const,
49+
aggCondition: '',
50+
aggConditionLanguage: 'sql',
51+
valueExpression: '',
52+
},
53+
],
54+
timestampValueExpression: source.timestampValueExpression,
55+
from: source.from,
56+
connection: source.connection,
57+
}}
58+
/>
59+
</Paper>
60+
);
61+
};

‎packages/app/src/components/Alerts.tsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Link from 'next/link';
22
import { Select, SelectProps } from 'react-hook-form-mantine';
3+
import { Label, ReferenceArea, ReferenceLine } from 'recharts';
34
import { Button, Group } from '@mantine/core';
45

56
import api from '@/api';
@@ -38,3 +39,40 @@ export const WebhookChannelForm = <T extends object>(
3839
</div>
3940
);
4041
};
42+
43+
export const getAlertReferenceLines = ({
44+
thresholdType,
45+
threshold,
46+
// TODO: zScore
47+
}: {
48+
thresholdType: 'above' | 'below';
49+
threshold: number;
50+
}) => (
51+
<>
52+
{threshold != null && thresholdType === 'below' && (
53+
<ReferenceArea
54+
y1={0}
55+
y2={threshold}
56+
ifOverflow="extendDomain"
57+
strokeWidth={0}
58+
fillOpacity={0.15}
59+
/>
60+
)}
61+
{threshold != null && thresholdType === 'above' && (
62+
<ReferenceArea
63+
y1={threshold}
64+
ifOverflow="extendDomain"
65+
strokeWidth={0}
66+
fillOpacity={0.15}
67+
/>
68+
)}
69+
{threshold != null && (
70+
<ReferenceLine
71+
y={threshold}
72+
label={<Label value="Alert Threshold" fill={'white'} />}
73+
stroke="red"
74+
strokeDasharray="3 3"
75+
/>
76+
)}
77+
</>
78+
);

‎packages/app/src/components/DBTimeChart.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,9 @@ export function DBTimeChart({
2525
config,
2626
sourceId,
2727
onSettled,
28-
alertThreshold,
29-
alertThresholdType,
28+
referenceLines,
3029
showDisplaySwitcher = true,
3130
setDisplayType,
32-
logReferenceTimestamp,
3331
queryKeyPrefix,
3432
enabled = true,
3533
onTimeRangeSelect,
@@ -38,11 +36,9 @@ export function DBTimeChart({
3836
config: ChartConfigWithDateRange;
3937
sourceId?: string;
4038
onSettled?: () => void;
41-
alertThreshold?: number;
42-
alertThresholdType?: 'above' | 'below';
4339
showDisplaySwitcher?: boolean;
4440
setDisplayType?: (type: DisplayType) => void;
45-
logReferenceTimestamp?: number;
41+
referenceLines?: React.ReactNode;
4642
queryKeyPrefix?: string;
4743
enabled?: boolean;
4844
onTimeRangeSelect?: (start: Date, end: Date) => void;
@@ -289,6 +285,7 @@ export function DBTimeChart({
289285
onTimeRangeSelect={onTimeRangeSelect}
290286
showLegend={showLegend}
291287
numberFormat={config.numberFormat}
288+
referenceLines={referenceLines}
292289
/>
293290
</div>
294291
</div>

0 commit comments

Comments
 (0)
Please sign in to comment.