From 80af43b49318f51e10d1bb841a82880c5b6274c9 Mon Sep 17 00:00:00 2001 From: Malachi Willey Date: Mon, 24 Nov 2025 13:32:15 -0800 Subject: [PATCH 1/4] feat(aci): Add threshold info to details page --- .../components/details/metric/detect.spec.tsx | 66 +++++++++++++ .../components/details/metric/detect.tsx | 96 ++++++++++++++++--- .../components/details/metric/sidebar.tsx | 86 +---------------- tests/js/fixtures/detectors.ts | 2 +- 4 files changed, 152 insertions(+), 98 deletions(-) create mode 100644 static/app/views/detectors/components/details/metric/detect.spec.tsx diff --git a/static/app/views/detectors/components/details/metric/detect.spec.tsx b/static/app/views/detectors/components/details/metric/detect.spec.tsx new file mode 100644 index 00000000000000..d2c005e07b70f8 --- /dev/null +++ b/static/app/views/detectors/components/details/metric/detect.spec.tsx @@ -0,0 +1,66 @@ +import {MetricDetectorFixture} from 'sentry-fixture/detectors'; + +import {render, screen} from 'sentry-test/reactTestingLibrary'; + +import {MetricDetectorDetailsDetect} from './detect'; + +describe('MetricDetectorDetailsDetect', () => { + it('renders dataset, visualize, where, interval, and threshold', () => { + const detector = MetricDetectorFixture(); + + render(); + + // Dataset + expect(screen.getByText('Dataset:')).toBeInTheDocument(); + expect(screen.getByText('Errors')).toBeInTheDocument(); + + // Visualize (aggregate) + expect(screen.getByText('Visualize')).toBeInTheDocument(); + // Aggregate function + expect(screen.getByText('count()')).toBeInTheDocument(); + // Query + expect(screen.getByText('Where')).toBeInTheDocument(); + expect(screen.getByLabelText('is:unresolved')).toBeInTheDocument(); + + // Interval is 60s by default in fixture + expect(screen.getByText('Interval:')).toBeInTheDocument(); + expect(screen.getByText('1 minute')).toBeInTheDocument(); + + // Threshold label for static detection + expect(screen.getByText('Threshold:')).toBeInTheDocument(); + expect(screen.getByText('Static threshold')).toBeInTheDocument(); + }); + + it('renders human readable priority conditions for static detection', () => { + const detector = MetricDetectorFixture(); + + render(); + + expect(screen.getByText(/Above 8/)).toBeInTheDocument(); + expect(screen.getByText('High')).toBeInTheDocument(); + }); + + it('renders percent change description with delta window', () => { + const detector = MetricDetectorFixture({ + config: {detectionType: 'percent', comparisonDelta: 60}, + }); + + render(); + + expect(screen.getByText('Percent change')).toBeInTheDocument(); + expect(screen.getByText(/8% higher than the previous 1 minute/)).toBeInTheDocument(); + }); + + it('renders dynamic detection notice', () => { + const detector = MetricDetectorFixture({ + config: {detectionType: 'dynamic'}, + }); + + render(); + + expect(screen.getByText('Dynamic threshold')).toBeInTheDocument(); + expect( + screen.getByText('Sentry will automatically update priority.') + ).toBeInTheDocument(); + }); +}); diff --git a/static/app/views/detectors/components/details/metric/detect.tsx b/static/app/views/detectors/components/details/metric/detect.tsx index 42c2aef2a7a308..225910dc00d480 100644 --- a/static/app/views/detectors/components/details/metric/detect.tsx +++ b/static/app/views/detectors/components/details/metric/detect.tsx @@ -1,6 +1,9 @@ import {Fragment} from 'react'; import styled from '@emotion/styled'; +import {Grid} from '@sentry/scraps/layout'; + +import {GroupPriorityBadge} from 'sentry/components/badge/groupPriority'; import {Flex} from 'sentry/components/core/layout'; import {Heading, Text} from 'sentry/components/core/text'; import {Tooltip} from 'sentry/components/core/tooltip'; @@ -9,20 +12,89 @@ import { ProvidedFormattedQuery, } from 'sentry/components/searchQueryBuilder/formattedQuery'; import {Container} from 'sentry/components/workflowEngine/ui/container'; +import {IconArrow} from 'sentry/icons'; import {t} from 'sentry/locale'; -import type { - MetricDetector, - SnubaQueryDataSource, -} from 'sentry/types/workflowEngine/detectors'; +import { + DataConditionType, + DETECTOR_PRIORITY_LEVEL_TO_PRIORITY_LEVEL, + DetectorPriorityLevel, +} from 'sentry/types/workflowEngine/dataConditions'; +import type {MetricDetector} from 'sentry/types/workflowEngine/detectors'; import {getExactDuration} from 'sentry/utils/duration/getExactDuration'; import {getDatasetConfig} from 'sentry/views/detectors/datasetConfig/getDatasetConfig'; import {getDetectorDataset} from 'sentry/views/detectors/datasetConfig/getDetectorDataset'; +import {getMetricDetectorSuffix} from 'sentry/views/detectors/utils/metricDetectorSuffix'; + +function getDetectorTypeLabel(detector: MetricDetector) { + if (detector.config.detectionType === 'dynamic') { + return t('Dynamic threshold'); + } + if (detector.config.detectionType === 'percent') { + return t('Percent change'); + } + return t('Static threshold'); +} + +function DetectorPriorities({detector}: {detector: MetricDetector}) { + if (detector.config.detectionType === 'dynamic') { + return
{t('Sentry will automatically update priority.')}
; + } + + const conditions = detector.conditionGroup?.conditions || []; + + // Filter out OK conditions and sort by priority level + const priorityConditions = conditions + .filter(condition => condition.conditionResult !== DetectorPriorityLevel.OK) + .sort((a, b) => (a.conditionResult || 0) - (b.conditionResult || 0)); + + if (priorityConditions.length === 0) { + return null; + } -interface MetricDetectorDetectProps { - detector: MetricDetector; + const getConditionLabel = (condition: (typeof priorityConditions)[0]) => { + const comparisonValue = + typeof condition.comparison === 'number' ? String(condition.comparison) : ''; + const unit = getMetricDetectorSuffix( + detector.config.detectionType, + detector.dataSources[0].queryObj?.snubaQuery?.aggregate || 'count()' + ); + + if (detector.config.detectionType === 'percent') { + const direction = + condition.type === DataConditionType.GREATER ? t('higher') : t('lower'); + const delta = detector.config.comparisonDelta; + const ago = t('than the previous %s', getExactDuration(delta)); + return `${comparisonValue}${unit} ${direction} ${ago}`; + } + + const typeLabel = + condition.type === DataConditionType.GREATER ? t('Above') : t('Below'); + return `${typeLabel} ${comparisonValue}${unit}`; + }; + + return ( + + {priorityConditions.map((condition, index) => ( + +
{getConditionLabel(condition)}
+ + +
+ ))} +
+ ); } -function SnubaQueryDetails({dataSource}: {dataSource: SnubaQueryDataSource}) { +export function MetricDetectorDetailsDetect({detector}: {detector: MetricDetector}) { + const dataSource = detector.dataSources?.[0]; + if (!dataSource.queryObj) { return {t('Query not found.')}; } @@ -75,16 +147,16 @@ function SnubaQueryDetails({dataSource}: {dataSource: SnubaQueryDataSource}) { {t('Interval:')} {getExactDuration(dataSource.queryObj.snubaQuery.timeWindow)} + + {t('Threshold:')} + {getDetectorTypeLabel(detector)} + + ); } -export function MetricDetectorDetailsDetect({detector}: MetricDetectorDetectProps) { - const dataSource = detector.dataSources?.[0]; - return ; -} - const Query = styled('dl')` display: grid; grid-template-columns: auto minmax(0, 1fr); diff --git a/static/app/views/detectors/components/details/metric/sidebar.tsx b/static/app/views/detectors/components/details/metric/sidebar.tsx index 7e36b783a5c765..d6e9b7f25a83f4 100644 --- a/static/app/views/detectors/components/details/metric/sidebar.tsx +++ b/static/app/views/detectors/components/details/metric/sidebar.tsx @@ -1,18 +1,10 @@ import {Fragment} from 'react'; import {Link} from 'react-router-dom'; -import styled from '@emotion/styled'; -import {GroupPriorityBadge} from 'sentry/components/badge/groupPriority'; import {Tooltip} from 'sentry/components/core/tooltip'; import Section from 'sentry/components/workflowEngine/ui/section'; -import {IconArrow} from 'sentry/icons'; import {t} from 'sentry/locale'; -import {space} from 'sentry/styles/space'; -import { - DataConditionType, - DETECTOR_PRIORITY_LEVEL_TO_PRIORITY_LEVEL, - DetectorPriorityLevel, -} from 'sentry/types/workflowEngine/dataConditions'; +import {DetectorPriorityLevel} from 'sentry/types/workflowEngine/dataConditions'; import type {MetricDetector} from 'sentry/types/workflowEngine/detectors'; import normalizeUrl from 'sentry/utils/url/normalizeUrl'; import useOrganization from 'sentry/utils/useOrganization'; @@ -28,60 +20,6 @@ interface DetectorDetailsSidebarProps { detector: MetricDetector; } -function DetectorPriorities({detector}: {detector: MetricDetector}) { - const detectionType = detector.config?.detectionType || 'static'; - - // For dynamic detectors, show the automatic priority message - if (detectionType === 'dynamic') { - return
{t('Sentry will automatically update priority.')}
; - } - - const conditions = detector.conditionGroup?.conditions || []; - - // Filter out OK conditions and sort by priority level - const priorityConditions = conditions - .filter(condition => condition.conditionResult !== DetectorPriorityLevel.OK) - .sort((a, b) => (a.conditionResult || 0) - (b.conditionResult || 0)); - - if (priorityConditions.length === 0) { - return null; - } - - const getConditionLabel = (condition: (typeof priorityConditions)[0]) => { - const typeLabel = - condition.type === DataConditionType.GREATER ? t('Above') : t('Below'); - - // For static/percent detectors, comparison should be a simple number - const comparisonValue = - typeof condition.comparison === 'number' ? String(condition.comparison) : ''; - const thresholdSuffix = getMetricDetectorSuffix( - detector.config?.detectionType || 'static', - detector.dataSources[0].queryObj?.snubaQuery?.aggregate || 'count()' - ); - - return `${typeLabel} ${comparisonValue}${thresholdSuffix}`; - }; - - return ( - - {priorityConditions.map((condition, index) => ( - - {getConditionLabel(condition)} - - - - ))} - - ); -} - function DetectorResolve({detector}: {detector: MetricDetector}) { const detectionType = detector.config?.detectionType || 'static'; const conditions = detector.conditionGroup?.conditions || []; @@ -146,9 +84,6 @@ export function MetricDetectorDetailsSidebar({detector}: DetectorDetailsSidebarP -
- -
@@ -163,22 +98,3 @@ export function MetricDetectorDetailsSidebar({detector}: DetectorDetailsSidebarP ); } - -const PrioritiesList = styled('div')` - display: grid; - grid-template-columns: auto auto auto; - align-items: center; - width: fit-content; - gap: ${space(0.5)} ${space(1)}; - - p { - margin: 0; - width: fit-content; - } -`; - -const PriorityCondition = styled('div')` - justify-self: flex-end; - font-size: ${p => p.theme.fontSize.md}; - color: ${p => p.theme.textColor}; -`; diff --git a/tests/js/fixtures/detectors.ts b/tests/js/fixtures/detectors.ts index 5f1d61f71cdf06..803cae14086ba9 100644 --- a/tests/js/fixtures/detectors.ts +++ b/tests/js/fixtures/detectors.ts @@ -155,7 +155,7 @@ export function SnubaQueryDataSourceFixture( aggregate: 'count()', dataset: Dataset.ERRORS, id: '', - query: '', + query: 'is:unresolved', timeWindow: 60, eventTypes: [EventTypes.ERROR], }, From 475c5d425811bc80b5baf86adb1b9b5d3315582f Mon Sep 17 00:00:00 2001 From: Malachi Willey Date: Mon, 24 Nov 2025 13:44:31 -0800 Subject: [PATCH 2/4] Add error boundary --- .../app/views/detectors/components/details/metric/detect.tsx | 2 +- .../views/detectors/components/details/metric/sidebar.tsx | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/static/app/views/detectors/components/details/metric/detect.tsx b/static/app/views/detectors/components/details/metric/detect.tsx index 225910dc00d480..8cfeab22db6e6c 100644 --- a/static/app/views/detectors/components/details/metric/detect.tsx +++ b/static/app/views/detectors/components/details/metric/detect.tsx @@ -93,7 +93,7 @@ function DetectorPriorities({detector}: {detector: MetricDetector}) { } export function MetricDetectorDetailsDetect({detector}: {detector: MetricDetector}) { - const dataSource = detector.dataSources?.[0]; + const dataSource = detector.dataSources[0]; if (!dataSource.queryObj) { return {t('Query not found.')}; diff --git a/static/app/views/detectors/components/details/metric/sidebar.tsx b/static/app/views/detectors/components/details/metric/sidebar.tsx index d6e9b7f25a83f4..cd0849f8b5dd71 100644 --- a/static/app/views/detectors/components/details/metric/sidebar.tsx +++ b/static/app/views/detectors/components/details/metric/sidebar.tsx @@ -2,6 +2,7 @@ import {Fragment} from 'react'; import {Link} from 'react-router-dom'; import {Tooltip} from 'sentry/components/core/tooltip'; +import ErrorBoundary from 'sentry/components/errorBoundary'; import Section from 'sentry/components/workflowEngine/ui/section'; import {t} from 'sentry/locale'; import {DetectorPriorityLevel} from 'sentry/types/workflowEngine/dataConditions'; @@ -81,7 +82,9 @@ export function MetricDetectorDetailsSidebar({detector}: DetectorDetailsSidebarP return (
- + + +
From eb71854dbe111dbde9c9ca657a8964d618a53501 Mon Sep 17 00:00:00 2001 From: Malachi Willey Date: Tue, 25 Nov 2025 10:17:40 -0800 Subject: [PATCH 3/4] Update design to include resolved --- .../components/details/metric/detect.spec.tsx | 10 +- .../components/details/metric/detect.tsx | 146 ++++++++++++------ .../components/details/metric/sidebar.tsx | 41 ----- .../components/forms/metric/metric.tsx | 9 +- .../detectors/components/priorityDot.tsx | 22 +++ 5 files changed, 135 insertions(+), 93 deletions(-) create mode 100644 static/app/views/detectors/components/priorityDot.tsx diff --git a/static/app/views/detectors/components/details/metric/detect.spec.tsx b/static/app/views/detectors/components/details/metric/detect.spec.tsx index d2c005e07b70f8..23675934ba3adf 100644 --- a/static/app/views/detectors/components/details/metric/detect.spec.tsx +++ b/static/app/views/detectors/components/details/metric/detect.spec.tsx @@ -36,8 +36,11 @@ describe('MetricDetectorDetailsDetect', () => { render(); - expect(screen.getByText(/Above 8/)).toBeInTheDocument(); expect(screen.getByText('High')).toBeInTheDocument(); + expect(screen.getByText(/Above 8/)).toBeInTheDocument(); + + expect(screen.getByText('Resolved')).toBeInTheDocument(); + expect(screen.getByText(/Below or equal to 8/)).toBeInTheDocument(); }); it('renders percent change description with delta window', () => { @@ -49,6 +52,11 @@ describe('MetricDetectorDetailsDetect', () => { expect(screen.getByText('Percent change')).toBeInTheDocument(); expect(screen.getByText(/8% higher than the previous 1 minute/)).toBeInTheDocument(); + + expect(screen.getByText('Resolved')).toBeInTheDocument(); + expect( + screen.getByText(/Less than 8% lower than the previous 1 minute/) + ).toBeInTheDocument(); }); it('renders dynamic detection notice', () => { diff --git a/static/app/views/detectors/components/details/metric/detect.tsx b/static/app/views/detectors/components/details/metric/detect.tsx index 8cfeab22db6e6c..b8c54d9ea63f50 100644 --- a/static/app/views/detectors/components/details/metric/detect.tsx +++ b/static/app/views/detectors/components/details/metric/detect.tsx @@ -3,7 +3,6 @@ import styled from '@emotion/styled'; import {Grid} from '@sentry/scraps/layout'; -import {GroupPriorityBadge} from 'sentry/components/badge/groupPriority'; import {Flex} from 'sentry/components/core/layout'; import {Heading, Text} from 'sentry/components/core/text'; import {Tooltip} from 'sentry/components/core/tooltip'; @@ -12,15 +11,18 @@ import { ProvidedFormattedQuery, } from 'sentry/components/searchQueryBuilder/formattedQuery'; import {Container} from 'sentry/components/workflowEngine/ui/container'; -import {IconArrow} from 'sentry/icons'; import {t} from 'sentry/locale'; import { DataConditionType, DETECTOR_PRIORITY_LEVEL_TO_PRIORITY_LEVEL, DetectorPriorityLevel, } from 'sentry/types/workflowEngine/dataConditions'; -import type {MetricDetector} from 'sentry/types/workflowEngine/detectors'; +import type { + MetricCondition, + MetricDetector, +} from 'sentry/types/workflowEngine/detectors'; import {getExactDuration} from 'sentry/utils/duration/getExactDuration'; +import {PriorityDot} from 'sentry/views/detectors/components/priorityDot'; import {getDatasetConfig} from 'sentry/views/detectors/datasetConfig/getDatasetConfig'; import {getDetectorDataset} from 'sentry/views/detectors/datasetConfig/getDetectorDataset'; import {getMetricDetectorSuffix} from 'sentry/views/detectors/utils/metricDetectorSuffix'; @@ -35,57 +37,115 @@ function getDetectorTypeLabel(detector: MetricDetector) { return t('Static threshold'); } -function DetectorPriorities({detector}: {detector: MetricDetector}) { - if (detector.config.detectionType === 'dynamic') { - return
{t('Sentry will automatically update priority.')}
; +function getConditionLabel({condition}: {condition: MetricCondition}) { + switch (condition.conditionResult) { + case DetectorPriorityLevel.OK: + return t('Resolved'); + case DetectorPriorityLevel.LOW: + return t('Low'); + case DetectorPriorityLevel.MEDIUM: + return t('Medium'); + case DetectorPriorityLevel.HIGH: + return t('High'); + default: + return t('Unknown'); } +} - const conditions = detector.conditionGroup?.conditions || []; +function makeDirectionText(condition: MetricCondition) { + switch (condition.type) { + case DataConditionType.GREATER: + return t('Above'); + case DataConditionType.LESS: + return t('Below'); + case DataConditionType.EQUAL: + return t('Equal to'); + case DataConditionType.NOT_EQUAL: + return t('Not equal to'); + case DataConditionType.GREATER_OR_EQUAL: + return t('Above or equal to'); + case DataConditionType.LESS_OR_EQUAL: + return t('Below or equal to'); + default: + return t('Unknown'); + } +} - // Filter out OK conditions and sort by priority level - const priorityConditions = conditions - .filter(condition => condition.conditionResult !== DetectorPriorityLevel.OK) - .sort((a, b) => (a.conditionResult || 0) - (b.conditionResult || 0)); +function getConditionDescription({ + aggregate, + config, + condition, +}: { + aggregate: string; + condition: MetricCondition; + config: MetricDetector['config']; +}) { + const comparisonValue = + typeof condition.comparison === 'number' ? String(condition.comparison) : ''; + const unit = getMetricDetectorSuffix(config.detectionType, aggregate); + + if (config.detectionType === 'percent') { + const direction = + condition.type === DataConditionType.GREATER ? t('higher') : t('lower'); + const delta = config.comparisonDelta; + const timeRange = getExactDuration(delta); + + if (condition.conditionResult === DetectorPriorityLevel.OK) { + return t( + `Less than %(comparisonValue)s%(unit)s %(direction)s than the previous %(timeRange)s`, + { + comparisonValue, + unit, + direction, + timeRange, + } + ); + } - if (priorityConditions.length === 0) { - return null; + return t( + `%(comparisonValue)s%(unit)s %(direction)s than the previous %(timeRange)s`, + { + comparisonValue, + unit, + direction, + timeRange, + } + ); } - const getConditionLabel = (condition: (typeof priorityConditions)[0]) => { - const comparisonValue = - typeof condition.comparison === 'number' ? String(condition.comparison) : ''; - const unit = getMetricDetectorSuffix( - detector.config.detectionType, - detector.dataSources[0].queryObj?.snubaQuery?.aggregate || 'count()' - ); + return `${makeDirectionText(condition)} ${comparisonValue}${unit}`; +} - if (detector.config.detectionType === 'percent') { - const direction = - condition.type === DataConditionType.GREATER ? t('higher') : t('lower'); - const delta = detector.config.comparisonDelta; - const ago = t('than the previous %s', getExactDuration(delta)); - return `${comparisonValue}${unit} ${direction} ${ago}`; - } +function DetectorPriorities({detector}: {detector: MetricDetector}) { + if (detector.config.detectionType === 'dynamic') { + return
{t('Sentry will automatically update priority.')}
; + } - const typeLabel = - condition.type === DataConditionType.GREATER ? t('Above') : t('Below'); - return `${typeLabel} ${comparisonValue}${unit}`; - }; + const conditions = detector.conditionGroup?.conditions || []; return ( - - {priorityConditions.map((condition, index) => ( + + {conditions.map((condition, index) => ( -
{getConditionLabel(condition)}
- - + + + {getConditionLabel({condition})} + + + {getConditionDescription({ + aggregate: detector.dataSources[0].queryObj.snubaQuery.aggregate, + condition, + config: detector.config, + })} +
))}
diff --git a/static/app/views/detectors/components/details/metric/sidebar.tsx b/static/app/views/detectors/components/details/metric/sidebar.tsx index cd0849f8b5dd71..0902e83268ebf8 100644 --- a/static/app/views/detectors/components/details/metric/sidebar.tsx +++ b/static/app/views/detectors/components/details/metric/sidebar.tsx @@ -5,7 +5,6 @@ import {Tooltip} from 'sentry/components/core/tooltip'; import ErrorBoundary from 'sentry/components/errorBoundary'; import Section from 'sentry/components/workflowEngine/ui/section'; import {t} from 'sentry/locale'; -import {DetectorPriorityLevel} from 'sentry/types/workflowEngine/dataConditions'; import type {MetricDetector} from 'sentry/types/workflowEngine/detectors'; import normalizeUrl from 'sentry/utils/url/normalizeUrl'; import useOrganization from 'sentry/utils/useOrganization'; @@ -14,48 +13,11 @@ import {DetectorDetailsAssignee} from 'sentry/views/detectors/components/details import {DetectorDetailsDescription} from 'sentry/views/detectors/components/details/common/description'; import {DetectorExtraDetails} from 'sentry/views/detectors/components/details/common/extraDetails'; import {MetricDetectorDetailsDetect} from 'sentry/views/detectors/components/details/metric/detect'; -import {getResolutionDescription} from 'sentry/views/detectors/utils/getDetectorResolutionDescription'; -import {getMetricDetectorSuffix} from 'sentry/views/detectors/utils/metricDetectorSuffix'; interface DetectorDetailsSidebarProps { detector: MetricDetector; } -function DetectorResolve({detector}: {detector: MetricDetector}) { - const detectionType = detector.config?.detectionType || 'static'; - const conditions = detector.conditionGroup?.conditions || []; - - // Get the main condition (first non-OK condition) - const mainCondition = conditions.find( - condition => condition.conditionResult !== DetectorPriorityLevel.OK - ); - - // Get the OK condition (resolution condition) - const okCondition = conditions.find( - condition => condition.conditionResult === DetectorPriorityLevel.OK - ); - - const thresholdSuffix = getMetricDetectorSuffix( - detector.config?.detectionType || 'static', - detector.dataSources[0].queryObj?.snubaQuery?.aggregate || 'count()' - ); - - const description = getResolutionDescription({ - detectionType, - conditionType: mainCondition?.type, - highThreshold: - typeof mainCondition?.comparison === 'number' - ? mainCondition.comparison - : undefined, - resolutionThreshold: - typeof okCondition?.comparison === 'number' ? okCondition.comparison : undefined, - comparisonDelta: (detector.config as any)?.comparison_delta, - thresholdSuffix, - }); - - return
{description}
; -} - function GoToMetricAlert({detector}: {detector: MetricDetector}) { const organization = useOrganization(); const user = useUser(); @@ -87,9 +49,6 @@ export function MetricDetectorDetailsSidebar({detector}: DetectorDetailsSidebarP
-
- -
diff --git a/static/app/views/detectors/components/forms/metric/metric.tsx b/static/app/views/detectors/components/forms/metric/metric.tsx index d2d28c5c094c63..9bc22122dfa3fb 100644 --- a/static/app/views/detectors/components/forms/metric/metric.tsx +++ b/static/app/views/detectors/components/forms/metric/metric.tsx @@ -49,6 +49,7 @@ import {useIntervalChoices} from 'sentry/views/detectors/components/forms/metric import {Visualize} from 'sentry/views/detectors/components/forms/metric/visualize'; import {NewDetectorLayout} from 'sentry/views/detectors/components/forms/newDetectorLayout'; import {SectionLabel} from 'sentry/views/detectors/components/forms/sectionLabel'; +import {PriorityDot} from 'sentry/views/detectors/components/priorityDot'; import {getDatasetConfig} from 'sentry/views/detectors/datasetConfig/getDatasetConfig'; import {DetectorDataset} from 'sentry/views/detectors/datasetConfig/types'; import {getMetricDetectorSuffix} from 'sentry/views/detectors/utils/metricDetectorSuffix'; @@ -682,14 +683,6 @@ const PriorityRowContainer = styled('div')` gap: ${space(1)}; `; -const PriorityDot = styled('div')<{priority: 'high' | 'medium'}>` - width: 8px; - height: 8px; - border-radius: 50%; - background-color: ${p => (p.priority === 'high' ? p.theme.red300 : p.theme.yellow400)}; - flex-shrink: 0; -`; - const PriorityLabel = styled('span')` min-width: 120px; font-weight: ${p => p.theme.fontWeight.normal}; diff --git a/static/app/views/detectors/components/priorityDot.tsx b/static/app/views/detectors/components/priorityDot.tsx new file mode 100644 index 00000000000000..7cf860edcfffd3 --- /dev/null +++ b/static/app/views/detectors/components/priorityDot.tsx @@ -0,0 +1,22 @@ +import styled from '@emotion/styled'; + +import {PriorityLevel} from 'sentry/types/group'; + +export const PriorityDot = styled('div')<{priority: PriorityLevel | 'resolved'}>` + width: 8px; + height: 8px; + border-radius: 50%; + background-color: ${p => { + switch (p.priority) { + case PriorityLevel.HIGH: + return p.theme.red300; + case PriorityLevel.MEDIUM: + return p.theme.yellow400; + case 'resolved': + return p.theme.green300; + default: + return p.theme.gray300; + } + }}; + flex-shrink: 0; +`; From 537b56ed6a2d6ee4533f6a29ad26694c30b88fb1 Mon Sep 17 00:00:00 2001 From: Malachi Willey Date: Tue, 25 Nov 2025 10:39:05 -0800 Subject: [PATCH 4/4] Fix type error --- .../detectors/components/forms/metric/metric.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/static/app/views/detectors/components/forms/metric/metric.tsx b/static/app/views/detectors/components/forms/metric/metric.tsx index 9bc22122dfa3fb..88b2873799cb07 100644 --- a/static/app/views/detectors/components/forms/metric/metric.tsx +++ b/static/app/views/detectors/components/forms/metric/metric.tsx @@ -16,6 +16,7 @@ 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 {PriorityLevel} from 'sentry/types/group'; import {DataConditionType} from 'sentry/types/workflowEngine/dataConditions'; import type {Detector, MetricDetectorConfig} from 'sentry/types/workflowEngine/detectors'; import {generateFieldAsString} from 'sentry/utils/discover/fields'; @@ -197,7 +198,7 @@ function validateMediumThreshold({ interface PriorityRowProps { aggregate: string; detectionType: 'static' | 'percent'; - priority: 'high' | 'medium'; + priority: PriorityLevel; showComparisonAgo?: boolean; } @@ -480,12 +481,12 @@ function DetectSection() { @@ -502,13 +503,13 @@ function DetectSection() {