From 46d4c0f569ec4ebf387bc2948dceabb9dbf84af2 Mon Sep 17 00:00:00 2001 From: Evan Purkhiser Date: Tue, 18 Nov 2025 16:09:49 -0500 Subject: [PATCH] feat(detectors): Add detector type to breadcrumbs --- .../detectors/components/details/common/header.tsx | 10 +++++++++- .../detectors/components/forms/common/breadcrumbs.tsx | 9 +++++++++ static/app/views/detectors/detail.spec.tsx | 2 +- static/app/views/detectors/pathnames.tsx | 11 +++++++++++ .../app/views/detectors/utils/detectorTypeConfig.tsx | 9 +++++++++ 5 files changed, 39 insertions(+), 2 deletions(-) diff --git a/static/app/views/detectors/components/details/common/header.tsx b/static/app/views/detectors/components/details/common/header.tsx index 8f59d4a16c5f41..7978c11655ed82 100644 --- a/static/app/views/detectors/components/details/common/header.tsx +++ b/static/app/views/detectors/components/details/common/header.tsx @@ -9,7 +9,11 @@ import { EditDetectorAction, } from 'sentry/views/detectors/components/details/common/actions'; import {MonitorFeedbackButton} from 'sentry/views/detectors/components/monitorFeedbackButton'; -import {makeMonitorBasePathname} from 'sentry/views/detectors/pathnames'; +import { + makeMonitorBasePathname, + makeMonitorTypePathname, +} from 'sentry/views/detectors/pathnames'; +import {getDetectorTypeLabel} from 'sentry/views/detectors/utils/detectorTypeConfig'; type DetectorDetailsHeaderProps = { detector: Detector; @@ -28,6 +32,10 @@ export function DetectorDetailsHeader({detector, project}: DetectorDetailsHeader label: t('Monitors'), to: makeMonitorBasePathname(organization.slug), }, + { + label: getDetectorTypeLabel(detector.type), + to: makeMonitorTypePathname(organization.slug, detector.type), + }, {label: detector.name}, ]} /> diff --git a/static/app/views/detectors/components/forms/common/breadcrumbs.tsx b/static/app/views/detectors/components/forms/common/breadcrumbs.tsx index de35ce71ae4dc1..65156bbd233406 100644 --- a/static/app/views/detectors/components/forms/common/breadcrumbs.tsx +++ b/static/app/views/detectors/components/forms/common/breadcrumbs.tsx @@ -5,6 +5,7 @@ import useOrganization from 'sentry/utils/useOrganization'; import { makeMonitorBasePathname, makeMonitorDetailsPathname, + makeMonitorTypePathname, } from 'sentry/views/detectors/pathnames'; import {getDetectorTypeLabel} from 'sentry/views/detectors/utils/detectorTypeConfig'; @@ -17,6 +18,10 @@ export function NewDetectorBreadcrumbs({detectorType}: {detectorType: DetectorTy label: t('Monitors'), to: makeMonitorBasePathname(organization.slug), }, + { + label: getDetectorTypeLabel(detectorType), + to: makeMonitorTypePathname(organization.slug, detectorType), + }, { label: t('New %s Monitor', getDetectorTypeLabel(detectorType)), }, @@ -34,6 +39,10 @@ export function EditDetectorBreadcrumbs({detector}: {detector: Detector}) { label: t('Monitors'), to: makeMonitorBasePathname(organization.slug), }, + { + label: getDetectorTypeLabel(detector.type), + to: makeMonitorTypePathname(organization.slug, detector.type), + }, { label: detector.name, to: makeMonitorDetailsPathname(organization.slug, detector.id), diff --git a/static/app/views/detectors/detail.spec.tsx b/static/app/views/detectors/detail.spec.tsx index 73fb4d756730f0..e44f5dfa48347f 100644 --- a/static/app/views/detectors/detail.spec.tsx +++ b/static/app/views/detectors/detail.spec.tsx @@ -307,7 +307,7 @@ describe('DetectorDetails', () => { expect(await screen.findByText('Recent Check-Ins')).toBeInTheDocument(); // Verify check-in data is displayed - expect(screen.getAllByText('Uptime')).toHaveLength(3); // section heading + timeline legend + check-in row + expect(screen.getAllByText('Uptime')).toHaveLength(4); // breadcrumb + section heading + timeline legend + check-in row expect(screen.getByText('200')).toBeInTheDocument(); expect(screen.getByText('US East')).toBeInTheDocument(); expect(screen.getAllByText('Failure')).toHaveLength(2); // timeline legend + check-in row diff --git a/static/app/views/detectors/pathnames.tsx b/static/app/views/detectors/pathnames.tsx index 459d6076b82c1a..89f000bf8f8251 100644 --- a/static/app/views/detectors/pathnames.tsx +++ b/static/app/views/detectors/pathnames.tsx @@ -1,9 +1,20 @@ +import type {DetectorType} from 'sentry/types/workflowEngine/detectors'; import normalizeUrl from 'sentry/utils/url/normalizeUrl'; +import {getDetectorTypePath} from './utils/detectorTypeConfig'; + export const makeMonitorBasePathname = (orgSlug: string) => { return normalizeUrl(`/organizations/${orgSlug}/monitors/`); }; +export const makeMonitorTypePathname = (orgSlug: string, detectorType: DetectorType) => { + const typePath = getDetectorTypePath(detectorType); + if (!typePath) { + return makeMonitorBasePathname(orgSlug); + } + return normalizeUrl(`${makeMonitorBasePathname(orgSlug)}${typePath}/`); +}; + export const makeMonitorDetailsPathname = (orgSlug: string, monitorId: string) => { return normalizeUrl(`${makeMonitorBasePathname(orgSlug)}${monitorId}/`); }; diff --git a/static/app/views/detectors/utils/detectorTypeConfig.tsx b/static/app/views/detectors/utils/detectorTypeConfig.tsx index 8b4de21732fffa..623da04164b445 100644 --- a/static/app/views/detectors/utils/detectorTypeConfig.tsx +++ b/static/app/views/detectors/utils/detectorTypeConfig.tsx @@ -5,25 +5,30 @@ import {UptimeMonitorMode} from 'sentry/views/alerts/rules/uptime/types'; type DetectorTypeConfig = { label: string; userCreateable: boolean; + path?: string; systemCreatedNotice?: (detector: Detector) => undefined | string; }; const DETECTOR_TYPE_CONFIG: Record = { error: { label: t('Error'), + path: 'errors', userCreateable: false, systemCreatedNotice: () => t('This monitor is managed by Sentry'), }, metric_issue: { label: t('Metric'), + path: 'metrics', userCreateable: true, }, monitor_check_in_failure: { label: t('Cron'), + path: 'crons', userCreateable: true, }, uptime_domain_failure: { label: t('Uptime'), + path: 'uptime', userCreateable: true, systemCreatedNotice: uptimeDetector => uptimeDetector.type === 'uptime_domain_failure' && @@ -53,3 +58,7 @@ export function getDetectorSystemCreatedNotice(detector: Detector) { export function getDetectorTypeLabel(detectorType: DetectorType) { return DETECTOR_TYPE_CONFIG[detectorType]?.label ?? 'Unknown'; } + +export function getDetectorTypePath(detectorType: DetectorType) { + return DETECTOR_TYPE_CONFIG[detectorType]?.path; +}