Skip to content

Commit 160871d

Browse files
authored
feat(aci): Add support for logs metric detectors (#94676)
1 parent c35757a commit 160871d

File tree

8 files changed

+89
-24
lines changed

8 files changed

+89
-24
lines changed

static/app/types/workflowEngine/detectors.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
import type {DataConditionGroup} from 'sentry/types/workflowEngine/dataConditions';
2-
import type {AlertRuleSensitivity} from 'sentry/views/alerts/rules/metric/types';
2+
import type {
3+
AlertRuleSensitivity,
4+
Dataset,
5+
EventTypes,
6+
} from 'sentry/views/alerts/rules/metric/types';
37

48
/**
59
* See SnubaQuerySerializer
610
*/
711
interface SnubaQuery {
812
aggregate: string;
9-
dataset: string;
13+
dataset: Dataset;
14+
eventTypes: EventTypes[];
1015
id: string;
1116
query: string;
1217
/**

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import {unreachable} from 'sentry/utils/unreachable';
12
import {ErrorsConfig} from 'sentry/views/dashboards/datasetConfig/errors';
3+
import {LogsConfig} from 'sentry/views/dashboards/datasetConfig/logs';
24
import {ReleasesConfig} from 'sentry/views/dashboards/datasetConfig/releases';
35
import {SpansConfig} from 'sentry/views/dashboards/datasetConfig/spans';
46
import {TransactionsConfig} from 'sentry/views/dashboards/datasetConfig/transactions';
@@ -17,7 +19,10 @@ export function getDatasetConfig(dataset: DetectorDataset) {
1719
return ReleasesConfig;
1820
case DetectorDataset.SPANS:
1921
return SpansConfig;
22+
case DetectorDataset.LOGS:
23+
return LogsConfig;
2024
default:
25+
unreachable(dataset);
2126
return ErrorsConfig;
2227
}
2328
}

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

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {useContext, useMemo} from 'react';
22
import styled from '@emotion/styled';
33

4+
import {FeatureBadge} from 'sentry/components/core/badge/featureBadge';
45
import {Button} from 'sentry/components/core/button';
56
import {Flex} from 'sentry/components/core/layout';
67
import {Tooltip} from 'sentry/components/core/tooltip';
@@ -14,6 +15,7 @@ import Section from 'sentry/components/workflowEngine/ui/section';
1415
import {IconAdd} from 'sentry/icons';
1516
import {t} from 'sentry/locale';
1617
import {space} from 'sentry/styles/space';
18+
import type {SelectValue} from 'sentry/types/core';
1719
import {
1820
DataConditionType,
1921
DetectorPriorityLevel,
@@ -42,6 +44,7 @@ import {TraceItemDataset} from 'sentry/views/explore/types';
4244

4345
function MetricDetectorFormContext({children}: {children: React.ReactNode}) {
4446
const projectId = useMetricDetectorFormField(METRIC_DETECTOR_FORM_FIELDS.projectId);
47+
const dataset = useMetricDetectorFormField(METRIC_DETECTOR_FORM_FIELDS.dataset);
4548
const {projects} = useProjects();
4649

4750
const traceItemProjects = useMemo(() => {
@@ -52,9 +55,14 @@ function MetricDetectorFormContext({children}: {children: React.ReactNode}) {
5255
return [project];
5356
}, [projectId, projects]);
5457

58+
let traceItemType = TraceItemDataset.SPANS;
59+
if (dataset === DetectorDataset.LOGS) {
60+
traceItemType = TraceItemDataset.LOGS;
61+
}
62+
5563
return (
5664
<TraceItemAttributeProvider
57-
traceItemType={TraceItemDataset.SPANS}
65+
traceItemType={traceItemType}
5866
projects={traceItemProjects}
5967
enabled
6068
>
@@ -198,13 +206,28 @@ function useDatasetChoices() {
198206
const organization = useOrganization();
199207

200208
return useMemo(() => {
201-
const datasetChoices: Array<[DetectorDataset, string]> = [
202-
[DetectorDataset.ERRORS, t('Errors')],
203-
[DetectorDataset.TRANSACTIONS, t('Transactions')],
209+
const datasetChoices: Array<SelectValue<DetectorDataset>> = [
210+
{
211+
value: DetectorDataset.ERRORS,
212+
label: t('Errors'),
213+
},
214+
{
215+
value: DetectorDataset.TRANSACTIONS,
216+
label: t('Transactions'),
217+
},
204218
...(organization.features.includes('visibility-explore-view')
205-
? ([[DetectorDataset.SPANS, t('Spans')]] as Array<[DetectorDataset, string]>)
219+
? [{value: DetectorDataset.SPANS, label: t('Spans')}]
220+
: []),
221+
...(organization.features.includes('ourlogs-alerts')
222+
? [
223+
{
224+
value: DetectorDataset.LOGS,
225+
label: t('Logs'),
226+
trailingItems: <FeatureBadge type="beta" />,
227+
},
228+
]
206229
: []),
207-
[DetectorDataset.RELEASES, t('Releases')],
230+
{value: DetectorDataset.RELEASES, label: t('Releases')},
208231
];
209232

210233
return datasetChoices;
@@ -239,7 +262,7 @@ function DetectSection() {
239262
</Tooltip>
240263
}
241264
name={METRIC_DETECTOR_FORM_FIELDS.dataset}
242-
choices={datasetChoices}
265+
options={datasetChoices}
243266
onChange={newDataset => {
244267
// Reset aggregate function to dataset default when dataset changes
245268
const datasetConfig = getDatasetConfig(newDataset);

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

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ import {
1010
} from 'sentry/types/workflowEngine/dataConditions';
1111
import type {Detector, DetectorConfig} from 'sentry/types/workflowEngine/detectors';
1212
import {defined} from 'sentry/utils';
13+
import {unreachable} from 'sentry/utils/unreachable';
1314
import {
1415
AlertRuleSensitivity,
1516
AlertRuleThresholdType,
1617
Dataset,
18+
EventTypes,
1719
} from 'sentry/views/alerts/rules/metric/types';
1820

1921
/**
@@ -24,6 +26,7 @@ export const enum DetectorDataset {
2426
TRANSACTIONS = 'transactions',
2527
SPANS = 'spans',
2628
RELEASES = 'releases',
29+
LOGS = 'logs',
2730
}
2831

2932
/**
@@ -223,18 +226,33 @@ function createConditions(data: MetricDetectorFormData): NewConditionGroup['cond
223226
/**
224227
* Convert backend dataset to our form dataset
225228
*/
226-
const getDetectorDataset = (backendDataset: string): DetectorDataset => {
229+
const getDetectorDataset = (
230+
backendDataset: Dataset,
231+
eventTypes: EventTypes[]
232+
): DetectorDataset => {
227233
switch (backendDataset) {
234+
case Dataset.REPLAYS:
235+
throw new Error('Unsupported dataset');
228236
case Dataset.ERRORS:
237+
case Dataset.ISSUE_PLATFORM:
229238
return DetectorDataset.ERRORS;
230239
case Dataset.TRANSACTIONS:
231240
case Dataset.GENERIC_METRICS:
232241
return DetectorDataset.TRANSACTIONS;
233242
case Dataset.EVENTS_ANALYTICS_PLATFORM:
234-
return DetectorDataset.SPANS;
243+
// Spans and logs use the same dataset
244+
if (eventTypes.includes(EventTypes.TRACE_ITEM_SPAN)) {
245+
return DetectorDataset.SPANS;
246+
}
247+
if (eventTypes.includes(EventTypes.TRACE_ITEM_LOG)) {
248+
return DetectorDataset.LOGS;
249+
}
250+
throw new Error('Unsupported event types');
235251
case Dataset.METRICS:
252+
case Dataset.SESSIONS:
236253
return DetectorDataset.RELEASES; // Maps metrics dataset to releases for crash rate
237254
default:
255+
unreachable(backendDataset);
238256
return DetectorDataset.ERRORS;
239257
}
240258
};
@@ -252,7 +270,10 @@ const getBackendDataset = (dataset: DetectorDataset): string => {
252270
return Dataset.EVENTS_ANALYTICS_PLATFORM;
253271
case DetectorDataset.RELEASES:
254272
return Dataset.METRICS; // Maps to metrics dataset for crash rate queries
273+
case DetectorDataset.LOGS:
274+
return Dataset.EVENTS_ANALYTICS_PLATFORM;
255275
default:
276+
unreachable(dataset);
256277
return Dataset.ERRORS;
257278
}
258279
};
@@ -271,7 +292,10 @@ function createDataSource(data: MetricDetectorFormData): NewDataSource {
271292
return ['trace_item_span'];
272293
case DetectorDataset.RELEASES:
273294
return []; // Crash rate queries don't have event types
295+
case DetectorDataset.LOGS:
296+
return ['trace_item_log'];
274297
default:
298+
unreachable(dataset);
275299
return ['error'];
276300
}
277301
};
@@ -285,10 +309,12 @@ function createDataSource(data: MetricDetectorFormData): NewDataSource {
285309
return SnubaQueryType.ERROR;
286310
case DetectorDataset.TRANSACTIONS:
287311
case DetectorDataset.SPANS:
312+
case DetectorDataset.LOGS:
288313
return SnubaQueryType.PERFORMANCE;
289314
case DetectorDataset.RELEASES:
290315
return SnubaQueryType.CRASH_RATE; // Maps to crash rate for metrics dataset
291316
default:
317+
unreachable(dataset);
292318
return SnubaQueryType.ERROR;
293319
}
294320
};
@@ -422,7 +448,7 @@ export function getMetricDetectorFormData(detector: Detector): MetricDetectorFor
422448
const conditionData = processDetectorConditions(detector);
423449

