Skip to content

Commit 92a1746

Browse files
authored
feat(aci): Add metric detector chart previews (#95011)
1 parent bb094bd commit 92a1746

File tree

15 files changed

+372
-55
lines changed

15 files changed

+372
-55
lines changed

static/app/views/detectors/components/forms/editDetectorLayout.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ type EditDetectorLayoutProps = {
3232
detector: Detector;
3333
detectorType: EditableDetectorType;
3434
handleSubmit?: OnSubmitCallback;
35+
previewChart?: React.ReactNode;
3536
};
3637

3738
function DetectorBreadcrumbs({detector}: {detector: Detector}) {
@@ -55,6 +56,7 @@ function DetectorDocumentTitle({detector}: {detector: Detector}) {
5556
}
5657

5758
export function EditDetectorLayout({
59+
previewChart,
5860
detector,
5961
children,
6062
detectorType,
@@ -101,7 +103,10 @@ export function EditDetectorLayout({
101103
<StyledLayoutHeader>
102104
<Layout.HeaderContent>
103105
<DetectorBreadcrumbs detector={detector} />
104-
<DetectorBaseFields />
106+
<Flex direction="column" gap={space(2)}>
107+
<DetectorBaseFields />
108+
{previewChart}
109+
</Flex>
105110
</Layout.HeaderContent>
106111
<EditDetectorActions detectorId={detector.id} />
107112
</StyledLayoutHeader>

static/app/views/detectors/components/forms/metric/metric.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
METRIC_DETECTOR_FORM_FIELDS,
3636
useMetricDetectorFormField,
3737
} from 'sentry/views/detectors/components/forms/metric/metricFormData';
38+
import {MetricDetectorPreviewChart} from 'sentry/views/detectors/components/forms/metric/previewChart';
3839
import {Visualize} from 'sentry/views/detectors/components/forms/metric/visualize';
3940
import {NewDetectorLayout} from 'sentry/views/detectors/components/forms/newDetectorLayout';
4041
import {SectionLabel} from 'sentry/views/detectors/components/forms/sectionLabel';
@@ -56,15 +57,22 @@ function MetricDetectorForm() {
5657

5758
export function EditExistingMetricDetectorForm({detector}: {detector: Detector}) {
5859
return (
59-
<EditDetectorLayout detector={detector} detectorType="metric_issue">
60+
<EditDetectorLayout
61+
detectorType="metric_issue"
62+
detector={detector}
63+
previewChart={<MetricDetectorPreviewChart />}
64+
>
6065
<MetricDetectorForm />
6166
</EditDetectorLayout>
6267
);
6368
}
6469

6570
export function NewMetricDetectorForm() {
6671
return (
67-
<NewDetectorLayout detectorType="metric_issue">
72+
<NewDetectorLayout
73+
detectorType="metric_issue"
74+
previewChart={<MetricDetectorPreviewChart />}
75+
>
6876
<MetricDetectorForm />
6977
</NewDetectorLayout>
7078
);
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import {useMemo} from 'react';
2+
import styled from '@emotion/styled';
3+
4+
import {AreaChart} from 'sentry/components/charts/areaChart';
5+
import ErrorPanel from 'sentry/components/charts/errorPanel';
6+
import {Flex} from 'sentry/components/core/layout';
7+
import Placeholder from 'sentry/components/placeholder';
8+
import {IconWarning} from 'sentry/icons';
9+
import {t} from 'sentry/locale';
10+
import {useApiQuery} from 'sentry/utils/queryClient';
11+
import useOrganization from 'sentry/utils/useOrganization';
12+
import {
13+
METRIC_DETECTOR_FORM_FIELDS,
14+
useMetricDetectorFormField,
15+
} from 'sentry/views/detectors/components/forms/metric/metricFormData';
16+
import {getDatasetConfig} from 'sentry/views/detectors/datasetConfig/getDatasetConfig';
17+
import {DETECTOR_DATASET_TO_DISCOVER_DATASET_MAP} from 'sentry/views/detectors/datasetConfig/utils/discoverDatasetMap';
18+
19+
const CHART_HEIGHT = 175;
20+
21+
export function MetricDetectorPreviewChart() {
22+
const organization = useOrganization();
23+
const dataset = useMetricDetectorFormField(METRIC_DETECTOR_FORM_FIELDS.dataset);
24+
const aggregate = useMetricDetectorFormField(
25+
METRIC_DETECTOR_FORM_FIELDS.aggregateFunction
26+
);
27+
const interval = useMetricDetectorFormField(METRIC_DETECTOR_FORM_FIELDS.interval);
28+
const query = useMetricDetectorFormField(METRIC_DETECTOR_FORM_FIELDS.query);
29+
const environment = useMetricDetectorFormField(METRIC_DETECTOR_FORM_FIELDS.environment);
30+
const projectId = useMetricDetectorFormField(METRIC_DETECTOR_FORM_FIELDS.projectId);
31+
32+
const datasetConfig = useMemo(() => getDatasetConfig(dataset), [dataset]);
33+
const seriesQueryOptions = datasetConfig.getSeriesQueryOptions({
34+
organization,
35+
aggregate,
36+
interval,
37+
query,
38+
environment,
39+
projectId: Number(projectId),
40+
dataset: DETECTOR_DATASET_TO_DISCOVER_DATASET_MAP[dataset],
41+
});
42+
43+
const {data, isPending, isError} = useApiQuery<
44+
Parameters<typeof datasetConfig.transformSeriesQueryData>[0]
45+
>(seriesQueryOptions, {
46+
// 5 minutes
47+
staleTime: 5 * 60 * 1000,
48+
});
49+
50+
const series = useMemo(() => {
51+
// TypeScript can't infer that each dataset config expects its own specific response type
52+
return datasetConfig.transformSeriesQueryData(data as any, aggregate);
53+
}, [datasetConfig, data, aggregate]);
54+
55+
if (isPending) {
56+
return (
57+
<PreviewChartContainer>
58+
<Placeholder height={`${CHART_HEIGHT}px`} />
59+
</PreviewChartContainer>
60+
);
61+
}
62+
63+
if (isError) {
64+
return (
65+
<PreviewChartContainer>
66+
<Flex style={{height: CHART_HEIGHT}} justify="center" align="center">
67+
<ErrorPanel>
68+
<IconWarning color="gray300" size="lg" />
69+
<div>{t('Error loading chart data')}</div>
70+
</ErrorPanel>
71+
</Flex>
72+
</PreviewChartContainer>
73+
);
74+
}
75+
76+
return (
77+
<PreviewChartContainer>
78+
<AreaChart
79+
series={series}
80+
height={CHART_HEIGHT}
81+
stacked={false}
82+
isGroupedByDate
83+
showTimeInTooltip
84+
/>
85+
</PreviewChartContainer>
86+
);
87+
}
88+
89+
const PreviewChartContainer = styled('div')`
90+
max-width: 1440px;
91+
border-top: 1px solid ${p => p.theme.border};
92+
`;

static/app/views/detectors/components/forms/metric/queryFilterBuilder.tsx

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,7 @@ import {Tooltip} from 'sentry/components/core/tooltip';
66
import FormContext from 'sentry/components/forms/formContext';
77
import {t} from 'sentry/locale';
88
import {space} from 'sentry/styles/space';
9-
import {DiscoverDatasets} from 'sentry/utils/discover/types';
10-
import {unreachable} from 'sentry/utils/unreachable';
119
import {
12-
DetectorDataset,
1310
METRIC_DETECTOR_FORM_FIELDS,
1411
useMetricDetectorFormField,
1512
} from 'sentry/views/detectors/components/forms/metric/metricFormData';
@@ -18,24 +15,7 @@ import {
1815
SectionLabelSecondary,
1916
} from 'sentry/views/detectors/components/forms/sectionLabel';
2017
import {getDatasetConfig} from 'sentry/views/detectors/datasetConfig/getDatasetConfig';
21-
22-
function getDiscoverDataset(dataset: DetectorDataset): DiscoverDatasets {
23-
switch (dataset) {
24-
case DetectorDataset.ERRORS:
25-
return DiscoverDatasets.ERRORS;
26-
case DetectorDataset.TRANSACTIONS:
27-
return DiscoverDatasets.TRANSACTIONS;
28-
case DetectorDataset.SPANS:
29-
return DiscoverDatasets.SPANS_EAP;
30-
case DetectorDataset.LOGS:
31-
return DiscoverDatasets.OURLOGS;
32-
case DetectorDataset.RELEASES:
33-
return DiscoverDatasets.DISCOVER;
34-
default:
35-
unreachable(dataset);
36-
return DiscoverDatasets.ERRORS;
37-
}
38-
}
18+
import {DETECTOR_DATASET_TO_DISCOVER_DATASET_MAP} from 'sentry/views/detectors/datasetConfig/utils/discoverDatasetMap';
3919

4020
export function DetectorQueryFilterBuilder() {
4121
const currentQuery = useMetricDetectorFormField(METRIC_DETECTOR_FORM_FIELDS.query);
@@ -76,7 +56,7 @@ export function DetectorQueryFilterBuilder() {
7656
projectIds={projectIds}
7757
onClose={handleQueryChange}
7858
onSearch={handleQueryChange}
79-
dataset={getDiscoverDataset(dataset)}
59+
dataset={DETECTOR_DATASET_TO_DISCOVER_DATASET_MAP[dataset]}
8060
/>
8161
</QueryFieldRowWrapper>
8262
</Flex>

static/app/views/detectors/components/forms/newDetectorLayout.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,14 @@ type NewDetectorLayoutProps = {
3838
detectorType: EditableDetectorType;
3939
handleSubmit?: OnSubmitCallback;
4040
initialFormData?: Partial<DetectorFormData>;
41+
previewChart?: React.ReactNode;
4142
};
4243

4344
export function NewDetectorLayout({
4445
children,
4546
handleSubmit,
4647
initialFormData,
48+
previewChart,
4749
detectorType,
4850
}: NewDetectorLayoutProps) {
4951
const location = useLocation();
@@ -117,7 +119,10 @@ export function NewDetectorLayout({
117119
},
118120
]}
119121
/>
120-
<DetectorBaseFields />
122+
<Flex direction="column" gap={space(2)}>
123+
<DetectorBaseFields />
124+
{previewChart}
125+
</Flex>
121126
</Layout.HeaderContent>
122127
</StyledLayoutHeader>
123128
<Layout.Body>

static/app/views/detectors/datasetConfig/base.tsx

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import type {SelectValue} from 'sentry/types/core';
2+
import type {Series} from 'sentry/types/echarts';
23
import type {TagCollection} from 'sentry/types/group';
34
import type {Organization} from 'sentry/types/organization';
45
import type {CustomMeasurementCollection} from 'sentry/utils/customMeasurements/customMeasurements';
56
import type {QueryFieldValue} from 'sentry/utils/discover/fields';
67
import type {DiscoverDatasets} from 'sentry/utils/discover/types';
8+
import type {ApiQueryKey} from 'sentry/utils/queryClient';
79
import type {FieldValue} from 'sentry/views/discover/table/types';
810

911
export interface DetectorSearchBarProps {
@@ -14,11 +16,30 @@ export interface DetectorSearchBarProps {
1416
dataset?: DiscoverDatasets;
1517
}
1618

19+
export interface DetectorSeriesQueryOptions {
20+
/**
21+
* The aggregate to use for the series query. eg: `count()`
22+
*/
23+
aggregate: string;
24+
dataset: DiscoverDatasets;
25+
environment: string;
26+
/**
27+
* example: `1h`
28+
*/
29+
interval: number;
30+
organization: Organization;
31+
projectId: number;
32+
/**
33+
* The filter query. eg: `span.op:http`
34+
*/
35+
query: string;
36+
}
37+
1738
/**
1839
* Minimal configuration interface for detector dataset configs.
1940
* Contains only the properties actually used by the detectors form.
2041
*/
21-
export interface DetectorDatasetConfig {
42+
export interface DetectorDatasetConfig<SeriesResponse> {
2243
/**
2344
* Dataset specific search bar for the 'Filter' step in the
2445
* widget builder.
@@ -36,4 +57,9 @@ export interface DetectorDatasetConfig {
3657
tags?: TagCollection,
3758
customMeasurements?: CustomMeasurementCollection
3859
) => Record<string, SelectValue<FieldValue>>;
60+
getSeriesQueryOptions: (options: DetectorSeriesQueryOptions) => ApiQueryKey;
61+
transformSeriesQueryData: (
62+
data: SeriesResponse | undefined,
63+
aggregate: string
64+
) => Series[];
3965
}
Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,26 @@
1+
import type {EventsStats} from 'sentry/types/organization';
2+
import {DiscoverDatasets} from 'sentry/utils/discover/types';
13
import {ErrorsConfig} from 'sentry/views/dashboards/datasetConfig/errors';
24
import {EventsSearchBar} from 'sentry/views/detectors/datasetConfig/components/eventSearchBar';
5+
import {
6+
getDiscoverSeriesQueryOptions,
7+
transformEventsStatsToSeries,
8+
} from 'sentry/views/detectors/datasetConfig/utils/discoverSeries';
39

410
import type {DetectorDatasetConfig} from './base';
511

6-
export const DetectorErrorsConfig: DetectorDatasetConfig = {
12+
type ErrorsSeriesResponse = EventsStats;
13+
14+
export const DetectorErrorsConfig: DetectorDatasetConfig<ErrorsSeriesResponse> = {
715
defaultField: ErrorsConfig.defaultField,
816
getAggregateOptions: ErrorsConfig.getTableFieldOptions,
917
SearchBar: EventsSearchBar,
18+
getSeriesQueryOptions: options =>
19+
getDiscoverSeriesQueryOptions({
20+
...options,
21+
dataset: DiscoverDatasets.ERRORS,
22+
}),
23+
transformSeriesQueryData: (data, aggregate) => {
24+
return [transformEventsStatsToSeries(data, aggregate)];
25+
},
1026
};
Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,20 @@
1-
import {unreachable} from 'sentry/utils/unreachable';
21
import {DetectorDataset} from 'sentry/views/detectors/components/forms/metric/metricFormData';
3-
import type {DetectorDatasetConfig} from 'sentry/views/detectors/datasetConfig/base';
42
import {DetectorErrorsConfig} from 'sentry/views/detectors/datasetConfig/errors';
53
import {DetectorLogsConfig} from 'sentry/views/detectors/datasetConfig/logs';
64
import {DetectorReleasesConfig} from 'sentry/views/detectors/datasetConfig/releases';
75
import {DetectorSpansConfig} from 'sentry/views/detectors/datasetConfig/spans';
86
import {DetectorTransactionsConfig} from 'sentry/views/detectors/datasetConfig/transactions';
97

10-
export function getDatasetConfig(dataset: DetectorDataset): DetectorDatasetConfig {
11-
switch (dataset) {
12-
case DetectorDataset.ERRORS:
13-
return DetectorErrorsConfig;
14-
case DetectorDataset.TRANSACTIONS:
15-
return DetectorTransactionsConfig;
16-
case DetectorDataset.RELEASES:
17-
return DetectorReleasesConfig;
18-
case DetectorDataset.SPANS:
19-
return DetectorSpansConfig;
20-
case DetectorDataset.LOGS:
21-
return DetectorLogsConfig;
22-
default:
23-
unreachable(dataset);
24-
return DetectorErrorsConfig;
25-
}
8+
const DATASET_CONFIG_MAP = {
9+
[DetectorDataset.ERRORS]: DetectorErrorsConfig,
10+
[DetectorDataset.TRANSACTIONS]: DetectorTransactionsConfig,
11+
[DetectorDataset.RELEASES]: DetectorReleasesConfig,
12+
[DetectorDataset.SPANS]: DetectorSpansConfig,
13+
[DetectorDataset.LOGS]: DetectorLogsConfig,
14+
} as const satisfies Record<DetectorDataset, any>;
15+
16+
export function getDatasetConfig<T extends DetectorDataset>(
17+
dataset: T
18+
): (typeof DATASET_CONFIG_MAP)[T] {
19+
return DATASET_CONFIG_MAP[dataset];
2620
}
Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
1+
import type {EventsStats} from 'sentry/types/organization';
12
import {LogsConfig} from 'sentry/views/dashboards/datasetConfig/logs';
23
import {TraceSearchBar} from 'sentry/views/detectors/datasetConfig/components/traceSearchBar';
4+
import {
5+
getDiscoverSeriesQueryOptions,
6+
transformEventsStatsToSeries,
7+
} from 'sentry/views/detectors/datasetConfig/utils/discoverSeries';
38

49
import type {DetectorDatasetConfig} from './base';
510

6-
export const DetectorLogsConfig: DetectorDatasetConfig = {
11+
type LogsSeriesRepsonse = EventsStats;
12+
13+
export const DetectorLogsConfig: DetectorDatasetConfig<LogsSeriesRepsonse> = {
714
defaultField: LogsConfig.defaultField,
815
getAggregateOptions: LogsConfig.getTableFieldOptions,
916
SearchBar: TraceSearchBar,
17+
getSeriesQueryOptions: getDiscoverSeriesQueryOptions,
18+
transformSeriesQueryData: (data, aggregate) => {
19+
return [transformEventsStatsToSeries(data, aggregate)];
20+
},
1021
};
Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
1+
import type {SessionApiResponse} from 'sentry/types/organization';
12
import {ReleasesConfig} from 'sentry/views/dashboards/datasetConfig/releases';
23
import {ReleaseSearchBar} from 'sentry/views/detectors/datasetConfig/components/releaseSearchBar';
4+
import {
5+
getReleasesSeriesQueryOptions,
6+
transformMetricsResponseToSeries,
7+
} from 'sentry/views/detectors/datasetConfig/utils/releasesSeries';
38

49
import type {DetectorDatasetConfig} from './base';
510

6-
export const DetectorReleasesConfig: DetectorDatasetConfig = {
11+
type ReleasesSeriesResponse = SessionApiResponse;
12+
13+
export const DetectorReleasesConfig: DetectorDatasetConfig<ReleasesSeriesResponse> = {
714
defaultField: ReleasesConfig.defaultField,
815
getAggregateOptions: ReleasesConfig.getTableFieldOptions,
916
SearchBar: ReleaseSearchBar,
17+
getSeriesQueryOptions: getReleasesSeriesQueryOptions,
18+
transformSeriesQueryData: (data, aggregate) => {
19+
return [transformMetricsResponseToSeries(data, aggregate)];
20+
},
1021
};

0 commit comments

Comments
 (0)