From 09b3fa399693cdc324018a3f7e6d6c72ab95c37c Mon Sep 17 00:00:00 2001 From: Evan Purkhiser Date: Tue, 18 Nov 2025 11:19:43 -0500 Subject: [PATCH] ref(crons): Show processing errors on cron detectors list Add processing errors alert to the cron detectors list page to match the existing behavior on the insights crons overview page. Created a shared GlobalMonitorProcessingErrors component to avoid code duplication. Fixes [NEW-633](https://linear.app/getsentry/issue/NEW-633/general-cron-errors-should-be-shown-on-the-cron-detectors-specific) --- static/app/views/detectors/list/cron.spec.tsx | 6 +++ static/app/views/detectors/list/cron.tsx | 6 +++ .../globalMonitorProcessingErrors.tsx | 52 +++++++++++++++++++ .../views/insights/crons/views/overview.tsx | 44 ++-------------- 4 files changed, 69 insertions(+), 39 deletions(-) create mode 100644 static/app/views/insights/crons/components/processingErrors/globalMonitorProcessingErrors.tsx diff --git a/static/app/views/detectors/list/cron.spec.tsx b/static/app/views/detectors/list/cron.spec.tsx index e1b8f2dbd15930..8ed7c30e889793 100644 --- a/static/app/views/detectors/list/cron.spec.tsx +++ b/static/app/views/detectors/list/cron.spec.tsx @@ -33,6 +33,12 @@ describe('CronDetectorsList', () => { body: UserFixture(), }); + // Mock processing errors endpoint (no errors by default) + MockApiClient.addMockResponse({ + url: '/organizations/org-slug/processing-errors/', + body: [], + }); + // Ensure a project is selected for queries PageFiltersStore.onInitializeUrlState(PageFiltersFixture({projects: [1]})); diff --git a/static/app/views/detectors/list/cron.tsx b/static/app/views/detectors/list/cron.tsx index 74c2ed986b48ad..a282a72e17c9c2 100644 --- a/static/app/views/detectors/list/cron.tsx +++ b/static/app/views/detectors/list/cron.tsx @@ -15,6 +15,7 @@ import {fadeIn} from 'sentry/styles/animations'; import type {CronDetector, Detector} from 'sentry/types/workflowEngine/detectors'; import {useDebouncedValue} from 'sentry/utils/useDebouncedValue'; import {useDimensions} from 'sentry/utils/useDimensions'; +import usePageFilters from 'sentry/utils/usePageFilters'; import {HeaderCell} from 'sentry/views/detectors/components/detectorListTable'; import {DetectorListActions} from 'sentry/views/detectors/list/common/detectorListActions'; import {DetectorListContent} from 'sentry/views/detectors/list/common/detectorListContent'; @@ -27,6 +28,7 @@ import { } from 'sentry/views/detectors/monitorViewContext'; import {CronsLandingPanel} from 'sentry/views/insights/crons/components/cronsLandingPanel'; import MonitorEnvironmentLabel from 'sentry/views/insights/crons/components/overviewTimeline/monitorEnvironmentLabel'; +import {GlobalMonitorProcessingErrors} from 'sentry/views/insights/crons/components/processingErrors/globalMonitorProcessingErrors'; import { checkInStatusPrecedent, statusToText, @@ -118,10 +120,13 @@ const DESCRIPTION = t( const DOCS_URL = 'https://docs.sentry.io/product/crons/'; export default function CronDetectorsList() { + const {selection} = usePageFilters(); const detectorListQuery = useDetectorListQuery({ detectorFilter: 'monitor_check_in_failure', }); + const selectedProjects = selection.projects.map(String); + const contextValue = useMemo(() => { return { additionalColumns: ADDITIONAL_COLUMNS, @@ -156,6 +161,7 @@ export default function CronDetectorsList() { docsUrl={DOCS_URL} > + } diff --git a/static/app/views/insights/crons/components/processingErrors/globalMonitorProcessingErrors.tsx b/static/app/views/insights/crons/components/processingErrors/globalMonitorProcessingErrors.tsx new file mode 100644 index 00000000000000..27533f67df3195 --- /dev/null +++ b/static/app/views/insights/crons/components/processingErrors/globalMonitorProcessingErrors.tsx @@ -0,0 +1,52 @@ +import {deleteProjectProcessingErrorByType} from 'sentry/actionCreators/monitors'; +import {t} from 'sentry/locale'; +import {useApiQuery} from 'sentry/utils/queryClient'; +import useApi from 'sentry/utils/useApi'; +import useOrganization from 'sentry/utils/useOrganization'; +import {MonitorProcessingErrors} from 'sentry/views/insights/crons/components/processingErrors/monitorProcessingErrors'; +import {makeMonitorListErrorsQueryKey} from 'sentry/views/insights/crons/components/processingErrors/utils'; +import type { + CheckinProcessingError, + ProcessingErrorType, +} from 'sentry/views/insights/crons/types'; + +interface GlobalMonitorProcessingErrorsProps { + project?: string[]; +} + +export function GlobalMonitorProcessingErrors({ + project, +}: GlobalMonitorProcessingErrorsProps) { + const api = useApi(); + const organization = useOrganization(); + + const processingErrorQueryKey = makeMonitorListErrorsQueryKey(organization, project); + const {data: processingErrors, refetch: refetchErrors} = useApiQuery< + CheckinProcessingError[] + >(processingErrorQueryKey, { + staleTime: 0, + }); + + async function handleDismissError(errorType: ProcessingErrorType, projectId: string) { + await deleteProjectProcessingErrorByType( + api, + organization.slug, + projectId, + errorType + ); + await refetchErrors(); + } + + if (!processingErrors?.length) { + return null; + } + + return ( + + {t('Errors were encountered while ingesting check-ins for the selected projects')} + + ); +} diff --git a/static/app/views/insights/crons/views/overview.tsx b/static/app/views/insights/crons/views/overview.tsx index 53b0c27d8795d9..8036df7ee92f48 100644 --- a/static/app/views/insights/crons/views/overview.tsx +++ b/static/app/views/insights/crons/views/overview.tsx @@ -5,7 +5,6 @@ import * as qs from 'query-string'; import {Alert} from '@sentry/scraps/alert'; import {openBulkEditMonitorsModal} from 'sentry/actionCreators/modal'; -import {deleteProjectProcessingErrorByType} from 'sentry/actionCreators/monitors'; import {Button} from 'sentry/components/core/button'; import {ButtonBar} from 'sentry/components/core/button/buttonBar'; import FeedbackWidgetButton from 'sentry/components/feedback/widget/feedbackWidgetButton'; @@ -30,7 +29,6 @@ import {useApiQuery} from 'sentry/utils/queryClient'; import {decodeList, decodeScalar} from 'sentry/utils/queryString'; import useRouteAnalyticsEventNames from 'sentry/utils/routeAnalytics/useRouteAnalyticsEventNames'; import useRouteAnalyticsParams from 'sentry/utils/routeAnalytics/useRouteAnalyticsParams'; -import useApi from 'sentry/utils/useApi'; import {useLocation} from 'sentry/utils/useLocation'; import {useNavigate} from 'sentry/utils/useNavigate'; import useOrganization from 'sentry/utils/useOrganization'; @@ -42,14 +40,9 @@ import { import {NewMonitorButton} from 'sentry/views/insights/crons/components/newMonitorButton'; import {OverviewTimeline} from 'sentry/views/insights/crons/components/overviewTimeline'; import {OwnerFilter} from 'sentry/views/insights/crons/components/ownerFilter'; -import {MonitorProcessingErrors} from 'sentry/views/insights/crons/components/processingErrors/monitorProcessingErrors'; -import {makeMonitorListErrorsQueryKey} from 'sentry/views/insights/crons/components/processingErrors/utils'; +import {GlobalMonitorProcessingErrors} from 'sentry/views/insights/crons/components/processingErrors/globalMonitorProcessingErrors'; import {MODULE_DESCRIPTION, MODULE_DOC_LINK} from 'sentry/views/insights/crons/settings'; -import type { - CheckinProcessingError, - Monitor, - ProcessingErrorType, -} from 'sentry/views/insights/crons/types'; +import type {Monitor} from 'sentry/views/insights/crons/types'; import {makeMonitorListQueryKey} from 'sentry/views/insights/crons/utils'; const CronsListPageHeader = HookOrDefault({ @@ -57,7 +50,6 @@ const CronsListPageHeader = HookOrDefault({ }); function CronsOverview() { - const api = useApi(); const organization = useOrganization(); const navigate = useNavigate(); const location = useLocation(); @@ -76,13 +68,6 @@ function CronsOverview() { staleTime: 0, }); - const processingErrorQueryKey = makeMonitorListErrorsQueryKey(organization, project); - const {data: processingErrors, refetch: refetchErrors} = useApiQuery< - CheckinProcessingError[] - >(processingErrorQueryKey, { - staleTime: 0, - }); - useRouteAnalyticsEventNames('monitors.page_viewed', 'Monitors: Page Viewed'); useRouteAnalyticsParams({empty_state: !monitorList || monitorList.length === 0}); @@ -96,16 +81,6 @@ function CronsOverview() { }); }; - async function handleDismissError(errorType: ProcessingErrorType, projectId: string) { - await deleteProjectProcessingErrorByType( - api, - organization.slug, - projectId, - errorType - ); - await refetchErrors(); - } - const showAddMonitor = !isValidPlatform(platform) || !isValidGuide(guide); const page = ( @@ -171,18 +146,9 @@ function CronsOverview() { onSearch={handleSearch} /> - {!!processingErrors?.length && ( - - - {t( - 'Errors were encountered while ingesting check-ins for the selected projects' - )} - - - )} + + + {isPending ? ( ) : monitorList?.length ? (