424450
const dataset = snubaQuery?.dataset
425-
? getDetectorDataset(snubaQuery.dataset)
451+
? getDetectorDataset(snubaQuery.dataset, snubaQuery.eventTypes)
426452
: DetectorDataset.SPANS;
427453

428454
return {

static/app/views/detectors/components/forms/metric/customMeasurements.tsx renamed to static/app/views/detectors/components/forms/metric/useCustomMeasurements.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,14 @@ import type {Query} from 'history';
44
import {getFieldTypeFromUnit} from 'sentry/components/events/eventCustomPerformanceMetrics';
55
import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse';
66
import type {PageFilters} from 'sentry/types/core';
7-
import type {Organization} from 'sentry/types/organization';
87
import type {CustomMeasurementCollection} from 'sentry/utils/customMeasurements/customMeasurements';
98
import {useApiQuery} from 'sentry/utils/queryClient';
9+
import useOrganization from 'sentry/utils/useOrganization';
1010

1111
type MeasurementsMetaResponse = Record<string, {functions: string[]; unit: string}>;
1212

13-
export function useCustomMeasurements(
14-
organization: Organization,
15-
selection?: PageFilters
16-
) {
13+
export function useCustomMeasurements(selection?: PageFilters) {
14+
const organization = useOrganization();
1715
const query: Query = selection?.datetime
1816
? normalizeDateTimeParams(selection.datetime)
1917
: {};

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

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@ import {ALLOWED_EXPLORE_VISUALIZE_AGGREGATES, prettifyTagKey} from 'sentry/utils
1717
import {unreachable} from 'sentry/utils/unreachable';
1818
import useOrganization from 'sentry/utils/useOrganization';
1919
import useTags from 'sentry/utils/useTags';
20-
import {useCustomMeasurements} from 'sentry/views/detectors/components/forms/metric/customMeasurements';
2120
import {getDatasetConfig} from 'sentry/views/detectors/components/forms/metric/getDatasetConfig';
2221
import {
2322
DetectorDataset,
2423
METRIC_DETECTOR_FORM_FIELDS,
2524
useMetricDetectorFormField,
2625
} from 'sentry/views/detectors/components/forms/metric/metricFormData';
2726
import {DetectorQueryFilterBuilder} from 'sentry/views/detectors/components/forms/metric/queryFilterBuilder';
27+
import {useCustomMeasurements} from 'sentry/views/detectors/components/forms/metric/useCustomMeasurements';
2828
import {SectionLabel} from 'sentry/views/detectors/components/forms/sectionLabel';
2929
import type {FieldValue} from 'sentry/views/discover/table/types';
3030
import {FieldValueKind} from 'sentry/views/discover/table/types';
@@ -84,7 +84,7 @@ function getAggregateOptions(
8484
tableFieldOptions: Record<string, SelectValue<FieldValue>>
8585
): Array<[string, string]> {
8686
// For spans dataset, use the predefined aggregates
87-
if (dataset === DetectorDataset.SPANS) {
87+
if (dataset === DetectorDataset.SPANS || dataset === DetectorDataset.LOGS) {
8888
return ALLOWED_EXPLORE_VISUALIZE_AGGREGATES.map(aggregate => [aggregate, aggregate]);
8989
}
9090

@@ -169,12 +169,14 @@ function buildAggregateFunction(aggregate: string, parameters: string[]): string
169169

170170
export function Visualize() {
171171
const organization = useOrganization();
172-
const {customMeasurements} = useCustomMeasurements(organization);
172+
const {customMeasurements} = useCustomMeasurements();
173173
const dataset = useMetricDetectorFormField(METRIC_DETECTOR_FORM_FIELDS.dataset);
174174
const aggregateFunction = useMetricDetectorFormField(
175175
METRIC_DETECTOR_FORM_FIELDS.aggregateFunction
176176
);
177177
const tags = useTags();
178+
179+
// See TraceItemAttributeProvider for how these contexts are populated
178180
const {tags: numericSpanTags} = useTraceItemTags('number');
179181
const {tags: stringSpanTags} = useTraceItemTags('string');
180182
const formContext = useContext(FormContext);
@@ -193,7 +195,7 @@ export function Visualize() {
193195

194196
const fieldOptions = useMemo(() => {
195197
// For Spans dataset, use span-specific options from the provider
196-
if (dataset === DetectorDataset.SPANS) {
198+
if (dataset === DetectorDataset.SPANS || dataset === DetectorDataset.LOGS) {
197199
const spanColumnOptions: Array<[string, string]> = [
198200
...Object.values(stringSpanTags).map((tag): [string, string] => [
199201
tag.key,
@@ -278,7 +280,9 @@ export function Visualize() {
278280
};
279281

280282
const hasVisibleParameters =
281-
Boolean(aggregateMetadata?.parameters?.length) && dataset !== DetectorDataset.SPANS;
283+
Boolean(aggregateMetadata?.parameters?.length) &&
284+
dataset !== DetectorDataset.SPANS &&
285+
dataset !== DetectorDataset.LOGS;
282286

283287
return (
284288
<CustomMeasurementsContext value={{customMeasurements}}>

static/app/views/detectors/list.spec.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
DataConditionType,
1919
DetectorPriorityLevel,
2020
} from 'sentry/types/workflowEngine/dataConditions';
21+
import {Dataset, EventTypes} from 'sentry/views/alerts/rules/metric/types';
2122
import DetectorsList from 'sentry/views/detectors/list';
2223

2324
describe('DetectorsList', function () {
@@ -71,10 +72,11 @@ describe('DetectorsList', function () {
7172
snubaQuery: {
7273
environment: 'production',
7374
aggregate: 'count()',
74-
dataset: 'events',
75+
dataset: Dataset.ERRORS,
7576
id: '1',
7677
query: 'event.type:error',
7778
timeWindow: 3600,
79+
eventTypes: [EventTypes.ERROR],
7880
},
7981
id: '1',
8082
status: 200,

tests/js/fixtures/detectors.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {DataConditionGroupFixture} from 'sentry-fixture/dataConditions';
22
import {UserFixture} from 'sentry-fixture/user';
33

44
import type {Detector, SnubaQueryDataSource} from 'sentry/types/workflowEngine/detectors';
5+
import {Dataset, EventTypes} from 'sentry/views/alerts/rules/metric/types';
56

67
export function DetectorFixture(params: Partial<Detector> = {}): Detector {
78
return {
@@ -40,10 +41,11 @@ export function SnubaQueryDataSourceFixture(
4041
subscription: '1',
4142
snubaQuery: {
4243
aggregate: '',
43-
dataset: '',
44+
dataset: Dataset.ERRORS,
4445
id: '',
4546
query: '',
4647
timeWindow: 60,
48+
eventTypes: [EventTypes.ERROR],
4749
},
4850
},
4951
...params,

0 commit comments

Comments
 (0)