diff --git a/static/app/actionCreators/monitors.tsx b/static/app/actionCreators/monitors.tsx index 3e45c06d2d7a68..60060d8e7349d9 100644 --- a/static/app/actionCreators/monitors.tsx +++ b/static/app/actionCreators/monitors.tsx @@ -102,7 +102,7 @@ export async function setEnvironmentIsMuted( try { const resp = await api.requestPromise( - `/projects/${orgId}/${monitor.project.slug}/monitors/${monitor.slug}/environments/${environment}`, + `/projects/${orgId}/${monitor.project.slug}/monitors/${monitor.slug}/environments/${environment}/`, {method: 'PUT', data: {isMuted}} ); clearIndicators(); diff --git a/static/app/views/detectors/components/details/cron/index.spec.tsx b/static/app/views/detectors/components/details/cron/index.spec.tsx index d5d1e677525ae3..79a4cebb719216 100644 --- a/static/app/views/detectors/components/details/cron/index.spec.tsx +++ b/static/app/views/detectors/components/details/cron/index.spec.tsx @@ -342,4 +342,71 @@ describe('CronDetectorDetails - check-ins', () => { expect(screen.getByRole('button', {name: 'Enable'})).toBeInTheDocument(); }); }); + + describe('environment muting', () => { + it('refetches detector when environment is muted', async () => { + const detectorWithMultipleEnvs = CronDetectorFixture({ + id: '1', + projectId: project.id, + dataSources: [ + CronMonitorDataSourceFixture({ + queryObj: { + ...CronMonitorDataSourceFixture().queryObj, + environments: [ + CronMonitorEnvironmentFixture({ + name: 'production', + lastCheckIn: '2025-01-01T00:00:00Z', + isMuted: false, + }), + CronMonitorEnvironmentFixture({ + name: 'staging', + lastCheckIn: '2025-01-01T00:00:00Z', + isMuted: false, + }), + ], + }, + }), + ], + }); + + const muteRequest = MockApiClient.addMockResponse({ + url: `/projects/org-slug/${project.slug}/monitors/${detectorWithMultipleEnvs.dataSources[0].queryObj.slug}/environments/production/`, + method: 'PUT', + body: {}, + }); + + const detectorRefetchRequest = MockApiClient.addMockResponse({ + url: `/organizations/org-slug/detectors/1/`, + body: detectorWithMultipleEnvs, + }); + + render( + + ); + + await screen.findByText('Recent Check-Ins'); + + expect(detectorRefetchRequest).toHaveBeenCalledTimes(1); + + const envButtons = screen.getAllByRole('button', { + name: 'Monitor environment actions', + }); + await userEvent.click(envButtons[0]!); + + await userEvent.click( + await screen.findByRole('menuitemradio', {name: 'Mute Environment'}) + ); + + expect(muteRequest).toHaveBeenCalledTimes(1); + expect(muteRequest).toHaveBeenCalledWith( + expect.stringContaining('/environments/production'), + expect.objectContaining({ + method: 'PUT', + data: {isMuted: true}, + }) + ); + + expect(detectorRefetchRequest).toHaveBeenCalledTimes(2); + }); + }); }); diff --git a/static/app/views/detectors/components/details/cron/index.tsx b/static/app/views/detectors/components/details/cron/index.tsx index 377d0f618eac39..e1364ca64ebab3 100644 --- a/static/app/views/detectors/components/details/cron/index.tsx +++ b/static/app/views/detectors/components/details/cron/index.tsx @@ -22,6 +22,7 @@ import {t, tn} from 'sentry/locale'; import type {Project} from 'sentry/types/project'; import type {CronDetector} from 'sentry/types/workflowEngine/detectors'; import toArray from 'sentry/utils/array/toArray'; +import {useQueryClient} from 'sentry/utils/queryClient'; import {useLocation} from 'sentry/utils/useLocation'; import useOrganization from 'sentry/utils/useOrganization'; import { @@ -35,7 +36,10 @@ import {DisabledAlert} from 'sentry/views/detectors/components/details/common/di import {DetectorExtraDetails} from 'sentry/views/detectors/components/details/common/extraDetails'; import {DetectorDetailsHeader} from 'sentry/views/detectors/components/details/common/header'; import {DetectorDetailsOpenPeriodIssues} from 'sentry/views/detectors/components/details/common/openPeriodIssues'; -import {useDetectorQuery} from 'sentry/views/detectors/hooks'; +import { + makeDetectorDetailsQueryKey, + useDetectorQuery, +} from 'sentry/views/detectors/hooks'; import {DetailsTimeline} from 'sentry/views/insights/crons/components/detailsTimeline'; import {DetailsTimelineLegend} from 'sentry/views/insights/crons/components/detailsTimelineLegend'; import {MonitorCheckIns} from 'sentry/views/insights/crons/components/monitorCheckIns'; @@ -69,6 +73,7 @@ export function CronDetectorDetails({detector, project}: CronDetectorDetailsProp const userTimezone = useTimezone(); const [timezoneOverride, setTimezoneOverride] = useState(userTimezone); const openDocsPanel = useDocsPanel(dataSource.queryObj.slug, project); + const queryClient = useQueryClient(); useDetectorQuery(detector.id, { staleTime: 0, @@ -83,6 +88,14 @@ export function CronDetectorDetails({detector, project}: CronDetectorDetailsProp }, }); + const handleEnvironmentUpdated = useCallback(() => { + const queryKey = makeDetectorDetailsQueryKey({ + orgSlug: organization.slug, + detectorId: detector.id, + }); + queryClient.invalidateQueries({queryKey}); + }, [queryClient, organization.slug, detector.id]); + const {checkinErrors, handleDismissError} = useMonitorProcessingErrors({ organization, projectId: project.id, @@ -174,6 +187,7 @@ export function CronDetectorDetails({detector, project}: CronDetectorDetailsProp void; /** * Called when monitor stats have been loaded for this timeline. */ onStatsLoaded?: (stats: MonitorBucket[]) => void; } -export function DetailsTimeline({monitor, onStatsLoaded}: Props) { +export function DetailsTimeline({monitor, onStatsLoaded, onEnvironmentUpdated}: Props) { const organization = useOrganization(); const location = useLocation(); const api = useApi(); @@ -89,6 +93,8 @@ export function DetailsTimeline({monitor, onStatsLoaded}: Props) { } : undefined; }); + + onEnvironmentUpdated?.(); }; const handleToggleMuteEnvironment = async (env: string, isMuted: boolean) => { @@ -119,6 +125,8 @@ export function DetailsTimeline({monitor, onStatsLoaded}: Props) { } : undefined; }); + + onEnvironmentUpdated?.(); }; return (