Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
216 changes: 83 additions & 133 deletions static/app/views/detectors/components/forms/metric/metric.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {Fragment, useContext, useEffect, useMemo} from 'react';
import {Fragment, useContext, useEffect} from 'react';
import styled from '@emotion/styled';
import toNumber from 'lodash/toNumber';

import {FeatureBadge} from 'sentry/components/core/badge/featureBadge';
import {Disclosure} from 'sentry/components/core/disclosure';
import {Flex} from 'sentry/components/core/layout';
import {Heading} from 'sentry/components/core/text/heading';
import {Text} from 'sentry/components/core/text/text';
Expand All @@ -15,29 +15,20 @@ import FormContext from 'sentry/components/forms/formContext';
import {Container} from 'sentry/components/workflowEngine/ui/container';
import {t} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import type {SelectValue} from 'sentry/types/core';
import {DataConditionType} from 'sentry/types/workflowEngine/dataConditions';
import type {
Detector,
MetricDetector,
MetricDetectorConfig,
} from 'sentry/types/workflowEngine/detectors';
import type {Detector, MetricDetectorConfig} from 'sentry/types/workflowEngine/detectors';
import {generateFieldAsString} from 'sentry/utils/discover/fields';
import useOrganization from 'sentry/utils/useOrganization';
import {
AlertRuleSensitivity,
AlertRuleThresholdType,
Dataset,
} from 'sentry/views/alerts/rules/metric/types';
import {hasLogAlerts} from 'sentry/views/alerts/wizard/utils';
import {
TRANSACTIONS_DATASET_DEPRECATION_MESSAGE,
TransactionsDatasetWarning,
} from 'sentry/views/detectors/components/details/metric/transactionsDatasetWarning';
import {AutomateSection} from 'sentry/views/detectors/components/forms/automateSection';
import {AssignSection} from 'sentry/views/detectors/components/forms/common/assignSection';
import {DescribeSection} from 'sentry/views/detectors/components/forms/common/describeSection';
import {useDetectorFormContext} from 'sentry/views/detectors/components/forms/context';
import {EditDetectorLayout} from 'sentry/views/detectors/components/forms/editDetectorLayout';
import type {MetricDetectorFormData} from 'sentry/views/detectors/components/forms/metric/metricFormData';
import {
Expand All @@ -49,7 +40,9 @@ import {
import {MetricDetectorPreviewChart} from 'sentry/views/detectors/components/forms/metric/previewChart';
import {DetectorQueryFilterBuilder} from 'sentry/views/detectors/components/forms/metric/queryFilterBuilder';
import {ResolveSection} from 'sentry/views/detectors/components/forms/metric/resolveSection';
import {TemplateSection} from 'sentry/views/detectors/components/forms/metric/templateSection';
import {useAutoMetricDetectorName} from 'sentry/views/detectors/components/forms/metric/useAutoMetricDetectorName';
import {useDatasetChoices} from 'sentry/views/detectors/components/forms/metric/useDatasetChoices';
import {useInitialMetricDetectorFormData} from 'sentry/views/detectors/components/forms/metric/useInitialMetricDetectorFormData';
import {useIntervalChoices} from 'sentry/views/detectors/components/forms/metric/useIntervalChoices';
import {Visualize} from 'sentry/views/detectors/components/forms/metric/visualize';
Expand All @@ -58,14 +51,14 @@ import {SectionLabel} from 'sentry/views/detectors/components/forms/sectionLabel
import {getDatasetConfig} from 'sentry/views/detectors/datasetConfig/getDatasetConfig';
import {DetectorDataset} from 'sentry/views/detectors/datasetConfig/types';
import {getMetricDetectorSuffix} from 'sentry/views/detectors/utils/metricDetectorSuffix';
import {deprecateTransactionAlerts} from 'sentry/views/insights/common/utils/hasEAPAlerts';

function MetricDetectorForm() {
useAutoMetricDetectorName();

return (
<FormStack>
<TransactionsDatasetWarningListener />
<TemplateSection />
<CustomizeMetricSection />
<DetectSection />
<AssignSection />
Expand Down Expand Up @@ -343,6 +336,7 @@ function IntervalPicker() {
placeholder={t('Interval')}
flexibleControlStateSize
inline={false}
preserveOnUnmount
label={
<Tooltip
title={t('The time period over which to evaluate your metric.')}
Expand All @@ -358,51 +352,6 @@ function IntervalPicker() {
);
}

function useDatasetChoices() {
const organization = useOrganization();

const {detector} = useDetectorFormContext();
const savedDataset = (detector as MetricDetector | undefined)?.dataSources[0]?.queryObj
?.snubaQuery?.dataset;
const isExistingTransactionsDetector =
Boolean(detector) &&
[Dataset.TRANSACTIONS, Dataset.GENERIC_METRICS].includes(savedDataset as Dataset);
const shouldHideTransactionsDataset =
!isExistingTransactionsDetector && deprecateTransactionAlerts(organization);

return useMemo(() => {
const datasetChoices: Array<SelectValue<DetectorDataset>> = [
{
value: DetectorDataset.ERRORS,
label: t('Errors'),
},
...(shouldHideTransactionsDataset
? []
: [
{
value: DetectorDataset.TRANSACTIONS,
label: t('Transactions'),
},
]),
...(organization.features.includes('visibility-explore-view')
? [{value: DetectorDataset.SPANS, label: t('Spans')}]
: []),
...(hasLogAlerts(organization)
? [
{
value: DetectorDataset.LOGS,
label: t('Logs'),
trailingItems: <FeatureBadge type="new" />,
},
]
: []),
{value: DetectorDataset.RELEASES, label: t('Releases')},
];

return datasetChoices;
}, [organization, shouldHideTransactionsDataset]);
}

function CustomizeMetricSection() {
const detectionType = useMetricDetectorFormField(
METRIC_DETECTOR_FORM_FIELDS.detectionType
Expand All @@ -414,72 +363,81 @@ function CustomizeMetricSection() {

return (
<Container>
<Flex direction="column" gap="xs">
<BorderBottomHeader>
<Heading as="h3">{t('Customize Metric')}</Heading>
</BorderBottomHeader>
<DatasetRow>
<DatasetField
placeholder={t('Dataset')}
flexibleControlStateSize
inline={false}
label={
<Tooltip
title={t('This reflects the type of information you want to use.')}
showUnderline
>
<SectionLabel>{t('Dataset')}</SectionLabel>
</Tooltip>
}
name={METRIC_DETECTOR_FORM_FIELDS.dataset}
options={datasetChoices}
onChange={newDataset => {
// Reset aggregate function to dataset default when dataset changes
const datasetConfig = getDatasetConfig(newDataset);
const defaultAggregate = generateFieldAsString(datasetConfig.defaultField);
formContext.form?.setValue(
METRIC_DETECTOR_FORM_FIELDS.aggregateFunction,
defaultAggregate
);

const supportedDetectionTypes = datasetConfig.supportedDetectionTypes;
if (!supportedDetectionTypes.includes(detectionType)) {
formContext.form?.setValue(
METRIC_DETECTOR_FORM_FIELDS.detectionType,
supportedDetectionTypes[0]
);
}
}}
/>
<Tooltip
title={TRANSACTIONS_DATASET_DEPRECATION_MESSAGE}
isHoverable
disabled={!isTransactionsDataset}
>
<DisabledSection disabled={isTransactionsDataset}>
<IntervalPicker />
</DisabledSection>
</Tooltip>
</DatasetRow>
</Flex>
<Tooltip
title={TRANSACTIONS_DATASET_DEPRECATION_MESSAGE}
isHoverable
disabled={!isTransactionsDataset}
>
<DisabledSection disabled={isTransactionsDataset}>
<Visualize />
</DisabledSection>
</Tooltip>
<Tooltip
title={TRANSACTIONS_DATASET_DEPRECATION_MESSAGE}
isHoverable
disabled={!isTransactionsDataset}
>
<FilterRow disabled={isTransactionsDataset}>
<DetectorQueryFilterBuilder />
</FilterRow>
</Tooltip>
<Disclosure as="section" size="md" role="region" defaultExpanded>
<Disclosure.Title aria-label={t('Customize Metric Section')}>
<Text size="lg">{t('Customize Metric')}</Text>
</Disclosure.Title>
<Disclosure.Content>
<Flex direction="column" gap="md">
<Flex direction="column" gap="xs">
<DatasetRow>
<DatasetField
placeholder={t('Dataset')}
flexibleControlStateSize
inline={false}
preserveOnUnmount
label={
<Tooltip
title={t('This reflects the type of information you want to use.')}
showUnderline
>
<SectionLabel>{t('Dataset')}</SectionLabel>
</Tooltip>
}
name={METRIC_DETECTOR_FORM_FIELDS.dataset}
options={datasetChoices}
onChange={newDataset => {
// Reset aggregate function to dataset default when dataset changes
const datasetConfig = getDatasetConfig(newDataset);
const defaultAggregate = generateFieldAsString(
datasetConfig.defaultField
);
formContext.form?.setValue(
METRIC_DETECTOR_FORM_FIELDS.aggregateFunction,
defaultAggregate
);

const supportedDetectionTypes = datasetConfig.supportedDetectionTypes;
if (!supportedDetectionTypes.includes(detectionType)) {
formContext.form?.setValue(
METRIC_DETECTOR_FORM_FIELDS.detectionType,
supportedDetectionTypes[0]
);
}
}}
/>
<Tooltip
title={TRANSACTIONS_DATASET_DEPRECATION_MESSAGE}
isHoverable
disabled={!isTransactionsDataset}
>
<DisabledSection disabled={isTransactionsDataset}>
<IntervalPicker />
</DisabledSection>
</Tooltip>
</DatasetRow>
</Flex>
<Tooltip
title={TRANSACTIONS_DATASET_DEPRECATION_MESSAGE}
isHoverable
disabled={!isTransactionsDataset}
>
<DisabledSection disabled={isTransactionsDataset}>
<Visualize />
</DisabledSection>
</Tooltip>
<Tooltip
title={TRANSACTIONS_DATASET_DEPRECATION_MESSAGE}
isHoverable
disabled={!isTransactionsDataset}
>
<FilterRow disabled={isTransactionsDataset}>
<DetectorQueryFilterBuilder />
</FilterRow>
</Tooltip>
</Flex>
</Disclosure.Content>
</Disclosure>
</Container>
);
}
Expand All @@ -496,9 +454,7 @@ function DetectSection() {
<Container>
<Flex direction="column" gap="lg">
<div>
<BorderBottomHeader>
<Heading as="h3">{t('Issue Detection')}</Heading>
</BorderBottomHeader>
<Heading as="h3">{t('Issue Detection')}</Heading>
<DetectionType />
<Flex direction="column">
{(!detectionType || detectionType === 'static') && (
Expand Down Expand Up @@ -620,12 +576,6 @@ const FilterRow = styled('div')<{disabled: boolean}>`
${p => (p.disabled ? `opacity: 0.6;` : '')}
`;

const BorderBottomHeader = styled('div')`
padding-bottom: ${p => p.theme.space.sm};
margin-bottom: ${p => p.theme.space.md};
border-bottom: 1px solid ${p => p.theme.border};
`;

const StyledSelectField = styled(SelectField)`
width: 180px;
padding: 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import {t} from 'sentry/locale';
import {SessionsAggregate} from 'sentry/views/alerts/rules/metric/types';
import type {MetricAlertType} from 'sentry/views/alerts/wizard/options';
import {DetectorDataset} from 'sentry/views/detectors/datasetConfig/types';

interface TemplateOption {
aggregate: string;
detectorDataset: DetectorDataset;
key: MetricAlertType;
label: string;
query?: string;
}

/**
* Template options for metric detectors.
* These define the available metric templates that users can select.
*/
export const METRIC_TEMPLATE_OPTIONS: TemplateOption[] = [
{
key: 'num_errors',
label: t('Number of Errors'),
detectorDataset: DetectorDataset.ERRORS,
aggregate: 'count()',
query: 'is:unresolved',
},
{
key: 'users_experiencing_errors',
label: t('Users Experiencing Errors'),
detectorDataset: DetectorDataset.ERRORS,
aggregate: 'count_unique(user)',
},
{
key: 'trace_item_throughput',
label: t('Throughput'),
detectorDataset: DetectorDataset.SPANS,
aggregate: 'count(span.duration)',
},
{
key: 'trace_item_duration',
label: t('Duration'),
detectorDataset: DetectorDataset.SPANS,
aggregate: 'p95(span.duration)',
},
{
key: 'trace_item_failure_rate',
label: t('Failure Rate'),
detectorDataset: DetectorDataset.SPANS,
aggregate: 'failure_rate()',
},
{
key: 'trace_item_lcp',
label: t('Largest Contentful Paint'),
detectorDataset: DetectorDataset.SPANS,
aggregate: 'p95(measurements.lcp)',
},
{
key: 'trace_item_logs',
label: t('Logs'),
detectorDataset: DetectorDataset.LOGS,
aggregate: 'count(message)',
},
{
key: 'crash_free_sessions',
label: t('Crash Free Session Rate'),
detectorDataset: DetectorDataset.RELEASES,
aggregate: SessionsAggregate.CRASH_FREE_SESSIONS,
},
{
key: 'crash_free_users',
label: t('Crash Free User Rate'),
detectorDataset: DetectorDataset.RELEASES,
aggregate: SessionsAggregate.CRASH_FREE_USERS,
},
];
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export function DetectorQueryFilterBuilder() {
name={METRIC_DETECTOR_FORM_FIELDS.query}
inline={false}
flexibleControlStateSize
preserveOnUnmount
label={t('Filter')}
hideLabel
disabled={dataset === DetectorDataset.TRANSACTIONS}
Expand Down
Loading
Loading