From caa756ee121335957a65d040df40334e833a432d Mon Sep 17 00:00:00 2001 From: kagora Date: Mon, 20 Oct 2025 13:40:44 +0200 Subject: [PATCH 1/2] upcoming: [DPS-35222] - Logs destination/stream delete modal error state does not reset --- .../Destinations/DeleteDestinationDialog.tsx | 37 ++++++++++---- .../Destinations/DestinationsLanding.test.tsx | 50 +++++++++++++++++++ .../Delivery/Streams/DeleteStreamDialog.tsx | 36 +++++++++---- .../Delivery/Streams/StreamsLanding.test.tsx | 46 +++++++++++++++++ 4 files changed, 147 insertions(+), 22 deletions(-) diff --git a/packages/manager/src/features/Delivery/Destinations/DeleteDestinationDialog.tsx b/packages/manager/src/features/Delivery/Destinations/DeleteDestinationDialog.tsx index 020f86a47e8..ee2a8391531 100644 --- a/packages/manager/src/features/Delivery/Destinations/DeleteDestinationDialog.tsx +++ b/packages/manager/src/features/Delivery/Destinations/DeleteDestinationDialog.tsx @@ -2,8 +2,10 @@ import { useDeleteDestinationMutation } from '@linode/queries'; import { ActionsPanel } from '@linode/ui'; import { enqueueSnackbar } from 'notistack'; import * as React from 'react'; +import { useEffect } from 'react'; import { ConfirmationDialog } from 'src/components/ConfirmationDialog/ConfirmationDialog'; +import { getAPIErrorOrDefault } from 'src/utilities/errorUtils'; import type { Destination } from '@linode/api-v4'; @@ -15,24 +17,37 @@ interface Props { export const DeleteDestinationDialog = React.memo((props: Props) => { const { onClose, open, destination } = props; - const { - mutateAsync: deleteDestination, - isPending, - error, - } = useDeleteDestinationMutation(); + const { mutateAsync: deleteDestination, isPending } = + useDeleteDestinationMutation(); + const [deleteError, setDeleteError] = React.useState(); const handleDelete = () => { const { id, label } = destination as Destination; deleteDestination({ id, - }).then(() => { - onClose(); - return enqueueSnackbar(`Destination ${label} deleted successfully`, { - variant: 'success', + }) + .then(() => { + onClose(); + return enqueueSnackbar(`Destination ${label} deleted successfully`, { + variant: 'success', + }); + }) + .catch((error) => { + setDeleteError( + getAPIErrorOrDefault( + error, + 'There was an issue deleting your destination' + )[0].reason + ); }); - }); }; + useEffect(() => { + if (open) { + setDeleteError(undefined); + } + }, [open]); + const actions = ( { return ( { await checkClosedModal(deleteDestinationModal); }); + + it('should show error when cannot delete destination', async () => { + const mockDeleteDestinationMutation = vi.fn().mockRejectedValue([ + { + reason: + 'Destination with id 1 is attached to a stream and cannot be deleted', + }, + ]); + queryMocks.useDeleteDestinationMutation.mockReturnValue({ + mutateAsync: mockDeleteDestinationMutation, + }); + + renderComponent(); + await clickOnActionMenu(); + await clickOnActionMenuItem('Delete'); + + const deleteDestinationModal = screen.getByText('Delete Destination'); + expect(deleteDestinationModal).toBeInTheDocument(); + + let errorIcon = screen.queryByTestId('ErrorOutlineIcon'); + expect(errorIcon).not.toBeInTheDocument(); + + // get delete Destination button + const deleteDestinationButton = screen.getByRole('button', { + name: 'Delete', + }); + await userEvent.click(deleteDestinationButton); + + expect(mockDeleteDestinationMutation).toHaveBeenCalledWith({ + id: 1, + }); + + // check for error state in modal + screen.getByTestId('ErrorOutlineIcon'); + + // close modal with Cancel button + const cancelModalDialogButton = screen.getByRole('button', { + name: 'Cancel', + }); + await userEvent.click(cancelModalDialogButton); + await checkClosedModal(deleteDestinationModal); + + // open delete confirmation modal again + await clickOnActionMenu(); + await clickOnActionMenuItem('Delete'); + + // check for error state to be reset + errorIcon = screen.queryByTestId('ErrorOutlineIcon'); + expect(errorIcon).not.toBeInTheDocument(); + }); }); }); }); diff --git a/packages/manager/src/features/Delivery/Streams/DeleteStreamDialog.tsx b/packages/manager/src/features/Delivery/Streams/DeleteStreamDialog.tsx index b2344bb573b..0bf28a7f0e6 100644 --- a/packages/manager/src/features/Delivery/Streams/DeleteStreamDialog.tsx +++ b/packages/manager/src/features/Delivery/Streams/DeleteStreamDialog.tsx @@ -2,8 +2,10 @@ import { useDeleteStreamMutation } from '@linode/queries'; import { ActionsPanel } from '@linode/ui'; import { enqueueSnackbar } from 'notistack'; import * as React from 'react'; +import { useEffect } from 'react'; import { ConfirmationDialog } from 'src/components/ConfirmationDialog/ConfirmationDialog'; +import { getAPIErrorOrDefault } from 'src/utilities/errorUtils'; import type { Stream } from '@linode/api-v4'; @@ -15,24 +17,36 @@ interface Props { export const DeleteStreamDialog = React.memo((props: Props) => { const { onClose, open, stream } = props; - const { - mutateAsync: deleteStream, - isPending, - error, - } = useDeleteStreamMutation(); + const { mutateAsync: deleteStream, isPending } = useDeleteStreamMutation(); + const [deleteError, setDeleteError] = React.useState(); const handleDelete = () => { const { id, label } = stream as Stream; deleteStream({ id, - }).then(() => { - onClose(); - return enqueueSnackbar(`Stream ${label} deleted successfully`, { - variant: 'success', + }) + .then(() => { + onClose(); + return enqueueSnackbar(`Stream ${label} deleted successfully`, { + variant: 'success', + }); + }) + .catch((error) => { + setDeleteError( + getAPIErrorOrDefault( + error, + 'There was an issue deleting your stream' + )[0].reason + ); }); - }); }; + useEffect(() => { + if (open) { + setDeleteError(undefined); + } + }, [open]); + const actions = ( { return ( { await checkClosedModal(deleteStreamModal); }); + + it('should show error when cannot delete stream', async () => { + const mockDeleteStreamMutation = vi + .fn() + .mockRejectedValue([{ reason: 'Unexpected error' }]); + queryMocks.useDeleteStreamMutation.mockReturnValue({ + mutateAsync: mockDeleteStreamMutation, + }); + + renderComponent(); + await clickOnActionMenu(); + await clickOnActionMenuItem('Delete'); + + const deleteStreamModal = screen.getByText('Delete Stream'); + expect(deleteStreamModal).toBeInTheDocument(); + + const errorIcon = screen.queryByTestId('ErrorOutlineIcon'); + expect(errorIcon).not.toBeInTheDocument(); + + // get delete Stream button + const deleteStreamButton = screen.getByRole('button', { + name: 'Delete', + }); + await userEvent.click(deleteStreamButton); + + expect(mockDeleteStreamMutation).toHaveBeenCalledWith({ + id: 1, + }); + + // check for error state in modal + screen.getByTestId('ErrorOutlineIcon'); + + // get modal Cancel button + const cancelModalDialogButton = screen.getByRole('button', { + name: 'Cancel', + }); + await userEvent.click(cancelModalDialogButton); + await checkClosedModal(deleteStreamModal); + + // open delete confirmation modal again + await clickOnActionMenu(); + await clickOnActionMenuItem('Delete'); + + // check for error state to be reset + expect(errorIcon).not.toBeInTheDocument(); + }); }); }); }); From ae5273559ce45579ce595f651a9d130eb32dc02b Mon Sep 17 00:00:00 2001 From: kagora Date: Mon, 20 Oct 2025 14:12:21 +0200 Subject: [PATCH 2/2] Added changeset: Logs Delivery Destinations/Stream Delete confirmation modal error state reset fix --- .../.changeset/pr-12996-upcoming-features-1760962341317.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 packages/manager/.changeset/pr-12996-upcoming-features-1760962341317.md diff --git a/packages/manager/.changeset/pr-12996-upcoming-features-1760962341317.md b/packages/manager/.changeset/pr-12996-upcoming-features-1760962341317.md new file mode 100644 index 00000000000..ec8379969f2 --- /dev/null +++ b/packages/manager/.changeset/pr-12996-upcoming-features-1760962341317.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Upcoming Features +--- + +Logs Delivery Destinations/Stream Delete confirmation modal error state reset fix ([#12996](https://github.com/linode/manager/pull/12996))