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 (