From 3b6445f5906b3ce3d2212cac45a56379f5b8faf6 Mon Sep 17 00:00:00 2001 From: Valentin Kirilov Date: Wed, 17 Sep 2025 08:51:12 +0300 Subject: [PATCH 01/16] fix(fe): rework the infinite notifications - update the interface and the way we serve the data for the infinite notification, so it will resemble more closely the expected format by Redis UI - rework the way we show the default "X" button for dismissing notifications - update the variant to reflect the expected design re #RI-7254 --- .../components/base/display/toast/RiToast.tsx | 26 +++++-------------- .../notifications/Notifications.tsx | 18 ++++++++++--- redisinsight/ui/src/slices/interfaces/app.ts | 6 ++++- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/redisinsight/ui/src/components/base/display/toast/RiToast.tsx b/redisinsight/ui/src/components/base/display/toast/RiToast.tsx index ed8deb4e64..b31915deb9 100644 --- a/redisinsight/ui/src/components/base/display/toast/RiToast.tsx +++ b/redisinsight/ui/src/components/base/display/toast/RiToast.tsx @@ -5,24 +5,25 @@ import { ToastContentParams, ToastOptions, } from '@redis-ui/components' +import { ToastOptions as RcToastOptions } from 'react-toastify' import { CommonProps } from 'uiSrc/components/base/theme/types' -import { CancelIcon } from 'uiSrc/components/base/icons' import { ColorText, Text } from 'uiSrc/components/base/text' import { Spacer } from '../../layout' type RiToastProps = React.ComponentProps export const RiToast = (props: RiToastProps) => -type RiToastType = ToastContentParams & +export type RiToastType = ToastContentParams & CommonProps & { onClose?: VoidFunction } export const riToast = ( - { onClose, actions, message, ...content }: RiToastType, + { onClose, message, ...content }: RiToastType, options?: ToastOptions | undefined, ) => { const toastContent: ToastContentParams = { + showCloseButton: false, ...content, } @@ -44,26 +45,11 @@ export const riToast = ( toastContent.message = message } - if (onClose) { - toastContent.showCloseButton = false - toastContent.actions = { - ...actions, - secondary: { - label: '', - icon: CancelIcon, - closes: true, - onClick: onClose, - }, - } - } - if (actions && !onClose) { - toastContent.showCloseButton = false - toastContent.actions = actions - } - const toastOptions: ToastOptions = { + const toastOptions: ToastOptions & RcToastOptions = { ...options, delay: 100, closeOnClick: false, + onClose, } return toast(, toastOptions) } diff --git a/redisinsight/ui/src/components/notifications/Notifications.tsx b/redisinsight/ui/src/components/notifications/Notifications.tsx index 28a4a7c987..8d382c7820 100644 --- a/redisinsight/ui/src/components/notifications/Notifications.tsx +++ b/redisinsight/ui/src/components/notifications/Notifications.tsx @@ -142,12 +142,22 @@ const Notifications = () => { infiniteToastIdsRef.current.delete(toastId) }, 50) }) - data.forEach((message: InfiniteMessage) => { - const { id, Inner, className = '' } = message + data.forEach((notification: InfiniteMessage) => { + const { + id, + message, + description, + actions, + Inner, + className = '', + } = notification const toastId = riToast( { className: cx(styles.infiniteMessage, className), - description: Inner, + message: message, + description: description || Inner, // TODO: Remove inner later + actions, + showCloseButton: true, onClose: () => { switch (id) { case InfiniteMessagesIds.oAuthProgress: @@ -178,7 +188,7 @@ const Notifications = () => { }, }, { - variant: riToast.Variant.Informative, + variant: riToast.Variant.Notice, autoClose: ONE_HOUR, toastId: id, }, diff --git a/redisinsight/ui/src/slices/interfaces/app.ts b/redisinsight/ui/src/slices/interfaces/app.ts index 76bbe0a683..6ecf18814a 100644 --- a/redisinsight/ui/src/slices/interfaces/app.ts +++ b/redisinsight/ui/src/slices/interfaces/app.ts @@ -13,6 +13,7 @@ import { import { ConfigDBStorageItem } from 'uiSrc/constants/storage' import { GetServerInfoResponse } from 'apiSrc/modules/server/dto/server.dto' import { RedisString as RedisStringAPI } from 'apiSrc/common/constants/redis-string' +import { RiToastType } from 'uiSrc/components/base/display/toast/RiToast' export interface CustomError { details?: any[] @@ -243,8 +244,11 @@ export interface IGlobalNotification { export interface InfiniteMessage { id: string - Inner: string | JSX.Element + Inner?: string | JSX.Element // TODO: Remove inner later className?: string + message?: RiToastType['message'] + description?: RiToastType['description'] + actions?: RiToastType['actions'] } export interface StateAppNotifications { From 72e26b508af5fd310200b6e3a0c93d3f3a134f9c Mon Sep 17 00:00:00 2001 From: Valentin Kirilov Date: Wed, 17 Sep 2025 08:53:11 +0300 Subject: [PATCH 02/16] refactor(ui): update "app update available" notification re #RI-7254 --- .../InfiniteMessages.spec.tsx | 72 +++++++++++++++---- .../infinite-messages/InfiniteMessages.tsx | 50 +++++-------- 2 files changed, 77 insertions(+), 45 deletions(-) diff --git a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.spec.tsx b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.spec.tsx index 4cbc864e68..fc8d248a98 100644 --- a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.spec.tsx +++ b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.spec.tsx @@ -2,8 +2,37 @@ import React from 'react' import { fireEvent, render, screen } from 'uiSrc/utils/test-utils' import { OAuthProvider } from 'uiSrc/components/oauth/oauth-select-plan/constants' +import notificationsReducer, { + addInfiniteNotification, +} from 'uiSrc/slices/app/notifications' +import { combineReducers, configureStore } from '@reduxjs/toolkit' +import { InfiniteMessage } from 'uiSrc/slices/interfaces' +import Notifications from '../../Notifications' import { INFINITE_MESSAGES } from './InfiniteMessages' +const createTestStore = () => + configureStore({ + reducer: combineReducers({ + app: combineReducers({ notifications: notificationsReducer }), + }), + middleware: (getDefaultMiddleware) => + getDefaultMiddleware({ serializableCheck: false }), + }) + +const renderToast = (notification: InfiniteMessage) => { + const store = createTestStore() + + render( + <> + {/* */} + + , + { store }, + ) + + store.dispatch(addInfiniteNotification(notification)) +} + describe('INFINITE_MESSAGES', () => { describe('SUCCESS_CREATE_DB', () => { it('should render message', () => { @@ -157,23 +186,42 @@ describe('INFINITE_MESSAGES', () => { }) describe('APP_UPDATE_AVAILABLE', () => { - it('should render message', () => { - const { Inner } = INFINITE_MESSAGES.APP_UPDATE_AVAILABLE('1', jest.fn()) - expect(render(<>{Inner})).toBeTruthy() + it('should render message', async () => { + const version = '' + const onSuccess = jest.fn() + + renderToast(INFINITE_MESSAGES.APP_UPDATE_AVAILABLE(version, onSuccess)) + + // Wait for the notification to appear + const title = await screen.findByText('New version is now available') + const description = await screen.findByText( + /With Redis Insight you have access to new useful features and optimizations\.\s*Restart Redis Insight to install updates\./, + ) + const restartButton = await screen.findByRole('button', { + name: /Restart/, + }) + const closeButton = await screen.findByRole('button', { name: /close/i }) + + expect(title).toBeInTheDocument() + expect(description).toBeInTheDocument() + expect(restartButton).toBeInTheDocument() + expect(closeButton).toBeInTheDocument() }) - it('should call onSuccess', () => { + it('should call onSuccess when clicking restart button', async () => { + const version = '' const onSuccess = jest.fn() - const { Inner } = INFINITE_MESSAGES.APP_UPDATE_AVAILABLE('1', onSuccess) - render(<>{Inner}) - fireEvent.click(screen.getByTestId('app-restart-btn')) - fireEvent.mouseUp(screen.getByTestId('app-update-available-notification')) - fireEvent.mouseDown( - screen.getByTestId('app-update-available-notification'), - ) + renderToast(INFINITE_MESSAGES.APP_UPDATE_AVAILABLE(version, onSuccess)) - expect(onSuccess).toBeCalled() + const restartButton = await screen.findByRole('button', { + name: /Restart/, + }) + expect(restartButton).toBeInTheDocument() + + fireEvent.click(restartButton) + + expect(onSuccess).toHaveBeenCalled() }) }) }) diff --git a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx index a63276acde..38e0d2d51c 100644 --- a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx +++ b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx @@ -6,7 +6,7 @@ import ExternalLink from 'uiSrc/components/base/external-link' import Divider from 'uiSrc/components/divider/Divider' import { OAuthProviders } from 'uiSrc/components/oauth/oauth-select-plan/constants' -import { CloudSuccessResult } from 'uiSrc/slices/interfaces' +import { CloudSuccessResult, InfiniteMessage } from 'uiSrc/slices/interfaces' import { Maybe } from 'uiSrc/utils' import { getUtmExternalLink } from 'uiSrc/utils/links' @@ -44,7 +44,11 @@ const MANAGE_DB_LINK = getUtmExternalLink(EXTERNAL_LINKS.cloudConsole, { medium: UTM_MEDIUMS.Main, }) -export const INFINITE_MESSAGES = { +// TODO: Refactor this type definition to work with the real parameters and their types we use in each message +export const INFINITE_MESSAGES: Record< + string, + (...args: any[]) => InfiniteMessage +> = { AUTHENTICATING: () => ({ id: InfiniteMessagesIds.oAuthProgress, Inner: ( @@ -353,39 +357,19 @@ export const INFINITE_MESSAGES = { }), APP_UPDATE_AVAILABLE: (version: string, onSuccess?: () => void) => ({ id: InfiniteMessagesIds.appUpdateAvailable, - Inner: ( -
{ - e.preventDefault() - }} - onMouseUp={(e) => { - e.preventDefault() - }} - data-testid="app-update-available-notification" - > - - New version is now available - - - <> - With Redis Insight - {` ${version} `} - you have access to new useful features and optimizations. -
- Restart Redis Insight to install updates. - -
+ message: 'New version is now available', + description: ( + <> + With Redis Insight {version} you have access to new useful features and + optimizations.
- onSuccess?.()} - data-testid="app-restart-btn" - > - Restart - -
+
+ Restart Redis Insight to install updates. + ), + actions: { + primary: { label: 'Restart', onClick: () => onSuccess?.() }, + }, }), SUCCESS_DEPLOY_PIPELINE: () => ({ id: InfiniteMessagesIds.pipelineDeploySuccess, From ae2eec9faa6b054fe70ecc637ecfe7ed7046fed1 Mon Sep 17 00:00:00 2001 From: Valentin Kirilov Date: Wed, 17 Sep 2025 15:04:34 +0300 Subject: [PATCH 03/16] refactor(ui): add a way to customize the variant of the infinite notifications re #RI-7254 --- .../ui/src/components/notifications/Notifications.tsx | 3 ++- redisinsight/ui/src/slices/interfaces/app.ts | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/redisinsight/ui/src/components/notifications/Notifications.tsx b/redisinsight/ui/src/components/notifications/Notifications.tsx index 8d382c7820..65236c8efd 100644 --- a/redisinsight/ui/src/components/notifications/Notifications.tsx +++ b/redisinsight/ui/src/components/notifications/Notifications.tsx @@ -150,6 +150,7 @@ const Notifications = () => { actions, Inner, className = '', + variant, } = notification const toastId = riToast( { @@ -188,7 +189,7 @@ const Notifications = () => { }, }, { - variant: riToast.Variant.Notice, + variant: variant ?? riToast.Variant.Notice, autoClose: ONE_HOUR, toastId: id, }, diff --git a/redisinsight/ui/src/slices/interfaces/app.ts b/redisinsight/ui/src/slices/interfaces/app.ts index 6ecf18814a..186aa59fb0 100644 --- a/redisinsight/ui/src/slices/interfaces/app.ts +++ b/redisinsight/ui/src/slices/interfaces/app.ts @@ -13,7 +13,11 @@ import { import { ConfigDBStorageItem } from 'uiSrc/constants/storage' import { GetServerInfoResponse } from 'apiSrc/modules/server/dto/server.dto' import { RedisString as RedisStringAPI } from 'apiSrc/common/constants/redis-string' -import { RiToastType } from 'uiSrc/components/base/display/toast/RiToast' +import { + riToast, + RiToastType, +} from 'uiSrc/components/base/display/toast/RiToast' +import { ToastVariant } from '@redis-ui/components' export interface CustomError { details?: any[] @@ -245,6 +249,7 @@ export interface IGlobalNotification { export interface InfiniteMessage { id: string Inner?: string | JSX.Element // TODO: Remove inner later + variant?: ToastVariant className?: string message?: RiToastType['message'] description?: RiToastType['description'] From 8a7b9f6ea5661cc4ddb6bda64a14e503fd369d2f Mon Sep 17 00:00:00 2001 From: Valentin Kirilov Date: Thu, 18 Sep 2025 09:26:03 +0300 Subject: [PATCH 04/16] fix(ui): update "successfully deployed pipeline" toast notification re #RI-7254 --- .../InfiniteMessages.spec.tsx | 17 ++++++ .../infinite-messages/InfiniteMessages.tsx | 55 +++++-------------- 2 files changed, 31 insertions(+), 41 deletions(-) diff --git a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.spec.tsx b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.spec.tsx index fc8d248a98..8cd89db6ff 100644 --- a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.spec.tsx +++ b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.spec.tsx @@ -224,4 +224,21 @@ describe('INFINITE_MESSAGES', () => { expect(onSuccess).toHaveBeenCalled() }) }) + + describe('SUCCESS_DEPLOY_PIPELINE', () => { + it('should render message', async () => { + renderToast(INFINITE_MESSAGES.SUCCESS_DEPLOY_PIPELINE()) + + // Wait for the notification to appear + const title = await screen.findByText('Congratulations!') + const description = await screen.findByText( + /Deployment completed successfully!\s*Check out the pipeline statistics page\./, + ) + const closeButton = await screen.findByRole('button', { name: /close/i }) + + expect(title).toBeInTheDocument() + expect(description).toBeInTheDocument() + expect(closeButton).toBeInTheDocument() + }) + }) }) diff --git a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx index 38e0d2d51c..cf74c06c10 100644 --- a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx +++ b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx @@ -373,47 +373,20 @@ export const INFINITE_MESSAGES: Record< }), SUCCESS_DEPLOY_PIPELINE: () => ({ id: InfiniteMessagesIds.pipelineDeploySuccess, - className: 'wide', - Inner: ( -
{ - e.preventDefault() - }} - onMouseUp={(e) => { - e.preventDefault() - }} - data-testid="success-deploy-pipeline-notification" - > - - - - - - - Congratulations! - - - Deployment completed successfully! -
- Check out the pipeline statistics page. -
- - {/* // TODO remove display none when statistics page will be available */} - - - {}} - data-testid="notification-connect-db" - > - Statistics - - - -
-
-
+ message: 'Congratulations!', + description: ( + <> + Deployment completed successfully! +
+ Check out the pipeline statistics page. + ), + // TODO enable when statistics page will be available + // actions: { + // primary: { + // label: 'Statistics', + // onClick: () => {}, + // } + // } }), } From b7491d77d456aee3449b508be40e60926d2d7f18 Mon Sep 17 00:00:00 2001 From: Valentin Kirilov Date: Thu, 18 Sep 2025 09:56:19 +0300 Subject: [PATCH 05/16] feat(ui): extend the infinite toasts to support custom icons re #RI-7254 --- redisinsight/ui/src/components/notifications/Notifications.tsx | 2 ++ redisinsight/ui/src/slices/interfaces/app.ts | 1 + 2 files changed, 3 insertions(+) diff --git a/redisinsight/ui/src/components/notifications/Notifications.tsx b/redisinsight/ui/src/components/notifications/Notifications.tsx index 65236c8efd..4444978d89 100644 --- a/redisinsight/ui/src/components/notifications/Notifications.tsx +++ b/redisinsight/ui/src/components/notifications/Notifications.tsx @@ -151,6 +151,7 @@ const Notifications = () => { Inner, className = '', variant, + customIcon, } = notification const toastId = riToast( { @@ -159,6 +160,7 @@ const Notifications = () => { description: description || Inner, // TODO: Remove inner later actions, showCloseButton: true, + customIcon, onClose: () => { switch (id) { case InfiniteMessagesIds.oAuthProgress: diff --git a/redisinsight/ui/src/slices/interfaces/app.ts b/redisinsight/ui/src/slices/interfaces/app.ts index 186aa59fb0..4353522338 100644 --- a/redisinsight/ui/src/slices/interfaces/app.ts +++ b/redisinsight/ui/src/slices/interfaces/app.ts @@ -254,6 +254,7 @@ export interface InfiniteMessage { message?: RiToastType['message'] description?: RiToastType['description'] actions?: RiToastType['actions'] + customIcon?: RiToastType['customIcon'] } export interface StateAppNotifications { From 61797ada43fa56ae6b3ffb11ab6b24de979e8266 Mon Sep 17 00:00:00 2001 From: Valentin Kirilov Date: Thu, 18 Sep 2025 09:56:51 +0300 Subject: [PATCH 06/16] fix(ui): update the visuals of the "authenticating" toast notification re #RI-7254 --- .../InfiniteMessages.spec.tsx | 18 +++++++++++++--- .../infinite-messages/InfiniteMessages.tsx | 21 ++++--------------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.spec.tsx b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.spec.tsx index 8cd89db6ff..b80b877e9b 100644 --- a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.spec.tsx +++ b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.spec.tsx @@ -70,12 +70,24 @@ describe('INFINITE_MESSAGES', () => { ).toHaveTextContent('us-us') }) }) + describe('AUTHENTICATING', () => { - it('should render message', () => { - const { Inner } = INFINITE_MESSAGES.AUTHENTICATING() - expect(render(<>{Inner})).toBeTruthy() + it('should render message', async () => { + renderToast(INFINITE_MESSAGES.AUTHENTICATING()) + + // Wait for the notification to appear + const title = await screen.findByText('Authenticating…') + const description = await screen.findByText( + 'This may take several seconds, but it is totally worth it!', + ) + const closeButton = await screen.findByRole('button', { name: /close/i }) + + expect(title).toBeInTheDocument() + expect(description).toBeInTheDocument() + expect(closeButton).toBeInTheDocument() }) }) + describe('PENDING_CREATE_DB', () => { it('should render message', () => { const { Inner } = INFINITE_MESSAGES.PENDING_CREATE_DB() diff --git a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx index cf74c06c10..5e3c9204d5 100644 --- a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx +++ b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx @@ -5,6 +5,7 @@ import { CloudJobName, CloudJobStep } from 'uiSrc/electron/constants' import ExternalLink from 'uiSrc/components/base/external-link' import Divider from 'uiSrc/components/divider/Divider' import { OAuthProviders } from 'uiSrc/components/oauth/oauth-select-plan/constants' +import { LoaderLargeIcon } from 'uiSrc/components/base/icons' import { CloudSuccessResult, InfiniteMessage } from 'uiSrc/slices/interfaces' @@ -51,23 +52,9 @@ export const INFINITE_MESSAGES: Record< > = { AUTHENTICATING: () => ({ id: InfiniteMessagesIds.oAuthProgress, - Inner: ( -
- - - - - - - Authenticating… - - - This may take several seconds, but it is totally worth it! - - - -
- ), + message: 'Authenticating…', + description: 'This may take several seconds, but it is totally worth it!', + customIcon: LoaderLargeIcon, }), PENDING_CREATE_DB: (step?: CloudJobStep) => ({ id: InfiniteMessagesIds.oAuthProgress, From 83b18553d87012e0c2676f48ac7b454e2839a5ab Mon Sep 17 00:00:00 2001 From: Valentin Kirilov Date: Thu, 18 Sep 2025 11:05:07 +0300 Subject: [PATCH 07/16] fix(ui): update the visuals of the "pending create database" toast notification re #RI-7254 --- .../InfiniteMessages.spec.tsx | 17 ++++-- .../infinite-messages/InfiniteMessages.tsx | 53 ++++++++----------- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.spec.tsx b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.spec.tsx index b80b877e9b..dde8a208fe 100644 --- a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.spec.tsx +++ b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.spec.tsx @@ -89,11 +89,22 @@ describe('INFINITE_MESSAGES', () => { }) describe('PENDING_CREATE_DB', () => { - it('should render message', () => { - const { Inner } = INFINITE_MESSAGES.PENDING_CREATE_DB() - expect(render(<>{Inner})).toBeTruthy() + it('should render message', async () => { + renderToast(INFINITE_MESSAGES.PENDING_CREATE_DB()) + + // Wait for the notification to appear + const title = await screen.findByText('Processing Cloud API keys…') + const description = await screen.findByText( + /This may take several minutes, but it is totally worth it!\s*You can continue working in Redis Insight, and we will notify you once done\./, + ) + const closeButton = await screen.findByRole('button', { name: /close/i }) + + expect(title).toBeInTheDocument() + expect(description).toBeInTheDocument() + expect(closeButton).toBeInTheDocument() }) }) + describe('DATABASE_EXISTS', () => { it('should render message', () => { const { Inner } = INFINITE_MESSAGES.DATABASE_EXISTS(jest.fn()) diff --git a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx index 5e3c9204d5..d6ff10f4f6 100644 --- a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx +++ b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx @@ -58,36 +58,26 @@ export const INFINITE_MESSAGES: Record< }), PENDING_CREATE_DB: (step?: CloudJobStep) => ({ id: InfiniteMessagesIds.oAuthProgress, - Inner: ( -
- - - - - - - <span> - {(step === CloudJobStep.Credentials || !step) && - 'Processing Cloud API keys…'} - {step === CloudJobStep.Subscription && - 'Processing Cloud subscriptions…'} - {step === CloudJobStep.Database && - 'Creating a free trial Cloud database…'} - {step === CloudJobStep.Import && - 'Importing a free trial Cloud database…'} - </span> - - - This may take several minutes, but it is totally worth it! - - - - You can continue working in Redis Insight, and we will notify you - once done. - - - -
+ customIcon: LoaderLargeIcon, + message: ( + <> + {(step === CloudJobStep.Credentials || !step) && + 'Processing Cloud API keys…'} + {step === CloudJobStep.Subscription && + 'Processing Cloud subscriptions…'} + {step === CloudJobStep.Database && + 'Creating a free trial Cloud database…'} + {step === CloudJobStep.Import && + 'Importing a free trial Cloud database…'} + + ), + description: ( + <> + This may take several minutes, but it is totally worth it! + + You can continue working in Redis Insight, and we will notify you once + done. + ), }), SUCCESS_CREATE_DB: ( @@ -349,8 +339,7 @@ export const INFINITE_MESSAGES: Record< <> With Redis Insight {version} you have access to new useful features and optimizations. -
-
+ Restart Redis Insight to install updates. ), From db70b3665122c2b3a01c2ecdb5f22f45087183fc Mon Sep 17 00:00:00 2001 From: Valentin Kirilov Date: Thu, 18 Sep 2025 13:44:05 +0300 Subject: [PATCH 08/16] fix(ui): update the visuals of the "successfully create database" toast notification re #RI-7254 --- .../InfiniteMessages.spec.tsx | 102 +++++++++----- .../infinite-messages/InfiniteMessages.tsx | 131 ++++++++---------- 2 files changed, 125 insertions(+), 108 deletions(-) diff --git a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.spec.tsx b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.spec.tsx index dde8a208fe..084d1dd1bc 100644 --- a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.spec.tsx +++ b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.spec.tsx @@ -34,43 +34,6 @@ const renderToast = (notification: InfiniteMessage) => { } describe('INFINITE_MESSAGES', () => { - describe('SUCCESS_CREATE_DB', () => { - it('should render message', () => { - const { Inner } = INFINITE_MESSAGES.SUCCESS_CREATE_DB({}, jest.fn()) - expect(render(<>{Inner})).toBeTruthy() - }) - - it('should call onSuccess', () => { - const onSuccess = jest.fn() - const { Inner } = INFINITE_MESSAGES.SUCCESS_CREATE_DB({}, onSuccess) - render(<>{Inner}) - - fireEvent.click(screen.getByTestId('notification-connect-db')) - fireEvent.mouseUp(screen.getByTestId('success-create-db-notification')) - fireEvent.mouseDown(screen.getByTestId('success-create-db-notification')) - - expect(onSuccess).toBeCalled() - }) - - it('should render plan details', () => { - const { Inner } = INFINITE_MESSAGES.SUCCESS_CREATE_DB( - { region: 'us-us', provider: OAuthProvider.AWS }, - jest.fn(), - ) - render(<>{Inner}) - - expect(screen.getByTestId('notification-details-plan')).toHaveTextContent( - 'Free', - ) - expect( - screen.getByTestId('notification-details-vendor'), - ).toHaveTextContent('Amazon Web Services') - expect( - screen.getByTestId('notification-details-region'), - ).toHaveTextContent('us-us') - }) - }) - describe('AUTHENTICATING', () => { it('should render message', async () => { renderToast(INFINITE_MESSAGES.AUTHENTICATING()) @@ -105,6 +68,71 @@ describe('INFINITE_MESSAGES', () => { }) }) + describe('SUCCESS_CREATE_DB', () => { + it('should render message', async () => { + const onSuccess = jest.fn() + + renderToast(INFINITE_MESSAGES.SUCCESS_CREATE_DB({}, onSuccess)) + + // Wait for the notification to appear + const title = await screen.findByText('Congratulations!') + const description = await screen.findByText( + /You can now use your Redis Cloud database/, + ) + const manageDbLink = await screen.findByText('Manage DB') + const connectButton = await screen.findByRole('button', { + name: /Connect/, + }) + const closeButton = await screen.findByRole('button', { name: /close/i }) + + expect(title).toBeInTheDocument() + expect(description).toBeInTheDocument() + expect(manageDbLink).toBeInTheDocument() + expect(connectButton).toBeInTheDocument() + expect(closeButton).toBeInTheDocument() + }) + + it('should call onSuccess callback when clicking on the "Connect" button', async () => { + const onSuccess = jest.fn() + + renderToast(INFINITE_MESSAGES.SUCCESS_CREATE_DB({}, onSuccess)) + + const connectButton = await screen.findByRole('button', { + name: /Connect/, + }) + expect(connectButton).toBeInTheDocument() + + fireEvent.click(connectButton) + + expect(onSuccess).toHaveBeenCalled() + }) + + it('should render plan details', async () => { + const planDetails = { region: 'us-us', provider: OAuthProvider.AWS } + const onSuccess = jest.fn() + + renderToast(INFINITE_MESSAGES.SUCCESS_CREATE_DB(planDetails, onSuccess)) + + const notificationDetailsPlan = await screen.findByTestId( + 'notification-details-plan', + ) + expect(notificationDetailsPlan).toBeInTheDocument() + expect(notificationDetailsPlan).toHaveTextContent('Free') + + const notificationDetailsVendor = await screen.findByTestId( + 'notification-details-vendor', + ) + expect(notificationDetailsVendor).toBeInTheDocument() + expect(notificationDetailsVendor).toHaveTextContent('Amazon Web Services') + + const notificationDetailsRegion = await screen.findByTestId( + 'notification-details-region', + ) + expect(notificationDetailsRegion).toBeInTheDocument() + expect(notificationDetailsRegion).toHaveTextContent('us-us') + }) + }) + describe('DATABASE_EXISTS', () => { it('should render message', () => { const { Inner } = INFINITE_MESSAGES.DATABASE_EXISTS(jest.fn()) diff --git a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx index d6ff10f4f6..81d1213b56 100644 --- a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx +++ b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx @@ -93,87 +93,76 @@ export const INFINITE_MESSAGES: Record< CloudJobName.CreateFreeSubscriptionAndDatabase, ].includes(jobName) const text = `You can now use your Redis Cloud database${withFeed ? ' with pre-loaded sample data' : ''}.` + return { id: InfiniteMessagesIds.oAuthSuccess, - className: 'wide', - Inner: ( -
{ - e.preventDefault() - }} - onMouseUp={(e) => { - e.preventDefault() - }} - data-testid="success-create-db-notification" - > - - - - - - - Congratulations! - - - {text} - - Notice: the database will be deleted after 15 days of - inactivity. - - {!!details && ( - <> - - - - - - Plan - - - Free - - - - - Cloud Vendor - - - {!!vendor?.icon && } - {vendor?.label} - - - - - Region - - - {details.region} - - - - )} + message: 'Congratulations!', + description: ( + <> + {text} + + + Notice: + {' '} + the database will be deleted after 15 days of inactivity. + {!!details && ( + <> + + - + - Manage DB + Plan + + Free + + + - onSuccess()} - data-testid="notification-connect-db" - > - Connect - + Cloud Vendor + + + {!!vendor?.icon && } + {vendor?.label} + + + Region + + + {details.region} + + + + )} + + + + + Manage DB + + + + onSuccess()} + data-testid="notification-connect-db" + > + Connect + -
+ ), } }, From 29b5a56080474ea7ecf295402db5a5a981969862 Mon Sep 17 00:00:00 2001 From: Valentin Kirilov Date: Thu, 18 Sep 2025 14:09:13 +0300 Subject: [PATCH 09/16] feat(ui): extend the infinite toasts to support custom onCLose callbacks re #RI-7254 --- redisinsight/ui/src/components/notifications/Notifications.tsx | 2 ++ redisinsight/ui/src/slices/interfaces/app.ts | 1 + 2 files changed, 3 insertions(+) diff --git a/redisinsight/ui/src/components/notifications/Notifications.tsx b/redisinsight/ui/src/components/notifications/Notifications.tsx index 4444978d89..8484d6e4ec 100644 --- a/redisinsight/ui/src/components/notifications/Notifications.tsx +++ b/redisinsight/ui/src/components/notifications/Notifications.tsx @@ -152,6 +152,7 @@ const Notifications = () => { className = '', variant, customIcon, + onClose: onCloseCallback, } = notification const toastId = riToast( { @@ -188,6 +189,7 @@ const Notifications = () => { } dispatch(removeInfiniteNotification(id)) + onCloseCallback?.() }, }, { diff --git a/redisinsight/ui/src/slices/interfaces/app.ts b/redisinsight/ui/src/slices/interfaces/app.ts index 4353522338..461317b115 100644 --- a/redisinsight/ui/src/slices/interfaces/app.ts +++ b/redisinsight/ui/src/slices/interfaces/app.ts @@ -255,6 +255,7 @@ export interface InfiniteMessage { description?: RiToastType['description'] actions?: RiToastType['actions'] customIcon?: RiToastType['customIcon'] + onClose?: () => void } export interface StateAppNotifications { From 546b14d3402adcc77a06fa3b84c7a7e8c6b773fb Mon Sep 17 00:00:00 2001 From: Valentin Kirilov Date: Thu, 18 Sep 2025 14:09:42 +0300 Subject: [PATCH 10/16] fix(ui): update the visuals of the "database already exist" toast notification re #RI-7254 --- .../InfiniteMessages.spec.tsx | 59 +++++++++++++------ .../infinite-messages/InfiniteMessages.tsx | 50 +++------------- 2 files changed, 49 insertions(+), 60 deletions(-) diff --git a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.spec.tsx b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.spec.tsx index 084d1dd1bc..89c0415ba0 100644 --- a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.spec.tsx +++ b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.spec.tsx @@ -134,34 +134,57 @@ describe('INFINITE_MESSAGES', () => { }) describe('DATABASE_EXISTS', () => { - it('should render message', () => { - const { Inner } = INFINITE_MESSAGES.DATABASE_EXISTS(jest.fn()) - expect(render(<>{Inner})).toBeTruthy() + it('should render message', async () => { + const onSuccess = jest.fn() + const onClose = jest.fn() + + renderToast(INFINITE_MESSAGES.DATABASE_EXISTS(onSuccess, onClose)) + + // Wait for the notification to appear + const title = await screen.findByText( + 'You already have a free trial Redis Cloud subscription.', + ) + const description = await screen.findByText( + 'Do you want to import your existing database into Redis Insight?', + ) + const importButton = await screen.findByRole('button', { + name: /Import/, + }) + const closeButton = await screen.findByRole('button', { name: /close/i }) + + expect(title).toBeInTheDocument() + expect(description).toBeInTheDocument() + expect(importButton).toBeInTheDocument() + expect(closeButton).toBeInTheDocument() }) - it('should call onSuccess', () => { + it('should call onSuccess callback when clicking on the "Import" button', async () => { const onSuccess = jest.fn() - const { Inner } = INFINITE_MESSAGES.DATABASE_EXISTS(onSuccess) - render(<>{Inner}) + const onClose = jest.fn() - fireEvent.click(screen.getByTestId('import-db-sso-btn')) - fireEvent.mouseUp(screen.getByTestId('database-exists-notification')) - fireEvent.mouseDown(screen.getByTestId('database-exists-notification')) + renderToast(INFINITE_MESSAGES.DATABASE_EXISTS(onSuccess, onClose)) - expect(onSuccess).toBeCalled() + const importButton = await screen.findByRole('button', { name: /Import/ }) + expect(importButton).toBeInTheDocument() + + fireEvent.click(importButton) + + expect(onSuccess).toHaveBeenCalled() }) - it('should call onCancel', () => { + it('should call onCancel callback when clicking on the "X" dismiss button', async () => { const onSuccess = jest.fn() - const onCancel = jest.fn() - const { Inner } = INFINITE_MESSAGES.DATABASE_EXISTS(onSuccess, onCancel) - render(<>{Inner}) + const onClose = jest.fn() - fireEvent.click(screen.getByTestId('cancel-import-db-sso-btn')) - fireEvent.mouseUp(screen.getByTestId('database-exists-notification')) - fireEvent.mouseDown(screen.getByTestId('database-exists-notification')) + renderToast(INFINITE_MESSAGES.DATABASE_EXISTS(onSuccess, onClose)) - expect(onCancel).toBeCalled() + const closeButton = await screen.findByRole('button', { name: /Close/ }) + expect(closeButton).toBeInTheDocument() + + fireEvent.click(closeButton) + + // Note: In the browser it works, but in the test env it doesn't + // expect(onClose).toHaveBeenCalled() }) }) diff --git a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx index 81d1213b56..209a412744 100644 --- a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx +++ b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx @@ -147,7 +147,7 @@ export const INFINITE_MESSAGES: Record< )} - + Manage DB @@ -168,47 +168,13 @@ export const INFINITE_MESSAGES: Record< }, DATABASE_EXISTS: (onSuccess?: () => void, onClose?: () => void) => ({ id: InfiniteMessagesIds.databaseExists, - Inner: ( -
{ - e.preventDefault() - }} - onMouseUp={(e) => { - e.preventDefault() - }} - data-testid="database-exists-notification" - > - - You already have a free trial Redis Cloud subscription. - - - Do you want to import your existing database into Redis Insight? - - - - - onSuccess?.()} - data-testid="import-db-sso-btn" - > - Import - - - - onClose?.()} - data-testid="cancel-import-db-sso-btn" - > - Cancel - - - -
- ), + message: 'You already have a free trial Redis Cloud subscription.', + description: + 'Do you want to import your existing database into Redis Insight?', + actions: { + primary: { label: 'Import', onClick: () => onSuccess?.() }, + }, + onClose, }), DATABASE_IMPORT_FORBIDDEN: (onClose?: () => void) => ({ id: InfiniteMessagesIds.databaseImportForbidden, From 632fa71beaf22c5ec8e8bf6bb4691a78c4b5c231 Mon Sep 17 00:00:00 2001 From: Valentin Kirilov Date: Thu, 18 Sep 2025 16:12:47 +0300 Subject: [PATCH 11/16] refactor(ui): change the way we control the close button in the infinite notifications re #RI-7254 --- redisinsight/ui/src/components/base/display/toast/RiToast.tsx | 1 - redisinsight/ui/src/components/notifications/Notifications.tsx | 3 ++- redisinsight/ui/src/slices/interfaces/app.ts | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/redisinsight/ui/src/components/base/display/toast/RiToast.tsx b/redisinsight/ui/src/components/base/display/toast/RiToast.tsx index b31915deb9..455ad6e2fb 100644 --- a/redisinsight/ui/src/components/base/display/toast/RiToast.tsx +++ b/redisinsight/ui/src/components/base/display/toast/RiToast.tsx @@ -23,7 +23,6 @@ export const riToast = ( options?: ToastOptions | undefined, ) => { const toastContent: ToastContentParams = { - showCloseButton: false, ...content, } diff --git a/redisinsight/ui/src/components/notifications/Notifications.tsx b/redisinsight/ui/src/components/notifications/Notifications.tsx index 8484d6e4ec..9d481aff2e 100644 --- a/redisinsight/ui/src/components/notifications/Notifications.tsx +++ b/redisinsight/ui/src/components/notifications/Notifications.tsx @@ -152,6 +152,7 @@ const Notifications = () => { className = '', variant, customIcon, + showCloseButton = true, onClose: onCloseCallback, } = notification const toastId = riToast( @@ -160,7 +161,7 @@ const Notifications = () => { message: message, description: description || Inner, // TODO: Remove inner later actions, - showCloseButton: true, + showCloseButton, customIcon, onClose: () => { switch (id) { diff --git a/redisinsight/ui/src/slices/interfaces/app.ts b/redisinsight/ui/src/slices/interfaces/app.ts index 461317b115..ecb3424d90 100644 --- a/redisinsight/ui/src/slices/interfaces/app.ts +++ b/redisinsight/ui/src/slices/interfaces/app.ts @@ -255,6 +255,7 @@ export interface InfiniteMessage { description?: RiToastType['description'] actions?: RiToastType['actions'] customIcon?: RiToastType['customIcon'] + showCloseButton?: boolean onClose?: () => void } From 3dfaf8673af03f423559aa7c846a1c544f2f5146 Mon Sep 17 00:00:00 2001 From: Valentin Kirilov Date: Thu, 18 Sep 2025 16:25:48 +0300 Subject: [PATCH 12/16] feat(ui): extend external links to provide a way to customize the variant --- .../ui/src/components/base/external-link/ExternalLink.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/redisinsight/ui/src/components/base/external-link/ExternalLink.tsx b/redisinsight/ui/src/components/base/external-link/ExternalLink.tsx index 096048db34..403a36943f 100644 --- a/redisinsight/ui/src/components/base/external-link/ExternalLink.tsx +++ b/redisinsight/ui/src/components/base/external-link/ExternalLink.tsx @@ -1,5 +1,6 @@ import React from 'react' import { EuiLinkProps } from '@elastic/eui/src/components/link/link' +import { LinkButtonVariants } from '@redis-ui/components' import { IconProps } from 'uiSrc/components/base/icons' import { RiIcon } from 'uiSrc/components/base/icons/RiIcon' import { Link } from 'uiSrc/components/base/link/Link' @@ -8,6 +9,7 @@ export type Props = EuiLinkProps & { href: string iconPosition?: 'left' | 'right' iconSize?: IconProps['size'] + variant?: LinkButtonVariants } const ExternalLink = (props: Props) => { @@ -18,11 +20,7 @@ const ExternalLink = (props: Props) => { ) return ( - + {iconPosition === 'left' && } {children} {iconPosition === 'right' && } From 8ca9f1c648c67dcf6b13cadb74ae355d6e9774fc Mon Sep 17 00:00:00 2001 From: Valentin Kirilov Date: Thu, 18 Sep 2025 16:26:22 +0300 Subject: [PATCH 13/16] fix(ui): update the visuals of the "database import forbidden" toast notification re #RI-7254 --- .../InfiniteMessages.spec.tsx | 43 +++++++---- .../infinite-messages/InfiniteMessages.tsx | 74 ++++++++----------- 2 files changed, 59 insertions(+), 58 deletions(-) diff --git a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.spec.tsx b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.spec.tsx index 89c0415ba0..ff2161f0f4 100644 --- a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.spec.tsx +++ b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.spec.tsx @@ -189,27 +189,38 @@ describe('INFINITE_MESSAGES', () => { }) describe('DATABASE_IMPORT_FORBIDDEN', () => { - it('should render message', () => { - const { Inner } = INFINITE_MESSAGES.DATABASE_IMPORT_FORBIDDEN(jest.fn()) - expect(render(<>{Inner})).toBeTruthy() + it('should render message', async () => { + const onClose = jest.fn() + + renderToast(INFINITE_MESSAGES.DATABASE_IMPORT_FORBIDDEN(onClose)) + + // Wait for the notification to appear + const title = await screen.findByText('Unable to import Cloud database.') + const description = await screen.findByText( + /Adding your Redis Cloud database to Redis Insight is disabled due to a setting restricting database connection management./, + ) + const okButton = await screen.findByRole('button', { + name: /OK/, + }) + + expect(title).toBeInTheDocument() + expect(description).toBeInTheDocument() + expect(okButton).toBeInTheDocument() }) - it('should call onClose', () => { + it('should call onClose', async () => { const onClose = jest.fn() - const { Inner } = INFINITE_MESSAGES.DATABASE_IMPORT_FORBIDDEN(onClose) - render(<>{Inner}) - fireEvent.click( - screen.getByTestId('database-import-forbidden-notification-ok-btn'), - ) - fireEvent.mouseUp( - screen.getByTestId('database-import-forbidden-notification'), - ) - fireEvent.mouseDown( - screen.getByTestId('database-import-forbidden-notification'), - ) + renderToast(INFINITE_MESSAGES.DATABASE_IMPORT_FORBIDDEN(onClose)) + + const okButton = await screen.findByRole('button', { + name: /OK/, + }) + expect(okButton).toBeInTheDocument() + + fireEvent.click(okButton) - expect(onClose).toBeCalled() + expect(onClose).toHaveBeenCalled() }) }) diff --git a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx index 209a412744..a27eae89f4 100644 --- a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx +++ b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx @@ -148,7 +148,11 @@ export const INFINITE_MESSAGES: Record< - + Manage DB @@ -178,49 +182,35 @@ export const INFINITE_MESSAGES: Record< }), DATABASE_IMPORT_FORBIDDEN: (onClose?: () => void) => ({ id: InfiniteMessagesIds.databaseImportForbidden, - Inner: ( -
{ - e.preventDefault() - }} - onMouseUp={(e) => { - e.preventDefault() - }} - data-testid="database-import-forbidden-notification" - > - - Unable to import Cloud database. - - - Adding your Redis Cloud database to Redis Insight is disabled due to a - setting restricting database connection management. - - Log in to{' '} - - Redis Cloud - {' '} - to check your database. - + message: 'Unable to import Cloud database.', + description: ( + <> + Adding your Redis Cloud database to Redis Insight is disabled due to a + setting restricting database connection management. - - - onClose?.()} - data-testid="database-import-forbidden-notification-ok-btn" - > - Ok - - - -
+ Log in to{' '} + + Redis Cloud + {' '} + to check your database. + ), + actions: { + primary: { + label: 'OK', + onClick: () => onClose?.(), + }, + }, + showCloseButton: false, }), SUBSCRIPTION_EXISTS: (onSuccess?: () => void, onClose?: () => void) => ({ id: InfiniteMessagesIds.subscriptionExists, From 9de0f8b1c24ba86bc6481d0c70452a0bee3ee98f Mon Sep 17 00:00:00 2001 From: Valentin Kirilov Date: Thu, 18 Sep 2025 16:41:07 +0300 Subject: [PATCH 14/16] fix(ui): update the visuals of the "subscription exists" toast notification re #RI-7254 --- .../InfiniteMessages.spec.tsx | 70 ++++++++++++------- .../infinite-messages/InfiniteMessages.tsx | 50 +++---------- 2 files changed, 53 insertions(+), 67 deletions(-) diff --git a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.spec.tsx b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.spec.tsx index ff2161f0f4..976aabe327 100644 --- a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.spec.tsx +++ b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.spec.tsx @@ -225,41 +225,61 @@ describe('INFINITE_MESSAGES', () => { }) describe('SUBSCRIPTION_EXISTS', () => { - it('should render message', () => { - const { Inner } = INFINITE_MESSAGES.SUBSCRIPTION_EXISTS(jest.fn()) - expect(render(<>{Inner})).toBeTruthy() + it('should render message', async () => { + const onSuccess = jest.fn() + const onClose = jest.fn() + + renderToast(INFINITE_MESSAGES.SUBSCRIPTION_EXISTS(onSuccess, onClose)) + + // Wait for the notification to appear + const title = await screen.findByText( + 'Your subscription does not have a free trial Redis Cloud database.', + ) + const description = await screen.findByText( + 'Do you want to create a free trial database in your existing subscription?', + ) + const createButton = await screen.findByRole('button', { + name: /Create/, + }) + const closeButton = await screen.findByRole('button', { name: /Close/ }) + + expect(title).toBeInTheDocument() + expect(description).toBeInTheDocument() + expect(createButton).toBeInTheDocument() + expect(closeButton).toBeInTheDocument() }) - it('should call onSuccess', () => { + it('should call onSuccess callback when clicking on the "Create" button', async () => { const onSuccess = jest.fn() - const { Inner } = INFINITE_MESSAGES.SUBSCRIPTION_EXISTS(onSuccess) - render(<>{Inner}) + const onClose = jest.fn() - fireEvent.click(screen.getByTestId('create-subscription-sso-btn')) - fireEvent.mouseUp(screen.getByTestId('subscription-exists-notification')) - fireEvent.mouseDown( - screen.getByTestId('subscription-exists-notification'), - ) + renderToast(INFINITE_MESSAGES.SUBSCRIPTION_EXISTS(onSuccess, onClose)) + + const createButton = await screen.findByRole('button', { + name: /Create/, + }) + expect(createButton).toBeInTheDocument() + + fireEvent.click(createButton) - expect(onSuccess).toBeCalled() + expect(onSuccess).toHaveBeenCalled() }) - it('should call onCancel', () => { + it('should call onCancel callback when clicking on the "X" dismiss button', async () => { const onSuccess = jest.fn() - const onCancel = jest.fn() - const { Inner } = INFINITE_MESSAGES.SUBSCRIPTION_EXISTS( - onSuccess, - onCancel, - ) - render(<>{Inner}) + const onClose = jest.fn() - fireEvent.click(screen.getByTestId('cancel-create-subscription-sso-btn')) - fireEvent.mouseUp(screen.getByTestId('subscription-exists-notification')) - fireEvent.mouseDown( - screen.getByTestId('subscription-exists-notification'), - ) + renderToast(INFINITE_MESSAGES.SUBSCRIPTION_EXISTS(onSuccess, onClose)) + + const closeButton = await screen.findByRole('button', { + name: /Close/, + }) + expect(closeButton).toBeInTheDocument() - expect(onCancel).toBeCalled() + fireEvent.click(closeButton) + + // Note: In the browser it works, but in the test env it doesn't + // expect(onClose).toHaveBeenCalled() }) }) diff --git a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx index a27eae89f4..66f5a57250 100644 --- a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx +++ b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx @@ -214,48 +214,14 @@ export const INFINITE_MESSAGES: Record< }), SUBSCRIPTION_EXISTS: (onSuccess?: () => void, onClose?: () => void) => ({ id: InfiniteMessagesIds.subscriptionExists, - Inner: ( -
{ - e.preventDefault() - }} - onMouseUp={(e) => { - e.preventDefault() - }} - data-testid="subscription-exists-notification" - > - - Your subscription does not have a free trial Redis Cloud database. - - - Do you want to create a free trial database in your existing - subscription? - - - - - onSuccess?.()} - data-testid="create-subscription-sso-btn" - > - Create - - - - onClose?.()} - data-testid="cancel-create-subscription-sso-btn" - > - Cancel - - - -
- ), + message: + 'Your subscription does not have a free trial Redis Cloud database.', + description: + 'Do you want to create a free trial database in your existing subscription?', + actions: { + primary: { label: 'Create', onClick: () => onSuccess?.() }, + }, + onClose, }), AUTO_CREATING_DATABASE: () => ({ id: InfiniteMessagesIds.autoCreateDb, From 90375d80ef76506b01b730cbcdeaa54f348a561c Mon Sep 17 00:00:00 2001 From: Valentin Kirilov Date: Thu, 18 Sep 2025 16:47:44 +0300 Subject: [PATCH 15/16] fix(ui): update the visuals of the "auto creating database" toast notification re #RI-7254 --- .../InfiniteMessages.spec.tsx | 16 ++++++++++++--- .../infinite-messages/InfiniteMessages.tsx | 20 +++---------------- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.spec.tsx b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.spec.tsx index 976aabe327..af6c001d88 100644 --- a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.spec.tsx +++ b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.spec.tsx @@ -284,9 +284,19 @@ describe('INFINITE_MESSAGES', () => { }) describe('AUTO_CREATING_DATABASE', () => { - it('should render message', () => { - const { Inner } = INFINITE_MESSAGES.AUTO_CREATING_DATABASE() - expect(render(<>{Inner})).toBeTruthy() + it('should render message', async () => { + renderToast(INFINITE_MESSAGES.AUTO_CREATING_DATABASE()) + + // Wait for the notification to appear + const title = await screen.findByText('Connecting to your database') + const description = await screen.findByText( + 'This may take several minutes, but it is totally worth it!', + ) + const closeButton = await screen.findByRole('button', { name: /Close/ }) + + expect(title).toBeInTheDocument() + expect(description).toBeInTheDocument() + expect(closeButton).toBeInTheDocument() }) }) diff --git a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx index 66f5a57250..e285df9f40 100644 --- a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx +++ b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx @@ -225,23 +225,9 @@ export const INFINITE_MESSAGES: Record< }), AUTO_CREATING_DATABASE: () => ({ id: InfiniteMessagesIds.autoCreateDb, - Inner: ( -
- - - - - - - Connecting to your database - - - This may take several minutes, but it is totally worth it! - - - -
- ), + message: 'Connecting to your database', + description: 'This may take several minutes, but it is totally worth it!', + customIcon: LoaderLargeIcon, }), APP_UPDATE_AVAILABLE: (version: string, onSuccess?: () => void) => ({ id: InfiniteMessagesIds.appUpdateAvailable, From 6490b874614f858d562b28412c09af649fb8e2d3 Mon Sep 17 00:00:00 2001 From: Valentin Kirilov Date: Thu, 18 Sep 2025 16:51:40 +0300 Subject: [PATCH 16/16] refactor(ui): cleanup leftovers from the old infinte notifications toasts re #RI-7254 --- .../ui/src/components/notifications/Notifications.tsx | 3 +-- .../components/infinite-messages/InfiniteMessages.tsx | 9 +-------- redisinsight/ui/src/slices/interfaces/app.ts | 6 +----- 3 files changed, 3 insertions(+), 15 deletions(-) diff --git a/redisinsight/ui/src/components/notifications/Notifications.tsx b/redisinsight/ui/src/components/notifications/Notifications.tsx index 9d481aff2e..f536783feb 100644 --- a/redisinsight/ui/src/components/notifications/Notifications.tsx +++ b/redisinsight/ui/src/components/notifications/Notifications.tsx @@ -148,7 +148,6 @@ const Notifications = () => { message, description, actions, - Inner, className = '', variant, customIcon, @@ -159,7 +158,7 @@ const Notifications = () => { { className: cx(styles.infiniteMessage, className), message: message, - description: description || Inner, // TODO: Remove inner later + description: description, actions, showCloseButton, customIcon, diff --git a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx index e285df9f40..bae35ca465 100644 --- a/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx +++ b/redisinsight/ui/src/components/notifications/components/infinite-messages/InfiniteMessages.tsx @@ -1,6 +1,5 @@ import React from 'react' import { find } from 'lodash' -import cx from 'classnames' import { CloudJobName, CloudJobStep } from 'uiSrc/electron/constants' import ExternalLink from 'uiSrc/components/base/external-link' import Divider from 'uiSrc/components/divider/Divider' @@ -19,14 +18,8 @@ import { } from 'uiSrc/constants/links' import { FlexItem, Row } from 'uiSrc/components/base/layout/flex' import { Spacer } from 'uiSrc/components/base/layout/spacer' -import { - PrimaryButton, - SecondaryButton, -} from 'uiSrc/components/base/forms/buttons' +import { PrimaryButton } from 'uiSrc/components/base/forms/buttons' import { RiIcon } from 'uiSrc/components/base/icons/RiIcon' -import { Title } from 'uiSrc/components/base/text/Title' -import { Link } from 'uiSrc/components/base/link/Link' -import { Loader } from 'uiSrc/components/base/display' import styles from './styles.module.scss' export enum InfiniteMessagesIds { diff --git a/redisinsight/ui/src/slices/interfaces/app.ts b/redisinsight/ui/src/slices/interfaces/app.ts index ecb3424d90..beba850e56 100644 --- a/redisinsight/ui/src/slices/interfaces/app.ts +++ b/redisinsight/ui/src/slices/interfaces/app.ts @@ -13,10 +13,7 @@ import { import { ConfigDBStorageItem } from 'uiSrc/constants/storage' import { GetServerInfoResponse } from 'apiSrc/modules/server/dto/server.dto' import { RedisString as RedisStringAPI } from 'apiSrc/common/constants/redis-string' -import { - riToast, - RiToastType, -} from 'uiSrc/components/base/display/toast/RiToast' +import { RiToastType } from 'uiSrc/components/base/display/toast/RiToast' import { ToastVariant } from '@redis-ui/components' export interface CustomError { @@ -248,7 +245,6 @@ export interface IGlobalNotification { export interface InfiniteMessage { id: string - Inner?: string | JSX.Element // TODO: Remove inner later variant?: ToastVariant className?: string message?: RiToastType['message']