diff --git a/packages/manager/.changeset/pr-9725-fixed-1695824674392.md b/packages/manager/.changeset/pr-9725-fixed-1695824674392.md new file mode 100644 index 00000000000..8e426699800 --- /dev/null +++ b/packages/manager/.changeset/pr-9725-fixed-1695824674392.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Fixed +--- + +Add Firewall and Linode Configuration success toast notifications ([#9725](https://github.com/linode/manager/pull/9725)) diff --git a/packages/manager/cypress/e2e/core/linodes/clone-linode.spec.ts b/packages/manager/cypress/e2e/core/linodes/clone-linode.spec.ts index 5662f41d5de..58e104becc1 100644 --- a/packages/manager/cypress/e2e/core/linodes/clone-linode.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/clone-linode.spec.ts @@ -116,7 +116,7 @@ describe('clone linode', () => { ui.toast.assertMessage(`Your Linode ${newLinodeLabel} is being created.`); ui.toast.assertMessage( - `Linode ${linode.label} has been cloned successfully to ${newLinodeLabel}.` + `Linode ${linode.label} successfully cloned to ${newLinodeLabel}.` ); }); }); diff --git a/packages/manager/cypress/e2e/core/linodes/linode-config.spec.ts b/packages/manager/cypress/e2e/core/linodes/linode-config.spec.ts index cbd4c5e8eb0..dcb632df4b2 100644 --- a/packages/manager/cypress/e2e/core/linodes/linode-config.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/linode-config.spec.ts @@ -170,7 +170,9 @@ describe('Linode Config', () => { .click(); }); - ui.toast.assertMessage('Successfully deleted config'); + ui.toast.assertMessage( + 'Configuration My Debian 10 Disk Profile successfully deleted' + ); cy.findByLabelText('List of Configurations').within(() => { containsVisible('No data to display.'); }); @@ -232,7 +234,7 @@ describe('Linode Config', () => { }); ui.toast.assertMessage( - 'Linode cy-test-clone-origin-linode has been cloned successfully to cy-test-clone-destination-linode.' + 'Linode cy-test-clone-origin-linode successfully cloned to cy-test-clone-destination-linode.' ); }); }); @@ -274,7 +276,9 @@ describe('Linode Config', () => { cy.wait('@deleteLinodeConfig') .its('response.statusCode') .should('eq', 200); - ui.toast.assertMessage('Successfully deleted config'); + ui.toast.assertMessage( + 'Configuration My Debian 10 Disk Profile successfully deleted' + ); cy.findByLabelText('List of Configurations').within(() => { containsVisible('No data to display.'); }); diff --git a/packages/manager/cypress/e2e/core/linodes/linode-storage.spec.ts b/packages/manager/cypress/e2e/core/linodes/linode-storage.spec.ts index e435800b8a3..960332fb59d 100644 --- a/packages/manager/cypress/e2e/core/linodes/linode-storage.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/linode-storage.spec.ts @@ -148,7 +148,7 @@ describe('linode storage tab', () => { cy.wait('@deleteDisk').its('response.statusCode').should('eq', 200); cy.findByText('Deleting', { exact: false }).should('be.visible'); ui.button.findByTitle('Add a Disk').should('be.enabled'); - ui.toast.assertMessage(`Disk ${diskName} deleted successfully.`); + ui.toast.assertMessage(`Disk ${diskName} successfully deleted.`); cy.findByLabelText('List of Disks').within(() => { cy.contains(diskName).should('not.exist'); }); @@ -209,7 +209,7 @@ describe('linode storage tab', () => { cy.wait('@resizeDisk').its('response.statusCode').should('eq', 200); ui.toast.assertMessage('Disk queued for resizing.'); // cy.findByText('Resizing', { exact: false }).should('be.visible'); - ui.toast.assertMessage(`Disk ${diskName} resized successfully.`); + ui.toast.assertMessage(`Disk ${diskName} successfully resized.`); }); }); }); diff --git a/packages/manager/cypress/e2e/core/linodes/resize-linode.spec.ts b/packages/manager/cypress/e2e/core/linodes/resize-linode.spec.ts index aba39f68fd3..bd443c6da77 100644 --- a/packages/manager/cypress/e2e/core/linodes/resize-linode.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/resize-linode.spec.ts @@ -186,7 +186,7 @@ describe('resize linode', () => { }); // Wait until the disk resize is done. - ui.toast.assertMessage(`Disk ${diskName} resized successfully.`); + ui.toast.assertMessage(`Disk ${diskName} successfully resized.`); cy.intercept( 'POST', diff --git a/packages/manager/src/features/Firewalls/FirewallDetail/Devices/RemoveDeviceDialog.tsx b/packages/manager/src/features/Firewalls/FirewallDetail/Devices/RemoveDeviceDialog.tsx index 78db05a90a8..055a555386e 100644 --- a/packages/manager/src/features/Firewalls/FirewallDetail/Devices/RemoveDeviceDialog.tsx +++ b/packages/manager/src/features/Firewalls/FirewallDetail/Devices/RemoveDeviceDialog.tsx @@ -1,4 +1,5 @@ import { FirewallDevice } from '@linode/api-v4'; +import { useSnackbar } from 'notistack'; import * as React from 'react'; import { useQueryClient } from 'react-query'; @@ -29,6 +30,8 @@ export const RemoveDeviceDialog = React.memo((props: Props) => { open, } = props; + const { enqueueSnackbar } = useSnackbar(); + const { error, isLoading, mutateAsync } = useRemoveFirewallDeviceMutation( firewallId, device?.id ?? -1 @@ -38,6 +41,9 @@ export const RemoveDeviceDialog = React.memo((props: Props) => { const onDelete = async () => { await mutateAsync(); + enqueueSnackbar(`Device ${device?.entity.label} successfully removed`, { + variant: 'success', + }); // Since the linode was removed as a device, invalidate the linode-specific firewall query queryClient.invalidateQueries([ diff --git a/packages/manager/src/features/Firewalls/FirewallDetail/Rules/FirewallRulesLanding.tsx b/packages/manager/src/features/Firewalls/FirewallDetail/Rules/FirewallRulesLanding.tsx index 5f27b18e6f2..cf410630280 100644 --- a/packages/manager/src/features/Firewalls/FirewallDetail/Rules/FirewallRulesLanding.tsx +++ b/packages/manager/src/features/Firewalls/FirewallDetail/Rules/FirewallRulesLanding.tsx @@ -9,6 +9,8 @@ import { Typography } from 'src/components/Typography'; import { useUpdateFirewallRulesMutation } from 'src/queries/firewalls'; import { getAPIErrorOrDefault } from 'src/utilities/errorUtils'; +import { useSnackbar } from 'notistack'; + import { FirewallRuleDrawer } from './FirewallRuleDrawer'; import { FirewallRuleTable } from './FirewallRuleTable'; import { @@ -49,6 +51,8 @@ const FirewallRulesLanding = (props: Props) => { firewallID ); + const { enqueueSnackbar } = useSnackbar(); + /** * inbound and outbound policy aren't part of any particular rule * so they are managed separately rather than through the reducer. @@ -191,6 +195,9 @@ const FirewallRulesLanding = (props: Props) => { // Reset editor state. inboundDispatch({ rules: _rules.inbound ?? [], type: 'RESET' }); outboundDispatch({ rules: _rules.outbound ?? [], type: 'RESET' }); + enqueueSnackbar('Firewall rules successfully updated', { + variant: 'success', + }); }) .catch((err) => { setSubmitting(false); @@ -218,6 +225,9 @@ const FirewallRulesLanding = (props: Props) => { }); } } + enqueueSnackbar('Failed to update Firewall rules', { + variant: 'error', + }); }); }; diff --git a/packages/manager/src/features/Firewalls/FirewallLanding/FirewallDialog.tsx b/packages/manager/src/features/Firewalls/FirewallLanding/FirewallDialog.tsx index fe186d8267c..3aae353da90 100644 --- a/packages/manager/src/features/Firewalls/FirewallLanding/FirewallDialog.tsx +++ b/packages/manager/src/features/Firewalls/FirewallLanding/FirewallDialog.tsx @@ -1,3 +1,4 @@ +import { useSnackbar } from 'notistack'; import * as React from 'react'; import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel'; @@ -16,6 +17,8 @@ interface Props { } export const FirewallDialog = React.memo((props: Props) => { + const { enqueueSnackbar } = useSnackbar(); + const { mode, onClose, @@ -55,6 +58,9 @@ export const FirewallDialog = React.memo((props: Props) => { const onSubmit = async () => { await requestMap[mode](); + enqueueSnackbar(`Firewall ${label} successfully ${mode}d`, { + variant: 'success', + }); onClose(); }; diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeConfigs/DeleteConfigDialog.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeConfigs/DeleteConfigDialog.tsx index 2fa13efa60d..6b9f42cbae0 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeConfigs/DeleteConfigDialog.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeConfigs/DeleteConfigDialog.tsx @@ -25,7 +25,9 @@ export const DeleteConfigDialog = (props: Props) => { const onDelete = async () => { await mutateAsync(); - enqueueSnackbar('Successfully deleted config', { variant: 'success' }); + enqueueSnackbar(`Configuration ${config?.label} successfully deleted`, { + variant: 'success', + }); onClose(); }; diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeConfigs/LinodeConfigDialog.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeConfigs/LinodeConfigDialog.tsx index 8990db27843..ef7e7b2d5ba 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeConfigs/LinodeConfigDialog.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeConfigs/LinodeConfigDialog.tsx @@ -7,6 +7,7 @@ import { APIError } from '@linode/api-v4/lib/types'; import Grid from '@mui/material/Unstable_Grid2'; import { styled, useTheme } from '@mui/material/styles'; import { useFormik } from 'formik'; +import { useSnackbar } from 'notistack'; import { equals, pathOr, repeat } from 'ramda'; import * as React from 'react'; import { useQueryClient } from 'react-query'; @@ -195,6 +196,8 @@ export const LinodeConfigDialog = (props: Props) => { const { data: linode } = useLinodeQuery(linodeId, open); + const { enqueueSnackbar } = useSnackbar(); + const { data: kernels, error: kernelsError, @@ -317,6 +320,7 @@ export const LinodeConfigDialog = (props: Props) => { delete configData.interfaces; } + const actionType = Boolean(config) ? 'updated' : 'created'; const handleSuccess = () => { formik.setSubmitting(false); queryClient.invalidateQueries(['linode', 'configs', props.linodeId]); @@ -328,6 +332,13 @@ export const LinodeConfigDialog = (props: Props) => { ) { queryClient.invalidateQueries('vlans'); } + + enqueueSnackbar( + `Configuration ${configData.label} successfully ${actionType}`, + { + variant: 'success', + } + ); onClose(); }; diff --git a/packages/manager/src/features/ToastNotifications/ToastNotifications.tsx b/packages/manager/src/features/ToastNotifications/ToastNotifications.tsx index 896e9b59f68..7392eed579c 100644 --- a/packages/manager/src/features/ToastNotifications/ToastNotifications.tsx +++ b/packages/manager/src/features/ToastNotifications/ToastNotifications.tsx @@ -116,7 +116,7 @@ export const ToastNotifications = () => { 'https://www.linode.com/docs/products/tools/images/#technical-specifications' ), persistFailureMessage: true, - successMessage: `Image ${secondaryLabel} created successfully.`, + successMessage: `Image ${secondaryLabel} successfully created.`, }); case 'disk_resize': return toastSuccessAndFailure({ @@ -134,7 +134,7 @@ export const ToastNotifications = () => { ) ), persistFailureMessage: true, - successMessage: `Disk ${secondaryLabel} resized successfully.`, + successMessage: `Disk ${secondaryLabel} successfully resized.`, }); case 'image_upload': const isDeletion = event.message === 'Upload canceled.'; @@ -155,7 +155,7 @@ export const ToastNotifications = () => { enqueueSnackbar, eventStatus: event.status, failureMessage: `Error deleting Image ${label}.`, - successMessage: `Image ${label} deleted successfully.`, + successMessage: `Image ${label} successfully deleted.`, }); case 'disk_delete': return toastSuccessAndFailure({ @@ -164,7 +164,7 @@ export const ToastNotifications = () => { failureMessage: `Unable to delete disk ${secondaryLabel} ${ label ? ` on ${label}` : '' }. Is it attached to a configuration profile that is in use?`, - successMessage: `Disk ${secondaryLabel} deleted successfully.`, + successMessage: `Disk ${secondaryLabel} successfully deleted.`, }); case 'linode_snapshot': return toastSuccessAndFailure({ @@ -193,24 +193,12 @@ export const ToastNotifications = () => { * We don't know if it's possible for these to fail, * but are including handling to be safe. */ - case 'linode_config_delete': - return toastSuccessAndFailure({ - enqueueSnackbar, - eventStatus: event.status, - failureMessage: `Error deleting config ${secondaryLabel}.`, - }); - case 'linode_config_create': - return toastSuccessAndFailure({ - enqueueSnackbar, - eventStatus: event.status, - failureMessage: `Error creating config ${secondaryLabel}.`, - }); case 'linode_clone': return toastSuccessAndFailure({ enqueueSnackbar, eventStatus: event.status, failureMessage: `Error cloning Linode ${label}.`, - successMessage: `Linode ${label} has been cloned successfully to ${secondaryLabel}.`, + successMessage: `Linode ${label} successfully cloned to ${secondaryLabel}.`, }); case 'linode_migrate_datacenter': case 'linode_migrate': @@ -218,44 +206,14 @@ export const ToastNotifications = () => { enqueueSnackbar, eventStatus: event.status, failureMessage: `Error migrating Linode ${label}.`, - successMessage: `Linode ${label} has been migrated successfully.`, + successMessage: `Linode ${label} successfully migrated.`, }); case 'linode_resize': return toastSuccessAndFailure({ enqueueSnackbar, eventStatus: event.status, failureMessage: `Error resizing Linode ${label}.`, - successMessage: `Linode ${label} has been resized successfully.`, - }); - case 'firewall_enable': - return toastSuccessAndFailure({ - enqueueSnackbar, - eventStatus: event.status, - failureMessage: `Error enabling Firewall ${label}.`, - }); - case 'firewall_disable': - return toastSuccessAndFailure({ - enqueueSnackbar, - eventStatus: event.status, - failureMessage: `Error disabling Firewall ${label}.`, - }); - case 'firewall_delete': - return toastSuccessAndFailure({ - enqueueSnackbar, - eventStatus: event.status, - failureMessage: `Error deleting Firewall ${label}.`, - }); - case 'firewall_device_add': - return toastSuccessAndFailure({ - enqueueSnackbar, - eventStatus: event.status, - failureMessage: `Error adding ${secondaryLabel} to Firewall ${label}.`, - }); - case 'firewall_device_remove': - return toastSuccessAndFailure({ - enqueueSnackbar, - eventStatus: event.status, - failureMessage: `Error removing ${secondaryLabel} from Firewall ${label}.`, + successMessage: `Linode ${label} successfully resized.`, }); case 'longviewclient_create': return toastSuccessAndFailure({