Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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))
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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<string | undefined>();

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 = (
<ActionsPanel
primaryButtonProps={{
Expand All @@ -49,7 +64,7 @@ export const DeleteDestinationDialog = React.memo((props: Props) => {
return (
<ConfirmationDialog
actions={actions}
error={error}
error={deleteError}
onClose={onClose}
open={open}
title="Delete Destination"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,56 @@ describe('Destinations Landing Table', () => {

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();
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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<string | undefined>();

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 = (
<ActionsPanel
primaryButtonProps={{
Expand All @@ -49,7 +63,7 @@ export const DeleteStreamDialog = React.memo((props: Props) => {
return (
<ConfirmationDialog
actions={actions}
error={error}
error={deleteError}
onClose={onClose}
open={open}
title="Delete Stream"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,52 @@ describe('Streams Landing Table', () => {

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();
});
});
});
});