diff --git a/packages/manager/.changeset/pr-10841-tech-stories-1724763185078.md b/packages/manager/.changeset/pr-10841-tech-stories-1724763185078.md new file mode 100644 index 00000000000..6173b8d24de --- /dev/null +++ b/packages/manager/.changeset/pr-10841-tech-stories-1724763185078.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Tech Stories +--- + +Refactor useToastNotification async toasts ([#10841](https://github.com/linode/manager/pull/10841)) diff --git a/packages/manager/cypress/e2e/core/images/machine-image-upload.spec.ts b/packages/manager/cypress/e2e/core/images/machine-image-upload.spec.ts index fbd28300d66..25339973135 100644 --- a/packages/manager/cypress/e2e/core/images/machine-image-upload.spec.ts +++ b/packages/manager/cypress/e2e/core/images/machine-image-upload.spec.ts @@ -81,9 +81,7 @@ const eventIntercept = ( * @param message - Expected failure message. */ const assertFailed = (label: string, id: string, message: string) => { - ui.toast.assertMessage( - `There was a problem uploading image ${label}: ${message}` - ); + ui.toast.assertMessage(`Image ${label} could not be uploaded: ${message}`); cy.get(`[data-qa-image-cell="${id}"]`).within(() => { fbtVisible(label); diff --git a/packages/manager/cypress/e2e/core/images/smoke-create-image.spec.ts b/packages/manager/cypress/e2e/core/images/smoke-create-image.spec.ts index f6e0a0e1cdc..1ee83565388 100644 --- a/packages/manager/cypress/e2e/core/images/smoke-create-image.spec.ts +++ b/packages/manager/cypress/e2e/core/images/smoke-create-image.spec.ts @@ -107,6 +107,6 @@ describe('create image (using mocks)', () => { cy.wait('@getEvents'); // Verify a success toast shows - ui.toast.assertMessage('Image My Config successfully created.'); + ui.toast.assertMessage('Image My Config has been created.'); }); }); 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 cd9a78f2258..c64e43abba6 100644 --- a/packages/manager/cypress/e2e/core/linodes/clone-linode.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/clone-linode.spec.ts @@ -106,7 +106,7 @@ describe('clone linode', () => { ui.toast.assertMessage(`Your Linode ${newLinodeLabel} is being created.`); ui.toast.assertMessage( - `Linode ${linode.label} successfully cloned to ${newLinodeLabel}.`, + `Linode ${linode.label} has been cloned to ${newLinodeLabel}.`, { timeout: CLONE_TIMEOUT } ); }); 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 a6ea27fbc80..2e73ca80de0 100644 --- a/packages/manager/cypress/e2e/core/linodes/linode-config.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/linode-config.spec.ts @@ -381,7 +381,7 @@ describe('Linode Config management', () => { // Confirm toast message and that UI updates to reflect clone in progress. ui.toast.assertMessage( - `Linode ${sourceLinode.label} successfully cloned to ${destLinode.label}.` + `Linode ${sourceLinode.label} has been cloned to ${destLinode.label}.` ); cy.findByText(/CLONING \(\d+%\)/).should('be.visible'); }); 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 6cd87caaf14..0e256bbbb41 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,9 @@ 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} successfully deleted.`); + ui.toast.assertMessage( + `Disk ${diskName} on Linode ${linode.label} has been deleted.` + ); cy.findByLabelText('List of Disks').within(() => { cy.contains(diskName).should('not.exist'); }); @@ -209,7 +211,9 @@ 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} successfully resized.`); + ui.toast.assertMessage( + `Disk ${diskName} on Linode ${linode.label} has been 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 0d003ddd864..ba2107cd62a 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,9 @@ describe('resize linode', () => { }); // Wait until the disk resize is done. - ui.toast.assertMessage(`Disk ${diskName} successfully resized.`); + ui.toast.assertMessage( + `Disk ${diskName} on Linode ${linode.label} has been resized.` + ); interceptLinodeResize(linode.id).as('linodeResize'); cy.visitWithLogin(`/linodes/${linode.id}?resize=true`); diff --git a/packages/manager/cypress/e2e/core/volumes/attach-volume.spec.ts b/packages/manager/cypress/e2e/core/volumes/attach-volume.spec.ts index 6e2835aa20b..76c2ec63a0f 100644 --- a/packages/manager/cypress/e2e/core/volumes/attach-volume.spec.ts +++ b/packages/manager/cypress/e2e/core/volumes/attach-volume.spec.ts @@ -114,7 +114,9 @@ describe('volume attach and detach flows', () => { // Confirm that volume has been attached to Linode. cy.wait('@attachVolume').its('response.statusCode').should('eq', 200); - ui.toast.assertMessage(`Volume ${volume.label} successfully attached.`); + ui.toast.assertMessage( + `Volume ${volume.label} has been attached to Linode ${linode.label}.` + ); cy.findByText(volume.label) .should('be.visible') .closest('tr') diff --git a/packages/manager/cypress/e2e/core/volumes/delete-volume.spec.ts b/packages/manager/cypress/e2e/core/volumes/delete-volume.spec.ts index fe441dcf865..7897d0c7f2c 100644 --- a/packages/manager/cypress/e2e/core/volumes/delete-volume.spec.ts +++ b/packages/manager/cypress/e2e/core/volumes/delete-volume.spec.ts @@ -87,7 +87,7 @@ describe('volume delete flow', () => { // Confirm that volume is deleted. cy.wait('@deleteVolume').its('response.statusCode').should('eq', 200); cy.findByText(volume.label).should('not.exist'); - ui.toast.assertMessage('Volume successfully deleted.'); + ui.toast.assertMessage(`Volume ${volume.label} has been deleted.`); } ); }); diff --git a/packages/manager/cypress/e2e/core/volumes/upgrade-volume.spec.ts b/packages/manager/cypress/e2e/core/volumes/upgrade-volume.spec.ts index 59436b4e983..f6d807e4c15 100644 --- a/packages/manager/cypress/e2e/core/volumes/upgrade-volume.spec.ts +++ b/packages/manager/cypress/e2e/core/volumes/upgrade-volume.spec.ts @@ -87,7 +87,7 @@ describe('volume upgrade/migration', () => { cy.findByText('active').should('be.visible'); - ui.toast.assertMessage(`Volume ${volume.label} successfully upgraded.`); + ui.toast.assertMessage(`Volume ${volume.label} has been migrated to NVMe.`); }); it('can upgrade an attached volume from the volumes landing page', () => { @@ -178,7 +178,7 @@ describe('volume upgrade/migration', () => { cy.findByText('active').should('be.visible'); - ui.toast.assertMessage(`Volume ${volume.label} successfully upgraded.`); + ui.toast.assertMessage(`Volume ${volume.label} has been migrated to NVMe.`); }); it('can upgrade an attached volume from the linode details page', () => { @@ -265,6 +265,6 @@ describe('volume upgrade/migration', () => { cy.findByText('active').should('be.visible'); - ui.toast.assertMessage(`Volume ${volume.label} successfully upgraded.`); + ui.toast.assertMessage(`Volume ${volume.label} has been migrated to NVMe.`); }); }); diff --git a/packages/manager/src/features/Events/asyncToasts.test.tsx b/packages/manager/src/features/Events/asyncToasts.test.tsx new file mode 100644 index 00000000000..0ed1b44a26a --- /dev/null +++ b/packages/manager/src/features/Events/asyncToasts.test.tsx @@ -0,0 +1,140 @@ +import { createToast } from './asyncToasts'; + +describe('createToast', () => { + it('should handle case with both failure and success options as true or empty objects', () => { + const trueOptions = { failure: true, success: true }; + const emptyObjectOptions = { failure: {}, success: {} }; + + [trueOptions, emptyObjectOptions].forEach((options) => { + const result = createToast(options); + const expected = { + failure: { + message: expect.any(Function), + }, + success: { + message: expect.any(Function), + }, + }; + + expect(result).toEqual(expected); + }); + }); + + it('should handle case with only failure option as true or empty object or with success as false', () => { + const scenarios = [ + { failure: true }, + { failure: {} }, + { failure: true, success: false }, + { failure: {}, success: false }, + ]; + + scenarios.forEach((options) => { + const result = createToast(options); + const expected = { + failure: { + message: expect.any(Function), + }, + }; + + expect(result).toEqual(expected); + }); + }); + + it('should handle case with only success option as true or empty object or with failure as false', () => { + const scenarios = [ + { success: true }, + { success: {} }, + { failure: false, success: true }, + { failure: false, success: {} }, + ]; + + scenarios.forEach((options) => { + const result = createToast(options); + const expected = { + success: { + message: expect.any(Function), + }, + }; + + expect(result).toEqual(expected); + }); + }); + + it('should return an empty object if both failure and success are false or not provided', () => { + const falseOptions = { failure: false, success: false }; + const emptyOptions = {}; + [falseOptions, emptyOptions].forEach((options) => { + const result = createToast(options); + + expect(result).toEqual({}); + }); + }); + + it('should handle cases with specific values for only failure or success options', () => { + // Only the failure options with specific values + const failureOnlyOptions = { + failure: { persist: true }, + }; + const result1 = createToast(failureOnlyOptions); + const expected1 = { + failure: { + message: expect.any(Function), + persist: true, + }, + }; + expect(result1).toEqual(expected1); + + // Only the success options with specific values + const successOnlyOptions = { + success: { invertVariant: true, persist: false }, + }; + const result2 = createToast(successOnlyOptions); + const expected2 = { + success: { + invertVariant: true, + message: expect.any(Function), + persist: false, + }, + }; + expect(result2).toEqual(expected2); + }); + + it('should handle case with both failure and success options with specific values', () => { + const options1 = { + failure: { invertVariant: true, persist: true }, + success: { invertVariant: true, persist: false }, + }; + const result1 = createToast(options1); + const expected1 = { + failure: { + invertVariant: true, + message: expect.any(Function), + persist: true, + }, + success: { + invertVariant: true, + message: expect.any(Function), + persist: false, + }, + }; + expect(result1).toEqual(expected1); + + const options2 = { + failure: { persist: true }, + success: { invertVariant: true }, + }; + + const result2 = createToast(options2); + const expected2 = { + failure: { + message: expect.any(Function), + persist: true, + }, + success: { + invertVariant: true, + message: expect.any(Function), + }, + }; + expect(result2).toEqual(expected2); + }); +}); diff --git a/packages/manager/src/features/Events/asyncToasts.tsx b/packages/manager/src/features/Events/asyncToasts.tsx new file mode 100644 index 00000000000..5c3cdc9553e --- /dev/null +++ b/packages/manager/src/features/Events/asyncToasts.tsx @@ -0,0 +1,95 @@ +import { getEventMessage } from './utils'; + +import type { Event, EventAction } from '@linode/api-v4'; + +interface ToastMessage { + /** + * If true, the toast will be displayed with an error variant for success messages \ + * or a success variant for error messages. + */ + invertVariant?: boolean; + message: ((event: Event) => JSX.Element | null | string | undefined) | string; + persist?: boolean; +} + +interface Toast { + failure?: ToastMessage; + success?: ToastMessage; +} + +type Toasts = { + [key in EventAction]?: Toast; +}; + +interface ToastOption { + invertVariant?: boolean; + persist?: boolean; +} + +interface ToastOptions { + failure?: ToastOption | boolean; + success?: ToastOption | boolean; +} + +export const createToast = (options: ToastOptions) => { + const toastConfig: Toast = {}; + + const getToastMessage = (option: ToastOption | boolean): ToastMessage => { + const message: ToastMessage['message'] = (e) => getEventMessage(e); + + if (typeof option === 'boolean') { + return { message }; + } + + return { + message, + ...(option.invertVariant !== undefined && { + invertVariant: option.invertVariant, + }), + ...(option.persist !== undefined && { persist: option.persist }), + }; + }; + + if (options.failure) { + toastConfig.failure = getToastMessage(options.failure); + } + + if (options.success) { + toastConfig.success = getToastMessage(options.success); + } + + return toastConfig; +}; + +/** + * This constant defines toast notifications that will be displayed + * when our events polling system gets a new event. + * + * Use this feature to notify users of *asynchronous tasks* such as migrating a Linode. + * + * DO NOT use this feature to notify the user of tasks like changing the label of a Linode. + * Toasts for that can be handled at the time of making the PUT request. + */ +export const toasts: Toasts = { + backups_restore: createToast({ failure: { persist: true } }), + disk_delete: createToast({ failure: false, success: true }), + disk_imagize: createToast({ failure: { persist: true }, success: true }), + disk_resize: createToast({ failure: { persist: true }, success: true }), + image_delete: createToast({ failure: true, success: true }), + image_upload: createToast({ failure: { persist: true }, success: true }), + linode_clone: createToast({ failure: true, success: true }), + linode_migrate: createToast({ failure: true, success: true }), + linode_migrate_datacenter: createToast({ failure: true, success: true }), + linode_resize: createToast({ failure: true, success: true }), + linode_snapshot: createToast({ failure: { persist: true } }), + longviewclient_create: createToast({ failure: true, success: true }), + tax_id_invalid: createToast({ + failure: true, + success: { invertVariant: true, persist: true }, + }), + volume_attach: createToast({ failure: true, success: true }), + volume_create: createToast({ failure: true, success: true }), + volume_delete: createToast({ failure: true, success: true }), + volume_detach: createToast({ failure: true, success: true }), + volume_migrate: createToast({ failure: true, success: true }), +}; diff --git a/packages/manager/src/features/Events/factories/backup.tsx b/packages/manager/src/features/Events/factories/backup.tsx index 31344a45197..0e45776e06d 100644 --- a/packages/manager/src/features/Events/factories/backup.tsx +++ b/packages/manager/src/features/Events/factories/backup.tsx @@ -22,11 +22,12 @@ export const backup: PartialEventMap<'backups'> = { backups_restore: { failed: (e) => ( <> - Backup could not be restored for + Backup could not be restored for{' '} {e.entity!.label}.{' '} - Learn more about limits and considerations. + Learn more about limits and considerations + . ), finished: (e) => ( diff --git a/packages/manager/src/features/Events/factories/disk.tsx b/packages/manager/src/features/Events/factories/disk.tsx index 8168cdd76e4..761fceafb21 100644 --- a/packages/manager/src/features/Events/factories/disk.tsx +++ b/packages/manager/src/features/Events/factories/disk.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { Link } from 'src/components/Link'; +import { sendLinodeDiskEvent } from 'src/utilities/analytics/customEventAnalytics'; import { EventLink } from '../EventLink'; @@ -76,32 +77,37 @@ export const disk: PartialEventMap<'disk'> = { disk_duplicate: { failed: (e) => ( <> - Disk on Linode could{' '} - not be duplicated. + Disk on Linode{' '} + could not be{' '} + duplicated. ), finished: (e) => ( <> - Disk on Linode has been{' '} - duplicated. + Disk on Linode{' '} + has been duplicated + . ), notification: (e) => ( <> - Disk on Linode has been{' '} - duplicated. + Disk on Linode{' '} + has been duplicated + . ), scheduled: (e) => ( <> - Disk on Linode is scheduled to be{' '} + Disk on Linode{' '} + is scheduled to be{' '} duplicated. ), started: (e) => ( <> - Disk on Linode is being{' '} - duplicated. + Disk on Linode{' '} + is being duplicated + . ), }, @@ -111,7 +117,7 @@ export const disk: PartialEventMap<'disk'> = { Image could{' '} not be created.{' '} - Learn more about image technical specifications. + Learn more about image technical specifications . @@ -138,36 +144,46 @@ export const disk: PartialEventMap<'disk'> = { disk_resize: { failed: (e) => ( <> - A disk on Linode could{' '} - not be resized.{' '} - + Disk on Linode{' '} + could not be{' '} + resized.{' '} + { + sendLinodeDiskEvent( + 'Resize', + 'Click:link', + 'Disk resize failed toast' + ); + }} + to="https://www.linode.com/docs/products/compute/compute-instances/guides/disks-and-storage/" + > Learn more ), - finished: (e) => ( <> - A disk on Linode has been{' '} - resized. + Disk on Linode{' '} + has been resized. ), notification: (e) => ( <> - A disk on Linode has been{' '} - resized. + Disk on Linode{' '} + has been resized. ), scheduled: (e) => ( <> - A disk on Linode is scheduled to be{' '} + Disk on Linode{' '} + is scheduled to be{' '} resized. ), started: (e) => ( <> - A disk on Linode is being{' '} - resized. + Disk on Linode{' '} + is being resized. ), }, diff --git a/packages/manager/src/features/Events/factories/image.tsx b/packages/manager/src/features/Events/factories/image.tsx index dbe4ef39cf2..a9912b6ab87 100644 --- a/packages/manager/src/features/Events/factories/image.tsx +++ b/packages/manager/src/features/Events/factories/image.tsx @@ -53,8 +53,7 @@ export const image: PartialEventMap<'image'> = { finished: (e) => ( <> - Image has been{' '} - uploaded. + Image is now available. ), notification: (e) => ( diff --git a/packages/manager/src/hooks/useToastNotifications.tsx b/packages/manager/src/hooks/useToastNotifications.tsx index 51633c484b3..8198aa0bb9c 100644 --- a/packages/manager/src/hooks/useToastNotifications.tsx +++ b/packages/manager/src/hooks/useToastNotifications.tsx @@ -1,220 +1,21 @@ import { useSnackbar } from 'notistack'; -import * as React from 'react'; -import { Link } from 'src/components/Link'; -import { SupportLink } from 'src/components/SupportLink'; -import { Typography } from 'src/components/Typography'; -import { sendLinodeDiskEvent } from 'src/utilities/analytics/customEventAnalytics'; +import { toasts } from 'src/features/Events/asyncToasts'; -import type { Event, EventAction } from '@linode/api-v4'; +import type { Event } from '@linode/api-v4'; export const getLabel = (event: Event) => event.entity?.label ?? ''; export const getSecondaryLabel = (event: Event) => event.secondary_entity?.label ?? ''; -const formatLink = (text: string, link: string, handleClick?: () => void) => { - return ( - - {text} - - ); -}; - -interface ToastMessage { - link?: JSX.Element; - message: ((event: Event) => string | undefined) | string; - persist?: boolean; -} - -interface Toast { - failure?: ToastMessage; - /** - * If true, the toast will be displayed with an error variant. - */ - invertVariant?: boolean; - success?: ToastMessage; -} - -type Toasts = { - [key in EventAction]?: Toast; -}; - -/** - * This constant defines toast notifications that will be displayed - * when our events polling system gets a new event. - * - * Use this feature to notify users of *asynchronous tasks* such as migrating a Linode. - * - * DO NOT use this feature to notify the user of tasks like changing the label of a Linode. - * Toasts for that can be handled at the time of making the PUT request. - */ -const toasts: Toasts = { - backups_restore: { - failure: { - link: formatLink( - 'Learn more about limits and considerations.', - 'https://www.linode.com/docs/products/storage/backups/#limits-and-considerations' - ), - message: (e) => `Backup restoration failed for ${getLabel(e)}.`, - persist: true, - }, - }, - disk_delete: { - failure: { - message: (e) => - `Unable to delete disk ${getSecondaryLabel(e)} ${ - getLabel(e) ? ` on ${getLabel(e)}` : '' - }. Is it attached to a configuration profile that is in use?`, - }, - success: { - message: (e) => `Disk ${getSecondaryLabel(e)} successfully deleted.`, - }, - }, - disk_imagize: { - failure: { - link: formatLink( - 'Learn more about image technical specifications.', - 'https://www.linode.com/docs/products/tools/images/#technical-specifications' - ), - message: (e) => - `There was a problem creating Image ${getSecondaryLabel(e)}.`, - persist: true, - }, - - success: { - message: (e) => `Image ${getSecondaryLabel(e)} successfully created.`, - }, - }, - disk_resize: { - failure: { - link: formatLink( - 'Learn more about resizing restrictions.', - 'https://www.linode.com/docs/products/compute/compute-instances/guides/disks-and-storage/', - () => - sendLinodeDiskEvent( - 'Resize', - 'Click:link', - 'Disk resize failed toast' - ) - ), - message: `Disk resize failed.`, - persist: true, - }, - success: { - message: (e) => `Disk ${getSecondaryLabel(e)} successfully resized.`, - }, - }, - image_delete: { - failure: { message: (e) => `Error deleting Image ${getLabel(e)}.` }, - success: { message: (e) => `Image ${getLabel(e)} successfully deleted.` }, - }, - image_upload: { - failure: { - message: (e) => { - const isDeletion = e.message === 'Upload canceled.'; - - if (isDeletion) { - return undefined; - } - - return `There was a problem uploading image ${getLabel( - e - )}: ${e.message?.replace(/(\d+)/g, '$1 MB')}`; - }, - persist: true, - }, - success: { message: (e) => `Image ${getLabel(e)} is now available.` }, - }, - linode_clone: { - failure: { message: (e) => `Error cloning Linode ${getLabel(e)}.` }, - success: { - message: (e) => - `Linode ${getLabel(e)} successfully cloned to ${getSecondaryLabel(e)}.`, - }, - }, - linode_migrate: { - failure: { message: (e) => `Error migrating Linode ${getLabel(e)}.` }, - success: { message: (e) => `Linode ${getLabel(e)} successfully migrated.` }, - }, - linode_migrate_datacenter: { - failure: { message: (e) => `Error migrating Linode ${getLabel(e)}.` }, - success: { message: (e) => `Linode ${getLabel(e)} successfully migrated.` }, - }, - linode_resize: { - failure: { message: (e) => `Error resizing Linode ${getLabel(e)}.` }, - success: { message: (e) => `Linode ${getLabel(e)} successfully resized.` }, - }, - linode_snapshot: { - failure: { - link: formatLink( - 'Learn more about limits and considerations.', - 'https://www.linode.com/docs/products/storage/backups/#limits-and-considerations' - ), - message: (e) => `Snapshot backup failed on Linode ${getLabel(e)}.`, - persist: true, - }, - }, - longviewclient_create: { - failure: { - message: (e) => `Error creating Longview Client ${getLabel(e)}.`, - }, - success: { - message: (e) => `Longview Client ${getLabel(e)} successfully created.`, - }, - }, - tax_id_invalid: { - failure: { message: 'Error validating Tax Identification Number.' }, - invertVariant: true, - success: { - message: - 'Tax Identification Number could not be verified. Please check your Tax ID for accuracy or contact support for assistance.', - persist: true, - }, - }, - volume_attach: { - failure: { message: (e) => `Error attaching Volume ${getLabel(e)}.` }, - success: { message: (e) => `Volume ${getLabel(e)} successfully attached.` }, - }, - volume_create: { - failure: { message: (e) => `Error creating Volume ${getLabel(e)}.` }, - success: { message: (e) => `Volume ${getLabel(e)} successfully created.` }, - }, - volume_delete: { - failure: { message: 'Error deleting Volume.' }, - success: { message: 'Volume successfully deleted.' }, - }, - volume_detach: { - failure: { message: (e) => `Error detaching Volume ${getLabel(e)}.` }, - success: { message: (e) => `Volume ${getLabel(e)} successfully detached.` }, - }, - volume_migrate: { - failure: { message: (e) => `Error upgrading Volume ${getLabel(e)}.` }, - success: { message: (e) => `Volume ${getLabel(e)} successfully upgraded.` }, - }, -}; const getToastMessage = ( - toastMessage: ((event: Event) => string | undefined) | string, + toastMessage: + | ((event: Event) => JSX.Element | null | string | undefined) + | string, event: Event -): string | undefined => +): JSX.Element | null | string | undefined => typeof toastMessage === 'function' ? toastMessage(event) : toastMessage; -const createFormattedMessage = ( - message: string | undefined, - link: JSX.Element | undefined, - hasSupportLink: boolean -) => ( - - {message?.replace(/ contact Support/i, '') ?? message} - {hasSupportLink && ( - <> -   - . - - )} - {link && <> {link}} - -); - export const useToastNotifications = (): { handleGlobalToast: (event: Event) => void; } => { @@ -229,41 +30,25 @@ export const useToastNotifications = (): { const isSuccessEvent = ['finished', 'notification'].includes(event.status); if (isSuccessEvent && toastInfo.success) { - const { link, message, persist } = toastInfo.success; + const { invertVariant, message, persist } = toastInfo.success; const successMessage = getToastMessage(message, event); if (successMessage) { - const formattedSuccessMessage = createFormattedMessage( - successMessage, - link, - false - ); - - enqueueSnackbar(formattedSuccessMessage, { + enqueueSnackbar(successMessage, { persist: persist ?? false, - variant: toastInfo.invertVariant ? 'error' : 'success', + variant: invertVariant ? 'error' : 'success', }); } } if (event.status === 'failed' && toastInfo.failure) { - const { link, message, persist } = toastInfo.failure; + const { invertVariant, message, persist } = toastInfo.failure; const failureMessage = getToastMessage(message, event); if (failureMessage) { - const hasSupportLink = failureMessage - .toLowerCase() - .includes('contact support'); - - const formattedFailureMessage = createFormattedMessage( - failureMessage, - link, - hasSupportLink - ); - - enqueueSnackbar(formattedFailureMessage, { + enqueueSnackbar(failureMessage, { persist: persist ?? false, - variant: toastInfo.invertVariant ? 'success' : 'error', + variant: invertVariant ? 'success' : 'error', }); } }