diff --git a/static/app/components/feedback/useDeleteFeedback.spec.tsx b/static/app/components/feedback/useDeleteFeedback.spec.tsx
new file mode 100644
index 000000000000..27951e15bb42
--- /dev/null
+++ b/static/app/components/feedback/useDeleteFeedback.spec.tsx
@@ -0,0 +1,97 @@
+import {Fragment} from 'react';
+import {OrganizationFixture} from 'sentry-fixture/organization';
+
+import {
+ render,
+ screen,
+ userEvent,
+ waitFor,
+ within,
+} from 'sentry-test/reactTestingLibrary';
+
+import {GlobalModal} from '@sentry/scraps/modal';
+
+import {clearIndicators} from 'sentry/actionCreators/indicator';
+import {useDeleteFeedback} from 'sentry/components/feedback/useDeleteFeedback';
+import Indicators from 'sentry/components/indicators';
+
+const mockRefetchFeedbackList = jest.fn();
+
+jest.mock('sentry/components/feedback/list/useRefetchFeedbackList', () => ({
+ useRefetchFeedbackList: () => ({refetchFeedbackList: mockRefetchFeedbackList}),
+}));
+
+const organization = OrganizationFixture();
+const initialPath = `/organizations/${organization.slug}/issues/feedback/123/`;
+
+function DeleteFeedbackButton() {
+ const deleteFeedback = useDeleteFeedback(['123'], 'project');
+
+ return ;
+}
+
+function renderDeleteFeedback() {
+ return render(
+
+
+
+
+ ,
+ {
+ organization,
+ initialRouterConfig: {
+ location: {pathname: initialPath},
+ route: '/organizations/:orgId/issues/feedback/:feedbackId/',
+ },
+ }
+ );
+}
+
+async function confirmDelete() {
+ await userEvent.click(screen.getByRole('button', {name: 'Delete feedback'}));
+ await userEvent.click(
+ within(await screen.findByRole('dialog')).getByRole('button', {name: 'Delete'})
+ );
+}
+
+describe('useDeleteFeedback', () => {
+ beforeEach(() => {
+ clearIndicators();
+ mockRefetchFeedbackList.mockClear();
+ });
+
+ it('navigates after deleting feedback successfully', async () => {
+ MockApiClient.addMockResponse({
+ url: `/projects/${organization.slug}/project/issues/`,
+ method: 'DELETE',
+ body: {},
+ });
+ const {router} = renderDeleteFeedback();
+
+ await confirmDelete();
+
+ await waitFor(() => {
+ expect(router.location.pathname).toBe(
+ `/organizations/${organization.slug}/issues/feedback/`
+ );
+ });
+ expect(mockRefetchFeedbackList).toHaveBeenCalledTimes(1);
+ });
+
+ it('refetches without navigating when deleting feedback fails', async () => {
+ MockApiClient.addMockResponse({
+ url: `/projects/${organization.slug}/project/issues/`,
+ method: 'DELETE',
+ statusCode: 500,
+ });
+ const {router} = renderDeleteFeedback();
+
+ await confirmDelete();
+
+ expect(
+ await screen.findByText('Unable to delete events. Please try again.')
+ ).toBeInTheDocument();
+ expect(mockRefetchFeedbackList).toHaveBeenCalledTimes(1);
+ expect(router.location.pathname).toBe(initialPath);
+ });
+});
diff --git a/static/app/components/feedback/useDeleteFeedback.tsx b/static/app/components/feedback/useDeleteFeedback.tsx
index 504266b853b9..a6a5918e2711 100644
--- a/static/app/components/feedback/useDeleteFeedback.tsx
+++ b/static/app/components/feedback/useDeleteFeedback.tsx
@@ -34,8 +34,7 @@ export const useDeleteFeedback = (feedbackIds: any, projectId: any) => {
itemIds: feedbackIds,
},
{
- complete: () => {
- refetchFeedbackList();
+ success: () => {
navigate(
normalizeUrl({
pathname: makeFeedbackPathname({
@@ -51,6 +50,7 @@ export const useDeleteFeedback = (feedbackIds: any, projectId: any) => {
})
);
},
+ complete: refetchFeedbackList,
}
);
},
diff --git a/static/app/views/issueDetails/actions/index.spec.tsx b/static/app/views/issueDetails/actions/index.spec.tsx
index 0a3add6feac2..bb55beed4bce 100644
--- a/static/app/views/issueDetails/actions/index.spec.tsx
+++ b/static/app/views/issueDetails/actions/index.spec.tsx
@@ -16,6 +16,7 @@ import {
import {GlobalModal} from '@sentry/scraps/modal';
+import {clearIndicators} from 'sentry/actionCreators/indicator';
import {
CMDKCollection,
CommandPaletteProvider,
@@ -23,6 +24,7 @@ import {
} from 'sentry/components/commandPalette/ui/cmdk';
import type {CollectionTreeNode} from 'sentry/components/commandPalette/ui/collection';
import {CommandPaletteSlot} from 'sentry/components/commandPalette/ui/commandPaletteSlot';
+import Indicators from 'sentry/components/indicators';
import {mockTour} from 'sentry/components/tours/testUtils';
import {ConfigStore} from 'sentry/stores/configStore';
import {ModalStore} from 'sentry/stores/modalStore';
@@ -92,6 +94,7 @@ describe('GroupActions', () => {
const analyticsSpy = jest.spyOn(analytics, 'trackAnalytics');
beforeEach(() => {
+ clearIndicators();
ConfigStore.init();
ProjectsStore.reset();
ProjectsStore.loadInitialData([project]);
@@ -288,6 +291,45 @@ describe('GroupActions', () => {
);
});
+ it('does not report success or navigate when deletion fails', async () => {
+ const org = OrganizationFixture({
+ ...organization,
+ access: [...organization.access, 'event:admin'],
+ });
+ MockApiClient.addMockResponse({
+ url: `/projects/${org.slug}/${project.slug}/issues/`,
+ method: 'DELETE',
+ statusCode: 500,
+ });
+ const initialPath = `/organizations/${org.slug}/issues/${group.id}/`;
+ const {router} = render(
+
+
+
+
+ ,
+ {
+ organization: org,
+ initialRouterConfig: {
+ location: {pathname: initialPath},
+ route: '/organizations/:orgId/issues/:groupId/',
+ },
+ }
+ );
+
+ await userEvent.click(screen.getByLabelText('More Actions'));
+ await userEvent.click(await screen.findByRole('menuitemradio', {name: 'Delete'}));
+ await userEvent.click(
+ within(screen.getByRole('dialog')).getByRole('button', {name: 'Delete'})
+ );
+
+ expect(
+ await screen.findByText('Unable to delete events. Please try again.')
+ ).toBeInTheDocument();
+ expect(screen.queryByText('Issue deleted')).not.toBeInTheDocument();
+ expect(router.location.pathname).toBe(initialPath);
+ });
+
it('delete for issue platform', async () => {
const org = OrganizationFixture({
...organization,
@@ -391,6 +433,69 @@ describe('GroupActions', () => {
);
});
+ it('does not report success when resolving fails', async () => {
+ MockApiClient.addMockResponse({
+ url: `/projects/${organization.slug}/project/issues/`,
+ method: 'PUT',
+ statusCode: 500,
+ });
+
+ render(
+
+
+
+ ,
+ {organization}
+ );
+
+ await userEvent.click(screen.getByRole('button', {name: 'Resolve'}));
+
+ expect(
+ await screen.findByText('Unable to update events. Please try again.')
+ ).toBeInTheDocument();
+ expect(screen.queryByText('Issue resolved')).not.toBeInTheDocument();
+ });
+
+ it('refetches group data when resolving fails', async () => {
+ MockApiClient.addMockResponse({
+ url: `/projects/${organization.slug}/project/issues/`,
+ method: 'PUT',
+ statusCode: 500,
+ });
+ const groupFetchApi = MockApiClient.addMockResponse({
+ url: `/organizations/${organization.slug}/issues/${group.id}/`,
+ method: 'GET',
+ body: group,
+ });
+
+ function GroupActionsWrapper() {
+ const {data: groupData, isLoading} = useGroup({groupId: group.id});
+
+ if (isLoading || !groupData) {
+ return
Loading...
;
+ }
+
+ return (
+
+ );
+ }
+
+ render(
+
+
+
+ ,
+ {organization}
+ );
+
+ await waitFor(() => expect(groupFetchApi).toHaveBeenCalledTimes(1));
+ await userEvent.click(await screen.findByRole('button', {name: 'Resolve'}));
+ expect(
+ await screen.findByText('Unable to update events. Please try again.')
+ ).toBeInTheDocument();
+ await waitFor(() => expect(groupFetchApi).toHaveBeenCalledTimes(2));
+ });
+
it('can archive issue', async () => {
const issuesApi = MockApiClient.addMockResponse({
url: `/projects/${organization.slug}/project/issues/`,
diff --git a/static/app/views/issueDetails/actions/index.tsx b/static/app/views/issueDetails/actions/index.tsx
index d952e30977f9..6e46f46d27a4 100644
--- a/static/app/views/issueDetails/actions/index.tsx
+++ b/static/app/views/issueDetails/actions/index.tsx
@@ -266,7 +266,7 @@ export function GroupActions({group, project, disabled, event}: GroupActionsProp
itemIds: [group.id],
},
{
- complete: () => {
+ success: () => {
clearIndicators();
addSuccessMessage(t('Issue deleted'));
@@ -294,12 +294,14 @@ export function GroupActions({group, project, disabled, event}: GroupActionsProp
data,
},
{
- complete: () => {
+ success: () => {
clearIndicators();
if (successMessage) {
addSuccessMessage(successMessage);
}
onComplete?.();
+ },
+ complete: () => {
queryClient.invalidateQueries({
queryKey: groupQueryKey({
organizationSlug: organization.slug,