Skip to content

Commit 7e59f27

Browse files
authored
feat(aci): Add detector deletion (#94567)
1 parent 98f8825 commit 7e59f27

File tree

4 files changed

+134
-0
lines changed

4 files changed

+134
-0
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import {DetectorFixture} from 'sentry-fixture/detectors';
2+
import {OrganizationFixture} from 'sentry-fixture/organization';
3+
4+
import {
5+
render,
6+
renderGlobalModal,
7+
screen,
8+
userEvent,
9+
within,
10+
} from 'sentry-test/reactTestingLibrary';
11+
12+
import {EditDetectorActions} from './editDetectorActions';
13+
14+
describe('EditDetectorActions', () => {
15+
it('calls delete mutation when deletion is confirmed', async () => {
16+
const detector = DetectorFixture();
17+
const organization = OrganizationFixture();
18+
19+
const mockDeleteDetector = MockApiClient.addMockResponse({
20+
url: `/organizations/${organization.slug}/detectors/${detector.id}/`,
21+
method: 'DELETE',
22+
});
23+
24+
const {router} = render(<EditDetectorActions detectorId={detector.id} />);
25+
renderGlobalModal();
26+
27+
await userEvent.click(screen.getByRole('button', {name: 'Delete'}));
28+
29+
// Confirm the deletion
30+
const dialog = await screen.findByRole('dialog');
31+
await userEvent.click(within(dialog).getByRole('button', {name: 'Delete'}));
32+
33+
expect(mockDeleteDetector).toHaveBeenCalledWith(
34+
`/organizations/${organization.slug}/detectors/${detector.id}/`,
35+
expect.anything()
36+
);
37+
38+
// Redirect to the monitors list
39+
expect(router.location.pathname).toBe(
40+
`/organizations/${organization.slug}/issues/monitors/`
41+
);
42+
});
43+
});
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import {useCallback} from 'react';
2+
3+
import {openConfirmModal} from 'sentry/components/confirm';
4+
import {Button} from 'sentry/components/core/button';
5+
import {ButtonBar} from 'sentry/components/core/button/buttonBar';
6+
import {t} from 'sentry/locale';
7+
import type {Detector} from 'sentry/types/workflowEngine/detectors';
8+
import {useNavigate} from 'sentry/utils/useNavigate';
9+
import useOrganization from 'sentry/utils/useOrganization';
10+
import {useDeleteDetectorMutation} from 'sentry/views/detectors/hooks/useDeleteDetectorMutation';
11+
import {makeMonitorBasePathname} from 'sentry/views/detectors/pathnames';
12+
13+
interface EditDetectorActionsProps {
14+
detectorId: Detector['id'];
15+
}
16+
17+
export function EditDetectorActions({detectorId}: EditDetectorActionsProps) {
18+
const organization = useOrganization();
19+
const navigate = useNavigate();
20+
const {mutate: deleteDetector, isPending: isDeleting} = useDeleteDetectorMutation();
21+
22+
const handleDelete = useCallback(() => {
23+
openConfirmModal({
24+
message: t('Are you sure you want to delete this monitor?'),
25+
confirmText: t('Delete'),
26+
priority: 'danger',
27+
onConfirm: () => {
28+
deleteDetector(detectorId, {
29+
onSuccess: () => {
30+
navigate(makeMonitorBasePathname(organization.slug));
31+
},
32+
});
33+
},
34+
});
35+
}, [deleteDetector, detectorId, navigate, organization.slug]);
36+
37+
return (
38+
<div>
39+
<ButtonBar gap={1}>
40+
<Button type="button" priority="default" size="sm" onClick={() => {}}>
41+
{t('Disable')}
42+
</Button>
43+
<Button
44+
priority="danger"
45+
onClick={handleDelete}
46+
disabled={isDeleting}
47+
busy={isDeleting}
48+
size="sm"
49+
>
50+
{t('Delete')}
51+
</Button>
52+
<Button type="submit" priority="primary" size="sm">
53+
{t('Save')}
54+
</Button>
55+
</ButtonBar>
56+
</div>
57+
);
58+
}

static/app/views/detectors/edit.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import useOrganization from 'sentry/utils/useOrganization';
2020
import {useParams} from 'sentry/utils/useParams';
2121
import {DetectorForm} from 'sentry/views/detectors/components/forms';
2222
import {DetectorBaseFields} from 'sentry/views/detectors/components/forms/detectorBaseFields';
23+
import {EditDetectorActions} from 'sentry/views/detectors/components/forms/editDetectorActions';
2324
import type {MetricDetectorFormData} from 'sentry/views/detectors/components/forms/metricFormData';
2425
import {
2526
getMetricDetectorFormData,
@@ -114,6 +115,7 @@ export default function DetectorEdit() {
114115
<DetectorBreadcrumbs detectorId={params.detectorId} />
115116
<DetectorBaseFields />
116117
</Layout.HeaderContent>
118+
<EditDetectorActions detectorId={detector.id} />
117119
</StyledLayoutHeader>
118120
<Layout.Body>
119121
<Layout.Main fullWidth>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
2+
import {t} from 'sentry/locale';
3+
import type {ApiQueryKey} from 'sentry/utils/queryClient';
4+
import {useMutation, useQueryClient} from 'sentry/utils/queryClient';
5+
import type RequestError from 'sentry/utils/requestError/requestError';
6+
import useApi from 'sentry/utils/useApi';
7+
import useOrganization from 'sentry/utils/useOrganization';
8+
9+
export function useDeleteDetectorMutation() {
10+
const org = useOrganization();
11+
const api = useApi({persistInFlight: true});
12+
const queryClient = useQueryClient();
13+
14+
return useMutation<void, RequestError, string>({
15+
mutationFn: (detectorId: string) =>
16+
api.requestPromise(`/organizations/${org.slug}/detectors/${detectorId}/`, {
17+
method: 'DELETE',
18+
}),
19+
onSuccess: () => {
20+
queryClient.invalidateQueries({
21+
// Invalidate list of detectors
22+
predicate: query =>
23+
(query.queryKey as ApiQueryKey)[0] === `/organizations/${org.slug}/detectors/`,
24+
});
25+
addSuccessMessage(t('Monitor deleted.'));
26+
},
27+
onError: error => {
28+
addErrorMessage(t('Unable to delete monitor: %s', error.message));
29+
},
30+
});
31+
}

0 commit comments

Comments
 (0)