From 6aaba367362d1bd14bce61ecafacb3ba1411afd3 Mon Sep 17 00:00:00 2001 From: Amir Allayarov Date: Wed, 1 Nov 2023 09:40:02 +0400 Subject: [PATCH 01/11] #RI-5009 - refactor database form --- redisinsight/ui/src/pages/home/HomePage.tsx | 93 +- .../InstanceForm/InstanceForm.spec.tsx | 1258 ----------------- .../InstanceForm/InstanceForm.tsx | 783 ---------- .../AddInstanceForm/InstanceForm/index.ts | 5 - .../InstanceFormWrapper.spec.tsx | 210 --- .../home/components/AddInstanceForm/index.ts | 3 - .../CloudConnectionForm/index.ts | 3 + .../CloudConnectionFormWrapper.tsx | 2 +- .../home/components/CloudConnection/index.ts | 3 + .../ClusterConnectionForm/index.ts | 3 + .../ClusterConnectionFormWrapper.tsx | 49 +- .../components/ClusterConnection/index.ts | 3 + .../DatabaseAlias/DatabaseAlias.tsx | 2 +- .../DatabasesList/index.ts | 3 + .../DatabasesListWrapper.spec.tsx | 2 +- .../DatabasesListWrapper.tsx | 2 +- .../DatabasesListComponent/index.ts | 3 + .../form-components => Form}/DatabaseForm.tsx | 12 +- .../form-components => Form}/DbCompressor.tsx | 4 +- .../form-components => Form}/DbIndex.tsx | 2 +- .../form-components => Form}/DbInfo.tsx | 0 .../form-components => Form}/Messages.tsx | 0 .../form-components => Form}/SSHDetails.tsx | 4 +- .../form-components => Form}/TlsDetails.tsx | 4 +- .../form-components => Form}/index.ts | 0 .../sentinel/DbInfoSentinel.tsx | 0 .../sentinel/PrimaryGroupSentinel.tsx | 2 +- .../sentinel/SentinelHostPort.tsx | 0 .../sentinel/SentinelMasterDatabase.tsx | 2 +- .../sentinel/index.ts | 0 .../ManualConnectionForm.tsx | 662 +++++++++ .../ManualConnectionForm/index.ts | 3 + .../ManualConnectionWrapper.tsx} | 224 +-- .../home/components/ManualConnection/index.ts | 3 + .../InstanceConnections.spec.tsx | 0 .../InstanceConnections.tsx | 3 +- .../RightPanel.spec.tsx} | 6 +- .../RightPanel.tsx} | 66 +- .../pages/home/components/RightPanel/index.ts | 3 + .../styles.module.scss | 0 .../SentinelConnectionForm.tsx | 200 +++ .../SentinelConnectionForm/index.ts | 3 + .../SentinelConnectionWrapper.tsx | 163 +++ .../components/SentinelConnection/index.ts | 3 + .../WelcomeComponent.spec.tsx | 2 +- .../WelcomeComponent/WelcomeComponent.tsx | 8 +- .../home/components/WelcomeComponent/index.ts | 3 + .../InstanceForm => }/styles.module.scss | 0 .../ui/src/pages/home/constants/database.ts | 4 + .../constants.ts => constants/form.ts} | 7 + .../ui/src/pages/home/constants/index.ts | 3 + .../interfaces.ts => interfaces/form.ts} | 7 +- .../ui/src/pages/home/interfaces/index.ts | 1 + redisinsight/ui/src/pages/home/utils/form.tsx | 229 +++ redisinsight/ui/src/pages/home/utils/index.ts | 1 + .../edit-connection/EditConnection.tsx | 6 +- .../UploadTutorialForm/UploadTutorialForm.tsx | 2 +- .../ui/src/slices/instances/instances.ts | 2 +- .../ui/src/slices/interfaces/instances.ts | 2 +- 59 files changed, 1464 insertions(+), 2609 deletions(-) delete mode 100644 redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/InstanceForm.spec.tsx delete mode 100644 redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/InstanceForm.tsx delete mode 100644 redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/index.ts delete mode 100644 redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceFormWrapper.spec.tsx delete mode 100644 redisinsight/ui/src/pages/home/components/AddInstanceForm/index.ts create mode 100644 redisinsight/ui/src/pages/home/components/CloudConnection/CloudConnectionForm/index.ts create mode 100644 redisinsight/ui/src/pages/home/components/CloudConnection/index.ts create mode 100644 redisinsight/ui/src/pages/home/components/ClusterConnection/ClusterConnectionForm/index.ts create mode 100644 redisinsight/ui/src/pages/home/components/ClusterConnection/index.ts create mode 100644 redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesList/index.ts create mode 100644 redisinsight/ui/src/pages/home/components/DatabasesListComponent/index.ts rename redisinsight/ui/src/pages/home/components/{AddInstanceForm/InstanceForm/form-components => Form}/DatabaseForm.tsx (97%) rename redisinsight/ui/src/pages/home/components/{AddInstanceForm/InstanceForm/form-components => Form}/DbCompressor.tsx (96%) rename redisinsight/ui/src/pages/home/components/{AddInstanceForm/InstanceForm/form-components => Form}/DbIndex.tsx (97%) rename redisinsight/ui/src/pages/home/components/{AddInstanceForm/InstanceForm/form-components => Form}/DbInfo.tsx (100%) rename redisinsight/ui/src/pages/home/components/{AddInstanceForm/InstanceForm/form-components => Form}/Messages.tsx (100%) rename redisinsight/ui/src/pages/home/components/{AddInstanceForm/InstanceForm/form-components => Form}/SSHDetails.tsx (98%) rename redisinsight/ui/src/pages/home/components/{AddInstanceForm/InstanceForm/form-components => Form}/TlsDetails.tsx (99%) rename redisinsight/ui/src/pages/home/components/{AddInstanceForm/InstanceForm/form-components => Form}/index.ts (100%) rename redisinsight/ui/src/pages/home/components/{AddInstanceForm/InstanceForm/form-components => Form}/sentinel/DbInfoSentinel.tsx (100%) rename redisinsight/ui/src/pages/home/components/{AddInstanceForm/InstanceForm/form-components => Form}/sentinel/PrimaryGroupSentinel.tsx (96%) rename redisinsight/ui/src/pages/home/components/{AddInstanceForm/InstanceForm/form-components => Form}/sentinel/SentinelHostPort.tsx (100%) rename redisinsight/ui/src/pages/home/components/{AddInstanceForm/InstanceForm/form-components => Form}/sentinel/SentinelMasterDatabase.tsx (97%) rename redisinsight/ui/src/pages/home/components/{AddInstanceForm/InstanceForm/form-components => Form}/sentinel/index.ts (100%) create mode 100644 redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionForm/ManualConnectionForm.tsx create mode 100644 redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionForm/index.ts rename redisinsight/ui/src/pages/home/components/{AddInstanceForm/InstanceFormWrapper.tsx => ManualConnection/ManualConnectionWrapper.tsx} (67%) create mode 100644 redisinsight/ui/src/pages/home/components/ManualConnection/index.ts rename redisinsight/ui/src/pages/home/components/{AddDatabases => RightPanel}/InstanceConnections/InstanceConnections.spec.tsx (100%) rename redisinsight/ui/src/pages/home/components/{AddDatabases => RightPanel}/InstanceConnections/InstanceConnections.tsx (98%) rename redisinsight/ui/src/pages/home/components/{AddDatabases/AddDatabasesContainer.spec.tsx => RightPanel/RightPanel.spec.tsx} (70%) rename redisinsight/ui/src/pages/home/components/{AddDatabases/AddDatabasesContainer.tsx => RightPanel/RightPanel.tsx} (82%) create mode 100644 redisinsight/ui/src/pages/home/components/RightPanel/index.ts rename redisinsight/ui/src/pages/home/components/{AddDatabases => RightPanel}/styles.module.scss (100%) create mode 100644 redisinsight/ui/src/pages/home/components/SentinelConnection/SentinelConnectionForm/SentinelConnectionForm.tsx create mode 100644 redisinsight/ui/src/pages/home/components/SentinelConnection/SentinelConnectionForm/index.ts create mode 100644 redisinsight/ui/src/pages/home/components/SentinelConnection/SentinelConnectionWrapper.tsx create mode 100644 redisinsight/ui/src/pages/home/components/SentinelConnection/index.ts create mode 100644 redisinsight/ui/src/pages/home/components/WelcomeComponent/index.ts rename redisinsight/ui/src/pages/home/components/{AddInstanceForm/InstanceForm => }/styles.module.scss (100%) create mode 100644 redisinsight/ui/src/pages/home/constants/database.ts rename redisinsight/ui/src/pages/home/{components/AddInstanceForm/InstanceForm/constants.ts => constants/form.ts} (83%) create mode 100644 redisinsight/ui/src/pages/home/constants/index.ts rename redisinsight/ui/src/pages/home/{components/AddInstanceForm/InstanceForm/interfaces.ts => interfaces/form.ts} (89%) create mode 100644 redisinsight/ui/src/pages/home/interfaces/index.ts create mode 100644 redisinsight/ui/src/pages/home/utils/form.tsx create mode 100644 redisinsight/ui/src/pages/home/utils/index.ts diff --git a/redisinsight/ui/src/pages/home/HomePage.tsx b/redisinsight/ui/src/pages/home/HomePage.tsx index a4c8fc529b..08af6d8c41 100644 --- a/redisinsight/ui/src/pages/home/HomePage.tsx +++ b/redisinsight/ui/src/pages/home/HomePage.tsx @@ -3,7 +3,7 @@ import React, { useEffect, useRef, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import cx from 'classnames' import { clusterSelector, resetDataRedisCluster, resetInstancesRedisCluster, } from 'uiSrc/slices/instances/cluster' -import { setTitle } from 'uiSrc/utils' +import { Nullable, setTitle } from 'uiSrc/utils' import { PageHeader } from 'uiSrc/components' import { BrowserStorageItem } from 'uiSrc/constants' import { resetKeys } from 'uiSrc/slices/browser/keys' @@ -25,19 +25,23 @@ import { fetchContentAction as fetchCreateRedisButtonsAction } from 'uiSrc/slice import { sendEventTelemetry, sendPageViewTelemetry, TelemetryEvent, TelemetryPageView } from 'uiSrc/telemetry' import { appRedirectionSelector, setUrlHandlingInitialState } from 'uiSrc/slices/app/url-handling' import { UrlHandlingActions } from 'uiSrc/slices/interfaces/urlHandling' -import AddDatabaseContainer, { AddDbType } from './components/AddDatabases/AddDatabasesContainer' -import DatabasesList from './components/DatabasesListComponent/DatabasesListWrapper' -import WelcomeComponent from './components/WelcomeComponent/WelcomeComponent' +import { AddDbType } from 'uiSrc/pages/home/constants' +import RightPanel from 'uiSrc/pages/home/components/RightPanel' +import DatabasesList from './components/DatabasesListComponent' +import WelcomeComponent from './components/WelcomeComponent' import HomeHeader from './components/HomeHeader' import './styles.scss' import styles from './styles.module.scss' +enum RightPanelName { + AddDatabase = 'add', + EditDatabase = 'edit' +} + const HomePage = () => { const [width, setWidth] = useState(0) - const [addDialogIsOpen, setAddDialogIsOpen] = useState(false) - const [editDialogIsOpen, setEditDialogIsOpen] = useState(false) - const [dialogIsOpen, setDialogIsOpen] = useState(false) + const [openRightPanel, setOpenRightPanel] = useState>(null) const [welcomeIsShow, setWelcomeIsShow] = useState( !localStorageService.get(BrowserStorageItem.instancesCount) ) @@ -86,8 +90,7 @@ const HomePage = () => { useEffect(() => { if (isChangedInstance) { - setAddDialogIsOpen(!isChangedInstance) - setEditDialogIsOpen(!isChangedInstance) + setOpenRightPanel(null) dispatch(setEditedInstance(null)) // send page view after adding database from welcome page sendPageViewTelemetry({ @@ -107,29 +110,25 @@ const HomePage = () => { useEffect(() => { if (clusterCredentials || cloudCredentials || sentinelInstance) { - setAddDialogIsOpen(true) + setOpenRightPanel(RightPanelName.AddDatabase) } }, [clusterCredentials, cloudCredentials, sentinelInstance]) useEffect(() => { if (action === UrlHandlingActions.Connect) { - setAddDialogIsOpen(true) + setOpenRightPanel(RightPanelName.AddDatabase) } }, [action, dbConnection]) useEffect(() => { - const isDialogOpen = !!instances.length && (addDialogIsOpen || editDialogIsOpen) - const instancesCashCount = JSON.parse( localStorageService.get(BrowserStorageItem.instancesCount) ?? '0' ) - const isShowWelcome = !instances.length && !addDialogIsOpen && !editDialogIsOpen && !instancesCashCount - - setDialogIsOpen(isDialogOpen) + const isShowWelcome = !instances.length && !openRightPanel && !instancesCashCount setWelcomeIsShow(isShowWelcome) - }, [addDialogIsOpen, editDialogIsOpen, instances, loading]) + }, [openRightPanel, instances, loading]) useEffect(() => { if (editedInstance) { @@ -152,7 +151,7 @@ const HomePage = () => { const closeEditDialog = () => { dispatch(setEditedInstance(null)) - setEditDialogIsOpen(false) + setOpenRightPanel(null) sendEventTelemetry({ event: TelemetryEvent.CONFIG_DATABASES_DATABASE_EDIT_CANCELLED_CLICKED, @@ -166,9 +165,8 @@ const HomePage = () => { dispatch(resetDataRedisCluster()) dispatch(resetDataSentinel()) - setAddDialogIsOpen(false) + setOpenRightPanel(null) dispatch(setEditedInstance(null)) - setEditDialogIsOpen(false) if (action === UrlHandlingActions.Connect) { dispatch(setUrlHandlingInitialState()) @@ -181,22 +179,23 @@ const HomePage = () => { const handleAddInstance = (addDbType = AddDbType.manual) => { initialDbTypeRef.current = addDbType - setAddDialogIsOpen(true) + setOpenRightPanel(RightPanelName.AddDatabase) dispatch(setEditedInstance(null)) - setEditDialogIsOpen(false) } const handleEditInstance = (editedInstance: Instance) => { if (editedInstance) { dispatch(fetchEditedInstanceAction(editedInstance)) - setEditDialogIsOpen(true) - setAddDialogIsOpen(false) + setOpenRightPanel(RightPanelName.EditDatabase) } } const handleDeleteInstances = (instances: Instance[]) => { - if (instances.find((instance) => instance.id === editedInstance?.id)) { + if ( + instances.find((instance) => instance.id === editedInstance?.id) + && openRightPanel === RightPanelName.EditDatabase + ) { dispatch(setEditedInstance(null)) - setEditDialogIsOpen(false) + setOpenRightPanel(null) } instances.forEach((instance) => { @@ -227,7 +226,7 @@ const HomePage = () => { onAddInstance={handleAddInstance} direction="row" /> - {dialogIsOpen ? ( + {openRightPanel && instances.length ? (
{(EuiResizablePanel, EuiResizableButton) => ( @@ -242,7 +241,7 @@ const HomePage = () => {
{ scrollable={false} initialSize={38} className={cx({ - [styles.contentActive]: editDialogIsOpen, + [styles.contentActive]: openRightPanel === RightPanelName.EditDatabase, })} id="form" paddingSize="none" style={{ minWidth: '494px' }} > - {editDialogIsOpen && ( - - )} - - {addDialogIsOpen && ( - )} @@ -297,14 +294,14 @@ const HomePage = () => { ) : ( <> - {addDialogIsOpen && ( - () -const mockedDbConnectionInfo = mock() - -const formFields = { - ...instance(mockedDbConnectionInfo), - host: 'localhost', - port: '6379', - name: 'lala', - caCertificates: [], - certificates: [], -} - -jest.mock('uiSrc/slices/instances/instances', () => ({ - checkConnectToInstanceAction: () => jest.fn, - resetInstanceUpdateAction: () => jest.fn, - changeInstanceAliasAction: () => jest.fn, - setConnectedInstanceId: jest.fn, -})) - -jest.mock('uiSrc/slices/app/url-handling', () => ({ - ...jest.requireActual('uiSrc/slices/app/url-handling'), - appRedirectionSelector: jest.fn().mockReturnValue(() => ({ action: null })), -})) - -describe('InstanceForm', () => { - it('should render', () => { - expect( - render( - - ) - ).toBeTruthy() - }) - - it('should render with ConnectionType.Sentinel', () => { - expect( - render( - - ) - ).toBeTruthy() - }) - - it('should render with ConnectionType.Cluster', () => { - expect( - render( - - ) - ).toBeTruthy() - }) - - it('should render tooltip with nodes', () => { - expect( - render( - - ) - ).toBeTruthy() - }) - - it('should render DatabaseForm', () => { - expect( - render( - - ) - ).toBeTruthy() - }) - - it('should change sentinelMasterUsername input properly', async () => { - const handleSubmit = jest.fn() - const handleTestConnection = jest.fn() - - render( -
- -
- ) - - await act(() => { - fireEvent.change(screen.getByTestId('sentinel-mater-username'), { - target: { value: 'user' }, - }) - }) - - const submitBtn = screen.getByTestId(BTN_SUBMIT) - const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) - - await act(() => { - fireEvent.click(testConnectionBtn) - }) - expect(handleTestConnection).toBeCalledWith( - expect.objectContaining({ - sentinelMasterUsername: 'user', - }) - ) - - await act(() => { - fireEvent.click(submitBtn) - }) - expect(handleSubmit).toBeCalledWith( - expect.objectContaining({ - sentinelMasterUsername: 'user', - }) - ) - }) - - it('should change port input properly', async () => { - const handleSubmit = jest.fn() - render( -
- -
- ) - - await act(() => { - fireEvent.change(screen.getByTestId('port'), { - target: { value: '123' }, - }) - }) - - const submitBtn = screen.getByTestId(BTN_SUBMIT) - await act(() => { - fireEvent.click(submitBtn) - }) - expect(handleSubmit).toBeCalledWith( - expect.objectContaining({ - port: '123', - }) - ) - }) - - it('should change tls checkbox', async () => { - const handleSubmit = jest.fn() - const handleTestConnection = jest.fn() - - render( -
- -
- ) - await act(() => { - fireEvent.click(screen.getByTestId('tls')) - }) - - const submitBtn = screen.getByTestId(BTN_SUBMIT) - const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) - - await act(() => { - fireEvent.click(testConnectionBtn) - }) - expect(handleTestConnection).toBeCalledWith( - expect.objectContaining({ - tls: ['on'], - }) - ) - - await act(() => { - fireEvent.click(submitBtn) - }) - - expect(handleSubmit).toBeCalledWith( - expect.objectContaining({ - tls: ['on'], - }) - ) - }) - - it('should change Database Index checkbox', async () => { - const handleSubmit = jest.fn() - const handleTestConnection = jest.fn() - render( -
- -
- ) - await act(() => { - fireEvent.click(screen.getByTestId('showDb')) - }) - - const submitBtn = screen.getByTestId(BTN_SUBMIT) - const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) - await act(() => { - fireEvent.click(testConnectionBtn) - }) - expect(handleTestConnection).toBeCalledWith( - expect.objectContaining({ - showDb: true, - }) - ) - await act(() => { - fireEvent.click(submitBtn) - }) - - expect(handleSubmit).toBeCalledWith( - expect.objectContaining({ - showDb: true, - }) - ) - }) - - it('should change db checkbox and value', async () => { - const handleSubmit = jest.fn() - const handleTestConnection = jest.fn() - render( -
- -
- ) - await act(() => { - fireEvent.click(screen.getByTestId('showDb')) - }) - - await act(() => { - fireEvent.change(screen.getByTestId('db'), { - target: { value: '12' }, - }) - }) - - const submitBtn = screen.getByTestId(BTN_SUBMIT) - const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) - - await act(() => { - fireEvent.click(testConnectionBtn) - }) - expect(handleTestConnection).toBeCalledWith( - expect.objectContaining({ - showDb: true, - db: '12' - }) - ) - await act(() => { - fireEvent.click(submitBtn) - }) - - expect(handleSubmit).toBeCalledWith( - expect.objectContaining({ - showDb: true, - db: '12' - }) - ) - }) - - it('should change "Use SNI" with prepopulated with host', async () => { - const handleSubmit = jest.fn() - const handleTestConnection = jest.fn() - render( -
- -
- ) - await act(() => { - fireEvent.click(screen.getByTestId('sni')) - }) - - const submitBtn = screen.getByTestId(BTN_SUBMIT) - const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) - await act(() => { - fireEvent.click(testConnectionBtn) - }) - expect(handleTestConnection).toBeCalledWith( - expect.objectContaining({ - sni: true, - servername: formFields.host - }) - ) - await act(() => { - fireEvent.click(submitBtn) - }) - - expect(handleSubmit).toBeCalledWith( - expect.objectContaining({ - sni: true, - servername: formFields.host - }) - ) - }) - - it('should change "Use SNI"', async () => { - const handleSubmit = jest.fn() - const handleTestConnection = jest.fn() - render( -
- -
- ) - await act(() => { - fireEvent.click(screen.getByTestId('sni')) - }) - - await act(() => { - fireEvent.change(screen.getByTestId('sni-servername'), { - target: { value: '12' }, - }) - }) - - const submitBtn = screen.getByTestId(BTN_SUBMIT) - const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) - await act(() => { - fireEvent.click(testConnectionBtn) - }) - expect(handleTestConnection).toBeCalledWith( - expect.objectContaining({ - sni: true, - servername: '12' - }) - ) - await act(() => { - fireEvent.click(submitBtn) - }) - - expect(handleSubmit).toBeCalledWith( - expect.objectContaining({ - sni: true, - servername: '12' - }) - ) - }) - - it('should change "Verify TLS Certificate"', async () => { - const handleSubmit = jest.fn() - const handleTestConnection = jest.fn() - render( -
- -
- ) - await act(() => { - fireEvent.click(screen.getByTestId('verify-tls-cert')) - }) - - const submitBtn = screen.getByTestId(BTN_SUBMIT) - const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) - await act(() => { - fireEvent.click(testConnectionBtn) - }) - expect(handleTestConnection).toBeCalledWith( - expect.objectContaining({ - verifyServerTlsCert: ['on'], - }) - ) - await act(() => { - fireEvent.click(submitBtn) - }) - - expect(handleSubmit).toBeCalledWith( - expect.objectContaining({ - verifyServerTlsCert: ['on'], - }) - ) - }) - - it('should select value from "CA Certificate"', async () => { - const handleSubmit = jest.fn() - const handleTestConnection = jest.fn() - const { queryByText } = render( -
- -
- ) - await act(() => { - fireEvent.click(screen.getByTestId('select-ca-cert')) - }) - await act(() => { - fireEvent.click(queryByText('Add new CA certificate') || document) - }) - - expect(screen.getByTestId(NEW_CA_CERT)).toBeInTheDocument() - await act(() => { - fireEvent.change(screen.getByTestId(NEW_CA_CERT), { - target: { value: '123' }, - }) - }) - - expect(screen.getByTestId(QA_CA_CERT)).toBeInTheDocument() - await act(() => { - fireEvent.change(screen.getByTestId(QA_CA_CERT), { - target: { value: '321' }, - }) - }) - - const submitBtn = screen.getByTestId(BTN_SUBMIT) - const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) - await act(() => { - fireEvent.click(testConnectionBtn) - }) - expect(handleTestConnection).toBeCalledWith( - expect.objectContaining({ - selectedCaCertName: ADD_NEW_CA_CERT, - newCaCertName: '321', - newCaCert: '123', - }) - ) - await act(() => { - fireEvent.click(submitBtn) - }) - - expect(handleSubmit).toBeCalledWith( - expect.objectContaining({ - selectedCaCertName: ADD_NEW_CA_CERT, - newCaCertName: '321', - newCaCert: '123', - }) - ) - }) - - it('should render fields for add new CA and change them properly', async () => { - const handleSubmit = jest.fn() - const handleTestConnection = jest.fn() - render( -
- -
- ) - - expect(screen.getByTestId(QA_CA_CERT)).toBeInTheDocument() - await act(() => { - fireEvent.change(screen.getByTestId(QA_CA_CERT), { - target: { value: '321' }, - }) - }) - - expect(screen.getByTestId(NEW_CA_CERT)).toBeInTheDocument() - await act(() => { - fireEvent.change(screen.getByTestId(NEW_CA_CERT), { - target: { value: '123' }, - }) - }) - - const submitBtn = screen.getByTestId(BTN_SUBMIT) - const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) - await act(() => { - fireEvent.click(testConnectionBtn) - }) - expect(handleTestConnection).toBeCalledWith( - expect.objectContaining({ - newCaCert: '123', - newCaCertName: '321', - }) - ) - await act(() => { - fireEvent.click(submitBtn) - }) - - expect(handleSubmit).toBeCalledWith( - expect.objectContaining({ - newCaCert: '123', - newCaCertName: '321', - }) - ) - }) - - it('should change "Requires TLS Client Authentication"', async () => { - const handleSubmit = jest.fn() - const handleTestConnection = jest.fn() - render( -
- -
- ) - await act(() => { - fireEvent.click(screen.getByTestId('tls-required-checkbox')) - }) - - const submitBtn = screen.getByTestId(BTN_SUBMIT) - const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) - await act(() => { - fireEvent.click(testConnectionBtn) - }) - expect(handleTestConnection).toBeCalledWith( - expect.objectContaining({ - tlsClientAuthRequired: ['on'], - }) - ) - await act(() => { - fireEvent.click(submitBtn) - }) - - expect(handleSubmit).toBeCalledWith( - expect.objectContaining({ - tlsClientAuthRequired: ['on'], - }) - ) - }) - - it('should render fields for add new CA with required tls auth and change them properly', async () => { - const handleSubmit = jest.fn() - const { container } = render( -
- -
- ) - - expect(screen.getByTestId('select-cert')).toBeInTheDocument() - - await act(() => { - fireEvent.click(screen.getByTestId('select-cert')) - }) - - await act(() => { - fireEvent.click( - container.querySelectorAll('.euiContextMenuItem__text')[0] || document - ) - }) - - expect(screen.getByTestId('new-tsl-cert-pair-name')).toBeInTheDocument() - await act(() => { - fireEvent.change(screen.getByTestId('new-tsl-cert-pair-name'), { - target: { value: '123' }, - }) - }) - - expect(screen.getByTestId('new-tls-client-cert')).toBeInTheDocument() - await act(() => { - fireEvent.change(screen.getByTestId('new-tls-client-cert'), { - target: { value: '321' }, - }) - }) - - expect(screen.getByTestId('new-tls-client-cert-key')).toBeInTheDocument() - await act(() => { - fireEvent.change(screen.getByTestId('new-tls-client-cert-key'), { - target: { value: '231' }, - }) - }) - - const submitBtn = screen.getByTestId(BTN_SUBMIT) - - await act(() => { - fireEvent.click(submitBtn) - }) - - expect(handleSubmit).toBeCalledWith( - expect.objectContaining({ - newTlsClientCert: '321', - newTlsCertPairName: '123', - newTlsClientKey: '231', - }) - ) - }) - - it('should render clone mode btn', () => { - render( - - ) - expect(screen.getByTestId('clone-db-btn')).toBeTruthy() - }) - - describe('should render proper fields with Clone mode', () => { - it('should render proper fields for standalone db', () => { - render( - - ) - const fieldsTestIds = ['host', 'port', 'username', 'password', 'showDb', 'tls'] - fieldsTestIds.forEach((id) => { - expect(screen.getByTestId(id)).toBeTruthy() - }) - }) - - it('should render proper fields for sentinel db', () => { - render( - - ) - const fieldsTestIds = [ - 'name', - 'primary-group', - 'sentinel-mater-username', - 'sentinel-master-password', - 'host', - 'port', - 'username', - 'password', - 'showDb', - 'tls' - ] - fieldsTestIds.forEach((id) => { - expect(screen.getByTestId(id)).toBeTruthy() - }) - }) - - it('should render selected logical database with proper db index', () => { - render( - - ) - expect(screen.getByTestId('showDb')).toBeChecked() - expect(screen.getByTestId('db')).toHaveValue('5') - }) - - it('should render proper database alias', () => { - render( - - ) - expect(screen.getByTestId('db-alias')).toHaveTextContent('Clone ') - }) - - it('should render proper default values for standalone', () => { - render( - - ) - expect(screen.getByTestId('host')).toHaveValue('127.0.0.1') - expect(screen.getByTestId('port')).toHaveValue('6379') - expect(screen.getByTestId('name')).toHaveValue('127.0.0.1:6379') - }) - - it('should render proper default values for sentinel', () => { - render( - - ) - expect(screen.getByTestId('host')).toHaveValue('127.0.0.1') - expect(screen.getByTestId('port')).toHaveValue('26379') - }) - }) - - it('should change Use SSH checkbox', async () => { - const handleSubmit = jest.fn() - render( -
- -
- ) - - fireEvent.click(screen.getByTestId('use-ssh')) - - expect(screen.getByTestId('use-ssh')).toBeChecked() - }) - - it('should not render Use SSH checkbox for redis stack buidlType', async () => { - const handleSubmit = jest.fn() - render( -
- -
- ) - - expect(screen.queryByTestId('use-ssh')).not.toBeInTheDocument() - }) - - it('should change Use SSH checkbox and show proper fields for password radio', async () => { - const handleSubmit = jest.fn() - render( -
- -
- ) - - fireEvent.click(screen.getByTestId('use-ssh')) - - expect(screen.getByTestId('sshHost')).toBeInTheDocument() - expect(screen.getByTestId('sshPort')).toBeInTheDocument() - expect(screen.getByTestId('sshPort')).toHaveValue('22') - expect(screen.getByTestId('sshPassword')).toBeInTheDocument() - expect(screen.queryByTestId('sshPrivateKey')).not.toBeInTheDocument() - expect(screen.queryByTestId('sshPassphrase')).not.toBeInTheDocument() - - const submitBtn = screen.getByTestId(BTN_SUBMIT) - expect(submitBtn).toBeDisabled() - }) - - it('should change Use SSH checkbox and show proper fields for passphrase radio', async () => { - const handleSubmit = jest.fn() - const { container } = render( -
- -
- ) - - await act(() => { - fireEvent.click(screen.getByTestId('use-ssh')) - fireEvent.click( - container.querySelector(RADIO_BTN_PRIVATE_KEY) as HTMLLabelElement - ) - }) - - expect(screen.getByTestId('sshHost')).toBeInTheDocument() - expect(screen.getByTestId('sshPort')).toBeInTheDocument() - expect(screen.getByTestId('sshPort')).toHaveValue('22') - expect(screen.queryByTestId('sshPassword')).not.toBeInTheDocument() - expect(screen.getByTestId('sshPrivateKey')).toBeInTheDocument() - expect(screen.getByTestId('sshPassphrase')).toBeInTheDocument() - - const submitBtn = screen.getByTestId(BTN_SUBMIT) - expect(submitBtn).toBeDisabled() - }) - - it('should be proper validation for ssh via ssh password', async () => { - const handleSubmit = jest.fn() - render( -
- -
- ) - - expect(screen.getByTestId(BTN_SUBMIT)).not.toBeDisabled() - - await act(() => { - fireEvent.click(screen.getByTestId('use-ssh')) - }) - - expect(screen.getByTestId(BTN_SUBMIT)).toBeDisabled() - - await act(() => { - fireEvent.change( - screen.getByTestId('sshHost'), - { target: { value: 'localhost' } } - ) - }) - - expect(screen.getByTestId(BTN_SUBMIT)).toBeDisabled() - - await act(() => { - fireEvent.change( - screen.getByTestId('sshUsername'), - { target: { value: 'username' } } - ) - }) - - expect(screen.getByTestId(BTN_SUBMIT)).not.toBeDisabled() - }) - - it('should be proper validation for ssh via ssh passphrase', async () => { - const handleSubmit = jest.fn() - const { container } = render( -
- -
- ) - - expect(screen.getByTestId(BTN_SUBMIT)).not.toBeDisabled() - - await act(() => { - fireEvent.click(screen.getByTestId('use-ssh')) - fireEvent.click( - container.querySelector(RADIO_BTN_PRIVATE_KEY) as HTMLLabelElement - ) - }) - - expect(screen.getByTestId(BTN_SUBMIT)).toBeDisabled() - - await act(() => { - fireEvent.change( - screen.getByTestId('sshHost'), - { target: { value: 'localhost' } } - ) - fireEvent.change( - screen.getByTestId('sshUsername'), - { target: { value: 'username' } } - ) - }) - - expect(screen.getByTestId(BTN_SUBMIT)).toBeDisabled() - - await act(() => { - fireEvent.change( - screen.getByTestId('sshPrivateKey'), - { target: { value: 'PRIVATEKEY' } } - ) - }) - - expect(screen.getByTestId(BTN_SUBMIT)).not.toBeDisabled() - }) - - it('should call submit btn with proper fields', async () => { - const handleSubmit = jest.fn() - render( -
- -
- ) - - await act(() => { - fireEvent.click(screen.getByTestId('use-ssh')) - }) - - await act(() => { - fireEvent.change( - screen.getByTestId('sshHost'), - { target: { value: 'localhost' } } - ) - - fireEvent.change( - screen.getByTestId('sshPort'), - { target: { value: '1771' } } - ) - - fireEvent.change( - screen.getByTestId('sshUsername'), - { target: { value: 'username' } } - ) - - fireEvent.change( - screen.getByTestId('sshPassword'), - { target: { value: '123' } } - ) - }) - - await act(() => { - fireEvent.click(screen.getByTestId(BTN_SUBMIT)) - }) - - expect(handleSubmit).toBeCalledWith( - expect.objectContaining({ - sshHost: 'localhost', - sshPort: '1771', - sshUsername: 'username', - sshPassword: '123', - }) - ) - }) - - it('should call submit btn with proper fields via passphrase', async () => { - const handleSubmit = jest.fn() - const { container } = render( -
- -
- ) - - await act(() => { - fireEvent.click(screen.getByTestId('use-ssh')) - fireEvent.click( - container.querySelector(RADIO_BTN_PRIVATE_KEY) as HTMLLabelElement - ) - }) - - await act(() => { - fireEvent.change( - screen.getByTestId('sshHost'), - { target: { value: 'localhost' } } - ) - - fireEvent.change( - screen.getByTestId('sshPort'), - { target: { value: '1771' } } - ) - - fireEvent.change( - screen.getByTestId('sshUsername'), - { target: { value: 'username' } } - ) - - fireEvent.change( - screen.getByTestId('sshPrivateKey'), - { target: { value: '123444' } } - ) - - fireEvent.change( - screen.getByTestId('sshPassphrase'), - { target: { value: '123444' } } - ) - }) - - await act(() => { - fireEvent.click(screen.getByTestId(BTN_SUBMIT)) - }) - - expect(handleSubmit).toBeCalledWith( - expect.objectContaining({ - sshHost: 'localhost', - sshPort: '1771', - sshUsername: 'username', - sshPrivateKey: '123444', - sshPassphrase: '123444', - }) - ) - }) - - it('should render password input with 10_000 length limit', () => { - render( - - ) - - expect(screen.getByTestId('password')).toHaveAttribute('maxLength', '10000') - }) - - it('should render security fields with proper attributes', () => { - render( - - ) - - expect(screen.getByTestId('password')).toHaveAttribute('value', '••••••••••••') - expect(screen.getByTestId('password')).toHaveAttribute('type', 'password') - expect(screen.getByTestId('sshPassphrase')).toHaveAttribute('value', '••••••••••••') - expect(screen.getByTestId('sshPassphrase')).toHaveAttribute('type', 'password') - - fireEvent.focus(screen.getByTestId('password')) - fireEvent.focus(screen.getByTestId('sshPassphrase')) - - expect(screen.getByTestId('password')).toHaveAttribute('value', '') - expect(screen.getByTestId('sshPassphrase')).toHaveAttribute('value', '') - }) - - it('should render ssh password with proper attributes', () => { - render( - - ) - - expect(screen.getByTestId('sshPassword')).toHaveAttribute('value', '••••••••••••') - expect(screen.getByTestId('sshPassword')).toHaveAttribute('type', 'password') - - fireEvent.focus(screen.getByTestId('sshPassword')) - - expect(screen.getByTestId('sshPassword')).toHaveAttribute('value', '') - }) - - it('should render ssh password input with 10_000 length limit', () => { - render( - - ) - - expect(screen.getByTestId('sshPassword')).toHaveAttribute('maxLength', '10000') - }) - - describe('timeout', () => { - it('should render timeout input with 7 length limit and 1_000_000 value', () => { - render( - - ) - - expect(screen.getByTestId('timeout')).toBeInTheDocument() - expect(screen.getByTestId('timeout')).toHaveAttribute('maxLength', '7') - - fireEvent.change( - screen.getByTestId('timeout'), - { target: { value: '2000000' } } - ) - - expect(screen.getByTestId('timeout')).toHaveAttribute('value', '1000000') - }) - - it('should put only numbers', () => { - render( - - ) - - fireEvent.change( - screen.getByTestId('timeout'), - { target: { value: '11a2EU$#@' } } - ) - - expect(screen.getByTestId('timeout')).toHaveAttribute('value', '112') - }) - }) - - describe('cloud', () => { - it('some fields should be readonly if instance data source from cloud', () => { - (appRedirectionSelector as jest.Mock).mockImplementation(() => ({ - action: UrlHandlingActions.Connect, - })) - - const { queryByTestId } = render( - - ) - - expect(queryByTestId('connection-type')).not.toBeInTheDocument() - expect(queryByTestId('host')).not.toBeInTheDocument() - expect(queryByTestId('port')).not.toBeInTheDocument() - expect(queryByTestId('db-info-port')).toBeInTheDocument() - expect(queryByTestId('db-info-host')).toBeInTheDocument() - }) - }) -}) diff --git a/redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/InstanceForm.tsx b/redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/InstanceForm.tsx deleted file mode 100644 index 27c019c9cf..0000000000 --- a/redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/InstanceForm.tsx +++ /dev/null @@ -1,783 +0,0 @@ -import { - EuiButton, - EuiCollapsibleNavGroup, - EuiForm, - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, - EuiToolTip, - keys, -} from '@elastic/eui' -import { FormikErrors, useFormik } from 'formik' -import { isEmpty, pick, toString } from 'lodash' -import React, { useEffect, useRef, useState } from 'react' -import ReactDOM from 'react-dom' -import { useDispatch, useSelector } from 'react-redux' -import { useHistory } from 'react-router' - -import { PageNames, Pages } from 'uiSrc/constants' -import validationErrors from 'uiSrc/constants/validationErrors' -import DatabaseAlias from 'uiSrc/pages/home/components/DatabaseAlias' -import { useResizableFormField } from 'uiSrc/services' -import { appContextSelector, setAppContextInitialState } from 'uiSrc/slices/app/context' -import { resetKeys } from 'uiSrc/slices/browser/keys' -import { - changeInstanceAliasAction, - checkConnectToInstanceAction, - resetInstanceUpdateAction, - setConnectedInstanceId, -} from 'uiSrc/slices/instances/instances' -import { ConnectionType, InstanceType, } from 'uiSrc/slices/interfaces' -import { getRedisModulesSummary, sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' -import { Nullable, getDiffKeysOfObjectValues, isRediStack } from 'uiSrc/utils' -import { BuildType } from 'uiSrc/constants/env' -import { appRedirectionSelector } from 'uiSrc/slices/app/url-handling' -import { UrlHandlingActions } from 'uiSrc/slices/interfaces/urlHandling' - -import { - ADD_NEW_CA_CERT, - NO_CA_CERT, - ADD_NEW, - fieldDisplayNames, - SshPassType, - DEFAULT_TIMEOUT, - NONE, -} from './constants' - -import { DbConnectionInfo, ISubmitButton } from './interfaces' -import { - DbIndex, - DbInfo, - MessageSentinel, - MessageStandalone, - TlsDetails, - DatabaseForm, - DbCompressor -} from './form-components' -import { - DbInfoSentinel, - PrimaryGroupSentinel, - SentinelHostPort, - SentinelMasterDatabase, -} from './form-components/sentinel' -import SSHDetails from './form-components/SSHDetails' -import { LoadingDatabaseText, SubmitBtnText, TitleDatabaseText, } from '../InstanceFormWrapper' - -export interface Props { - width: number - isResizablePanel?: boolean - formFields: DbConnectionInfo - submitButtonText?: SubmitBtnText - titleText?: TitleDatabaseText - loading: boolean - buildType?: BuildType - instanceType: InstanceType - loadingMsg: LoadingDatabaseText - isEditMode: boolean - isCloneMode: boolean - setIsCloneMode: (value: boolean) => void - initialValues: DbConnectionInfo - onSubmit: (values: DbConnectionInfo) => void - onTestConnection: (values: DbConnectionInfo) => void - updateEditingName: (name: string) => void - onHostNamePaste: (content: string) => boolean - onClose?: () => void - onAliasEdited?: (value: string) => void - setErrorMsgRef?: (database: HTMLDivElement | null) => void - urlHandlingAction?: Nullable -} - -const getInitFieldsDisplayNames = ({ host, port, name, instanceType }: any) => { - if (!host || !port) { - if (!name && instanceType !== InstanceType.Sentinel) { - return pick(fieldDisplayNames, ['host', 'port', 'name']) - } - return pick(fieldDisplayNames, ['host', 'port']) - } - return {} -} - -const getDefaultHost = () => '127.0.0.1' -const getDefaultPort = (instanceType: InstanceType) => (instanceType === InstanceType.Sentinel ? '26379' : '6379') - -const AddStandaloneForm = (props: Props) => { - const { - formFields: { - id, - host, - name, - port, - tls, - db = null, - compressor = NONE, - nameFromProvider, - sentinelMaster, - connectionType, - nodes = null, - tlsClientAuthRequired, - certificates, - selectedTlsClientCertId = '', - verifyServerTlsCert, - caCertificates, - selectedCaCertName, - username, - password, - timeout, - modules, - sentinelMasterPassword, - sentinelMasterUsername, - servername, - provider, - ssh, - sshPassType = SshPassType.Password, - sshOptions, - version, - }, - initialValues: initialValuesProp, - width, - onClose, - onSubmit, - onTestConnection, - onHostNamePaste, - submitButtonText, - instanceType, - buildType, - loading, - isEditMode, - isCloneMode, - setIsCloneMode, - onAliasEdited, - } = props - - const { contextInstanceId, lastPage } = useSelector(appContextSelector) - const { action } = useSelector(appRedirectionSelector) - - const prepareInitialValues = () => ({ - host: host ?? getDefaultHost(), - port: port ? port.toString() : getDefaultPort(instanceType), - timeout: timeout ? timeout.toString() : toString(DEFAULT_TIMEOUT / 1_000), - name: name ?? `${getDefaultHost()}:${getDefaultPort(instanceType)}`, - username, - password, - tls, - db, - compressor, - modules, - showDb: !!db, - showCompressor: compressor !== NONE, - sni: !!servername, - servername, - newCaCert: '', - newCaCertName: '', - selectedCaCertName, - tlsClientAuthRequired, - verifyServerTlsCert, - newTlsCertPairName: '', - selectedTlsClientCertId, - newTlsClientCert: '', - newTlsClientKey: '', - sentinelMasterName: sentinelMaster?.name || '', - sentinelMasterUsername, - sentinelMasterPassword, - ssh, - sshPassType, - sshHost: sshOptions?.host ?? '', - sshPort: sshOptions?.port ?? 22, - sshUsername: sshOptions?.username ?? '', - sshPassword: sshOptions?.password ?? '', - sshPrivateKey: sshOptions?.privateKey ?? '', - sshPassphrase: sshOptions?.passphrase ?? '' - }) - - const [initialValues, setInitialValues] = useState(prepareInitialValues()) - - const [errors, setErrors] = useState>( - getInitFieldsDisplayNames({ host, port, name, instanceType }) - ) - - useEffect(() => { - const values = prepareInitialValues() - - setInitialValues(values) - formik.setValues(values) - }, [initialValuesProp, isCloneMode]) - - const history = useHistory() - const dispatch = useDispatch() - - const formRef = useRef(null) - - const submitIsDisable = () => !isEmpty(errors) - const isFromCloud = action === UrlHandlingActions.Connect - - const validate = (values: DbConnectionInfo) => { - const errs: FormikErrors = {} - - if (!values.host) { - errs.host = fieldDisplayNames.host - } - if (!values.port) { - errs.port = fieldDisplayNames.port - } - - if (!values.name && instanceType !== InstanceType.Sentinel) { - errs.name = fieldDisplayNames.name - } - - if ( - values.tls - && values.verifyServerTlsCert - && values.selectedCaCertName === NO_CA_CERT - ) { - errs.selectedCaCertName = fieldDisplayNames.selectedCaCertName - } - - if ( - values.tls - && values.selectedCaCertName === ADD_NEW_CA_CERT - && values.newCaCertName === '' - ) { - errs.newCaCertName = fieldDisplayNames.newCaCertName - } - - if ( - values.tls - && values.selectedCaCertName === ADD_NEW_CA_CERT - && values.newCaCert === '' - ) { - errs.newCaCert = fieldDisplayNames.newCaCert - } - - if ( - values.tls - && values.sni - && values.servername === '' - ) { - errs.servername = fieldDisplayNames.servername - } - - if ( - values.tls - && values.tlsClientAuthRequired - && values.selectedTlsClientCertId === ADD_NEW - ) { - if (values.newTlsCertPairName === '') { - errs.newTlsCertPairName = fieldDisplayNames.newTlsCertPairName - } - if (values.newTlsClientCert === '') { - errs.newTlsClientCert = fieldDisplayNames.newTlsClientCert - } - if (values.newTlsClientKey === '') { - errs.newTlsClientKey = fieldDisplayNames.newTlsClientKey - } - } - - if (isCloneMode && connectionType === ConnectionType.Sentinel && !values.sentinelMasterName) { - errs.sentinelMasterName = fieldDisplayNames.sentinelMasterName - } - - if (values.ssh) { - if (!values.sshHost) { - errs.sshHost = fieldDisplayNames.sshHost - } - if (!values.sshPort) { - errs.sshPort = fieldDisplayNames.sshPort - } - if (!values.sshUsername) { - errs.sshUsername = fieldDisplayNames.sshUsername - } - if (values.sshPassType === SshPassType.PrivateKey && !values.sshPrivateKey) { - errs.sshPrivateKey = fieldDisplayNames.sshPrivateKey - } - } - - setErrors(errs) - return errs - } - - const formik = useFormik({ - initialValues, - validate, - enableReinitialize: true, - onSubmit: (values: any) => { - if (isCloneMode) { - const diffKeys = getDiffKeysOfObjectValues(formik.initialValues, values) - sendEventTelemetry({ - event: TelemetryEvent.CONFIG_DATABASES_DATABASE_CLONE_CONFIRMED, - eventData: { - fieldsModified: diffKeys - } - }) - } - onSubmit(values) - }, - }) - - const [flexGroupClassName, flexItemClassName] = useResizableFormField( - formRef, - width - ) - - const onKeyDown = (event: React.KeyboardEvent) => { - if (event.key === keys.ENTER && !submitIsDisable()) { - // event. - formik.submitForm() - } - } - - useEffect(() => - // componentWillUnmount - () => { - if (isEditMode) { - dispatch(resetInstanceUpdateAction()) - } - }, - []) - - const handleCheckConnectToInstance = () => { - const modulesSummary = getRedisModulesSummary(modules) - sendEventTelemetry({ - event: TelemetryEvent.CONFIG_DATABASES_OPEN_DATABASE_BUTTON_CLICKED, - eventData: { - databaseId: id, - provider, - ...modulesSummary, - } - }) - dispatch(checkConnectToInstanceAction(id, connectToInstance)) - } - - const handleCloneDatabase = () => { - setIsCloneMode(true) - sendEventTelemetry({ - event: TelemetryEvent.CONFIG_DATABASES_DATABASE_CLONE_REQUESTED, - eventData: { - databaseId: id - } - }) - } - - const handleBackCloneDatabase = () => { - setIsCloneMode(false) - sendEventTelemetry({ - event: TelemetryEvent.CONFIG_DATABASES_DATABASE_CLONE_CANCELLED, - eventData: { - databaseId: id - } - }) - } - - const handleTestConnectionDatabase = () => { - onTestConnection(formik.values) - } - - const handleChangeDatabaseAlias = ( - value: string, - onSuccess?: () => void, - onFail?: () => void - ) => { - dispatch(changeInstanceAliasAction( - id, - value, - () => { - onAliasEdited?.(value) - onSuccess?.() - }, - onFail - )) - } - - const connectToInstance = () => { - if (contextInstanceId && contextInstanceId !== id) { - dispatch(resetKeys()) - dispatch(setAppContextInitialState()) - } - dispatch(setConnectedInstanceId(id ?? '')) - - if (lastPage === PageNames.workbench && contextInstanceId === id) { - history.push(Pages.workbench(id)) - return - } - history.push(Pages.browser(id)) - } - - const getSubmitButtonContent = (submitIsDisabled?: boolean) => { - const maxErrorsCount = 5 - const errorsArr = Object.values(errors).map((err) => [ - err, -
, - ]) - - if (errorsArr.length > maxErrorsCount) { - errorsArr.splice(maxErrorsCount, errorsArr.length, ['...']) - } - return submitIsDisabled ? ( - {errorsArr} - ) : null - } - - const SubmitButton = ({ - text = '', - onClick, - submitIsDisabled, - }: ISubmitButton) => ( - - - {text} - - - ) - - const Footer = () => { - const footerEl = document.getElementById('footerDatabaseForm') - - if (footerEl) { - return ReactDOM.createPortal( - - - {instanceType !== InstanceType.Sentinel && ( - - - Test Connection - - - )} - - - - - {onClose && ( - - Cancel - - )} - - - - , - footerEl - ) - } - return null - } - - return ( -
- {isEditMode && name && ( -
- -
- )} -
- {!isEditMode && instanceType === InstanceType.Standalone && !isFromCloud && ( - <> - -
- - )} - {!isEditMode && instanceType === InstanceType.Sentinel && ( - <> - -
- - )} - {!isEditMode && !isFromCloud && ( - - - {instanceType !== InstanceType.Sentinel && ( - - )} - {instanceType !== InstanceType.Sentinel && ( - - )} - - {instanceType !== InstanceType.Sentinel && buildType !== BuildType.RedisStack && ( - - )} - - )} - {(isEditMode || isCloneMode || isFromCloud) && connectionType !== ConnectionType.Sentinel && ( - <> - {!isCloneMode && ( - - )} - - - {isCloneMode && ( - - )} - - - {buildType !== BuildType.RedisStack && ( - - )} - - - )} - {(isEditMode || isCloneMode) && connectionType === ConnectionType.Sentinel && ( - <> - - {!isCloneMode && ( - <> - - - - - - - - - - - - - - )} - {isCloneMode && ( - <> - - - - - - - - - - - - - )} - - - )} -
-
-
- ) -} - -export default AddStandaloneForm diff --git a/redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/index.ts b/redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/index.ts deleted file mode 100644 index a9a9fd384e..0000000000 --- a/redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import InstanceForm from './InstanceForm' - -export * from './InstanceForm' - -export default InstanceForm diff --git a/redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceFormWrapper.spec.tsx b/redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceFormWrapper.spec.tsx deleted file mode 100644 index bc5a0b77c2..0000000000 --- a/redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceFormWrapper.spec.tsx +++ /dev/null @@ -1,210 +0,0 @@ -import React from 'react' -import { instance, mock } from 'ts-mockito' -import { cloneDeep, toString } from 'lodash' -import { cleanup, fireEvent, mockedStore, render, screen } from 'uiSrc/utils/test-utils' -import { Instance } from 'uiSrc/slices/interfaces' -import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' -import { UrlHandlingActions } from 'uiSrc/slices/interfaces/urlHandling' -import { defaultInstanceChanging } from 'uiSrc/slices/instances/instances' -import InstanceFormWrapper, { Props } from './InstanceFormWrapper' -import InstanceForm, { Props as InstanceProps, } from './InstanceForm/InstanceForm' - -const mockedProps = mock() -const mockedEditedInstance: Instance = { - name: 'name', - host: 'host', - port: 123, - timeout: 10_000, - id: '123', - modules: [], - tls: true, - caCert: { id: 'zxc' }, - clientCert: { id: 'zxc' }, -} - -const mockedValues = { - newCaCert: '', - tls: true, - newCaCertName: '', - selectedCaCertName: '', - tlsClientAuthRequired: false, - verifyServerTlsCert: true, - newTlsCertPairName: '', - selectedTlsClientCertId: '', - newTlsClientCert: '', - newTlsClientKey: '', -} - -jest.mock('./InstanceForm/InstanceForm', () => ({ - __esModule: true, - namedExport: jest.fn(), - default: jest.fn(), -})) - -jest.mock('uiSrc/telemetry', () => ({ - ...jest.requireActual('uiSrc/telemetry'), - sendEventTelemetry: jest.fn(), -})) - -jest.mock('uiSrc/slices/instances/instances', () => ({ - ...jest.requireActual('uiSrc/slices/instances/instances'), - updateInstanceAction: () => jest.fn, - testInstanceStandaloneAction: () => jest.fn, - instancesSelector: jest.fn().mockReturnValue({ loadingChanging: false }), -})) - -jest.mock('uiSrc/slices/instances/clientCerts', () => ({ - clientCertsSelector: () => jest.fn().mockReturnValue({ data: [] }), - fetchClientCerts: jest.fn, -})) - -jest.mock('uiSrc/slices/instances/caCerts', () => ({ - caCertsSelector: () => jest.fn().mockReturnValue({ data: [] }), - fetchCaCerts: () => jest.fn, -})) - -jest.mock('uiSrc/slices/instances/sentinel', () => ({ - sentinelSelector: () => jest.fn().mockReturnValue({ loading: false }), - fetchMastersSentinelAction: () => jest.fn, -})) - -let store: typeof mockedStore -beforeEach(() => { - cleanup() - store = cloneDeep(mockedStore) - store.clearActions() -}) - -const MockInstanceForm = (props: InstanceProps) => ( -
- - - - -
-) - -describe('InstanceFormWrapper', () => { - beforeAll(() => { - InstanceForm.mockImplementation(MockInstanceForm) - }) - it('should render', () => { - expect( - render( - - ) - ).toBeTruthy() - }) - - it('should send prop timeout / 1_000 (in seconds)', () => { - expect( - render( - - ) - ).toBeTruthy() - - expect(InstanceForm).toHaveBeenCalledWith( - expect.objectContaining({ - formFields: expect.objectContaining({ - timeout: toString(mockedEditedInstance?.timeout / 1_000), - }), - }), - {}, - ) - }) - - it('should call onClose', () => { - const onClose = jest.fn() - render( - - ) - fireEvent.click(screen.getByTestId('close-btn')) - expect(onClose).toBeCalled() - }) - - it('should submit with editMode', () => { - const component = render( - - ) - fireEvent.click(screen.getByTestId('submit-form-btn')) - expect(component).toBeTruthy() - }) - - it('should call onHostNamePaste', () => { - const component = render( - - ) - fireEvent.click(screen.getByTestId('paste-hostName-btn')) - expect(component).toBeTruthy() - }) - - it('should call proper telemetry events after click test connection', () => { - const sendEventTelemetryMock = jest.fn() - - sendEventTelemetry.mockImplementation(() => sendEventTelemetryMock) - - render( - - ) - fireEvent.click(screen.getByTestId('btn-test-connection')) - expect(sendEventTelemetry).toBeCalledWith({ - event: TelemetryEvent.CONFIG_DATABASES_TEST_CONNECTION_CLICKED, - }) - sendEventTelemetry.mockRestore() - }) - - it('should call proper actions onSubmit with url handling', () => { - render( - - ) - fireEvent.click(screen.getByTestId('submit-form-btn')) - expect(store.getActions()).toEqual([ - defaultInstanceChanging() - ]) - }) -}) diff --git a/redisinsight/ui/src/pages/home/components/AddInstanceForm/index.ts b/redisinsight/ui/src/pages/home/components/AddInstanceForm/index.ts deleted file mode 100644 index df91655e04..0000000000 --- a/redisinsight/ui/src/pages/home/components/AddInstanceForm/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import InstanceFormWrapper from './InstanceFormWrapper' - -export default InstanceFormWrapper diff --git a/redisinsight/ui/src/pages/home/components/CloudConnection/CloudConnectionForm/index.ts b/redisinsight/ui/src/pages/home/components/CloudConnection/CloudConnectionForm/index.ts new file mode 100644 index 0000000000..0c05734c8c --- /dev/null +++ b/redisinsight/ui/src/pages/home/components/CloudConnection/CloudConnectionForm/index.ts @@ -0,0 +1,3 @@ +import CloudConnectionForm from './CloudConnectionForm' + +export default CloudConnectionForm diff --git a/redisinsight/ui/src/pages/home/components/CloudConnection/CloudConnectionFormWrapper.tsx b/redisinsight/ui/src/pages/home/components/CloudConnection/CloudConnectionFormWrapper.tsx index 5436af2f92..9da568212c 100644 --- a/redisinsight/ui/src/pages/home/components/CloudConnection/CloudConnectionFormWrapper.tsx +++ b/redisinsight/ui/src/pages/home/components/CloudConnection/CloudConnectionFormWrapper.tsx @@ -8,7 +8,7 @@ import { useResizableFormField } from 'uiSrc/services' import { resetErrors } from 'uiSrc/slices/app/notifications' import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' -import CloudConnectionForm from './CloudConnectionForm/CloudConnectionForm' +import CloudConnectionForm from './CloudConnectionForm' export interface Props { width: number diff --git a/redisinsight/ui/src/pages/home/components/CloudConnection/index.ts b/redisinsight/ui/src/pages/home/components/CloudConnection/index.ts new file mode 100644 index 0000000000..90a70f2f19 --- /dev/null +++ b/redisinsight/ui/src/pages/home/components/CloudConnection/index.ts @@ -0,0 +1,3 @@ +import CloudConnectionFormWrapper from './CloudConnectionFormWrapper' + +export default CloudConnectionFormWrapper diff --git a/redisinsight/ui/src/pages/home/components/ClusterConnection/ClusterConnectionForm/index.ts b/redisinsight/ui/src/pages/home/components/ClusterConnection/ClusterConnectionForm/index.ts new file mode 100644 index 0000000000..44ec2d4262 --- /dev/null +++ b/redisinsight/ui/src/pages/home/components/ClusterConnection/ClusterConnectionForm/index.ts @@ -0,0 +1,3 @@ +import ClusterConnectionForm from './ClusterConnectionForm' + +export default ClusterConnectionForm diff --git a/redisinsight/ui/src/pages/home/components/ClusterConnection/ClusterConnectionFormWrapper.tsx b/redisinsight/ui/src/pages/home/components/ClusterConnection/ClusterConnectionFormWrapper.tsx index 2f2d590be5..5d72654536 100644 --- a/redisinsight/ui/src/pages/home/components/ClusterConnection/ClusterConnectionFormWrapper.tsx +++ b/redisinsight/ui/src/pages/home/components/ClusterConnection/ClusterConnectionFormWrapper.tsx @@ -1,17 +1,17 @@ import React, { useEffect, useRef, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' -import { ConnectionString } from 'connection-string' import { useHistory } from 'react-router-dom' import { clusterSelector, fetchInstancesRedisCluster, } from 'uiSrc/slices/instances/cluster' -import { REDIS_URI_SCHEMES, Pages } from 'uiSrc/constants' +import { Pages } from 'uiSrc/constants' import { useResizableFormField } from 'uiSrc/services' import { resetErrors } from 'uiSrc/slices/app/notifications' -import { ICredentialsRedisCluster } from 'uiSrc/slices/interfaces' +import { ICredentialsRedisCluster, InstanceType } from 'uiSrc/slices/interfaces' import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' +import { autoFillFormDetails } from 'uiSrc/pages/home/utils' import ClusterConnectionForm from './ClusterConnectionForm/ClusterConnectionForm' @@ -70,44 +70,9 @@ const ClusterConnectionFormWrapper = ({ onClose, width }: Props) => { history.push(Pages.redisEnterpriseAutodiscovery) } - const autoFillFormDetails = (content: string): boolean => { - try { - const details = new ConnectionString(content) - - /* If a protocol exists, it should be a redis protocol */ - if (details.protocol && !REDIS_URI_SCHEMES.includes(details.protocol)) return false - /* - * Auto fill logic: - * 1) If the port is parsed, we are sure that the user has indeed copied a connection string. - * '172.18.0.2:12000' => {host: '172,18.0.2', port: 12000} - * 'redis-12000.cluster.local:12000' => {host: 'redis-12000.cluster.local', port: 12000} - * 'lorem ipsum' => {host: undefined, port: undefined} - * 2) If the port is `undefined` but a redis URI scheme is present as protocol, we follow - * the "Scheme semantics" as mentioned in the official URI schemes. - * i) redis:// - https://www.iana.org/assignments/uri-schemes/prov/redis - * ii) rediss:// - https://www.iana.org/assignments/uri-schemes/prov/rediss - */ - if ( - details.port !== undefined - || REDIS_URI_SCHEMES.includes(details.protocol || '') - ) { - setInitialValues({ - host: details.hostname || initialValues.host || 'localhost', - port: `${details.port || initialValues.port || 9443}`, - username: details.user || '', - password: details.password || '', - }) - /* - * auto fill was successfull so return true - */ - return true - } - } catch (err) { - /* The pasted content is not a connection URI so ignore. */ - return false - } - return false - } + const handlePostHostName = (content: string) => ( + autoFillFormDetails(content, initialValues, setInitialValues, InstanceType.RedisEnterpriseCluster) + ) return (
@@ -117,7 +82,7 @@ const ClusterConnectionFormWrapper = ({ onClose, width }: Props) => { username={credentials?.username ?? ''} password={credentials?.password ?? ''} initialValues={initialValues} - onHostNamePaste={autoFillFormDetails} + onHostNamePaste={handlePostHostName} flexGroupClassName={flexGroupClassName} flexItemClassName={flexItemClassName} onClose={onClose} diff --git a/redisinsight/ui/src/pages/home/components/ClusterConnection/index.ts b/redisinsight/ui/src/pages/home/components/ClusterConnection/index.ts new file mode 100644 index 0000000000..405d374c5f --- /dev/null +++ b/redisinsight/ui/src/pages/home/components/ClusterConnection/index.ts @@ -0,0 +1,3 @@ +import ClusterConnectionFormWrapper from './ClusterConnectionFormWrapper' + +export default ClusterConnectionFormWrapper diff --git a/redisinsight/ui/src/pages/home/components/DatabaseAlias/DatabaseAlias.tsx b/redisinsight/ui/src/pages/home/components/DatabaseAlias/DatabaseAlias.tsx index 207270b79a..24faa0fe03 100644 --- a/redisinsight/ui/src/pages/home/components/DatabaseAlias/DatabaseAlias.tsx +++ b/redisinsight/ui/src/pages/home/components/DatabaseAlias/DatabaseAlias.tsx @@ -28,7 +28,7 @@ import styles from './styles.module.scss' export interface Props { alias: string - database?: Nullable + database?: Nullable onOpen: () => void onClone: () => void onCloneBack: () => void diff --git a/redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesList/index.ts b/redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesList/index.ts new file mode 100644 index 0000000000..06b6eca03c --- /dev/null +++ b/redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesList/index.ts @@ -0,0 +1,3 @@ +import DatabasesList from './DatabasesList' + +export default DatabasesList diff --git a/redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesListWrapper.spec.tsx b/redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesListWrapper.spec.tsx index 9cffb5a5a7..ea442a4d66 100644 --- a/redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesListWrapper.spec.tsx +++ b/redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesListWrapper.spec.tsx @@ -11,7 +11,7 @@ import { RootState, store } from 'uiSrc/slices/store' import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' import { errorHandlers } from 'uiSrc/mocks/res/responseComposition' import DatabasesListWrapper, { Props } from './DatabasesListWrapper' -import DatabasesList, { Props as DatabasesListProps } from './DatabasesList/DatabasesList' +import DatabasesList, { Props as DatabasesListProps } from './DatabasesList' const mockedProps = mock() diff --git a/redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesListWrapper.tsx b/redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesListWrapper.tsx index c163666a4e..54c6ca183b 100644 --- a/redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesListWrapper.tsx +++ b/redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesListWrapper.tsx @@ -40,7 +40,7 @@ import RediStackLightLogo from 'uiSrc/assets/img/modules/redistack/RedisStackLog import RediStackDarkLogo from 'uiSrc/assets/img/modules/redistack/RedisStackLogoDark.svg' import { ReactComponent as CloudLinkIcon } from 'uiSrc/assets/img/oauth/cloud_link.svg' import { EXTERNAL_LINKS } from 'uiSrc/constants/links' -import DatabasesList from './DatabasesList/DatabasesList' +import DatabasesList from './DatabasesList' import styles from './styles.module.scss' diff --git a/redisinsight/ui/src/pages/home/components/DatabasesListComponent/index.ts b/redisinsight/ui/src/pages/home/components/DatabasesListComponent/index.ts new file mode 100644 index 0000000000..5685d6dfb3 --- /dev/null +++ b/redisinsight/ui/src/pages/home/components/DatabasesListComponent/index.ts @@ -0,0 +1,3 @@ +import DatabasesListWrapper from './DatabasesListWrapper' + +export default DatabasesListWrapper diff --git a/redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/form-components/DatabaseForm.tsx b/redisinsight/ui/src/pages/home/components/Form/DatabaseForm.tsx similarity index 97% rename from redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/form-components/DatabaseForm.tsx rename to redisinsight/ui/src/pages/home/components/Form/DatabaseForm.tsx index 9ed4aa02a0..c64a440d57 100644 --- a/redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/form-components/DatabaseForm.tsx +++ b/redisinsight/ui/src/pages/home/components/Form/DatabaseForm.tsx @@ -16,18 +16,18 @@ import { SECURITY_FIELD } from 'uiSrc/constants' import { appInfoSelector } from 'uiSrc/slices/app/info' import { handlePasteHostName, MAX_PORT_NUMBER, MAX_TIMEOUT_NUMBER, selectOnFocus, validateField, validatePortNumber, validateTimeoutNumber } from 'uiSrc/utils' import { ConnectionType, InstanceType } from 'uiSrc/slices/interfaces' -import { DbConnectionInfo } from '../interfaces' +import { DbConnectionInfo } from 'uiSrc/pages/home/interfaces' export interface Props { flexGroupClassName?: string flexItemClassName?: string formik: FormikProps - isEditMode: boolean - isCloneMode: boolean + isEditMode?: boolean + isCloneMode?: boolean onHostNamePaste: (content: string) => boolean instanceType: InstanceType connectionType?: ConnectionType - isFromCloud: boolean + isFromCloud?: boolean } const DatabaseForm = (props: Props) => { @@ -35,8 +35,8 @@ const DatabaseForm = (props: Props) => { flexGroupClassName = '', flexItemClassName = '', formik, - isEditMode, - isCloneMode, + isEditMode = false, + isCloneMode = false, onHostNamePaste, instanceType, connectionType, diff --git a/redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/form-components/DbCompressor.tsx b/redisinsight/ui/src/pages/home/components/Form/DbCompressor.tsx similarity index 96% rename from redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/form-components/DbCompressor.tsx rename to redisinsight/ui/src/pages/home/components/Form/DbCompressor.tsx index 320811ea2c..6bbe25cb4a 100644 --- a/redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/form-components/DbCompressor.tsx +++ b/redisinsight/ui/src/pages/home/components/Form/DbCompressor.tsx @@ -12,8 +12,8 @@ import cx from 'classnames' import { FormikProps } from 'formik' import { KeyValueCompressor } from 'uiSrc/constants' -import { DbConnectionInfo } from '../interfaces' -import { NONE } from '../constants' +import { DbConnectionInfo } from 'uiSrc/pages/home/interfaces' +import { NONE } from 'uiSrc/pages/home/constants' import styles from '../styles.module.scss' export interface Props { diff --git a/redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/form-components/DbIndex.tsx b/redisinsight/ui/src/pages/home/components/Form/DbIndex.tsx similarity index 97% rename from redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/form-components/DbIndex.tsx rename to redisinsight/ui/src/pages/home/components/Form/DbIndex.tsx index 0681dbd941..c209bb3be7 100644 --- a/redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/form-components/DbIndex.tsx +++ b/redisinsight/ui/src/pages/home/components/Form/DbIndex.tsx @@ -5,7 +5,7 @@ import { FormikProps } from 'formik' import { validateNumber } from 'uiSrc/utils' -import { DbConnectionInfo } from '../interfaces' +import { DbConnectionInfo } from 'uiSrc/pages/home/interfaces' import styles from '../styles.module.scss' export interface Props { diff --git a/redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/form-components/DbInfo.tsx b/redisinsight/ui/src/pages/home/components/Form/DbInfo.tsx similarity index 100% rename from redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/form-components/DbInfo.tsx rename to redisinsight/ui/src/pages/home/components/Form/DbInfo.tsx diff --git a/redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/form-components/Messages.tsx b/redisinsight/ui/src/pages/home/components/Form/Messages.tsx similarity index 100% rename from redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/form-components/Messages.tsx rename to redisinsight/ui/src/pages/home/components/Form/Messages.tsx diff --git a/redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/form-components/SSHDetails.tsx b/redisinsight/ui/src/pages/home/components/Form/SSHDetails.tsx similarity index 98% rename from redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/form-components/SSHDetails.tsx rename to redisinsight/ui/src/pages/home/components/Form/SSHDetails.tsx index 98ba411718..9a7ef33918 100644 --- a/redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/form-components/SSHDetails.tsx +++ b/redisinsight/ui/src/pages/home/components/Form/SSHDetails.tsx @@ -23,8 +23,8 @@ import { } from 'uiSrc/utils' import { SECURITY_FIELD } from 'uiSrc/constants' -import { SshPassType } from '../constants' -import { DbConnectionInfo } from '../interfaces' +import { SshPassType } from 'uiSrc/pages/home/constants' +import { DbConnectionInfo } from 'uiSrc/pages/home/interfaces' import styles from '../styles.module.scss' diff --git a/redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/form-components/TlsDetails.tsx b/redisinsight/ui/src/pages/home/components/Form/TlsDetails.tsx similarity index 99% rename from redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/form-components/TlsDetails.tsx rename to redisinsight/ui/src/pages/home/components/Form/TlsDetails.tsx index 9f5b6c8884..dfeef17320 100644 --- a/redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/form-components/TlsDetails.tsx +++ b/redisinsight/ui/src/pages/home/components/Form/TlsDetails.tsx @@ -18,8 +18,8 @@ import { validateCertName, validateField } from 'uiSrc/utils' import { ADD_NEW_CA_CERT, NO_CA_CERT -} from '../constants' -import { DbConnectionInfo } from '../interfaces' +} from 'uiSrc/pages/home/constants' +import { DbConnectionInfo } from 'uiSrc/pages/home/interfaces' import styles from '../styles.module.scss' diff --git a/redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/form-components/index.ts b/redisinsight/ui/src/pages/home/components/Form/index.ts similarity index 100% rename from redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/form-components/index.ts rename to redisinsight/ui/src/pages/home/components/Form/index.ts diff --git a/redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/form-components/sentinel/DbInfoSentinel.tsx b/redisinsight/ui/src/pages/home/components/Form/sentinel/DbInfoSentinel.tsx similarity index 100% rename from redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/form-components/sentinel/DbInfoSentinel.tsx rename to redisinsight/ui/src/pages/home/components/Form/sentinel/DbInfoSentinel.tsx diff --git a/redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/form-components/sentinel/PrimaryGroupSentinel.tsx b/redisinsight/ui/src/pages/home/components/Form/sentinel/PrimaryGroupSentinel.tsx similarity index 96% rename from redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/form-components/sentinel/PrimaryGroupSentinel.tsx rename to redisinsight/ui/src/pages/home/components/Form/sentinel/PrimaryGroupSentinel.tsx index f913d8ee1a..c867a9e90b 100644 --- a/redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/form-components/sentinel/PrimaryGroupSentinel.tsx +++ b/redisinsight/ui/src/pages/home/components/Form/sentinel/PrimaryGroupSentinel.tsx @@ -2,7 +2,7 @@ import React from 'react' import { EuiFieldText, EuiFlexGroup, EuiFlexItem, EuiFormRow } from '@elastic/eui' import { FormikProps } from 'formik' -import { DbConnectionInfo } from '../../interfaces' +import { DbConnectionInfo } from 'uiSrc/pages/home/interfaces' export interface Props { flexGroupClassName?: string diff --git a/redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/form-components/sentinel/SentinelHostPort.tsx b/redisinsight/ui/src/pages/home/components/Form/sentinel/SentinelHostPort.tsx similarity index 100% rename from redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/form-components/sentinel/SentinelHostPort.tsx rename to redisinsight/ui/src/pages/home/components/Form/sentinel/SentinelHostPort.tsx diff --git a/redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/form-components/sentinel/SentinelMasterDatabase.tsx b/redisinsight/ui/src/pages/home/components/Form/sentinel/SentinelMasterDatabase.tsx similarity index 97% rename from redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/form-components/sentinel/SentinelMasterDatabase.tsx rename to redisinsight/ui/src/pages/home/components/Form/sentinel/SentinelMasterDatabase.tsx index 9f29f47e9b..00937d6625 100644 --- a/redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/form-components/sentinel/SentinelMasterDatabase.tsx +++ b/redisinsight/ui/src/pages/home/components/Form/sentinel/SentinelMasterDatabase.tsx @@ -11,7 +11,7 @@ import { import { FormikProps } from 'formik' import { Nullable } from 'uiSrc/utils' -import { DbConnectionInfo } from '../../interfaces' +import { DbConnectionInfo } from 'uiSrc/pages/home/interfaces' import styles from '../../styles.module.scss' export interface Props { diff --git a/redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/form-components/sentinel/index.ts b/redisinsight/ui/src/pages/home/components/Form/sentinel/index.ts similarity index 100% rename from redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/form-components/sentinel/index.ts rename to redisinsight/ui/src/pages/home/components/Form/sentinel/index.ts diff --git a/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionForm/ManualConnectionForm.tsx b/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionForm/ManualConnectionForm.tsx new file mode 100644 index 0000000000..5582d422ac --- /dev/null +++ b/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionForm/ManualConnectionForm.tsx @@ -0,0 +1,662 @@ +import { + EuiButton, + EuiCollapsibleNavGroup, + EuiForm, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiToolTip, + keys, +} from '@elastic/eui' +import { FormikErrors, useFormik } from 'formik' +import { isEmpty, pick, toString } from 'lodash' +import React, { useEffect, useRef, useState } from 'react' +import ReactDOM from 'react-dom' +import { useDispatch, useSelector } from 'react-redux' +import { useHistory } from 'react-router' + +import { PageNames, Pages } from 'uiSrc/constants' +import validationErrors from 'uiSrc/constants/validationErrors' +import DatabaseAlias from 'uiSrc/pages/home/components/DatabaseAlias' +import { useResizableFormField } from 'uiSrc/services' +import { appContextSelector, setAppContextInitialState } from 'uiSrc/slices/app/context' +import { resetKeys } from 'uiSrc/slices/browser/keys' +import { + changeInstanceAliasAction, + checkConnectToInstanceAction, + resetInstanceUpdateAction, + setConnectedInstanceId, +} from 'uiSrc/slices/instances/instances' +import { ConnectionType, InstanceType, } from 'uiSrc/slices/interfaces' +import { getRedisModulesSummary, sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' +import { getDiffKeysOfObjectValues, isRediStack } from 'uiSrc/utils' +import { BuildType } from 'uiSrc/constants/env' +import { appRedirectionSelector } from 'uiSrc/slices/app/url-handling' +import { UrlHandlingActions } from 'uiSrc/slices/interfaces/urlHandling' + +import { + fieldDisplayNames, + SshPassType, + DEFAULT_TIMEOUT, + NONE, +} from 'uiSrc/pages/home/constants' +import { getFormErrors, getSubmitButtonContent } from 'uiSrc/pages/home/utils' +import { DbConnectionInfo, ISubmitButton } from 'uiSrc/pages/home/interfaces' +import { + DbIndex, + DbInfo, + MessageStandalone, + TlsDetails, + DatabaseForm, + DbCompressor, + SSHDetails, +} from 'uiSrc/pages/home/components/Form' +import { + DbInfoSentinel, + PrimaryGroupSentinel, + SentinelHostPort, + SentinelMasterDatabase, +} from 'uiSrc/pages/home/components/Form/sentinel' +import { SubmitBtnText } from 'uiSrc/pages/home/components/ManualConnection/ManualConnectionWrapper' + +export interface Props { + width: number + formFields: DbConnectionInfo + submitButtonText?: SubmitBtnText + loading: boolean + buildType?: BuildType + isEditMode: boolean + isCloneMode: boolean + setIsCloneMode: (value: boolean) => void + initialValues: DbConnectionInfo + onSubmit: (values: DbConnectionInfo) => void + onTestConnection: (values: DbConnectionInfo) => void + onHostNamePaste: (content: string) => boolean + onClose?: () => void + onAliasEdited?: (value: string) => void +} + +const getInitFieldsDisplayNames = ({ host, port, name }: any) => { + if ((!host || !port) && !name) { + return pick(fieldDisplayNames, ['host', 'port', 'name']) + } + return {} +} + +const getDefaultHost = () => '127.0.0.1' +const getDefaultPort = () => '6379' + +const ManualConnectionForm = (props: Props) => { + const { + formFields: { + id, + host, + name, + port, + tls, + db = null, + compressor = NONE, + nameFromProvider, + sentinelMaster, + connectionType, + nodes = null, + tlsClientAuthRequired, + certificates, + selectedTlsClientCertId = '', + verifyServerTlsCert, + caCertificates, + selectedCaCertName, + username, + password, + timeout, + modules, + sentinelMasterPassword, + sentinelMasterUsername, + servername, + provider, + ssh, + sshPassType = SshPassType.Password, + sshOptions, + version, + }, + initialValues: initialValuesProp, + width, + onClose, + onSubmit, + onTestConnection, + onHostNamePaste, + submitButtonText, + buildType, + loading, + isEditMode, + isCloneMode, + setIsCloneMode, + onAliasEdited, + } = props + + const { contextInstanceId, lastPage } = useSelector(appContextSelector) + const { action } = useSelector(appRedirectionSelector) + + const prepareInitialValues = () => ({ + host: host ?? getDefaultHost(), + port: port ? port.toString() : getDefaultPort(), + timeout: timeout ? timeout.toString() : toString(DEFAULT_TIMEOUT / 1_000), + name: name ?? `${getDefaultHost()}:${getDefaultPort()}`, + username, + password, + tls, + db, + compressor, + modules, + showDb: !!db, + showCompressor: compressor !== NONE, + sni: !!servername, + servername, + newCaCert: '', + newCaCertName: '', + selectedCaCertName, + tlsClientAuthRequired, + verifyServerTlsCert, + newTlsCertPairName: '', + selectedTlsClientCertId, + newTlsClientCert: '', + newTlsClientKey: '', + sentinelMasterName: sentinelMaster?.name || '', + sentinelMasterUsername, + sentinelMasterPassword, + ssh, + sshPassType, + sshHost: sshOptions?.host ?? '', + sshPort: sshOptions?.port ?? 22, + sshUsername: sshOptions?.username ?? '', + sshPassword: sshOptions?.password ?? '', + sshPrivateKey: sshOptions?.privateKey ?? '', + sshPassphrase: sshOptions?.passphrase ?? '' + }) + + const [initialValues, setInitialValues] = useState(prepareInitialValues()) + + const [errors, setErrors] = useState>( + getInitFieldsDisplayNames({ host, port, name }) + ) + + useEffect(() => { + const values = prepareInitialValues() + + setInitialValues(values) + formik.setValues(values) + }, [initialValuesProp, isCloneMode]) + + const history = useHistory() + const dispatch = useDispatch() + + const formRef = useRef(null) + + const submitIsDisable = () => !isEmpty(errors) + const isFromCloud = action === UrlHandlingActions.Connect + + const validate = (values: DbConnectionInfo) => { + const errs = getFormErrors(values) + setErrors(errs) + return errs + } + + const formik = useFormik({ + initialValues, + validate, + enableReinitialize: true, + onSubmit: (values: any) => { + if (isCloneMode) { + const diffKeys = getDiffKeysOfObjectValues(formik.initialValues, values) + sendEventTelemetry({ + event: TelemetryEvent.CONFIG_DATABASES_DATABASE_CLONE_CONFIRMED, + eventData: { + fieldsModified: diffKeys + } + }) + } + onSubmit(values) + }, + }) + + const [flexGroupClassName, flexItemClassName] = useResizableFormField( + formRef, + width + ) + + const onKeyDown = (event: React.KeyboardEvent) => { + if (event.key === keys.ENTER && !submitIsDisable()) { + // event. + formik.submitForm() + } + } + + useEffect(() => + // componentWillUnmount + () => { + if (isEditMode) { + dispatch(resetInstanceUpdateAction()) + } + }, + []) + + const handleCheckConnectToInstance = () => { + const modulesSummary = getRedisModulesSummary(modules) + sendEventTelemetry({ + event: TelemetryEvent.CONFIG_DATABASES_OPEN_DATABASE_BUTTON_CLICKED, + eventData: { + databaseId: id, + provider, + ...modulesSummary, + } + }) + dispatch(checkConnectToInstanceAction(id, connectToInstance)) + } + + const handleCloneDatabase = () => { + setIsCloneMode(true) + sendEventTelemetry({ + event: TelemetryEvent.CONFIG_DATABASES_DATABASE_CLONE_REQUESTED, + eventData: { + databaseId: id + } + }) + } + + const handleBackCloneDatabase = () => { + setIsCloneMode(false) + sendEventTelemetry({ + event: TelemetryEvent.CONFIG_DATABASES_DATABASE_CLONE_CANCELLED, + eventData: { + databaseId: id + } + }) + } + + const handleTestConnectionDatabase = () => { + onTestConnection(formik.values) + } + + const handleChangeDatabaseAlias = ( + value: string, + onSuccess?: () => void, + onFail?: () => void + ) => { + dispatch(changeInstanceAliasAction( + id, + value, + () => { + onAliasEdited?.(value) + onSuccess?.() + }, + onFail + )) + } + + const connectToInstance = () => { + if (contextInstanceId && contextInstanceId !== id) { + dispatch(resetKeys()) + dispatch(setAppContextInitialState()) + } + dispatch(setConnectedInstanceId(id ?? '')) + + if (lastPage === PageNames.workbench && contextInstanceId === id) { + history.push(Pages.workbench(id)) + return + } + history.push(Pages.browser(id)) + } + + const SubmitButton = ({ + text = '', + onClick, + submitIsDisabled, + }: ISubmitButton) => ( + + + {text} + + + ) + + const Footer = () => { + const footerEl = document.getElementById('footerDatabaseForm') + + if (footerEl) { + return ReactDOM.createPortal( + + + + + Test Connection + + + + + + + {onClose && ( + + Cancel + + )} + + + + , + footerEl + ) + } + return null + } + + return ( +
+ {isEditMode && name && ( +
+ +
+ )} +
+ {!isEditMode && !isFromCloud && ( + <> + +
+ + )} + {!isEditMode && !isFromCloud && ( + + + + + + {buildType !== BuildType.RedisStack && ( + + )} + + )} + {(isEditMode || isCloneMode || isFromCloud) && connectionType !== ConnectionType.Sentinel && ( + <> + {!isCloneMode && ( + + )} + + + {isCloneMode && ( + + )} + + + {buildType !== BuildType.RedisStack && ( + + )} + + + )} + {(isEditMode || isCloneMode) && connectionType === ConnectionType.Sentinel && ( + <> + + {!isCloneMode && ( + <> + + + + + + + + + + + + + + )} + {isCloneMode && ( + <> + + + + + + + + + + + + + )} + + + )} +
+
+
+ ) +} + +export default ManualConnectionForm diff --git a/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionForm/index.ts b/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionForm/index.ts new file mode 100644 index 0000000000..600d1f3024 --- /dev/null +++ b/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionForm/index.ts @@ -0,0 +1,3 @@ +import ManualConnectionForm from './ManualConnectionForm' + +export default ManualConnectionForm diff --git a/redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceFormWrapper.tsx b/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionWrapper.tsx similarity index 67% rename from redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceFormWrapper.tsx rename to redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionWrapper.tsx index 9c71b881e7..429eff83c6 100644 --- a/redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceFormWrapper.tsx +++ b/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionWrapper.tsx @@ -1,6 +1,4 @@ -/* eslint-disable no-nested-ternary */ -import { ConnectionString } from 'connection-string' -import { isUndefined, pick, toNumber, toString, omit } from 'lodash' +import { pick, toNumber, toString, omit } from 'lodash' import React, { useEffect, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import { useHistory } from 'react-router' @@ -13,26 +11,24 @@ import { updateInstanceAction, cloneInstanceAction, } from 'uiSrc/slices/instances/instances' -import { fetchMastersSentinelAction, sentinelSelector, } from 'uiSrc/slices/instances/sentinel' import { Nullable, removeEmpty, getFormUpdates, transformQueryParamsObject } from 'uiSrc/utils' +import { BuildType } from 'uiSrc/constants/env' import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' import { caCertsSelector, fetchCaCerts } from 'uiSrc/slices/instances/caCerts' -import { ConnectionType, Instance, InstanceType, } from 'uiSrc/slices/interfaces' -import { DbType, Pages, REDIS_URI_SCHEMES } from 'uiSrc/constants' +import { ConnectionType, Instance, InstanceType } from 'uiSrc/slices/interfaces' +import { DbType, Pages } from 'uiSrc/constants' import { clientCertsSelector, fetchClientCerts, } from 'uiSrc/slices/instances/clientCerts' import { appInfoSelector } from 'uiSrc/slices/app/info' - import { UrlHandlingActions } from 'uiSrc/slices/interfaces/urlHandling' import { appRedirectionSelector, setUrlHandlingInitialState } from 'uiSrc/slices/app/url-handling' import { getRedirectionPage } from 'uiSrc/utils/routing' -import InstanceForm from './InstanceForm' -import { DbConnectionInfo } from './InstanceForm/interfaces' -import { ADD_NEW, ADD_NEW_CA_CERT, DEFAULT_TIMEOUT, NO_CA_CERT, SshPassType } from './InstanceForm/constants' +import { DbConnectionInfo } from 'uiSrc/pages/home/interfaces' +import { applyTlSDatabase, applySSHDatabase, autoFillFormDetails } from 'uiSrc/pages/home/utils' +import { ADD_NEW, ADD_NEW_CA_CERT, DEFAULT_TIMEOUT, NO_CA_CERT, SshPassType, SubmitBtnText } from 'uiSrc/pages/home/constants' +import ManualConnectionForm from './ManualConnectionForm' export interface Props { width: number - isResizablePanel?: boolean - instanceType: InstanceType editMode: boolean urlHandlingAction?: Nullable initialValues?: Nullable> @@ -42,18 +38,6 @@ export interface Props { onAliasEdited?: (value: string) => void } -export enum SubmitBtnText { - AddDatabase = 'Add Redis Database', - EditDatabase = 'Apply changes', - ConnectToSentinel = 'Discover database', - CloneDatabase = 'Clone Database' -} - -export enum LoadingDatabaseText { - AddDatabase = 'Adding database...', - EditDatabase = 'Editing database...', -} - export enum TitleDatabaseText { AddDatabase = 'Add Redis Database', EditDatabase = 'Edit Redis Database', @@ -77,12 +61,10 @@ const getInitialValues = (editedInstance?: Nullable>) => ({ : SshPassType.Password }) -const InstanceFormWrapper = (props: Props) => { +const ManualConnectionWrapper = (props: Props) => { const { editMode, width, - instanceType, - isResizablePanel = false, onClose, onDbEdited, onAliasEdited, @@ -96,7 +78,6 @@ const InstanceFormWrapper = (props: Props) => { const { host, port, name, username, password, timeout, tls, ssh, sshPassType, servername } = initialValues const { loadingChanging: loadingStandalone } = useSelector(instancesSelector) - const { loading: loadingSentinel } = useSelector(sentinelSelector) const { data: caCertificates } = useSelector(caCertsSelector) const { data: certificates } = useSelector(clientCertsSelector) const { server } = useSelector(appInfoSelector) @@ -153,40 +134,28 @@ const InstanceFormWrapper = (props: Props) => { return } - if (instanceType === InstanceType.Sentinel) { - sendEventTelemetry({ - event: TelemetryEvent.CONFIG_DATABASES_REDIS_SENTINEL_AUTODISCOVERY_SUBMITTED - }) - - delete payload.name - delete payload.db - dispatch(fetchMastersSentinelAction(payload, onMastersSentinelFetched)) - } else { - sendEventTelemetry({ - event: TelemetryEvent.CONFIG_DATABASES_MANUALLY_SUBMITTED - }) + sendEventTelemetry({ + event: TelemetryEvent.CONFIG_DATABASES_MANUALLY_SUBMITTED + }) - if (urlHandlingAction === UrlHandlingActions.Connect) { - const cloudDetails = transformQueryParamsObject( - pick( - urlHandlingProperties, - ['cloudId', 'subscriptionType', 'planMemoryLimit', 'memoryLimitMeasurementUnit', 'free'] - ) + if (urlHandlingAction === UrlHandlingActions.Connect) { + const cloudDetails = transformQueryParamsObject( + pick( + urlHandlingProperties, + ['cloudId', 'subscriptionType', 'planMemoryLimit', 'memoryLimitMeasurementUnit', 'free'] ) + ) - const db = { ...payload } - if (cloudDetails?.cloudId) { - db.cloudDetails = cloudDetails - } - - dispatch(createInstanceStandaloneAction(db, undefined, handleSuccessConnectWithRedirect)) - return + const db = { ...payload } + if (cloudDetails?.cloudId) { + db.cloudDetails = cloudDetails } - dispatch( - createInstanceStandaloneAction(payload, onMastersSentinelFetched) - ) + dispatch(createInstanceStandaloneAction(db, undefined, handleSuccessConnectWithRedirect)) + return } + + dispatch(createInstanceStandaloneAction(payload, onMastersSentinelFetched)) } const handleEditDatabase = (payload: any) => { dispatch(updateInstanceAction(payload, onDbEdited)) @@ -261,8 +230,8 @@ const InstanceFormWrapper = (props: Props) => { clientCert: !tls ? undefined : typeof selectedTlsClientCertId === 'string' - && tlsClientAuthRequired - && selectedTlsClientCertId !== ADD_NEW + && tlsClientAuthRequired + && selectedTlsClientCertId !== ADD_NEW ? { id: selectedTlsClientCertId } : selectedTlsClientCertId === ADD_NEW && tlsClientAuthRequired ? { @@ -308,117 +277,6 @@ const InstanceFormWrapper = (props: Props) => { } } - const autoFillFormDetails = (content: string): boolean => { - try { - const details = new ConnectionString(content) - - /* If a protocol exists, it should be a redis protocol */ - if (details.protocol && !REDIS_URI_SCHEMES.includes(details.protocol)) return false - /* - * Auto fill logic: - * 1) If the port is parsed, we are sure that the user has indeed copied a connection string. - * '172.18.0.2:12000' => {host: '172,18.0.2', port: 12000} - * 'redis-12000.cluster.local:12000' => {host: 'redis-12000.cluster.local', port: 12000} - * 'lorem ipsum' => {host: undefined, port: undefined} - * 2) If the port is `undefined` but a redis URI scheme is present as protocol, we follow - * the "Scheme semantics" as mentioned in the official URI schemes. - * i) redis:// - https://www.iana.org/assignments/uri-schemes/prov/redis - * ii) rediss:// - https://www.iana.org/assignments/uri-schemes/prov/rediss - */ - if ( - details.port !== undefined - || REDIS_URI_SCHEMES.includes(details.protocol || '') - ) { - setInitialValues({ - name: details.host || name || 'localhost:6379', - host: details.hostname || host || 'localhost', - port: `${details.port || port || 9443}`, - username: details.user || '', - password: details.password, - tls: details.protocol === 'rediss', - ssh: false, - sshPassType: SshPassType.Password - } as any) - /* - * auto fill was successfull so return true - */ - return true - } - } catch (err) { - /* The pasted content is not a connection URI so ignore. */ - return false - } - return false - } - - const applyTlSDatabase = (database: any, tlsSettings: any) => { - const { useTls, verifyServerCert, servername, caCert, clientAuth, clientCert } = tlsSettings - if (!useTls) return - - database.tls = useTls - database.tlsServername = servername - database.verifyServerCert = !!verifyServerCert - - if (!isUndefined(caCert?.new)) { - database.caCert = { - name: caCert?.new.name, - certificate: caCert?.new.certificate, - } - } - - if (!isUndefined(caCert?.name)) { - database.caCert = { id: caCert?.name } - } - - if (clientAuth) { - if (!isUndefined(clientCert.new)) { - database.clientCert = { - name: clientCert.new.name, - certificate: clientCert.new.certificate, - key: clientCert.new.key, - } - } - - if (!isUndefined(clientCert.id)) { - database.clientCert = { id: clientCert.id } - } - } - } - - const applySSHDatabase = (database: any, values: DbConnectionInfo) => { - const { - ssh, - sshPassType, - sshHost, - sshPort, - sshPassword, - sshUsername, - sshPassphrase, - sshPrivateKey, - } = values - - if (ssh) { - database.ssh = true - database.sshOptions = { - host: sshHost, - port: +sshPort, - username: sshUsername, - } - - if (sshPassType === SshPassType.Password) { - database.sshOptions.password = sshPassword - database.sshOptions.passphrase = null - database.sshOptions.privateKey = null - } - - if (sshPassType === SshPassType.PrivateKey) { - database.sshOptions.password = null - database.sshOptions.passphrase = sshPassphrase - database.sshOptions.privateKey = sshPrivateKey - } - } - } - const editDatabase = (tlsSettings: any, values: DbConnectionInfo, isCloneMode: boolean) => { const { name, @@ -541,8 +399,8 @@ const InstanceFormWrapper = (props: Props) => { clientCert: !tls ? undefined : typeof selectedTlsClientCertId === 'string' - && tlsClientAuthRequired - && selectedTlsClientCertId !== ADD_NEW + && tlsClientAuthRequired + && selectedTlsClientCertId !== ADD_NEW ? { id: selectedTlsClientCertId } : selectedTlsClientCertId === ADD_NEW && tlsClientAuthRequired ? { @@ -600,9 +458,6 @@ const InstanceFormWrapper = (props: Props) => { } const getSubmitButtonText = () => { - if (instanceType === InstanceType.Sentinel) { - return SubmitBtnText.ConnectToSentinel - } if (isCloneMode) { return SubmitBtnText.CloneDatabase } @@ -612,21 +467,18 @@ const InstanceFormWrapper = (props: Props) => { return SubmitBtnText.AddDatabase } + const handlePostHostName = (content: string): boolean => ( + autoFillFormDetails(content, initialValues, setInitialValues, InstanceType.Standalone) + ) + return (
- { onSubmit={handleConnectionFormSubmit} onTestConnection={handleTestConnectionDatabase} onClose={handleOnClose} - onHostNamePaste={autoFillFormDetails} + onHostNamePaste={handlePostHostName} isEditMode={editMode} isCloneMode={isCloneMode} setIsCloneMode={setIsCloneMode} @@ -647,4 +499,4 @@ const InstanceFormWrapper = (props: Props) => { ) } -export default InstanceFormWrapper +export default ManualConnectionWrapper diff --git a/redisinsight/ui/src/pages/home/components/ManualConnection/index.ts b/redisinsight/ui/src/pages/home/components/ManualConnection/index.ts new file mode 100644 index 0000000000..28d4f3af8c --- /dev/null +++ b/redisinsight/ui/src/pages/home/components/ManualConnection/index.ts @@ -0,0 +1,3 @@ +import ManualConnectionWrapper from './ManualConnectionWrapper' + +export default ManualConnectionWrapper diff --git a/redisinsight/ui/src/pages/home/components/AddDatabases/InstanceConnections/InstanceConnections.spec.tsx b/redisinsight/ui/src/pages/home/components/RightPanel/InstanceConnections/InstanceConnections.spec.tsx similarity index 100% rename from redisinsight/ui/src/pages/home/components/AddDatabases/InstanceConnections/InstanceConnections.spec.tsx rename to redisinsight/ui/src/pages/home/components/RightPanel/InstanceConnections/InstanceConnections.spec.tsx diff --git a/redisinsight/ui/src/pages/home/components/AddDatabases/InstanceConnections/InstanceConnections.tsx b/redisinsight/ui/src/pages/home/components/RightPanel/InstanceConnections/InstanceConnections.tsx similarity index 98% rename from redisinsight/ui/src/pages/home/components/AddDatabases/InstanceConnections/InstanceConnections.tsx rename to redisinsight/ui/src/pages/home/components/RightPanel/InstanceConnections/InstanceConnections.tsx index dc7e73e4d0..a5703dcf5d 100644 --- a/redisinsight/ui/src/pages/home/components/AddDatabases/InstanceConnections/InstanceConnections.tsx +++ b/redisinsight/ui/src/pages/home/components/RightPanel/InstanceConnections/InstanceConnections.tsx @@ -13,8 +13,7 @@ import LightActiveManualSvg from 'uiSrc/assets/img/light_theme/active_manual.svg import LightNotActiveManualSvg from 'uiSrc/assets/img/light_theme/n_active_manual.svg' import LightActiveAutoSvg from 'uiSrc/assets/img/light_theme/active_auto.svg' import LightNotActiveAutoSvg from 'uiSrc/assets/img/light_theme/n_active_auto.svg' - -import { AddDbType } from '../AddDatabasesContainer' +import { AddDbType } from 'uiSrc/pages/home/constants' import styles from '../styles.module.scss' diff --git a/redisinsight/ui/src/pages/home/components/AddDatabases/AddDatabasesContainer.spec.tsx b/redisinsight/ui/src/pages/home/components/RightPanel/RightPanel.spec.tsx similarity index 70% rename from redisinsight/ui/src/pages/home/components/AddDatabases/AddDatabasesContainer.spec.tsx rename to redisinsight/ui/src/pages/home/components/RightPanel/RightPanel.spec.tsx index 63013b6e49..238300f3b3 100644 --- a/redisinsight/ui/src/pages/home/components/AddDatabases/AddDatabasesContainer.spec.tsx +++ b/redisinsight/ui/src/pages/home/components/RightPanel/RightPanel.spec.tsx @@ -1,19 +1,19 @@ import React from 'react' import { instance, mock } from 'ts-mockito' import { render, screen, fireEvent } from 'uiSrc/utils/test-utils' -import AddDatabasesContainer, { Props } from './AddDatabasesContainer' +import RightPanel, { Props } from './RightPanel' const mockedProps = mock() describe('AddDatabasesContainer', () => { it('should render', () => { expect( - render() + render() ).toBeTruthy() }) it('should render instance types after click on auto discover', () => { - render() + render() fireEvent.click(screen.getByTestId('add-auto')) expect(screen.getByTestId('db-types')).toBeInTheDocument() }) diff --git a/redisinsight/ui/src/pages/home/components/AddDatabases/AddDatabasesContainer.tsx b/redisinsight/ui/src/pages/home/components/RightPanel/RightPanel.tsx similarity index 82% rename from redisinsight/ui/src/pages/home/components/AddDatabases/AddDatabasesContainer.tsx rename to redisinsight/ui/src/pages/home/components/RightPanel/RightPanel.tsx index 4d06fd40c1..305178786e 100644 --- a/redisinsight/ui/src/pages/home/components/AddDatabases/AddDatabasesContainer.tsx +++ b/redisinsight/ui/src/pages/home/components/RightPanel/RightPanel.tsx @@ -20,10 +20,12 @@ import { sentinelSelector, resetDataSentinel } from 'uiSrc/slices/instances/sent import { UrlHandlingActions } from 'uiSrc/slices/interfaces/urlHandling' import { appRedirectionSelector, setUrlHandlingInitialState } from 'uiSrc/slices/app/url-handling' +import { AddDbType } from 'uiSrc/pages/home/constants' +import ClusterConnectionFormWrapper from 'uiSrc/pages/home/components/ClusterConnection' +import CloudConnectionFormWrapper from 'uiSrc/pages/home/components/CloudConnection' +import SentinelConnectionWrapper from 'uiSrc/pages/home/components/SentinelConnection' +import ManualConnectionWrapper from 'uiSrc/pages/home/components/ManualConnection' import InstanceConnections from './InstanceConnections/InstanceConnections' -import InstanceFormWrapper from '../AddInstanceForm/InstanceFormWrapper' -import ClusterConnectionFormWrapper from '../ClusterConnection/ClusterConnectionFormWrapper' -import CloudConnectionFormWrapper from '../CloudConnection/CloudConnectionFormWrapper' import styles from './styles.module.scss' @@ -41,12 +43,7 @@ export interface Props { initConnectionType?: AddDbType } -export enum AddDbType { - manual, - auto, -} - -const AddDatabasesContainer = React.memo((props: Props) => { +const RightPanel = React.memo((props: Props) => { const { editMode, isResizablePanel, @@ -183,15 +180,12 @@ const AddDatabasesContainer = React.memo((props: Props) => { const Form = () => ( <> {connectionType === AddDbType.manual && ( - + )} {connectionType === AddDbType.auto && ( <> {typeSelected === InstanceType.Sentinel && ( - + )} {typeSelected === InstanceType.RedisEnterpriseCluster && ( @@ -208,29 +202,29 @@ const AddDatabasesContainer = React.memo((props: Props) => { <>
{!isFullWidth && onClose && ( - - - + + + )} {!editMode && ( - <> - -

Discover and Add Redis Databases

-
- - {connectionType === AddDbType.auto && } - + <> + +

Discover and Add Redis Databases

+
+ + {connectionType === AddDbType.auto && } + )} {Form()}
@@ -239,4 +233,4 @@ const AddDatabasesContainer = React.memo((props: Props) => { ) }) -export default AddDatabasesContainer +export default RightPanel diff --git a/redisinsight/ui/src/pages/home/components/RightPanel/index.ts b/redisinsight/ui/src/pages/home/components/RightPanel/index.ts new file mode 100644 index 0000000000..932f2fecad --- /dev/null +++ b/redisinsight/ui/src/pages/home/components/RightPanel/index.ts @@ -0,0 +1,3 @@ +import RightPanel from './RightPanel' + +export default RightPanel diff --git a/redisinsight/ui/src/pages/home/components/AddDatabases/styles.module.scss b/redisinsight/ui/src/pages/home/components/RightPanel/styles.module.scss similarity index 100% rename from redisinsight/ui/src/pages/home/components/AddDatabases/styles.module.scss rename to redisinsight/ui/src/pages/home/components/RightPanel/styles.module.scss diff --git a/redisinsight/ui/src/pages/home/components/SentinelConnection/SentinelConnectionForm/SentinelConnectionForm.tsx b/redisinsight/ui/src/pages/home/components/SentinelConnection/SentinelConnectionForm/SentinelConnectionForm.tsx new file mode 100644 index 0000000000..0cf13ed72e --- /dev/null +++ b/redisinsight/ui/src/pages/home/components/SentinelConnection/SentinelConnectionForm/SentinelConnectionForm.tsx @@ -0,0 +1,200 @@ +import { + EuiButton, + EuiForm, + EuiFlexGroup, + EuiFlexItem, + EuiToolTip, + keys, +} from '@elastic/eui' +import { FormikErrors, useFormik } from 'formik' +import { isEmpty, pick } from 'lodash' +import React, { useRef, useState } from 'react' +import ReactDOM from 'react-dom' + +import validationErrors from 'uiSrc/constants/validationErrors' +import { useResizableFormField } from 'uiSrc/services' +import { ConnectionType, InstanceType } from 'uiSrc/slices/interfaces' + +import { + fieldDisplayNames, + SubmitBtnText, +} from 'uiSrc/pages/home/constants' +import { getFormErrors, getSubmitButtonContent } from 'uiSrc/pages/home/utils' +import { DbConnectionInfo, ISubmitButton } from 'uiSrc/pages/home/interfaces' +import { + MessageSentinel, + TlsDetails, + DatabaseForm, +} from 'uiSrc/pages/home/components/Form' + +export interface Props { + width: number + submitButtonText?: SubmitBtnText + loading: boolean + initialValues: DbConnectionInfo + certificates: { id: string; name: string }[], + caCertificates: { id: string; name: string }[], + onSubmit: (values: DbConnectionInfo) => void + onHostNamePaste: (content: string) => boolean + onClose?: () => void +} + +const getInitFieldsDisplayNames = ({ host, port }: any) => { + if (!host || !port) { + return pick(fieldDisplayNames, ['host', 'port']) + } + return {} +} + +const SentinelConnectionForm = (props: Props) => { + const { + initialValues, + width, + onClose, + onSubmit, + onHostNamePaste, + submitButtonText, + loading, + certificates, + caCertificates, + } = props + + const [errors, setErrors] = useState>( + getInitFieldsDisplayNames(initialValues) + ) + + const formRef = useRef(null) + + const submitIsDisable = () => !isEmpty(errors) + + const validate = (values: DbConnectionInfo) => { + const errs = getFormErrors(values) + setErrors(errs) + return errs + } + + const formik = useFormik({ + initialValues, + validate, + enableReinitialize: true, + onSubmit: (values: any) => { + onSubmit(values) + }, + }) + + const [flexGroupClassName, flexItemClassName] = useResizableFormField( + formRef, + width + ) + + const onKeyDown = (event: React.KeyboardEvent) => { + if (event.key === keys.ENTER && !submitIsDisable()) { + // event. + formik.submitForm() + } + } + + const SubmitButton = ({ + text = '', + onClick, + submitIsDisabled, + }: ISubmitButton) => ( + + + {text} + + + ) + + const Footer = () => { + const footerEl = document.getElementById('footerDatabaseForm') + + if (footerEl) { + return ReactDOM.createPortal( + + + + + {onClose && ( + + Cancel + + )} + + + + , + footerEl + ) + } + return null + } + + return ( +
+
+ +
+ + + + +
+
+
+ ) +} + +export default SentinelConnectionForm diff --git a/redisinsight/ui/src/pages/home/components/SentinelConnection/SentinelConnectionForm/index.ts b/redisinsight/ui/src/pages/home/components/SentinelConnection/SentinelConnectionForm/index.ts new file mode 100644 index 0000000000..e598e90aae --- /dev/null +++ b/redisinsight/ui/src/pages/home/components/SentinelConnection/SentinelConnectionForm/index.ts @@ -0,0 +1,3 @@ +import SentinelConnectionForm from './SentinelConnectionForm' + +export default SentinelConnectionForm diff --git a/redisinsight/ui/src/pages/home/components/SentinelConnection/SentinelConnectionWrapper.tsx b/redisinsight/ui/src/pages/home/components/SentinelConnection/SentinelConnectionWrapper.tsx new file mode 100644 index 0000000000..2c08e4f14b --- /dev/null +++ b/redisinsight/ui/src/pages/home/components/SentinelConnection/SentinelConnectionWrapper.tsx @@ -0,0 +1,163 @@ +import React, { useEffect, useState } from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { useHistory } from 'react-router' + +import { fetchMastersSentinelAction, sentinelSelector, } from 'uiSrc/slices/instances/sentinel' +import { removeEmpty } from 'uiSrc/utils' +import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' +import { caCertsSelector, fetchCaCerts } from 'uiSrc/slices/instances/caCerts' +import { Pages } from 'uiSrc/constants' +import { clientCertsSelector, fetchClientCerts, } from 'uiSrc/slices/instances/clientCerts' + +import { DbConnectionInfo } from 'uiSrc/pages/home/interfaces' +import { applyTlSDatabase, autoFillFormDetails } from 'uiSrc/pages/home/utils' +import { ADD_NEW, ADD_NEW_CA_CERT, NO_CA_CERT, SubmitBtnText } from 'uiSrc/pages/home/constants' +import { InstanceType } from 'uiSrc/slices/interfaces' +import SentinelConnectionForm from './SentinelConnectionForm' + +export interface Props { + width: number + onClose?: () => void +} +const getDefaultHost = () => '127.0.0.1' +const getDefaultPort = () => '26379' + +const INITIAL_VALUES = { + host: getDefaultHost(), + port: getDefaultPort(), + username: '', + password: '', + tls: false, + tlsClientAuthRequired: false, + selectedTlsClientCertId: ADD_NEW, + verifyServerTlsCert: false, + selectedCaCertName: NO_CA_CERT, + +} + +const SentinelConnectionWrapper = (props: Props) => { + const { + width, + onClose, + } = props + const [initialValues, setInitialValues] = useState(INITIAL_VALUES) + + const { loading } = useSelector(sentinelSelector) + const { data: caCertificates } = useSelector(caCertsSelector) + const { data: certificates } = useSelector(clientCertsSelector) + + const history = useHistory() + const dispatch = useDispatch() + + useEffect(() => { + dispatch(fetchCaCerts()) + dispatch(fetchClientCerts()) + }, []) + + const onMastersSentinelFetched = () => { + history.push(Pages.sentinelDatabases) + } + + const handleSubmitDatabase = (payload: any) => { + sendEventTelemetry({ + event: TelemetryEvent.CONFIG_DATABASES_REDIS_SENTINEL_AUTODISCOVERY_SUBMITTED + }) + + dispatch(fetchMastersSentinelAction(payload, onMastersSentinelFetched)) + } + + const addDatabase = (tlsSettings: any, values: DbConnectionInfo) => { + const { + host, + port, + username, + password, + } = values + const database: any = { + host, + port: +port, + username, + password, + } + + // add tls for database + applyTlSDatabase(database, tlsSettings) + handleSubmitDatabase(removeEmpty(database)) + } + + const handleConnectionFormSubmit = (values: DbConnectionInfo) => { + const { + newCaCert, + tls, + sni, + servername, + newCaCertName, + selectedCaCertName, + tlsClientAuthRequired, + verifyServerTlsCert, + newTlsCertPairName, + selectedTlsClientCertId, + newTlsClientCert, + newTlsClientKey, + } = values + + const tlsSettings = { + useTls: tls, + servername: (sni && servername) || undefined, + verifyServerCert: verifyServerTlsCert, + caCert: + !tls || selectedCaCertName === NO_CA_CERT + ? undefined + : selectedCaCertName === ADD_NEW_CA_CERT + ? { + new: { + name: newCaCertName, + certificate: newCaCert, + }, + } + : { + name: selectedCaCertName, + }, + clientAuth: tls && tlsClientAuthRequired, + clientCert: !tls + ? undefined + : typeof selectedTlsClientCertId === 'string' + && tlsClientAuthRequired + && selectedTlsClientCertId !== ADD_NEW + ? { id: selectedTlsClientCertId } + : selectedTlsClientCertId === ADD_NEW && tlsClientAuthRequired + ? { + new: { + name: newTlsCertPairName, + certificate: newTlsClientCert, + key: newTlsClientKey, + }, + } + : undefined, + } + + addDatabase(tlsSettings, values) + } + + const handlePostHostName = (content: string): boolean => ( + autoFillFormDetails(content, initialValues, setInitialValues, InstanceType.Sentinel) + ) + + return ( +
+ +
+ ) +} + +export default SentinelConnectionWrapper diff --git a/redisinsight/ui/src/pages/home/components/SentinelConnection/index.ts b/redisinsight/ui/src/pages/home/components/SentinelConnection/index.ts new file mode 100644 index 0000000000..0947d0226b --- /dev/null +++ b/redisinsight/ui/src/pages/home/components/SentinelConnection/index.ts @@ -0,0 +1,3 @@ +import SentinelConnectionWrapper from './SentinelConnectionWrapper' + +export default SentinelConnectionWrapper diff --git a/redisinsight/ui/src/pages/home/components/WelcomeComponent/WelcomeComponent.spec.tsx b/redisinsight/ui/src/pages/home/components/WelcomeComponent/WelcomeComponent.spec.tsx index b5d92c3fbc..3f8301c8c8 100644 --- a/redisinsight/ui/src/pages/home/components/WelcomeComponent/WelcomeComponent.spec.tsx +++ b/redisinsight/ui/src/pages/home/components/WelcomeComponent/WelcomeComponent.spec.tsx @@ -4,7 +4,7 @@ import { cloneDeep } from 'lodash' import { render, screen, fireEvent, mockedStore, cleanup } from 'uiSrc/utils/test-utils' import { contentSelector } from 'uiSrc/slices/content/create-redis-buttons' import { MOCKED_CREATE_REDIS_BTN_CONTENT } from 'uiSrc/mocks/content/content' -import { AddDbType } from 'uiSrc/pages/home/components/AddDatabases/AddDatabasesContainer' +import { AddDbType } from 'uiSrc/pages/home/constants' import { setSocialDialogState } from 'uiSrc/slices/oauth/cloud' import { OAuthSocialSource } from 'uiSrc/slices/interfaces' import { appFeatureFlagsFeaturesSelector } from 'uiSrc/slices/app/features' diff --git a/redisinsight/ui/src/pages/home/components/WelcomeComponent/WelcomeComponent.tsx b/redisinsight/ui/src/pages/home/components/WelcomeComponent/WelcomeComponent.tsx index 3c05be266c..e5b9e275fe 100644 --- a/redisinsight/ui/src/pages/home/components/WelcomeComponent/WelcomeComponent.tsx +++ b/redisinsight/ui/src/pages/home/components/WelcomeComponent/WelcomeComponent.tsx @@ -10,14 +10,14 @@ import { ThemeContext } from 'uiSrc/contexts/themeContext' import { sendEventTelemetry, sendPageViewTelemetry, TelemetryEvent, TelemetryPageView } from 'uiSrc/telemetry' import darkLogo from 'uiSrc/assets/img/dark_logo.svg' import lightLogo from 'uiSrc/assets/img/light_logo.svg' -import { AddDbType } from 'uiSrc/pages/home/components/AddDatabases/AddDatabasesContainer' import { ReactComponent as CloudStars } from 'uiSrc/assets/img/oauth/stars.svg' import { ReactComponent as CloudIcon } from 'uiSrc/assets/img/oauth/cloud.svg' import { appFeatureFlagsFeaturesSelector } from 'uiSrc/slices/app/features' import { contentSelector } from 'uiSrc/slices/content/create-redis-buttons' import { getContentByFeature } from 'uiSrc/utils/content' -import { HELP_LINKS, IHelpGuide } from 'uiSrc/pages/home/constants/help-links' +import { AddDbType, HELP_LINKS, IHelpGuide } from 'uiSrc/pages/home/constants' + import { ContentCreateRedis } from 'uiSrc/slices/interfaces/content' import { FeatureFlagComponent, @@ -34,7 +34,7 @@ export interface Props { onAddInstance: (addDbType?: AddDbType) => void } -const Welcome = ({ onAddInstance }: Props) => { +const WelcomeComponent = ({ onAddInstance }: Props) => { const featureFlags = useSelector(appFeatureFlagsFeaturesSelector) const { loading, data } = useSelector(contentSelector) @@ -293,4 +293,4 @@ const Welcome = ({ onAddInstance }: Props) => { ) } -export default Welcome +export default WelcomeComponent diff --git a/redisinsight/ui/src/pages/home/components/WelcomeComponent/index.ts b/redisinsight/ui/src/pages/home/components/WelcomeComponent/index.ts new file mode 100644 index 0000000000..b198b75e9b --- /dev/null +++ b/redisinsight/ui/src/pages/home/components/WelcomeComponent/index.ts @@ -0,0 +1,3 @@ +import WelcomeComponent from './WelcomeComponent' + +export default WelcomeComponent diff --git a/redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/styles.module.scss b/redisinsight/ui/src/pages/home/components/styles.module.scss similarity index 100% rename from redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/styles.module.scss rename to redisinsight/ui/src/pages/home/components/styles.module.scss diff --git a/redisinsight/ui/src/pages/home/constants/database.ts b/redisinsight/ui/src/pages/home/constants/database.ts new file mode 100644 index 0000000000..edd8e9585c --- /dev/null +++ b/redisinsight/ui/src/pages/home/constants/database.ts @@ -0,0 +1,4 @@ +export enum AddDbType { + manual, + auto, +} diff --git a/redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/constants.ts b/redisinsight/ui/src/pages/home/constants/form.ts similarity index 83% rename from redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/constants.ts rename to redisinsight/ui/src/pages/home/constants/form.ts index 057ce40220..ca72f717f8 100644 --- a/redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/constants.ts +++ b/redisinsight/ui/src/pages/home/constants/form.ts @@ -29,3 +29,10 @@ export const fieldDisplayNames = { const DEFAULT_TIMEOUT_ENV = process.env.CONNECTIONS_TIMEOUT_DEFAULT || '30000' // 30 sec export const DEFAULT_TIMEOUT = parseInt(DEFAULT_TIMEOUT_ENV, 10) + +export enum SubmitBtnText { + AddDatabase = 'Add Redis Database', + EditDatabase = 'Apply changes', + ConnectToSentinel = 'Discover database', + CloneDatabase = 'Clone Database' +} diff --git a/redisinsight/ui/src/pages/home/constants/index.ts b/redisinsight/ui/src/pages/home/constants/index.ts new file mode 100644 index 0000000000..59ec3920ec --- /dev/null +++ b/redisinsight/ui/src/pages/home/constants/index.ts @@ -0,0 +1,3 @@ +export * from './form' +export * from './help-links' +export * from './database' diff --git a/redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/interfaces.ts b/redisinsight/ui/src/pages/home/interfaces/form.ts similarity index 89% rename from redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/interfaces.ts rename to redisinsight/ui/src/pages/home/interfaces/form.ts index a52a19322a..2f372161bb 100644 --- a/redisinsight/ui/src/pages/home/components/AddInstanceForm/InstanceForm/interfaces.ts +++ b/redisinsight/ui/src/pages/home/interfaces/form.ts @@ -1,7 +1,8 @@ import { Instance } from 'uiSrc/slices/interfaces' -import { ADD_NEW_CA_CERT, NO_CA_CERT } from './constants' +import { ADD_NEW_CA_CERT, NO_CA_CERT } from 'uiSrc/pages/home/constants' export interface DbConnectionInfo extends Instance { + id?: string port: string tlsClientAuthRequired?: boolean certificates?: { id: number; name: string }[] @@ -26,8 +27,8 @@ export interface DbConnectionInfo extends Instance { sentinelMasterName?: string ssh?: boolean sshPassType?: string - sshHost: string - sshPort: string + sshHost?: string + sshPort?: string sshUsername?: string sshPassword?: string | true sshPrivateKey?: string | true diff --git a/redisinsight/ui/src/pages/home/interfaces/index.ts b/redisinsight/ui/src/pages/home/interfaces/index.ts new file mode 100644 index 0000000000..54151771d5 --- /dev/null +++ b/redisinsight/ui/src/pages/home/interfaces/index.ts @@ -0,0 +1 @@ +export * from './form' diff --git a/redisinsight/ui/src/pages/home/utils/form.tsx b/redisinsight/ui/src/pages/home/utils/form.tsx new file mode 100644 index 0000000000..bd02e85369 --- /dev/null +++ b/redisinsight/ui/src/pages/home/utils/form.tsx @@ -0,0 +1,229 @@ +import { ConnectionString } from 'connection-string' +import { isUndefined } from 'lodash' +import React from 'react' +import { FormikErrors } from 'formik' +import { REDIS_URI_SCHEMES } from 'uiSrc/constants' +import { InstanceType } from 'uiSrc/slices/interfaces' +import {ADD_NEW, ADD_NEW_CA_CERT, fieldDisplayNames, NO_CA_CERT, SshPassType} from 'uiSrc/pages/home/constants' +import { DbConnectionInfo } from 'uiSrc/pages/home/interfaces' + +export const applyTlSDatabase = (database: any, tlsSettings: any) => { + const { useTls, verifyServerCert, servername, caCert, clientAuth, clientCert } = tlsSettings + if (!useTls) return + + database.tls = useTls + database.tlsServername = servername + database.verifyServerCert = !!verifyServerCert + + if (!isUndefined(caCert?.new)) { + database.caCert = { + name: caCert?.new.name, + certificate: caCert?.new.certificate, + } + } + + if (!isUndefined(caCert?.name)) { + database.caCert = { id: caCert?.name } + } + + if (clientAuth) { + if (!isUndefined(clientCert.new)) { + database.clientCert = { + name: clientCert.new.name, + certificate: clientCert.new.certificate, + key: clientCert.new.key, + } + } + + if (!isUndefined(clientCert.id)) { + database.clientCert = { id: clientCert.id } + } + } +} + +export const applySSHDatabase = (database: any, values: DbConnectionInfo) => { + const { + ssh, + sshPassType, + sshHost, + sshPort, + sshPassword, + sshUsername, + sshPassphrase, + sshPrivateKey, + } = values + + if (ssh) { + database.ssh = true + database.sshOptions = { + host: sshHost, + port: +sshPort, + username: sshUsername, + } + + if (sshPassType === SshPassType.Password) { + database.sshOptions.password = sshPassword + database.sshOptions.passphrase = null + database.sshOptions.privateKey = null + } + + if (sshPassType === SshPassType.PrivateKey) { + database.sshOptions.password = null + database.sshOptions.passphrase = sshPassphrase + database.sshOptions.privateKey = sshPrivateKey + } + } +} + +export const getFormErrors = (values: DbConnectionInfo) => { + const errs: FormikErrors = {} + + if (!values.host) { + errs.host = fieldDisplayNames.host + } + if (!values.port) { + errs.port = fieldDisplayNames.port + } + + if ( + values.tls + && values.verifyServerTlsCert + && values.selectedCaCertName === NO_CA_CERT + ) { + errs.selectedCaCertName = fieldDisplayNames.selectedCaCertName + } + + if ( + values.tls + && values.selectedCaCertName === ADD_NEW_CA_CERT + && values.newCaCertName === '' + ) { + errs.newCaCertName = fieldDisplayNames.newCaCertName + } + + if ( + values.tls + && values.selectedCaCertName === ADD_NEW_CA_CERT + && values.newCaCert === '' + ) { + errs.newCaCert = fieldDisplayNames.newCaCert + } + + if ( + values.tls + && values.sni + && values.servername === '' + ) { + errs.servername = fieldDisplayNames.servername + } + + if ( + values.tls + && values.tlsClientAuthRequired + && values.selectedTlsClientCertId === ADD_NEW + ) { + if (values.newTlsCertPairName === '') { + errs.newTlsCertPairName = fieldDisplayNames.newTlsCertPairName + } + if (values.newTlsClientCert === '') { + errs.newTlsClientCert = fieldDisplayNames.newTlsClientCert + } + if (values.newTlsClientKey === '') { + errs.newTlsClientKey = fieldDisplayNames.newTlsClientKey + } + } + + return errs +} + +export const autoFillFormDetails = ( + content: string, + initialValues: any, + setInitialValues: (data: any) => void, + instanceType: InstanceType +): boolean => { + try { + const details = new ConnectionString(content) + + /* If a protocol exists, it should be a redis protocol */ + if (details.protocol && !REDIS_URI_SCHEMES.includes(details.protocol)) return false + /* + * Auto fill logic: + * 1) If the port is parsed, we are sure that the user has indeed copied a connection string. + * '172.18.0.2:12000' => {host: '172,18.0.2', port: 12000} + * 'redis-12000.cluster.local:12000' => {host: 'redis-12000.cluster.local', port: 12000} + * 'lorem ipsum' => {host: undefined, port: undefined} + * 2) If the port is `undefined` but a redis URI scheme is present as protocol, we follow + * the "Scheme semantics" as mentioned in the official URI schemes. + * i) redis:// - https://www.iana.org/assignments/uri-schemes/prov/redis + * ii) rediss:// - https://www.iana.org/assignments/uri-schemes/prov/rediss + */ + if ( + details.port !== undefined + || REDIS_URI_SCHEMES.includes(details.protocol || '') + ) { + const getUpdatedInitialValues = () => { + switch (instanceType) { + case InstanceType.RedisEnterpriseCluster: { + return ({ + host: details.hostname || initialValues.host || 'localhost', + port: `${details.port || initialValues.port || 9443}`, + username: details.user || '', + password: details.password || '', + }) + } + + case InstanceType.Sentinel: { + return ({ + host: details.hostname || initialValues.host || 'localhost', + port: `${details.port || initialValues.port || 9443}`, + username: details.user || '', + password: details.password, + tls: details.protocol === 'rediss', + }) + } + + case InstanceType.Standalone: { + return ({ + name: details.host || initialValues.name || 'localhost:6379', + host: details.hostname || initialValues.host || 'localhost', + port: `${details.port || initialValues.port || 9443}`, + username: details.user || '', + password: details.password, + tls: details.protocol === 'rediss', + ssh: false, + sshPassType: SshPassType.Password + }) + } + default: { + return {} + } + } + } + setInitialValues(getUpdatedInitialValues()) + /* + * autofill was successfull so return true + */ + return true + } + } catch (err) { + /* The pasted content is not a connection URI so ignore. */ + return false + } + return false +} + +export const getSubmitButtonContent = (errors: FormikErrors, submitIsDisabled?: boolean) => { + const maxErrorsCount = 5 + const errorsArr = Object.values(errors).map((err) => [ + err, +
, + ]) + + if (errorsArr.length > maxErrorsCount) { + errorsArr.splice(maxErrorsCount, errorsArr.length, ['...']) + } + return submitIsDisabled ? ( + {errorsArr} + ) : null +} diff --git a/redisinsight/ui/src/pages/home/utils/index.ts b/redisinsight/ui/src/pages/home/utils/index.ts new file mode 100644 index 0000000000..54151771d5 --- /dev/null +++ b/redisinsight/ui/src/pages/home/utils/index.ts @@ -0,0 +1 @@ +export * from './form' diff --git a/redisinsight/ui/src/pages/redisStack/components/edit-connection/EditConnection.tsx b/redisinsight/ui/src/pages/redisStack/components/edit-connection/EditConnection.tsx index dea2c66625..874e8cf71f 100644 --- a/redisinsight/ui/src/pages/redisStack/components/edit-connection/EditConnection.tsx +++ b/redisinsight/ui/src/pages/redisStack/components/edit-connection/EditConnection.tsx @@ -11,17 +11,17 @@ import { addErrorNotification } from 'uiSrc/slices/app/notifications' import { setConnectedInstanceId } from 'uiSrc/slices/instances/instances' import { appInfoSelector } from 'uiSrc/slices/app/info' import { Instance } from 'uiSrc/slices/interfaces' -import AddDatabaseContainer from 'uiSrc/pages/home/components/AddDatabases/AddDatabasesContainer' import { ContentCreateRedis } from 'uiSrc/slices/interfaces/content' import PromoLink from 'uiSrc/components/promo-link/PromoLink' import { getPathToResource } from 'uiSrc/services/resourcesService' -import { HELP_LINKS } from 'uiSrc/pages/home/constants/help-links' +import { HELP_LINKS } from 'uiSrc/pages/home/constants' import { sendEventTelemetry } from 'uiSrc/telemetry' import { ThemeContext } from 'uiSrc/contexts/themeContext' import { contentSelector } from 'uiSrc/slices/content/create-redis-buttons' import './styles.scss' import styles from './styles.module.scss' +import RightPanel from 'uiSrc/pages/home/components/RightPanel/RightPanel' interface IState { loading: boolean; @@ -119,7 +119,7 @@ const EditConnection = () => { )}
- { const maxErrorsCount = 5 const errorsArr = Object.values(errors).map((err) => [ err, -
, +
, ]) if (errorsArr.length > maxErrorsCount) { diff --git a/redisinsight/ui/src/slices/instances/instances.ts b/redisinsight/ui/src/slices/instances/instances.ts index 81590223fb..775ca7f200 100644 --- a/redisinsight/ui/src/slices/instances/instances.ts +++ b/redisinsight/ui/src/slices/instances/instances.ts @@ -471,7 +471,7 @@ function autoCreateAndConnectToInstanceActionSuccess( } // Asynchronous thunk action -export function updateInstanceAction({ id, ...payload }: Instance, onSuccess?: () => void) { +export function updateInstanceAction({ id, ...payload }: Partial, onSuccess?: () => void) { return async (dispatch: AppDispatch) => { dispatch(defaultInstanceChanging()) diff --git a/redisinsight/ui/src/slices/interfaces/instances.ts b/redisinsight/ui/src/slices/interfaces/instances.ts index 5df4bb4edf..39f09e333f 100644 --- a/redisinsight/ui/src/slices/interfaces/instances.ts +++ b/redisinsight/ui/src/slices/interfaces/instances.ts @@ -15,7 +15,7 @@ import { CreateSentinelDatabaseDto } from 'apiSrc/modules/redis-sentinel/dto/cre import { CreateSentinelDatabaseResponse } from 'apiSrc/modules/redis-sentinel/dto/create.sentinel.database.response' import { RedisNodeInfoResponse } from 'apiSrc/modules/database/dto/redis-info.dto' -export interface Instance extends DatabaseInstanceResponse { +export interface Instance extends Partial { host: string port: number nameFromProvider?: Nullable From 9aff8d97796291cc2a8dcfa905507493a64a1a6c Mon Sep 17 00:00:00 2001 From: Amir Allayarov Date: Wed, 1 Nov 2023 10:59:29 +0400 Subject: [PATCH 02/11] #RI-5009 - remove deprecated code --- .../ManualConnectionForm.tsx | 345 +++++++++--------- .../ManualConnectionWrapper.tsx | 126 +------ .../SentinelConnectionWrapper.tsx | 62 +--- redisinsight/ui/src/pages/home/utils/form.tsx | 37 +- .../edit-connection/EditConnection.tsx | 2 +- 5 files changed, 220 insertions(+), 352 deletions(-) diff --git a/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionForm/ManualConnectionForm.tsx b/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionForm/ManualConnectionForm.tsx index 5582d422ac..42f081107f 100644 --- a/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionForm/ManualConnectionForm.tsx +++ b/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionForm/ManualConnectionForm.tsx @@ -39,6 +39,7 @@ import { SshPassType, DEFAULT_TIMEOUT, NONE, + SubmitBtnText, } from 'uiSrc/pages/home/constants' import { getFormErrors, getSubmitButtonContent } from 'uiSrc/pages/home/utils' import { DbConnectionInfo, ISubmitButton } from 'uiSrc/pages/home/interfaces' @@ -57,7 +58,6 @@ import { SentinelHostPort, SentinelMasterDatabase, } from 'uiSrc/pages/home/components/Form/sentinel' -import { SubmitBtnText } from 'uiSrc/pages/home/components/ManualConnection/ManualConnectionWrapper' export interface Props { width: number @@ -418,68 +418,12 @@ const ManualConnectionForm = (props: Props) => { )}
{!isEditMode && !isFromCloud && ( - <> - -
- + <> + +
+ )} {!isEditMode && !isFromCloud && ( - - - - - - {buildType !== BuildType.RedisStack && ( - - )} - - )} - {(isEditMode || isCloneMode || isFromCloud) && connectionType !== ConnectionType.Sentinel && ( - <> - {!isCloneMode && ( - - )} { > - {isCloneMode && ( - )} { caCertificates={caCertificates} /> {buildType !== BuildType.RedisStack && ( - + )} - )} - {(isEditMode || isCloneMode) && connectionType === ConnectionType.Sentinel && ( - <> - + {(isEditMode || isCloneMode || isFromCloud) && connectionType !== ConnectionType.Sentinel && ( + <> {!isCloneMode && ( - <> - - - - - - - - - - - - - )} - {isCloneMode && ( - <> - + - - - - - - - - + )} { certificates={certificates} caCertificates={caCertificates} /> - - )} - - + {buildType !== BuildType.RedisStack && ( + + )} + + + )} + {(isEditMode || isCloneMode) && connectionType === ConnectionType.Sentinel && ( + <> + + {!isCloneMode && ( + <> + + + + + + + + + + + + + )} + {isCloneMode && ( + <> + + + + + + + + + + + + + )} + + )}
diff --git a/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionWrapper.tsx b/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionWrapper.tsx index 429eff83c6..e36fb9b484 100644 --- a/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionWrapper.tsx +++ b/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionWrapper.tsx @@ -23,8 +23,8 @@ import { UrlHandlingActions } from 'uiSrc/slices/interfaces/urlHandling' import { appRedirectionSelector, setUrlHandlingInitialState } from 'uiSrc/slices/app/url-handling' import { getRedirectionPage } from 'uiSrc/utils/routing' import { DbConnectionInfo } from 'uiSrc/pages/home/interfaces' -import { applyTlSDatabase, applySSHDatabase, autoFillFormDetails } from 'uiSrc/pages/home/utils' -import { ADD_NEW, ADD_NEW_CA_CERT, DEFAULT_TIMEOUT, NO_CA_CERT, SshPassType, SubmitBtnText } from 'uiSrc/pages/home/constants' +import { applyTlSDatabase, applySSHDatabase, autoFillFormDetails, getTlsSettings } from 'uiSrc/pages/home/utils' +import { ADD_NEW, DEFAULT_TIMEOUT, NO_CA_CERT, SshPassType, SubmitBtnText } from 'uiSrc/pages/home/constants' import ManualConnectionForm from './ManualConnectionForm' export interface Props { @@ -38,11 +38,6 @@ export interface Props { onAliasEdited?: (value: string) => void } -export enum TitleDatabaseText { - AddDatabase = 'Add Redis Database', - EditDatabase = 'Edit Redis Database', -} - const getInitialValues = (editedInstance?: Nullable>) => ({ // undefined - to show default value, empty string - for existing db host: editedInstance?.host ?? (editedInstance ? '' : undefined), @@ -165,20 +160,6 @@ const ManualConnectionWrapper = (props: Props) => { dispatch(cloneInstanceAction(payload)) } - const handleUpdateEditingName = (name: string) => { - const requiredFields = [ - 'id', - 'host', - 'port', - 'username', - 'password', - 'tls', - 'sentinelMaster', - ] - const database = pick(editedInstance, ...requiredFields) - dispatch(updateInstanceAction({ ...database, name })) - } - const handleTestConnectionDatabase = (values: DbConnectionInfo) => { sendEventTelemetry({ event: TelemetryEvent.CONFIG_DATABASES_TEST_CONNECTION_CLICKED @@ -195,54 +176,9 @@ const ManualConnectionWrapper = (props: Props) => { sentinelMasterName, sentinelMasterUsername, sentinelMasterPassword, - newCaCert, - tls, - sni, - servername, - newCaCertName, - selectedCaCertName, - tlsClientAuthRequired, - verifyServerTlsCert, - newTlsCertPairName, - selectedTlsClientCertId, - newTlsClientCert, - newTlsClientKey, } = values - const tlsSettings = { - useTls: tls, - servername: (sni && servername) || undefined, - verifyServerCert: verifyServerTlsCert, - caCert: - !tls || selectedCaCertName === NO_CA_CERT - ? undefined - : selectedCaCertName === ADD_NEW_CA_CERT - ? { - new: { - name: newCaCertName, - certificate: newCaCert, - }, - } - : { - name: selectedCaCertName, - }, - clientAuth: tls && tlsClientAuthRequired, - clientCert: !tls - ? undefined - : typeof selectedTlsClientCertId === 'string' - && tlsClientAuthRequired - && selectedTlsClientCertId !== ADD_NEW - ? { id: selectedTlsClientCertId } - : selectedTlsClientCertId === ADD_NEW && tlsClientAuthRequired - ? { - new: { - name: newTlsCertPairName, - certificate: newTlsClientCert, - key: newTlsClientKey, - }, - } - : undefined, - } + const tlsSettings = getTlsSettings(values) const database: any = { name, @@ -363,55 +299,7 @@ const ManualConnectionWrapper = (props: Props) => { } const handleConnectionFormSubmit = (values: DbConnectionInfo) => { - const { - newCaCert, - tls, - sni, - servername, - newCaCertName, - selectedCaCertName, - tlsClientAuthRequired, - verifyServerTlsCert, - newTlsCertPairName, - selectedTlsClientCertId, - newTlsClientCert, - newTlsClientKey, - } = values - - const tlsSettings = { - useTls: tls, - servername: (sni && servername) || undefined, - verifyServerCert: verifyServerTlsCert, - caCert: - !tls || selectedCaCertName === NO_CA_CERT - ? undefined - : selectedCaCertName === ADD_NEW_CA_CERT - ? { - new: { - name: newCaCertName, - certificate: newCaCert, - }, - } - : { - name: selectedCaCertName, - }, - clientAuth: tls && tlsClientAuthRequired, - clientCert: !tls - ? undefined - : typeof selectedTlsClientCertId === 'string' - && tlsClientAuthRequired - && selectedTlsClientCertId !== ADD_NEW - ? { id: selectedTlsClientCertId } - : selectedTlsClientCertId === ADD_NEW && tlsClientAuthRequired - ? { - new: { - name: newTlsCertPairName, - certificate: newTlsClientCert, - key: newTlsClientKey, - }, - } - : undefined, - } + const tlsSettings = getTlsSettings(values) if (editMode) { editDatabase(tlsSettings, values, isCloneMode) @@ -480,11 +368,6 @@ const ManualConnectionWrapper = (props: Props) => { loading={loadingStandalone} buildType={server?.buildType as BuildType} submitButtonText={getSubmitButtonText()} - titleText={ - editMode - ? TitleDatabaseText.EditDatabase - : TitleDatabaseText.AddDatabase - } onSubmit={handleConnectionFormSubmit} onTestConnection={handleTestConnectionDatabase} onClose={handleOnClose} @@ -492,7 +375,6 @@ const ManualConnectionWrapper = (props: Props) => { isEditMode={editMode} isCloneMode={isCloneMode} setIsCloneMode={setIsCloneMode} - updateEditingName={handleUpdateEditingName} onAliasEdited={onAliasEdited} />
diff --git a/redisinsight/ui/src/pages/home/components/SentinelConnection/SentinelConnectionWrapper.tsx b/redisinsight/ui/src/pages/home/components/SentinelConnection/SentinelConnectionWrapper.tsx index 2c08e4f14b..d190ccb76f 100644 --- a/redisinsight/ui/src/pages/home/components/SentinelConnection/SentinelConnectionWrapper.tsx +++ b/redisinsight/ui/src/pages/home/components/SentinelConnection/SentinelConnectionWrapper.tsx @@ -10,8 +10,8 @@ import { Pages } from 'uiSrc/constants' import { clientCertsSelector, fetchClientCerts, } from 'uiSrc/slices/instances/clientCerts' import { DbConnectionInfo } from 'uiSrc/pages/home/interfaces' -import { applyTlSDatabase, autoFillFormDetails } from 'uiSrc/pages/home/utils' -import { ADD_NEW, ADD_NEW_CA_CERT, NO_CA_CERT, SubmitBtnText } from 'uiSrc/pages/home/constants' +import { applyTlSDatabase, autoFillFormDetails, getTlsSettings } from 'uiSrc/pages/home/utils' +import { ADD_NEW, NO_CA_CERT, SubmitBtnText } from 'uiSrc/pages/home/constants' import { InstanceType } from 'uiSrc/slices/interfaces' import SentinelConnectionForm from './SentinelConnectionForm' @@ -19,12 +19,12 @@ export interface Props { width: number onClose?: () => void } -const getDefaultHost = () => '127.0.0.1' -const getDefaultPort = () => '26379' +const DEFAULT_SENTINEL_HOST = '127.0.0.1' +const DEFAULT_SENTINEL_PORT = '26379' const INITIAL_VALUES = { - host: getDefaultHost(), - port: getDefaultPort(), + host: DEFAULT_SENTINEL_HOST, + port: DEFAULT_SENTINEL_PORT, username: '', password: '', tls: false, @@ -86,55 +86,7 @@ const SentinelConnectionWrapper = (props: Props) => { } const handleConnectionFormSubmit = (values: DbConnectionInfo) => { - const { - newCaCert, - tls, - sni, - servername, - newCaCertName, - selectedCaCertName, - tlsClientAuthRequired, - verifyServerTlsCert, - newTlsCertPairName, - selectedTlsClientCertId, - newTlsClientCert, - newTlsClientKey, - } = values - - const tlsSettings = { - useTls: tls, - servername: (sni && servername) || undefined, - verifyServerCert: verifyServerTlsCert, - caCert: - !tls || selectedCaCertName === NO_CA_CERT - ? undefined - : selectedCaCertName === ADD_NEW_CA_CERT - ? { - new: { - name: newCaCertName, - certificate: newCaCert, - }, - } - : { - name: selectedCaCertName, - }, - clientAuth: tls && tlsClientAuthRequired, - clientCert: !tls - ? undefined - : typeof selectedTlsClientCertId === 'string' - && tlsClientAuthRequired - && selectedTlsClientCertId !== ADD_NEW - ? { id: selectedTlsClientCertId } - : selectedTlsClientCertId === ADD_NEW && tlsClientAuthRequired - ? { - new: { - name: newTlsCertPairName, - certificate: newTlsClientCert, - key: newTlsClientKey, - }, - } - : undefined, - } + const tlsSettings = getTlsSettings(values) addDatabase(tlsSettings, values) } diff --git a/redisinsight/ui/src/pages/home/utils/form.tsx b/redisinsight/ui/src/pages/home/utils/form.tsx index bd02e85369..eda2a1f610 100644 --- a/redisinsight/ui/src/pages/home/utils/form.tsx +++ b/redisinsight/ui/src/pages/home/utils/form.tsx @@ -4,9 +4,44 @@ import React from 'react' import { FormikErrors } from 'formik' import { REDIS_URI_SCHEMES } from 'uiSrc/constants' import { InstanceType } from 'uiSrc/slices/interfaces' -import {ADD_NEW, ADD_NEW_CA_CERT, fieldDisplayNames, NO_CA_CERT, SshPassType} from 'uiSrc/pages/home/constants' +import { ADD_NEW, ADD_NEW_CA_CERT, fieldDisplayNames, NO_CA_CERT, SshPassType } from 'uiSrc/pages/home/constants' import { DbConnectionInfo } from 'uiSrc/pages/home/interfaces' +export const getTlsSettings = (values: DbConnectionInfo) => ({ + useTls: values.tls, + servername: (values.sni && values.servername) || undefined, + verifyServerCert: values.verifyServerTlsCert, + caCert: + !values.tls || values.selectedCaCertName === NO_CA_CERT + ? undefined + : values.selectedCaCertName === ADD_NEW_CA_CERT + ? { + new: { + name: values.newCaCertName, + certificate: values.newCaCert, + }, + } + : { + name: values.selectedCaCertName, + }, + clientAuth: values.tls && values.tlsClientAuthRequired, + clientCert: !values.tls + ? undefined + : typeof values.selectedTlsClientCertId === 'string' + && values.tlsClientAuthRequired + && values.selectedTlsClientCertId !== ADD_NEW + ? { id: values.selectedTlsClientCertId } + : values.selectedTlsClientCertId === ADD_NEW && values.tlsClientAuthRequired + ? { + new: { + name: values.newTlsCertPairName, + certificate: values.newTlsClientCert, + key: values.newTlsClientKey, + }, + } + : undefined, +}) + export const applyTlSDatabase = (database: any, tlsSettings: any) => { const { useTls, verifyServerCert, servername, caCert, clientAuth, clientCert } = tlsSettings if (!useTls) return diff --git a/redisinsight/ui/src/pages/redisStack/components/edit-connection/EditConnection.tsx b/redisinsight/ui/src/pages/redisStack/components/edit-connection/EditConnection.tsx index 874e8cf71f..c167d4b33a 100644 --- a/redisinsight/ui/src/pages/redisStack/components/edit-connection/EditConnection.tsx +++ b/redisinsight/ui/src/pages/redisStack/components/edit-connection/EditConnection.tsx @@ -18,10 +18,10 @@ import { HELP_LINKS } from 'uiSrc/pages/home/constants' import { sendEventTelemetry } from 'uiSrc/telemetry' import { ThemeContext } from 'uiSrc/contexts/themeContext' import { contentSelector } from 'uiSrc/slices/content/create-redis-buttons' +import RightPanel from 'uiSrc/pages/home/components/RightPanel/RightPanel' import './styles.scss' import styles from './styles.module.scss' -import RightPanel from 'uiSrc/pages/home/components/RightPanel/RightPanel' interface IState { loading: boolean; From 139fb5bca84dd0e32bacd93b9a0b717879e80b0d Mon Sep 17 00:00:00 2001 From: Amir Allayarov Date: Wed, 1 Nov 2023 11:47:49 +0400 Subject: [PATCH 03/11] add tests --- .../ManualConnectionForm.tsx | 9 + .../ManualConnectionFrom.spec.tsx | 1259 +++++++++++++++++ .../SentinelConnectionForm.spec.tsx | 14 + .../SentinelConnectionForm.tsx | 2 +- redisinsight/ui/src/pages/home/utils/form.tsx | 15 + 5 files changed, 1298 insertions(+), 1 deletion(-) create mode 100644 redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionForm/ManualConnectionFrom.spec.tsx create mode 100644 redisinsight/ui/src/pages/home/components/SentinelConnection/SentinelConnectionForm/SentinelConnectionForm.spec.tsx diff --git a/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionForm/ManualConnectionForm.tsx b/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionForm/ManualConnectionForm.tsx index 42f081107f..8854d5a8f5 100644 --- a/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionForm/ManualConnectionForm.tsx +++ b/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionForm/ManualConnectionForm.tsx @@ -197,6 +197,15 @@ const ManualConnectionForm = (props: Props) => { const validate = (values: DbConnectionInfo) => { const errs = getFormErrors(values) + + if (isCloneMode && connectionType === ConnectionType.Sentinel && !values.sentinelMasterName) { + errs.sentinelMasterName = fieldDisplayNames.sentinelMasterName + } + + if (!values.name) { + errs.name = fieldDisplayNames.name + } + setErrors(errs) return errs } diff --git a/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionForm/ManualConnectionFrom.spec.tsx b/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionForm/ManualConnectionFrom.spec.tsx new file mode 100644 index 0000000000..88607e7c09 --- /dev/null +++ b/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionForm/ManualConnectionFrom.spec.tsx @@ -0,0 +1,1259 @@ +import React from 'react' +import { instance, mock } from 'ts-mockito' +import { act, fireEvent, render, screen } from 'uiSrc/utils/test-utils' +import { ConnectionType } from 'uiSrc/slices/interfaces' +import { BuildType } from 'uiSrc/constants/env' +import { appRedirectionSelector } from 'uiSrc/slices/app/url-handling' +import { UrlHandlingActions } from 'uiSrc/slices/interfaces/urlHandling' +import { ADD_NEW_CA_CERT, SshPassType } from 'uiSrc/pages/home/constants' +import { DbConnectionInfo } from 'uiSrc/pages/home/interfaces' + +import ManualConnectionForm, { Props } from './ManualConnectionForm' + +const BTN_SUBMIT = 'btn-submit' +const NEW_CA_CERT = 'new-ca-cert' +const QA_CA_CERT = 'qa-ca-cert' +const RADIO_BTN_PRIVATE_KEY = '[data-test-subj="radio-btn-privateKey"] label' +const BTN_TEST_CONNECTION = 'btn-test-connection' + +const mockedProps = mock() +const mockedDbConnectionInfo = mock() + +const formFields = { + ...instance(mockedDbConnectionInfo), + host: 'localhost', + port: '6379', + name: 'lala', + caCertificates: [], + certificates: [], +} + +jest.mock('uiSrc/slices/instances/instances', () => ({ + checkConnectToInstanceAction: () => jest.fn, + resetInstanceUpdateAction: () => jest.fn, + changeInstanceAliasAction: () => jest.fn, + setConnectedInstanceId: jest.fn, +})) + +jest.mock('uiSrc/slices/app/url-handling', () => ({ + ...jest.requireActual('uiSrc/slices/app/url-handling'), + appRedirectionSelector: jest.fn().mockReturnValue(() => ({ action: null })), +})) + +describe('InstanceForm', () => { + it('should render', () => { + expect( + render( + + ) + ).toBeTruthy() + }) + + it('should render with ConnectionType.Sentinel', () => { + expect( + render( + + ) + ).toBeTruthy() + }) + + it('should render with ConnectionType.Cluster', () => { + expect( + render( + + ) + ).toBeTruthy() + }) + + it('should render tooltip with nodes', () => { + expect( + render( + + ) + ).toBeTruthy() + }) + + it('should render DatabaseForm', () => { + expect( + render( + + ) + ).toBeTruthy() + }) + + it('should change sentinelMasterUsername input properly', async () => { + const handleSubmit = jest.fn() + const handleTestConnection = jest.fn() + + render( +
+ +
+ ) + + await act(() => { + fireEvent.change(screen.getByTestId('sentinel-mater-username'), { + target: { value: 'user' }, + }) + }) + + const submitBtn = screen.getByTestId(BTN_SUBMIT) + const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) + + await act(() => { + fireEvent.click(testConnectionBtn) + }) + expect(handleTestConnection).toBeCalledWith( + expect.objectContaining({ + sentinelMasterUsername: 'user', + }) + ) + + await act(() => { + fireEvent.click(submitBtn) + }) + expect(handleSubmit).toBeCalledWith( + expect.objectContaining({ + sentinelMasterUsername: 'user', + }) + ) + }) + + it('should change port input properly', async () => { + const handleSubmit = jest.fn() + render( +
+ +
+ ) + + await act(() => { + fireEvent.change(screen.getByTestId('port'), { + target: { value: '123' }, + }) + }) + + const submitBtn = screen.getByTestId(BTN_SUBMIT) + await act(() => { + fireEvent.click(submitBtn) + }) + expect(handleSubmit).toBeCalledWith( + expect.objectContaining({ + port: '123', + }) + ) + }) + + it('should change tls checkbox', async () => { + const handleSubmit = jest.fn() + const handleTestConnection = jest.fn() + + render( +
+ +
+ ) + await act(() => { + fireEvent.click(screen.getByTestId('tls')) + }) + + const submitBtn = screen.getByTestId(BTN_SUBMIT) + const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) + + await act(() => { + fireEvent.click(testConnectionBtn) + }) + expect(handleTestConnection).toBeCalledWith( + expect.objectContaining({ + tls: ['on'], + }) + ) + + await act(() => { + fireEvent.click(submitBtn) + }) + + expect(handleSubmit).toBeCalledWith( + expect.objectContaining({ + tls: ['on'], + }) + ) + }) + + it('should change Database Index checkbox', async () => { + const handleSubmit = jest.fn() + const handleTestConnection = jest.fn() + render( +
+ +
+ ) + await act(() => { + fireEvent.click(screen.getByTestId('showDb')) + }) + + const submitBtn = screen.getByTestId(BTN_SUBMIT) + const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) + await act(() => { + fireEvent.click(testConnectionBtn) + }) + expect(handleTestConnection).toBeCalledWith( + expect.objectContaining({ + showDb: true, + }) + ) + await act(() => { + fireEvent.click(submitBtn) + }) + + expect(handleSubmit).toBeCalledWith( + expect.objectContaining({ + showDb: true, + }) + ) + }) + + it('should change db checkbox and value', async () => { + const handleSubmit = jest.fn() + const handleTestConnection = jest.fn() + render( +
+ +
+ ) + await act(() => { + fireEvent.click(screen.getByTestId('showDb')) + }) + + await act(() => { + fireEvent.change(screen.getByTestId('db'), { + target: { value: '12' }, + }) + }) + + const submitBtn = screen.getByTestId(BTN_SUBMIT) + const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) + + await act(() => { + fireEvent.click(testConnectionBtn) + }) + expect(handleTestConnection).toBeCalledWith( + expect.objectContaining({ + showDb: true, + db: '12' + }) + ) + await act(() => { + fireEvent.click(submitBtn) + }) + + expect(handleSubmit).toBeCalledWith( + expect.objectContaining({ + showDb: true, + db: '12' + }) + ) + }) + + it('should change "Use SNI" with prepopulated with host', async () => { + const handleSubmit = jest.fn() + const handleTestConnection = jest.fn() + render( +
+ +
+ ) + await act(() => { + fireEvent.click(screen.getByTestId('sni')) + }) + + const submitBtn = screen.getByTestId(BTN_SUBMIT) + const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) + await act(() => { + fireEvent.click(testConnectionBtn) + }) + expect(handleTestConnection).toBeCalledWith( + expect.objectContaining({ + sni: true, + servername: formFields.host + }) + ) + await act(() => { + fireEvent.click(submitBtn) + }) + + expect(handleSubmit).toBeCalledWith( + expect.objectContaining({ + sni: true, + servername: formFields.host + }) + ) + }) + + it('should change "Use SNI"', async () => { + const handleSubmit = jest.fn() + const handleTestConnection = jest.fn() + render( +
+ +
+ ) + await act(() => { + fireEvent.click(screen.getByTestId('sni')) + }) + + await act(() => { + fireEvent.change(screen.getByTestId('sni-servername'), { + target: { value: '12' }, + }) + }) + + const submitBtn = screen.getByTestId(BTN_SUBMIT) + const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) + await act(() => { + fireEvent.click(testConnectionBtn) + }) + expect(handleTestConnection).toBeCalledWith( + expect.objectContaining({ + sni: true, + servername: '12' + }) + ) + await act(() => { + fireEvent.click(submitBtn) + }) + + expect(handleSubmit).toBeCalledWith( + expect.objectContaining({ + sni: true, + servername: '12' + }) + ) + }) + + it('should change "Verify TLS Certificate"', async () => { + const handleSubmit = jest.fn() + const handleTestConnection = jest.fn() + render( +
+ +
+ ) + await act(() => { + fireEvent.click(screen.getByTestId('verify-tls-cert')) + }) + + const submitBtn = screen.getByTestId(BTN_SUBMIT) + const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) + await act(() => { + fireEvent.click(testConnectionBtn) + }) + expect(handleTestConnection).toBeCalledWith( + expect.objectContaining({ + verifyServerTlsCert: ['on'], + }) + ) + await act(() => { + fireEvent.click(submitBtn) + }) + + expect(handleSubmit).toBeCalledWith( + expect.objectContaining({ + verifyServerTlsCert: ['on'], + }) + ) + }) + + it('should select value from "CA Certificate"', async () => { + const handleSubmit = jest.fn() + const handleTestConnection = jest.fn() + const { queryByText } = render( +
+ +
+ ) + await act(() => { + fireEvent.click(screen.getByTestId('select-ca-cert')) + }) + await act(() => { + fireEvent.click(queryByText('Add new CA certificate') || document) + }) + + expect(screen.getByTestId(NEW_CA_CERT)).toBeInTheDocument() + await act(() => { + fireEvent.change(screen.getByTestId(NEW_CA_CERT), { + target: { value: '123' }, + }) + }) + + expect(screen.getByTestId(QA_CA_CERT)).toBeInTheDocument() + await act(() => { + fireEvent.change(screen.getByTestId(QA_CA_CERT), { + target: { value: '321' }, + }) + }) + + const submitBtn = screen.getByTestId(BTN_SUBMIT) + const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) + await act(() => { + fireEvent.click(testConnectionBtn) + }) + expect(handleTestConnection).toBeCalledWith( + expect.objectContaining({ + selectedCaCertName: ADD_NEW_CA_CERT, + newCaCertName: '321', + newCaCert: '123', + }) + ) + await act(() => { + fireEvent.click(submitBtn) + }) + + expect(handleSubmit).toBeCalledWith( + expect.objectContaining({ + selectedCaCertName: ADD_NEW_CA_CERT, + newCaCertName: '321', + newCaCert: '123', + }) + ) + }) + + it('should render fields for add new CA and change them properly', async () => { + const handleSubmit = jest.fn() + const handleTestConnection = jest.fn() + render( +
+ +
+ ) + + expect(screen.getByTestId(QA_CA_CERT)).toBeInTheDocument() + await act(() => { + fireEvent.change(screen.getByTestId(QA_CA_CERT), { + target: { value: '321' }, + }) + }) + + expect(screen.getByTestId(NEW_CA_CERT)).toBeInTheDocument() + await act(() => { + fireEvent.change(screen.getByTestId(NEW_CA_CERT), { + target: { value: '123' }, + }) + }) + + const submitBtn = screen.getByTestId(BTN_SUBMIT) + const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) + await act(() => { + fireEvent.click(testConnectionBtn) + }) + expect(handleTestConnection).toBeCalledWith( + expect.objectContaining({ + newCaCert: '123', + newCaCertName: '321', + }) + ) + await act(() => { + fireEvent.click(submitBtn) + }) + + expect(handleSubmit).toBeCalledWith( + expect.objectContaining({ + newCaCert: '123', + newCaCertName: '321', + }) + ) + }) + + it('should change "Requires TLS Client Authentication"', async () => { + const handleSubmit = jest.fn() + const handleTestConnection = jest.fn() + render( +
+ +
+ ) + await act(() => { + fireEvent.click(screen.getByTestId('tls-required-checkbox')) + }) + + const submitBtn = screen.getByTestId(BTN_SUBMIT) + const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) + await act(() => { + fireEvent.click(testConnectionBtn) + }) + expect(handleTestConnection).toBeCalledWith( + expect.objectContaining({ + tlsClientAuthRequired: ['on'], + }) + ) + await act(() => { + fireEvent.click(submitBtn) + }) + + expect(handleSubmit).toBeCalledWith( + expect.objectContaining({ + tlsClientAuthRequired: ['on'], + }) + ) + }) + + it('should render fields for add new CA with required tls auth and change them properly', async () => { + const handleSubmit = jest.fn() + const { container } = render( +
+ +
+ ) + + expect(screen.getByTestId('select-cert')).toBeInTheDocument() + + await act(() => { + fireEvent.click(screen.getByTestId('select-cert')) + }) + + await act(() => { + fireEvent.click( + container.querySelectorAll('.euiContextMenuItem__text')[0] || document + ) + }) + + expect(screen.getByTestId('new-tsl-cert-pair-name')).toBeInTheDocument() + await act(() => { + fireEvent.change(screen.getByTestId('new-tsl-cert-pair-name'), { + target: { value: '123' }, + }) + }) + + expect(screen.getByTestId('new-tls-client-cert')).toBeInTheDocument() + await act(() => { + fireEvent.change(screen.getByTestId('new-tls-client-cert'), { + target: { value: '321' }, + }) + }) + + expect(screen.getByTestId('new-tls-client-cert-key')).toBeInTheDocument() + await act(() => { + fireEvent.change(screen.getByTestId('new-tls-client-cert-key'), { + target: { value: '231' }, + }) + }) + + const submitBtn = screen.getByTestId(BTN_SUBMIT) + + await act(() => { + fireEvent.click(submitBtn) + }) + + expect(handleSubmit).toBeCalledWith( + expect.objectContaining({ + newTlsClientCert: '321', + newTlsCertPairName: '123', + newTlsClientKey: '231', + }) + ) + }) + + it('should render clone mode btn', () => { + render( + + ) + expect(screen.getByTestId('clone-db-btn')).toBeTruthy() + }) + + describe('should render proper fields with Clone mode', () => { + it('should render proper fields for standalone db', () => { + render( + + ) + const fieldsTestIds = ['host', 'port', 'username', 'password', 'showDb', 'tls'] + fieldsTestIds.forEach((id) => { + expect(screen.getByTestId(id)).toBeTruthy() + }) + }) + + it('should render proper fields for sentinel db', () => { + render( + + ) + const fieldsTestIds = [ + 'name', + 'primary-group', + 'sentinel-mater-username', + 'sentinel-master-password', + 'host', + 'port', + 'username', + 'password', + 'showDb', + 'tls' + ] + fieldsTestIds.forEach((id) => { + expect(screen.getByTestId(id)).toBeTruthy() + }) + }) + + it('should render selected logical database with proper db index', () => { + render( + + ) + expect(screen.getByTestId('showDb')).toBeChecked() + expect(screen.getByTestId('db')).toHaveValue('5') + }) + + it('should render proper database alias', () => { + render( + + ) + expect(screen.getByTestId('db-alias')).toHaveTextContent('Clone ') + }) + + it('should render proper default values for standalone', () => { + render( + + ) + expect(screen.getByTestId('host')).toHaveValue('127.0.0.1') + expect(screen.getByTestId('port')).toHaveValue('6379') + expect(screen.getByTestId('name')).toHaveValue('127.0.0.1:6379') + }) + + // it('should render proper default values for sentinel', () => { + // render( + // + // ) + // expect(screen.getByTestId('host')).toHaveValue('127.0.0.1') + // expect(screen.getByTestId('port')).toHaveValue('26379') + // }) + }) + + it('should change Use SSH checkbox', async () => { + const handleSubmit = jest.fn() + render( +
+ +
+ ) + + fireEvent.click(screen.getByTestId('use-ssh')) + + expect(screen.getByTestId('use-ssh')).toBeChecked() + }) + + it('should not render Use SSH checkbox for redis stack buidlType', async () => { + const handleSubmit = jest.fn() + render( +
+ +
+ ) + + expect(screen.queryByTestId('use-ssh')).not.toBeInTheDocument() + }) + + it('should change Use SSH checkbox and show proper fields for password radio', async () => { + const handleSubmit = jest.fn() + render( +
+ +
+ ) + + fireEvent.click(screen.getByTestId('use-ssh')) + + expect(screen.getByTestId('sshHost')).toBeInTheDocument() + expect(screen.getByTestId('sshPort')).toBeInTheDocument() + expect(screen.getByTestId('sshPort')).toHaveValue('22') + expect(screen.getByTestId('sshPassword')).toBeInTheDocument() + expect(screen.queryByTestId('sshPrivateKey')).not.toBeInTheDocument() + expect(screen.queryByTestId('sshPassphrase')).not.toBeInTheDocument() + + const submitBtn = screen.getByTestId(BTN_SUBMIT) + expect(submitBtn).toBeDisabled() + }) + + it('should change Use SSH checkbox and show proper fields for passphrase radio', async () => { + const handleSubmit = jest.fn() + const { container } = render( +
+ +
+ ) + + await act(() => { + fireEvent.click(screen.getByTestId('use-ssh')) + fireEvent.click( + container.querySelector(RADIO_BTN_PRIVATE_KEY) as HTMLLabelElement + ) + }) + + expect(screen.getByTestId('sshHost')).toBeInTheDocument() + expect(screen.getByTestId('sshPort')).toBeInTheDocument() + expect(screen.getByTestId('sshPort')).toHaveValue('22') + expect(screen.queryByTestId('sshPassword')).not.toBeInTheDocument() + expect(screen.getByTestId('sshPrivateKey')).toBeInTheDocument() + expect(screen.getByTestId('sshPassphrase')).toBeInTheDocument() + + const submitBtn = screen.getByTestId(BTN_SUBMIT) + expect(submitBtn).toBeDisabled() + }) + + it('should be proper validation for ssh via ssh password', async () => { + const handleSubmit = jest.fn() + render( +
+ +
+ ) + + expect(screen.getByTestId(BTN_SUBMIT)).not.toBeDisabled() + + await act(() => { + fireEvent.click(screen.getByTestId('use-ssh')) + }) + + expect(screen.getByTestId(BTN_SUBMIT)).toBeDisabled() + + await act(() => { + fireEvent.change( + screen.getByTestId('sshHost'), + { target: { value: 'localhost' } } + ) + }) + + expect(screen.getByTestId(BTN_SUBMIT)).toBeDisabled() + + await act(() => { + fireEvent.change( + screen.getByTestId('sshUsername'), + { target: { value: 'username' } } + ) + }) + + expect(screen.getByTestId(BTN_SUBMIT)).not.toBeDisabled() + }) + + it('should be proper validation for ssh via ssh passphrase', async () => { + const handleSubmit = jest.fn() + const { container } = render( +
+ +
+ ) + + expect(screen.getByTestId(BTN_SUBMIT)).not.toBeDisabled() + + await act(() => { + fireEvent.click(screen.getByTestId('use-ssh')) + fireEvent.click( + container.querySelector(RADIO_BTN_PRIVATE_KEY) as HTMLLabelElement + ) + }) + + expect(screen.getByTestId(BTN_SUBMIT)).toBeDisabled() + + await act(() => { + fireEvent.change( + screen.getByTestId('sshHost'), + { target: { value: 'localhost' } } + ) + fireEvent.change( + screen.getByTestId('sshUsername'), + { target: { value: 'username' } } + ) + }) + + expect(screen.getByTestId(BTN_SUBMIT)).toBeDisabled() + + await act(() => { + fireEvent.change( + screen.getByTestId('sshPrivateKey'), + { target: { value: 'PRIVATEKEY' } } + ) + }) + + expect(screen.getByTestId(BTN_SUBMIT)).not.toBeDisabled() + }) + + it('should call submit btn with proper fields', async () => { + const handleSubmit = jest.fn() + render( +
+ +
+ ) + + await act(() => { + fireEvent.click(screen.getByTestId('use-ssh')) + }) + + await act(() => { + fireEvent.change( + screen.getByTestId('sshHost'), + { target: { value: 'localhost' } } + ) + + fireEvent.change( + screen.getByTestId('sshPort'), + { target: { value: '1771' } } + ) + + fireEvent.change( + screen.getByTestId('sshUsername'), + { target: { value: 'username' } } + ) + + fireEvent.change( + screen.getByTestId('sshPassword'), + { target: { value: '123' } } + ) + }) + + await act(() => { + fireEvent.click(screen.getByTestId(BTN_SUBMIT)) + }) + + expect(handleSubmit).toBeCalledWith( + expect.objectContaining({ + sshHost: 'localhost', + sshPort: '1771', + sshUsername: 'username', + sshPassword: '123', + }) + ) + }) + + it('should call submit btn with proper fields via passphrase', async () => { + const handleSubmit = jest.fn() + const { container } = render( +
+ +
+ ) + + await act(() => { + fireEvent.click(screen.getByTestId('use-ssh')) + fireEvent.click( + container.querySelector(RADIO_BTN_PRIVATE_KEY) as HTMLLabelElement + ) + }) + + await act(() => { + fireEvent.change( + screen.getByTestId('sshHost'), + { target: { value: 'localhost' } } + ) + + fireEvent.change( + screen.getByTestId('sshPort'), + { target: { value: '1771' } } + ) + + fireEvent.change( + screen.getByTestId('sshUsername'), + { target: { value: 'username' } } + ) + + fireEvent.change( + screen.getByTestId('sshPrivateKey'), + { target: { value: '123444' } } + ) + + fireEvent.change( + screen.getByTestId('sshPassphrase'), + { target: { value: '123444' } } + ) + }) + + await act(() => { + fireEvent.click(screen.getByTestId(BTN_SUBMIT)) + }) + + expect(handleSubmit).toBeCalledWith( + expect.objectContaining({ + sshHost: 'localhost', + sshPort: '1771', + sshUsername: 'username', + sshPrivateKey: '123444', + sshPassphrase: '123444', + }) + ) + }) + + it('should render password input with 10_000 length limit', () => { + render( + + ) + + expect(screen.getByTestId('password')).toHaveAttribute('maxLength', '10000') + }) + + it('should render security fields with proper attributes', () => { + render( + + ) + + expect(screen.getByTestId('password')).toHaveAttribute('value', '••••••••••••') + expect(screen.getByTestId('password')).toHaveAttribute('type', 'password') + expect(screen.getByTestId('sshPassphrase')).toHaveAttribute('value', '••••••••••••') + expect(screen.getByTestId('sshPassphrase')).toHaveAttribute('type', 'password') + + fireEvent.focus(screen.getByTestId('password')) + fireEvent.focus(screen.getByTestId('sshPassphrase')) + + expect(screen.getByTestId('password')).toHaveAttribute('value', '') + expect(screen.getByTestId('sshPassphrase')).toHaveAttribute('value', '') + }) + + it('should render ssh password with proper attributes', () => { + render( + + ) + + expect(screen.getByTestId('sshPassword')).toHaveAttribute('value', '••••••••••••') + expect(screen.getByTestId('sshPassword')).toHaveAttribute('type', 'password') + + fireEvent.focus(screen.getByTestId('sshPassword')) + + expect(screen.getByTestId('sshPassword')).toHaveAttribute('value', '') + }) + + it('should render ssh password input with 10_000 length limit', () => { + render( + + ) + + expect(screen.getByTestId('sshPassword')).toHaveAttribute('maxLength', '10000') + }) + + describe('timeout', () => { + it('should render timeout input with 7 length limit and 1_000_000 value', () => { + render( + + ) + + expect(screen.getByTestId('timeout')).toBeInTheDocument() + expect(screen.getByTestId('timeout')).toHaveAttribute('maxLength', '7') + + fireEvent.change( + screen.getByTestId('timeout'), + { target: { value: '2000000' } } + ) + + expect(screen.getByTestId('timeout')).toHaveAttribute('value', '1000000') + }) + + it('should put only numbers', () => { + render( + + ) + + fireEvent.change( + screen.getByTestId('timeout'), + { target: { value: '11a2EU$#@' } } + ) + + expect(screen.getByTestId('timeout')).toHaveAttribute('value', '112') + }) + }) + + describe('cloud', () => { + it('some fields should be readonly if instance data source from cloud', () => { + (appRedirectionSelector as jest.Mock).mockImplementation(() => ({ + action: UrlHandlingActions.Connect, + })) + + const { queryByTestId } = render( + + ) + + expect(queryByTestId('connection-type')).not.toBeInTheDocument() + expect(queryByTestId('host')).not.toBeInTheDocument() + expect(queryByTestId('port')).not.toBeInTheDocument() + expect(queryByTestId('db-info-port')).toBeInTheDocument() + expect(queryByTestId('db-info-host')).toBeInTheDocument() + }) + }) +}) diff --git a/redisinsight/ui/src/pages/home/components/SentinelConnection/SentinelConnectionForm/SentinelConnectionForm.spec.tsx b/redisinsight/ui/src/pages/home/components/SentinelConnection/SentinelConnectionForm/SentinelConnectionForm.spec.tsx new file mode 100644 index 0000000000..610bae62f9 --- /dev/null +++ b/redisinsight/ui/src/pages/home/components/SentinelConnection/SentinelConnectionForm/SentinelConnectionForm.spec.tsx @@ -0,0 +1,14 @@ +import React from 'react' +import { instance, mock } from 'ts-mockito' +import { render } from 'uiSrc/utils/test-utils' +import SentinelConnectionForm, { Props } from './SentinelConnectionForm' + +const mockedProps = mock() + +describe('SentinelConnectionForm', () => { + it('should render', () => { + expect( + render() + ).toBeTruthy() + }) +}) diff --git a/redisinsight/ui/src/pages/home/components/SentinelConnection/SentinelConnectionForm/SentinelConnectionForm.tsx b/redisinsight/ui/src/pages/home/components/SentinelConnection/SentinelConnectionForm/SentinelConnectionForm.tsx index 0cf13ed72e..f72040a3d2 100644 --- a/redisinsight/ui/src/pages/home/components/SentinelConnection/SentinelConnectionForm/SentinelConnectionForm.tsx +++ b/redisinsight/ui/src/pages/home/components/SentinelConnection/SentinelConnectionForm/SentinelConnectionForm.tsx @@ -48,7 +48,7 @@ const getInitFieldsDisplayNames = ({ host, port }: any) => { const SentinelConnectionForm = (props: Props) => { const { - initialValues, + initialValues = {}, width, onClose, onSubmit, diff --git a/redisinsight/ui/src/pages/home/utils/form.tsx b/redisinsight/ui/src/pages/home/utils/form.tsx index eda2a1f610..dc2fa59483 100644 --- a/redisinsight/ui/src/pages/home/utils/form.tsx +++ b/redisinsight/ui/src/pages/home/utils/form.tsx @@ -168,6 +168,21 @@ export const getFormErrors = (values: DbConnectionInfo) => { } } + if (values.ssh) { + if (!values.sshHost) { + errs.sshHost = fieldDisplayNames.sshHost + } + if (!values.sshPort) { + errs.sshPort = fieldDisplayNames.sshPort + } + if (!values.sshUsername) { + errs.sshUsername = fieldDisplayNames.sshUsername + } + if (values.sshPassType === SshPassType.PrivateKey && !values.sshPrivateKey) { + errs.sshPrivateKey = fieldDisplayNames.sshPrivateKey + } + } + return errs } From 0e25648344327e6f42a2207506ed70e250a960c3 Mon Sep 17 00:00:00 2001 From: Amir Allayarov Date: Thu, 2 Nov 2023 12:52:54 +0400 Subject: [PATCH 04/11] #RI-5009 - resolve comments --- redisinsight/ui/src/pages/home/HomePage.tsx | 6 +- .../DatabasePanel.spec.tsx} | 8 +- .../DatabasePanel.tsx} | 4 +- .../InstanceConnections.spec.tsx | 0 .../InstanceConnections.tsx | 0 .../home/components/DatabasePanel/index.ts | 3 + .../styles.module.scss | 0 .../home/components/Form/DatabaseForm.tsx | 52 +++--- .../ManualConnectionForm.tsx | 158 +++++------------- .../ManualConnectionFrom.spec.tsx | 12 -- .../ManualConnectionWrapper.tsx | 79 ++------- .../pages/home/components/RightPanel/index.ts | 3 - .../SentinelConnectionForm.tsx | 8 +- .../ui/src/pages/home/constants/form.ts | 3 + .../ui/src/pages/home/interfaces/form.ts | 5 + redisinsight/ui/src/pages/home/utils/form.tsx | 53 +++++- .../edit-connection/EditConnection.tsx | 4 +- 17 files changed, 162 insertions(+), 236 deletions(-) rename redisinsight/ui/src/pages/home/components/{RightPanel/RightPanel.spec.tsx => DatabasePanel/DatabasePanel.spec.tsx} (68%) rename redisinsight/ui/src/pages/home/components/{RightPanel/RightPanel.tsx => DatabasePanel/DatabasePanel.tsx} (98%) rename redisinsight/ui/src/pages/home/components/{RightPanel => DatabasePanel}/InstanceConnections/InstanceConnections.spec.tsx (100%) rename redisinsight/ui/src/pages/home/components/{RightPanel => DatabasePanel}/InstanceConnections/InstanceConnections.tsx (100%) create mode 100644 redisinsight/ui/src/pages/home/components/DatabasePanel/index.ts rename redisinsight/ui/src/pages/home/components/{RightPanel => DatabasePanel}/styles.module.scss (100%) delete mode 100644 redisinsight/ui/src/pages/home/components/RightPanel/index.ts diff --git a/redisinsight/ui/src/pages/home/HomePage.tsx b/redisinsight/ui/src/pages/home/HomePage.tsx index 08af6d8c41..3e05265023 100644 --- a/redisinsight/ui/src/pages/home/HomePage.tsx +++ b/redisinsight/ui/src/pages/home/HomePage.tsx @@ -2,6 +2,7 @@ import { EuiPage, EuiPageBody, EuiResizableContainer, EuiResizeObserver } from ' import React, { useEffect, useRef, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import cx from 'classnames' +import DatabasePanel from 'redisinsight/ui/src/pages/home/components/DatabasePanel' import { clusterSelector, resetDataRedisCluster, resetInstancesRedisCluster, } from 'uiSrc/slices/instances/cluster' import { Nullable, setTitle } from 'uiSrc/utils' import { PageHeader } from 'uiSrc/components' @@ -26,7 +27,6 @@ import { sendEventTelemetry, sendPageViewTelemetry, TelemetryEvent, TelemetryPag import { appRedirectionSelector, setUrlHandlingInitialState } from 'uiSrc/slices/app/url-handling' import { UrlHandlingActions } from 'uiSrc/slices/interfaces/urlHandling' import { AddDbType } from 'uiSrc/pages/home/constants' -import RightPanel from 'uiSrc/pages/home/components/RightPanel' import DatabasesList from './components/DatabasesListComponent' import WelcomeComponent from './components/WelcomeComponent' import HomeHeader from './components/HomeHeader' @@ -262,7 +262,7 @@ const HomePage = () => { style={{ minWidth: '494px' }} > {!!openRightPanel && ( - { ) : ( <> {openRightPanel === RightPanelName.AddDatabase && ( - () -describe('AddDatabasesContainer', () => { +describe('DatabasePanel', () => { it('should render', () => { expect( - render() + render() ).toBeTruthy() }) it('should render instance types after click on auto discover', () => { - render() + render() fireEvent.click(screen.getByTestId('add-auto')) expect(screen.getByTestId('db-types')).toBeInTheDocument() }) diff --git a/redisinsight/ui/src/pages/home/components/RightPanel/RightPanel.tsx b/redisinsight/ui/src/pages/home/components/DatabasePanel/DatabasePanel.tsx similarity index 98% rename from redisinsight/ui/src/pages/home/components/RightPanel/RightPanel.tsx rename to redisinsight/ui/src/pages/home/components/DatabasePanel/DatabasePanel.tsx index 305178786e..b93409759b 100644 --- a/redisinsight/ui/src/pages/home/components/RightPanel/RightPanel.tsx +++ b/redisinsight/ui/src/pages/home/components/DatabasePanel/DatabasePanel.tsx @@ -43,7 +43,7 @@ export interface Props { initConnectionType?: AddDbType } -const RightPanel = React.memo((props: Props) => { +const DatabasePanel = React.memo((props: Props) => { const { editMode, isResizablePanel, @@ -233,4 +233,4 @@ const RightPanel = React.memo((props: Props) => { ) }) -export default RightPanel +export default DatabasePanel diff --git a/redisinsight/ui/src/pages/home/components/RightPanel/InstanceConnections/InstanceConnections.spec.tsx b/redisinsight/ui/src/pages/home/components/DatabasePanel/InstanceConnections/InstanceConnections.spec.tsx similarity index 100% rename from redisinsight/ui/src/pages/home/components/RightPanel/InstanceConnections/InstanceConnections.spec.tsx rename to redisinsight/ui/src/pages/home/components/DatabasePanel/InstanceConnections/InstanceConnections.spec.tsx diff --git a/redisinsight/ui/src/pages/home/components/RightPanel/InstanceConnections/InstanceConnections.tsx b/redisinsight/ui/src/pages/home/components/DatabasePanel/InstanceConnections/InstanceConnections.tsx similarity index 100% rename from redisinsight/ui/src/pages/home/components/RightPanel/InstanceConnections/InstanceConnections.tsx rename to redisinsight/ui/src/pages/home/components/DatabasePanel/InstanceConnections/InstanceConnections.tsx diff --git a/redisinsight/ui/src/pages/home/components/DatabasePanel/index.ts b/redisinsight/ui/src/pages/home/components/DatabasePanel/index.ts new file mode 100644 index 0000000000..18eecda936 --- /dev/null +++ b/redisinsight/ui/src/pages/home/components/DatabasePanel/index.ts @@ -0,0 +1,3 @@ +import DatabasePanel from './DatabasePanel' + +export default DatabasePanel diff --git a/redisinsight/ui/src/pages/home/components/RightPanel/styles.module.scss b/redisinsight/ui/src/pages/home/components/DatabasePanel/styles.module.scss similarity index 100% rename from redisinsight/ui/src/pages/home/components/RightPanel/styles.module.scss rename to redisinsight/ui/src/pages/home/components/DatabasePanel/styles.module.scss diff --git a/redisinsight/ui/src/pages/home/components/Form/DatabaseForm.tsx b/redisinsight/ui/src/pages/home/components/Form/DatabaseForm.tsx index c64a440d57..fe839b983c 100644 --- a/redisinsight/ui/src/pages/home/components/Form/DatabaseForm.tsx +++ b/redisinsight/ui/src/pages/home/components/Form/DatabaseForm.tsx @@ -14,20 +14,32 @@ import { import { BuildType } from 'uiSrc/constants/env' import { SECURITY_FIELD } from 'uiSrc/constants' import { appInfoSelector } from 'uiSrc/slices/app/info' -import { handlePasteHostName, MAX_PORT_NUMBER, MAX_TIMEOUT_NUMBER, selectOnFocus, validateField, validatePortNumber, validateTimeoutNumber } from 'uiSrc/utils' -import { ConnectionType, InstanceType } from 'uiSrc/slices/interfaces' -import { DbConnectionInfo } from 'uiSrc/pages/home/interfaces' +import { + handlePasteHostName, + MAX_PORT_NUMBER, + MAX_TIMEOUT_NUMBER, + selectOnFocus, + validateField, + validatePortNumber, + validateTimeoutNumber, +} from 'uiSrc/utils' +import { DbConnectionInfo, IPasswordType } from 'uiSrc/pages/home/interfaces' + +interface IShowFields { + alias: boolean + host: boolean + port: boolean + timeout: boolean +} export interface Props { flexGroupClassName?: string flexItemClassName?: string formik: FormikProps - isEditMode?: boolean - isCloneMode?: boolean onHostNamePaste: (content: string) => boolean - instanceType: InstanceType - connectionType?: ConnectionType - isFromCloud?: boolean + showFields: IShowFields + autoFocus?: boolean + passwordType?: IPasswordType } const DatabaseForm = (props: Props) => { @@ -35,12 +47,10 @@ const DatabaseForm = (props: Props) => { flexGroupClassName = '', flexItemClassName = '', formik, - isEditMode = false, - isCloneMode = false, onHostNamePaste, - instanceType, - connectionType, - isFromCloud, + autoFocus = false, + showFields, + passwordType = IPasswordType.Password, } = props const { server } = useSelector(appInfoSelector) @@ -84,11 +94,11 @@ const DatabaseForm = (props: Props) => { return ( <> - {(!isEditMode || isCloneMode) && !isFromCloud && ( + {showFields.host && ( { )} - {server?.buildType !== BuildType.RedisStack && !isFromCloud && ( + {server?.buildType !== BuildType.RedisStack && showFields.port && ( { )} - {( - (!isEditMode || isCloneMode) - && instanceType !== InstanceType.Sentinel - && connectionType !== ConnectionType.Sentinel - ) && ( + {showFields.alias && ( @@ -179,7 +185,7 @@ const DatabaseForm = (props: Props) => { { - {connectionType !== ConnectionType.Sentinel && instanceType !== InstanceType.Sentinel && ( + {showFields.timeout && ( void - initialValues: DbConnectionInfo onSubmit: (values: DbConnectionInfo) => void onTestConnection: (values: DbConnectionInfo) => void onHostNamePaste: (content: string) => boolean @@ -77,49 +75,15 @@ export interface Props { } const getInitFieldsDisplayNames = ({ host, port, name }: any) => { - if ((!host || !port) && !name) { + if (!host || !port || !name) { return pick(fieldDisplayNames, ['host', 'port', 'name']) } return {} } -const getDefaultHost = () => '127.0.0.1' -const getDefaultPort = () => '6379' - const ManualConnectionForm = (props: Props) => { const { - formFields: { - id, - host, - name, - port, - tls, - db = null, - compressor = NONE, - nameFromProvider, - sentinelMaster, - connectionType, - nodes = null, - tlsClientAuthRequired, - certificates, - selectedTlsClientCertId = '', - verifyServerTlsCert, - caCertificates, - selectedCaCertName, - username, - password, - timeout, - modules, - sentinelMasterPassword, - sentinelMasterUsername, - servername, - provider, - ssh, - sshPassType = SshPassType.Password, - sshOptions, - version, - }, - initialValues: initialValuesProp, + formFields, width, onClose, onSubmit, @@ -134,59 +98,30 @@ const ManualConnectionForm = (props: Props) => { onAliasEdited, } = props - const { contextInstanceId, lastPage } = useSelector(appContextSelector) - const { action } = useSelector(appRedirectionSelector) - - const prepareInitialValues = () => ({ - host: host ?? getDefaultHost(), - port: port ? port.toString() : getDefaultPort(), - timeout: timeout ? timeout.toString() : toString(DEFAULT_TIMEOUT / 1_000), - name: name ?? `${getDefaultHost()}:${getDefaultPort()}`, - username, - password, - tls, - db, - compressor, + const { + id, + host, + name, + port, + db = null, + nameFromProvider, + sentinelMaster, + connectionType, + nodes = null, modules, - showDb: !!db, - showCompressor: compressor !== NONE, - sni: !!servername, - servername, - newCaCert: '', - newCaCertName: '', - selectedCaCertName, - tlsClientAuthRequired, - verifyServerTlsCert, - newTlsCertPairName: '', - selectedTlsClientCertId, - newTlsClientCert: '', - newTlsClientKey: '', - sentinelMasterName: sentinelMaster?.name || '', - sentinelMasterUsername, - sentinelMasterPassword, - ssh, - sshPassType, - sshHost: sshOptions?.host ?? '', - sshPort: sshOptions?.port ?? 22, - sshUsername: sshOptions?.username ?? '', - sshPassword: sshOptions?.password ?? '', - sshPrivateKey: sshOptions?.privateKey ?? '', - sshPassphrase: sshOptions?.passphrase ?? '' - }) + provider, + version, + } = formFields - const [initialValues, setInitialValues] = useState(prepareInitialValues()) + const { contextInstanceId, lastPage } = useSelector(appContextSelector) + const { action } = useSelector(appRedirectionSelector) + const { data: caCertificates } = useSelector(caCertsSelector) + const { data: certificates } = useSelector(clientCertsSelector) const [errors, setErrors] = useState>( getInitFieldsDisplayNames({ host, port, name }) ) - useEffect(() => { - const values = prepareInitialValues() - - setInitialValues(values) - formik.setValues(values) - }, [initialValuesProp, isCloneMode]) - const history = useHistory() const dispatch = useDispatch() @@ -211,7 +146,7 @@ const ManualConnectionForm = (props: Props) => { } const formik = useFormik({ - initialValues, + initialValues: formFields, validate, enableReinitialize: true, onSubmit: (values: any) => { @@ -313,7 +248,7 @@ const ManualConnectionForm = (props: Props) => { history.push(Pages.workbench(id)) return } - history.push(Pages.browser(id)) + history.push(Pages.browser(id ?? '')) } const SubmitButton = ({ @@ -385,14 +320,14 @@ const ManualConnectionForm = (props: Props) => { {onClose && ( - - Cancel - + + Cancel + )} { formik={formik} flexItemClassName={flexItemClassName} flexGroupClassName={flexGroupClassName} - isCloneMode={isCloneMode} - isEditMode={isEditMode} - connectionType={connectionType} - instanceType={InstanceType.Standalone} onHostNamePaste={onHostNamePaste} + showFields={{ host: true, alias: true, port: true, timeout: true }} /> { > {isCloneMode && ( @@ -578,10 +512,7 @@ const ManualConnectionForm = (props: Props) => { formik={formik} flexItemClassName={flexItemClassName} flexGroupClassName={flexGroupClassName} - isCloneMode={isCloneMode} - isEditMode={isEditMode} - connectionType={connectionType} - instanceType={InstanceType.Standalone} + showFields={{ host: false, port: true, alias: false, timeout: false }} onHostNamePaste={onHostNamePaste} /> @@ -631,10 +562,7 @@ const ManualConnectionForm = (props: Props) => { formik={formik} flexItemClassName={flexItemClassName} flexGroupClassName={flexGroupClassName} - isCloneMode={isCloneMode} - isEditMode={isEditMode} - connectionType={connectionType} - instanceType={InstanceType.Standalone} + showFields={{ host: true, port: true, alias: false, timeout: false }} onHostNamePaste={onHostNamePaste} /> diff --git a/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionForm/ManualConnectionFrom.spec.tsx b/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionForm/ManualConnectionFrom.spec.tsx index 88607e7c09..6cbfdbfde0 100644 --- a/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionForm/ManualConnectionFrom.spec.tsx +++ b/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionForm/ManualConnectionFrom.spec.tsx @@ -804,18 +804,6 @@ describe('InstanceForm', () => { expect(screen.getByTestId('port')).toHaveValue('6379') expect(screen.getByTestId('name')).toHaveValue('127.0.0.1:6379') }) - - // it('should render proper default values for sentinel', () => { - // render( - // - // ) - // expect(screen.getByTestId('host')).toHaveValue('127.0.0.1') - // expect(screen.getByTestId('port')).toHaveValue('26379') - // }) }) it('should change Use SSH checkbox', async () => { diff --git a/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionWrapper.tsx b/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionWrapper.tsx index e36fb9b484..25cb5409e4 100644 --- a/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionWrapper.tsx +++ b/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionWrapper.tsx @@ -1,4 +1,4 @@ -import { pick, toNumber, toString, omit } from 'lodash' +import { pick, toNumber, omit } from 'lodash' import React, { useEffect, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import { useHistory } from 'react-router' @@ -14,17 +14,20 @@ import { import { Nullable, removeEmpty, getFormUpdates, transformQueryParamsObject } from 'uiSrc/utils' import { BuildType } from 'uiSrc/constants/env' import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' -import { caCertsSelector, fetchCaCerts } from 'uiSrc/slices/instances/caCerts' +import { fetchCaCerts } from 'uiSrc/slices/instances/caCerts' import { ConnectionType, Instance, InstanceType } from 'uiSrc/slices/interfaces' import { DbType, Pages } from 'uiSrc/constants' -import { clientCertsSelector, fetchClientCerts, } from 'uiSrc/slices/instances/clientCerts' +import { fetchClientCerts, } from 'uiSrc/slices/instances/clientCerts' import { appInfoSelector } from 'uiSrc/slices/app/info' import { UrlHandlingActions } from 'uiSrc/slices/interfaces/urlHandling' import { appRedirectionSelector, setUrlHandlingInitialState } from 'uiSrc/slices/app/url-handling' import { getRedirectionPage } from 'uiSrc/utils/routing' import { DbConnectionInfo } from 'uiSrc/pages/home/interfaces' -import { applyTlSDatabase, applySSHDatabase, autoFillFormDetails, getTlsSettings } from 'uiSrc/pages/home/utils' -import { ADD_NEW, DEFAULT_TIMEOUT, NO_CA_CERT, SshPassType, SubmitBtnText } from 'uiSrc/pages/home/constants' +import { applyTlSDatabase, applySSHDatabase, autoFillFormDetails, getTlsSettings, getFormValues } from 'uiSrc/pages/home/utils' +import { + DEFAULT_TIMEOUT, + SubmitBtnText, +} from 'uiSrc/pages/home/constants' import ManualConnectionForm from './ManualConnectionForm' export interface Props { @@ -38,24 +41,6 @@ export interface Props { onAliasEdited?: (value: string) => void } -const getInitialValues = (editedInstance?: Nullable>) => ({ - // undefined - to show default value, empty string - for existing db - host: editedInstance?.host ?? (editedInstance ? '' : undefined), - port: editedInstance?.port?.toString() ?? (editedInstance ? '' : undefined), - name: editedInstance?.name ?? (editedInstance ? '' : undefined), - username: editedInstance?.username ?? '', - password: editedInstance?.password ?? '', - timeout: editedInstance?.timeout - ? toString(editedInstance?.timeout / 1_000) - : (editedInstance ? '' : undefined), - tls: !!editedInstance?.tls ?? false, - ssh: !!editedInstance?.ssh ?? false, - servername: editedInstance?.tlsServername, - sshPassType: editedInstance?.sshOptions - ? (editedInstance.sshOptions.privateKey ? SshPassType.PrivateKey : SshPassType.Password) - : SshPassType.Password -}) - const ManualConnectionWrapper = (props: Props) => { const { editMode, @@ -67,24 +52,14 @@ const ManualConnectionWrapper = (props: Props) => { urlHandlingAction, initialValues: initialValuesProp } = props - const [initialValues, setInitialValues] = useState(getInitialValues(editedInstance || initialValuesProp)) - const [isCloneMode, setIsCloneMode] = useState(false) + const [formFields, setFormFields] = useState(getFormValues(editedInstance || initialValuesProp)) - const { host, port, name, username, password, timeout, tls, ssh, sshPassType, servername } = initialValues + const [isCloneMode, setIsCloneMode] = useState(false) const { loadingChanging: loadingStandalone } = useSelector(instancesSelector) - const { data: caCertificates } = useSelector(caCertsSelector) - const { data: certificates } = useSelector(clientCertsSelector) const { server } = useSelector(appInfoSelector) const { properties: urlHandlingProperties } = useSelector(appRedirectionSelector) - const tlsClientAuthRequired = !!editedInstance?.clientCert?.id ?? false - const selectedTlsClientCertId = editedInstance?.clientCert?.id ?? ADD_NEW - const verifyServerTlsCert = editedInstance?.verifyServerCert ?? false - const selectedCaCertName = editedInstance?.caCert?.id ?? NO_CA_CERT - const sentinelMasterUsername = editedInstance?.sentinelMaster?.username ?? '' - const sentinelMasterPassword = editedInstance?.sentinelMaster?.password ?? '' - const connectionType = editedInstance?.connectionType ?? DbType.STANDALONE const masterName = editedInstance?.sentinelMaster?.name @@ -97,10 +72,7 @@ const ManualConnectionWrapper = (props: Props) => { }, []) useEffect(() => { - (editedInstance || initialValuesProp) && setInitialValues({ - ...initialValues, - ...getInitialValues(editedInstance || initialValuesProp) - }) + setFormFields(getFormValues(editedInstance || initialValuesProp || null)) setIsCloneMode(false) }, [editedInstance, initialValuesProp]) @@ -322,29 +294,6 @@ const ManualConnectionWrapper = (props: Props) => { onClose?.() } - const connectionFormData = { - ...editedInstance, - name, - host, - port, - tls, - username, - password, - timeout, - connectionType, - tlsClientAuthRequired, - certificates, - selectedTlsClientCertId, - caCertificates, - verifyServerTlsCert, - selectedCaCertName, - sentinelMasterUsername, - sentinelMasterPassword, - ssh, - sshPassType, - servername, - } - const getSubmitButtonText = () => { if (isCloneMode) { return SubmitBtnText.CloneDatabase @@ -356,15 +305,15 @@ const ManualConnectionWrapper = (props: Props) => { } const handlePostHostName = (content: string): boolean => ( - autoFillFormDetails(content, initialValues, setInitialValues, InstanceType.Standalone) + autoFillFormDetails(content, formFields, setFormFields, InstanceType.Standalone) ) return (
{ formik={formik} flexItemClassName={flexItemClassName} flexGroupClassName={flexGroupClassName} - connectionType={ConnectionType.Sentinel} - instanceType={InstanceType.Sentinel} + passwordType={IPasswordType.dual} + showFields={{ host: true, port: true, alias: false, timeout: false }} onHostNamePaste={onHostNamePaste} - isFromCloud={false} /> ({ useTls: values.tls, @@ -277,3 +285,44 @@ export const getSubmitButtonContent = (errors: FormikErrors, s {errorsArr} ) : null } + +export const getFormValues = (instance: Nullable>) => ({ + host: instance?.host ?? (instance ? '' : DEFAULT_HOST), + port: instance?.port?.toString() ?? (instance ? '' : DEFAULT_PORT), + timeout: instance?.timeout + ? toString(instance?.timeout / 1_000) + : toString(DEFAULT_TIMEOUT / 1_000), + name: instance?.name ?? (instance ? '' : DEFAULT_ALIAS), + username: instance?.username ?? '', + password: instance?.password ?? '', + tls: instance?.tls ?? false, + db: instance?.db, + compressor: instance?.compressor ?? NONE, + modules: instance?.modules, + showDb: !!instance?.db, + showCompressor: instance && instance.compressor !== NONE, + sni: !!instance?.servername, + servername: instance?.servername, + newCaCert: '', + newCaCertName: '', + selectedCaCertName: instance?.caCert?.id ?? NO_CA_CERT, + tlsClientAuthRequired: instance?.clientCert?.id ?? false, + verifyServerTlsCert: instance?.verifyServerCert ?? false, + newTlsCertPairName: '', + selectedTlsClientCertId: instance?.clientCert?.id ?? ADD_NEW, + newTlsClientCert: '', + newTlsClientKey: '', + sentinelMasterName: instance?.sentinelMaster?.name || '', + sentinelMasterUsername: instance?.sentinelMasterUsername, + sentinelMasterPassword: instance?.sentinelMasterPassword, + ssh: instance?.ssh ?? false, + sshPassType: instance?.sshOptions + ? (instance.sshOptions.privateKey ? SshPassType.PrivateKey : SshPassType.Password) + : SshPassType.Password, + sshHost: instance?.sshOptions?.host ?? '', + sshPort: instance?.sshOptions?.port ?? 22, + sshUsername: instance?.sshOptions?.username ?? '', + sshPassword: instance?.sshOptions?.password ?? '', + sshPrivateKey: instance?.sshOptions?.privateKey ?? '', + sshPassphrase: instance?.sshOptions?.passphrase ?? '' +}) diff --git a/redisinsight/ui/src/pages/redisStack/components/edit-connection/EditConnection.tsx b/redisinsight/ui/src/pages/redisStack/components/edit-connection/EditConnection.tsx index c167d4b33a..0d8d34eaeb 100644 --- a/redisinsight/ui/src/pages/redisStack/components/edit-connection/EditConnection.tsx +++ b/redisinsight/ui/src/pages/redisStack/components/edit-connection/EditConnection.tsx @@ -18,7 +18,7 @@ import { HELP_LINKS } from 'uiSrc/pages/home/constants' import { sendEventTelemetry } from 'uiSrc/telemetry' import { ThemeContext } from 'uiSrc/contexts/themeContext' import { contentSelector } from 'uiSrc/slices/content/create-redis-buttons' -import RightPanel from 'uiSrc/pages/home/components/RightPanel/RightPanel' +import DatabasePanel from 'uiSrc/pages/home/components/DatabasePanel/DatabasePanel' import './styles.scss' import styles from './styles.module.scss' @@ -119,7 +119,7 @@ const EditConnection = () => { )}
- Date: Thu, 9 Nov 2023 12:10:23 +0400 Subject: [PATCH 05/11] #RI-5009 - resolve comments --- redisinsight/ui/src/pages/home/HomePage.tsx | 8 +- .../CloudConnectionFormWrapper.spec.tsx | 0 .../CloudConnectionFormWrapper.tsx | 2 +- .../CloudConnectionForm.spec.tsx | 0 .../CloudConnectionForm.tsx | 0 .../cloud-connection-form}/index.ts | 0 .../index.ts | 0 .../styles.module.scss | 0 .../ClusterConnectionFormWrapper.spec.tsx | 5 +- .../ClusterConnectionFormWrapper.tsx | 2 +- .../ClusterConnectionForm.spec.tsx | 0 .../ClusterConnectionForm.tsx | 0 .../cluster-connection-form}/index.ts | 0 .../index.ts | 0 .../styles.module.scss | 0 .../types.ts | 0 .../DatabaseAlias.spec.tsx | 0 .../DatabaseAlias.tsx | 95 +- .../index.ts | 0 .../styles.module.scss | 0 .../DatabasePanel.spec.tsx | 0 .../DatabasePanel.tsx | 16 +- .../index.ts | 0 .../InstanceConnections.spec.tsx | 0 .../InstanceConnections.tsx | 0 .../instance-connections/index.ts | 3 + .../styles.module.scss | 0 .../DatabasesListWrapper.spec.tsx | 4 +- .../DatabasesListWrapper.tsx | 2 +- .../databases-list}/DatabasesList.spec.tsx | 0 .../databases-list}/DatabasesList.tsx | 0 .../components/action-bar/ActionBar.spec.tsx | 0 .../components/action-bar/ActionBar.tsx | 0 .../components/action-bar/styles.module.scss | 0 .../delete-action/DeleteAction.spec.tsx | 0 .../components/delete-action/DeleteAction.tsx | 0 .../export-action/ExportAction.spec.tsx | 0 .../components/export-action/ExportAction.tsx | 0 .../databases-list}/components/index.ts | 0 .../components/styles.module.scss | 0 .../databases-list}/index.ts | 0 .../index.ts | 0 .../styles.module.scss | 0 .../HelpLinksMenu.tsx | 0 .../HelpLinskMenu.spec.tsx | 0 .../index.ts | 0 .../styles.module.scss | 0 .../HomeHeader.spec.tsx | 0 .../HomeHeader.tsx | 8 +- .../{HomeHeader => home-header}/index.ts | 0 .../styles.module.scss | 0 .../ManualConnectionWrapper.spec.tsx | 1247 +++++++++++++++++ .../ManualConnectionWrapper.tsx | 130 +- .../index.ts | 0 .../ManualConnectionForm.tsx | 91 +- .../ManualConnectionFrom.spec.tsx | 24 +- .../manual-connection-form}/index.ts | 0 .../SearchDatabasesList.spec.tsx | 0 .../SearchDatabasesList.tsx | 0 .../index.ts | 0 .../styles.module.scss | 0 .../SentinelConnectionWrapper.tsx | 5 +- .../index.ts | 0 .../SentinelConnectionForm.spec.tsx | 0 .../SentinelConnectionForm.tsx | 5 +- .../sentinel-connection-form}/index.ts | 0 .../WelcomeComponent.spec.tsx | 0 .../WelcomeComponent.tsx | 0 .../index.ts | 0 .../styles.module.scss | 0 .../ui/src/pages/home/constants/form.ts | 3 +- redisinsight/ui/src/pages/home/utils/form.tsx | 3 +- .../edit-connection/EditConnection.tsx | 2 +- 73 files changed, 1415 insertions(+), 240 deletions(-) rename redisinsight/ui/src/pages/home/components/{CloudConnection => cloud-connection}/CloudConnectionFormWrapper.spec.tsx (100%) rename redisinsight/ui/src/pages/home/components/{CloudConnection => cloud-connection}/CloudConnectionFormWrapper.tsx (96%) rename redisinsight/ui/src/pages/home/components/{CloudConnection/CloudConnectionForm => cloud-connection/cloud-connection-form}/CloudConnectionForm.spec.tsx (100%) rename redisinsight/ui/src/pages/home/components/{CloudConnection/CloudConnectionForm => cloud-connection/cloud-connection-form}/CloudConnectionForm.tsx (100%) rename redisinsight/ui/src/pages/home/components/{CloudConnection/CloudConnectionForm => cloud-connection/cloud-connection-form}/index.ts (100%) rename redisinsight/ui/src/pages/home/components/{CloudConnection => cloud-connection}/index.ts (100%) rename redisinsight/ui/src/pages/home/components/{CloudConnection => cloud-connection}/styles.module.scss (100%) rename redisinsight/ui/src/pages/home/components/{ClusterConnection => cluster-connection}/ClusterConnectionFormWrapper.spec.tsx (91%) rename redisinsight/ui/src/pages/home/components/{ClusterConnection => cluster-connection}/ClusterConnectionFormWrapper.tsx (95%) rename redisinsight/ui/src/pages/home/components/{ClusterConnection/ClusterConnectionForm => cluster-connection/cluster-connection-form}/ClusterConnectionForm.spec.tsx (100%) rename redisinsight/ui/src/pages/home/components/{ClusterConnection/ClusterConnectionForm => cluster-connection/cluster-connection-form}/ClusterConnectionForm.tsx (100%) rename redisinsight/ui/src/pages/home/components/{ClusterConnection/ClusterConnectionForm => cluster-connection/cluster-connection-form}/index.ts (100%) rename redisinsight/ui/src/pages/home/components/{ClusterConnection => cluster-connection}/index.ts (100%) rename redisinsight/ui/src/pages/home/components/{ClusterConnection => cluster-connection}/styles.module.scss (100%) rename redisinsight/ui/src/pages/home/components/{ClusterConnection => cluster-connection}/types.ts (100%) rename redisinsight/ui/src/pages/home/components/{DatabaseAlias => database-alias}/DatabaseAlias.spec.tsx (100%) rename redisinsight/ui/src/pages/home/components/{DatabaseAlias => database-alias}/DatabaseAlias.tsx (74%) rename redisinsight/ui/src/pages/home/components/{DatabaseAlias => database-alias}/index.ts (100%) rename redisinsight/ui/src/pages/home/components/{DatabaseAlias => database-alias}/styles.module.scss (100%) rename redisinsight/ui/src/pages/home/components/{DatabasePanel => database-panel}/DatabasePanel.spec.tsx (100%) rename redisinsight/ui/src/pages/home/components/{DatabasePanel => database-panel}/DatabasePanel.tsx (95%) rename redisinsight/ui/src/pages/home/components/{DatabasePanel => database-panel}/index.ts (100%) rename redisinsight/ui/src/pages/home/components/{DatabasePanel/InstanceConnections => database-panel/instance-connections}/InstanceConnections.spec.tsx (100%) rename redisinsight/ui/src/pages/home/components/{DatabasePanel/InstanceConnections => database-panel/instance-connections}/InstanceConnections.tsx (100%) create mode 100644 redisinsight/ui/src/pages/home/components/database-panel/instance-connections/index.ts rename redisinsight/ui/src/pages/home/components/{DatabasePanel => database-panel}/styles.module.scss (100%) rename redisinsight/ui/src/pages/home/components/{DatabasesListComponent => databases-list-component}/DatabasesListWrapper.spec.tsx (97%) rename redisinsight/ui/src/pages/home/components/{DatabasesListComponent => databases-list-component}/DatabasesListWrapper.tsx (99%) rename redisinsight/ui/src/pages/home/components/{DatabasesListComponent/DatabasesList => databases-list-component/databases-list}/DatabasesList.spec.tsx (100%) rename redisinsight/ui/src/pages/home/components/{DatabasesListComponent/DatabasesList => databases-list-component/databases-list}/DatabasesList.tsx (100%) rename redisinsight/ui/src/pages/home/components/{DatabasesListComponent/DatabasesList => databases-list-component/databases-list}/components/action-bar/ActionBar.spec.tsx (100%) rename redisinsight/ui/src/pages/home/components/{DatabasesListComponent/DatabasesList => databases-list-component/databases-list}/components/action-bar/ActionBar.tsx (100%) rename redisinsight/ui/src/pages/home/components/{DatabasesListComponent/DatabasesList => databases-list-component/databases-list}/components/action-bar/styles.module.scss (100%) rename redisinsight/ui/src/pages/home/components/{DatabasesListComponent/DatabasesList => databases-list-component/databases-list}/components/delete-action/DeleteAction.spec.tsx (100%) rename redisinsight/ui/src/pages/home/components/{DatabasesListComponent/DatabasesList => databases-list-component/databases-list}/components/delete-action/DeleteAction.tsx (100%) rename redisinsight/ui/src/pages/home/components/{DatabasesListComponent/DatabasesList => databases-list-component/databases-list}/components/export-action/ExportAction.spec.tsx (100%) rename redisinsight/ui/src/pages/home/components/{DatabasesListComponent/DatabasesList => databases-list-component/databases-list}/components/export-action/ExportAction.tsx (100%) rename redisinsight/ui/src/pages/home/components/{DatabasesListComponent/DatabasesList => databases-list-component/databases-list}/components/index.ts (100%) rename redisinsight/ui/src/pages/home/components/{DatabasesListComponent/DatabasesList => databases-list-component/databases-list}/components/styles.module.scss (100%) rename redisinsight/ui/src/pages/home/components/{DatabasesListComponent/DatabasesList => databases-list-component/databases-list}/index.ts (100%) rename redisinsight/ui/src/pages/home/components/{DatabasesListComponent => databases-list-component}/index.ts (100%) rename redisinsight/ui/src/pages/home/components/{DatabasesListComponent => databases-list-component}/styles.module.scss (100%) rename redisinsight/ui/src/pages/home/components/{HelpLinksMenu => help-links-menu}/HelpLinksMenu.tsx (100%) rename redisinsight/ui/src/pages/home/components/{HelpLinksMenu => help-links-menu}/HelpLinskMenu.spec.tsx (100%) rename redisinsight/ui/src/pages/home/components/{HelpLinksMenu => help-links-menu}/index.ts (100%) rename redisinsight/ui/src/pages/home/components/{HelpLinksMenu => help-links-menu}/styles.module.scss (100%) rename redisinsight/ui/src/pages/home/components/{HomeHeader => home-header}/HomeHeader.spec.tsx (100%) rename redisinsight/ui/src/pages/home/components/{HomeHeader => home-header}/HomeHeader.tsx (95%) rename redisinsight/ui/src/pages/home/components/{HomeHeader => home-header}/index.ts (100%) rename redisinsight/ui/src/pages/home/components/{HomeHeader => home-header}/styles.module.scss (100%) create mode 100644 redisinsight/ui/src/pages/home/components/manual-connection/ManualConnectionWrapper.spec.tsx rename redisinsight/ui/src/pages/home/components/{ManualConnection => manual-connection}/ManualConnectionWrapper.tsx (71%) rename redisinsight/ui/src/pages/home/components/{ManualConnection => manual-connection}/index.ts (100%) rename redisinsight/ui/src/pages/home/components/{ManualConnection/ManualConnectionForm => manual-connection/manual-connection-form}/ManualConnectionForm.tsx (87%) rename redisinsight/ui/src/pages/home/components/{ManualConnection/ManualConnectionForm => manual-connection/manual-connection-form}/ManualConnectionFrom.spec.tsx (99%) rename redisinsight/ui/src/pages/home/components/{ManualConnection/ManualConnectionForm => manual-connection/manual-connection-form}/index.ts (100%) rename redisinsight/ui/src/pages/home/components/{SearchDatabasesList => search-databases-list}/SearchDatabasesList.spec.tsx (100%) rename redisinsight/ui/src/pages/home/components/{SearchDatabasesList => search-databases-list}/SearchDatabasesList.tsx (100%) rename redisinsight/ui/src/pages/home/components/{SearchDatabasesList => search-databases-list}/index.ts (100%) rename redisinsight/ui/src/pages/home/components/{SearchDatabasesList => search-databases-list}/styles.module.scss (100%) rename redisinsight/ui/src/pages/home/components/{SentinelConnection => sentinel-connection}/SentinelConnectionWrapper.tsx (94%) rename redisinsight/ui/src/pages/home/components/{SentinelConnection => sentinel-connection}/index.ts (100%) rename redisinsight/ui/src/pages/home/components/{SentinelConnection/SentinelConnectionForm => sentinel-connection/sentinel-connection-form}/SentinelConnectionForm.spec.tsx (100%) rename redisinsight/ui/src/pages/home/components/{SentinelConnection/SentinelConnectionForm => sentinel-connection/sentinel-connection-form}/SentinelConnectionForm.tsx (98%) rename redisinsight/ui/src/pages/home/components/{SentinelConnection/SentinelConnectionForm => sentinel-connection/sentinel-connection-form}/index.ts (100%) rename redisinsight/ui/src/pages/home/components/{WelcomeComponent => welcome-component}/WelcomeComponent.spec.tsx (100%) rename redisinsight/ui/src/pages/home/components/{WelcomeComponent => welcome-component}/WelcomeComponent.tsx (100%) rename redisinsight/ui/src/pages/home/components/{WelcomeComponent => welcome-component}/index.ts (100%) rename redisinsight/ui/src/pages/home/components/{WelcomeComponent => welcome-component}/styles.module.scss (100%) diff --git a/redisinsight/ui/src/pages/home/HomePage.tsx b/redisinsight/ui/src/pages/home/HomePage.tsx index 3e05265023..10853c7b63 100644 --- a/redisinsight/ui/src/pages/home/HomePage.tsx +++ b/redisinsight/ui/src/pages/home/HomePage.tsx @@ -2,7 +2,7 @@ import { EuiPage, EuiPageBody, EuiResizableContainer, EuiResizeObserver } from ' import React, { useEffect, useRef, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import cx from 'classnames' -import DatabasePanel from 'redisinsight/ui/src/pages/home/components/DatabasePanel' +import DatabasePanel from 'uiSrc/pages/home/components/database-panel' import { clusterSelector, resetDataRedisCluster, resetInstancesRedisCluster, } from 'uiSrc/slices/instances/cluster' import { Nullable, setTitle } from 'uiSrc/utils' import { PageHeader } from 'uiSrc/components' @@ -27,9 +27,9 @@ import { sendEventTelemetry, sendPageViewTelemetry, TelemetryEvent, TelemetryPag import { appRedirectionSelector, setUrlHandlingInitialState } from 'uiSrc/slices/app/url-handling' import { UrlHandlingActions } from 'uiSrc/slices/interfaces/urlHandling' import { AddDbType } from 'uiSrc/pages/home/constants' -import DatabasesList from './components/DatabasesListComponent' -import WelcomeComponent from './components/WelcomeComponent' -import HomeHeader from './components/HomeHeader' +import DatabasesList from './components/databases-list-component' +import WelcomeComponent from './components/welcome-component' +import HomeHeader from './components/home-header' import './styles.scss' import styles from './styles.module.scss' diff --git a/redisinsight/ui/src/pages/home/components/CloudConnection/CloudConnectionFormWrapper.spec.tsx b/redisinsight/ui/src/pages/home/components/cloud-connection/CloudConnectionFormWrapper.spec.tsx similarity index 100% rename from redisinsight/ui/src/pages/home/components/CloudConnection/CloudConnectionFormWrapper.spec.tsx rename to redisinsight/ui/src/pages/home/components/cloud-connection/CloudConnectionFormWrapper.spec.tsx diff --git a/redisinsight/ui/src/pages/home/components/CloudConnection/CloudConnectionFormWrapper.tsx b/redisinsight/ui/src/pages/home/components/cloud-connection/CloudConnectionFormWrapper.tsx similarity index 96% rename from redisinsight/ui/src/pages/home/components/CloudConnection/CloudConnectionFormWrapper.tsx rename to redisinsight/ui/src/pages/home/components/cloud-connection/CloudConnectionFormWrapper.tsx index 9da568212c..71f07cd700 100644 --- a/redisinsight/ui/src/pages/home/components/CloudConnection/CloudConnectionFormWrapper.tsx +++ b/redisinsight/ui/src/pages/home/components/cloud-connection/CloudConnectionFormWrapper.tsx @@ -8,7 +8,7 @@ import { useResizableFormField } from 'uiSrc/services' import { resetErrors } from 'uiSrc/slices/app/notifications' import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' -import CloudConnectionForm from './CloudConnectionForm' +import CloudConnectionForm from './cloud-connection-form' export interface Props { width: number diff --git a/redisinsight/ui/src/pages/home/components/CloudConnection/CloudConnectionForm/CloudConnectionForm.spec.tsx b/redisinsight/ui/src/pages/home/components/cloud-connection/cloud-connection-form/CloudConnectionForm.spec.tsx similarity index 100% rename from redisinsight/ui/src/pages/home/components/CloudConnection/CloudConnectionForm/CloudConnectionForm.spec.tsx rename to redisinsight/ui/src/pages/home/components/cloud-connection/cloud-connection-form/CloudConnectionForm.spec.tsx diff --git a/redisinsight/ui/src/pages/home/components/CloudConnection/CloudConnectionForm/CloudConnectionForm.tsx b/redisinsight/ui/src/pages/home/components/cloud-connection/cloud-connection-form/CloudConnectionForm.tsx similarity index 100% rename from redisinsight/ui/src/pages/home/components/CloudConnection/CloudConnectionForm/CloudConnectionForm.tsx rename to redisinsight/ui/src/pages/home/components/cloud-connection/cloud-connection-form/CloudConnectionForm.tsx diff --git a/redisinsight/ui/src/pages/home/components/CloudConnection/CloudConnectionForm/index.ts b/redisinsight/ui/src/pages/home/components/cloud-connection/cloud-connection-form/index.ts similarity index 100% rename from redisinsight/ui/src/pages/home/components/CloudConnection/CloudConnectionForm/index.ts rename to redisinsight/ui/src/pages/home/components/cloud-connection/cloud-connection-form/index.ts diff --git a/redisinsight/ui/src/pages/home/components/CloudConnection/index.ts b/redisinsight/ui/src/pages/home/components/cloud-connection/index.ts similarity index 100% rename from redisinsight/ui/src/pages/home/components/CloudConnection/index.ts rename to redisinsight/ui/src/pages/home/components/cloud-connection/index.ts diff --git a/redisinsight/ui/src/pages/home/components/CloudConnection/styles.module.scss b/redisinsight/ui/src/pages/home/components/cloud-connection/styles.module.scss similarity index 100% rename from redisinsight/ui/src/pages/home/components/CloudConnection/styles.module.scss rename to redisinsight/ui/src/pages/home/components/cloud-connection/styles.module.scss diff --git a/redisinsight/ui/src/pages/home/components/ClusterConnection/ClusterConnectionFormWrapper.spec.tsx b/redisinsight/ui/src/pages/home/components/cluster-connection/ClusterConnectionFormWrapper.spec.tsx similarity index 91% rename from redisinsight/ui/src/pages/home/components/ClusterConnection/ClusterConnectionFormWrapper.spec.tsx rename to redisinsight/ui/src/pages/home/components/cluster-connection/ClusterConnectionFormWrapper.spec.tsx index a0569dca26..341145e689 100644 --- a/redisinsight/ui/src/pages/home/components/ClusterConnection/ClusterConnectionFormWrapper.spec.tsx +++ b/redisinsight/ui/src/pages/home/components/cluster-connection/ClusterConnectionFormWrapper.spec.tsx @@ -1,14 +1,15 @@ import React from 'react' import { instance, mock } from 'ts-mockito' import { fireEvent, render, screen } from 'uiSrc/utils/test-utils' +import ClusterConnectionForm, { Props as ClusterConnectionFormProps } from + 'uiSrc/pages/home/components/cluster-connection/cluster-connection-form/ClusterConnectionForm' import ClusterConnectionFormWrapper, { Props, } from './ClusterConnectionFormWrapper' -import ClusterConnectionForm, { Props as ClusterConnectionFormProps } from './ClusterConnectionForm/ClusterConnectionForm' const mockedProps = mock() -jest.mock('./ClusterConnectionForm/ClusterConnectionForm', () => ({ +jest.mock('./cluster-connection-form/cluster-connection-form', () => ({ __esModule: true, namedExport: jest.fn(), default: jest.fn(), diff --git a/redisinsight/ui/src/pages/home/components/ClusterConnection/ClusterConnectionFormWrapper.tsx b/redisinsight/ui/src/pages/home/components/cluster-connection/ClusterConnectionFormWrapper.tsx similarity index 95% rename from redisinsight/ui/src/pages/home/components/ClusterConnection/ClusterConnectionFormWrapper.tsx rename to redisinsight/ui/src/pages/home/components/cluster-connection/ClusterConnectionFormWrapper.tsx index 5d72654536..863c6ac500 100644 --- a/redisinsight/ui/src/pages/home/components/ClusterConnection/ClusterConnectionFormWrapper.tsx +++ b/redisinsight/ui/src/pages/home/components/cluster-connection/ClusterConnectionFormWrapper.tsx @@ -13,7 +13,7 @@ import { ICredentialsRedisCluster, InstanceType } from 'uiSrc/slices/interfaces' import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' import { autoFillFormDetails } from 'uiSrc/pages/home/utils' -import ClusterConnectionForm from './ClusterConnectionForm/ClusterConnectionForm' +import ClusterConnectionForm from 'uiSrc/pages/home/components/cluster-connection/cluster-connection-form/ClusterConnectionForm' export interface Props { width: number; diff --git a/redisinsight/ui/src/pages/home/components/ClusterConnection/ClusterConnectionForm/ClusterConnectionForm.spec.tsx b/redisinsight/ui/src/pages/home/components/cluster-connection/cluster-connection-form/ClusterConnectionForm.spec.tsx similarity index 100% rename from redisinsight/ui/src/pages/home/components/ClusterConnection/ClusterConnectionForm/ClusterConnectionForm.spec.tsx rename to redisinsight/ui/src/pages/home/components/cluster-connection/cluster-connection-form/ClusterConnectionForm.spec.tsx diff --git a/redisinsight/ui/src/pages/home/components/ClusterConnection/ClusterConnectionForm/ClusterConnectionForm.tsx b/redisinsight/ui/src/pages/home/components/cluster-connection/cluster-connection-form/ClusterConnectionForm.tsx similarity index 100% rename from redisinsight/ui/src/pages/home/components/ClusterConnection/ClusterConnectionForm/ClusterConnectionForm.tsx rename to redisinsight/ui/src/pages/home/components/cluster-connection/cluster-connection-form/ClusterConnectionForm.tsx diff --git a/redisinsight/ui/src/pages/home/components/ClusterConnection/ClusterConnectionForm/index.ts b/redisinsight/ui/src/pages/home/components/cluster-connection/cluster-connection-form/index.ts similarity index 100% rename from redisinsight/ui/src/pages/home/components/ClusterConnection/ClusterConnectionForm/index.ts rename to redisinsight/ui/src/pages/home/components/cluster-connection/cluster-connection-form/index.ts diff --git a/redisinsight/ui/src/pages/home/components/ClusterConnection/index.ts b/redisinsight/ui/src/pages/home/components/cluster-connection/index.ts similarity index 100% rename from redisinsight/ui/src/pages/home/components/ClusterConnection/index.ts rename to redisinsight/ui/src/pages/home/components/cluster-connection/index.ts diff --git a/redisinsight/ui/src/pages/home/components/ClusterConnection/styles.module.scss b/redisinsight/ui/src/pages/home/components/cluster-connection/styles.module.scss similarity index 100% rename from redisinsight/ui/src/pages/home/components/ClusterConnection/styles.module.scss rename to redisinsight/ui/src/pages/home/components/cluster-connection/styles.module.scss diff --git a/redisinsight/ui/src/pages/home/components/ClusterConnection/types.ts b/redisinsight/ui/src/pages/home/components/cluster-connection/types.ts similarity index 100% rename from redisinsight/ui/src/pages/home/components/ClusterConnection/types.ts rename to redisinsight/ui/src/pages/home/components/cluster-connection/types.ts diff --git a/redisinsight/ui/src/pages/home/components/DatabaseAlias/DatabaseAlias.spec.tsx b/redisinsight/ui/src/pages/home/components/database-alias/DatabaseAlias.spec.tsx similarity index 100% rename from redisinsight/ui/src/pages/home/components/DatabaseAlias/DatabaseAlias.spec.tsx rename to redisinsight/ui/src/pages/home/components/database-alias/DatabaseAlias.spec.tsx diff --git a/redisinsight/ui/src/pages/home/components/DatabaseAlias/DatabaseAlias.tsx b/redisinsight/ui/src/pages/home/components/database-alias/DatabaseAlias.tsx similarity index 74% rename from redisinsight/ui/src/pages/home/components/DatabaseAlias/DatabaseAlias.tsx rename to redisinsight/ui/src/pages/home/components/database-alias/DatabaseAlias.tsx index 24faa0fe03..52a726911f 100644 --- a/redisinsight/ui/src/pages/home/components/DatabaseAlias/DatabaseAlias.tsx +++ b/redisinsight/ui/src/pages/home/components/database-alias/DatabaseAlias.tsx @@ -11,12 +11,15 @@ import { EuiToolTip, } from '@elastic/eui' import cx from 'classnames' -import { useSelector } from 'react-redux' +import { useDispatch, useSelector } from 'react-redux' import { toNumber } from 'lodash' +import { useHistory } from 'react-router' + +import { AdditionalRedisModule } from 'apiSrc/modules/database/models/additional.redis.module' import { BuildType } from 'uiSrc/constants/env' import { appInfoSelector } from 'uiSrc/slices/app/info' import { Nullable, getDbIndex } from 'uiSrc/utils' -import { Theme } from 'uiSrc/constants' +import { PageNames, Pages, Theme } from 'uiSrc/constants' import { ThemeContext } from 'uiSrc/contexts/themeContext' import InlineItemEditor from 'uiSrc/components/inline-item-editor/InlineItemEditor' import RediStackDarkMin from 'uiSrc/assets/img/modules/redistack/RediStackDark-min.svg' @@ -24,29 +27,52 @@ import RediStackLightMin from 'uiSrc/assets/img/modules/redistack/RediStackLight import RediStackLightLogo from 'uiSrc/assets/img/modules/redistack/RedisStackLogoLight.svg' import RediStackDarkLogo from 'uiSrc/assets/img/modules/redistack/RedisStackLogoDark.svg' +import { getRedisModulesSummary, sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' +import { + changeInstanceAliasAction, + checkConnectToInstanceAction, + setConnectedInstanceId +} from 'uiSrc/slices/instances/instances' +import { resetKeys } from 'uiSrc/slices/browser/keys' +import { appContextSelector, setAppContextInitialState } from 'uiSrc/slices/app/context' import styles from './styles.module.scss' export interface Props { alias: string database?: Nullable - onOpen: () => void - onClone: () => void - onCloneBack: () => void isLoading: boolean - onApplyChanges: (value: string, onSuccess?: () => void, onFail?: () => void) => void + onAliasEdited?: (value: string) => void isRediStack?: boolean isCloneMode: boolean + id?: string + provider?: string + setIsCloneMode: (value: boolean) => void + modules: AdditionalRedisModule[] } const DatabaseAlias = (props: Props) => { - const { alias, database, onOpen, onClone, onCloneBack, onApplyChanges, isLoading, isRediStack, isCloneMode } = props + const { + alias, + database, + id, + provider, + onAliasEdited, + isLoading, + isRediStack, + isCloneMode, + setIsCloneMode, + modules, + } = props const { server } = useSelector(appInfoSelector) + const { contextInstanceId, lastPage } = useSelector(appContextSelector) const [isEditing, setIsEditing] = useState(false) const [value, setValue] = useState(alias) const { theme } = useContext(ThemeContext) + const history = useHistory() + const dispatch = useDispatch() useEffect(() => { setValue(alias) @@ -60,21 +86,68 @@ const DatabaseAlias = (props: Props) => { isEditing && setValue(value) } + const connectToInstance = () => { + if (contextInstanceId && contextInstanceId !== id) { + dispatch(resetKeys()) + dispatch(setAppContextInitialState()) + } + dispatch(setConnectedInstanceId(id ?? '')) + + if (lastPage === PageNames.workbench && contextInstanceId === id) { + history.push(Pages.workbench(id)) + return + } + history.push(Pages.browser(id ?? '')) + } + const handleOpen = (event: any) => { event.stopPropagation() event.preventDefault() - onOpen() + const modulesSummary = getRedisModulesSummary(modules) + sendEventTelemetry({ + event: TelemetryEvent.CONFIG_DATABASES_OPEN_DATABASE_BUTTON_CLICKED, + eventData: { + databaseId: id, + provider, + ...modulesSummary, + } + }) + dispatch(checkConnectToInstanceAction(id, connectToInstance)) + // onOpen() } const handleClone = (e: React.MouseEvent) => { e.stopPropagation() e.preventDefault() - onClone() + setIsCloneMode(true) + sendEventTelemetry({ + event: TelemetryEvent.CONFIG_DATABASES_DATABASE_CLONE_REQUESTED, + eventData: { + databaseId: id + } + }) } const handleApplyChanges = () => { setIsEditing(false) - onApplyChanges(value, () => {}, () => setValue(alias)) + dispatch(changeInstanceAliasAction( + id, + value, + () => { + onAliasEdited?.(value) + }, + () => setValue(alias) + )) + } + + const handleCloneBack = () => { + setIsCloneMode(false) + sendEventTelemetry({ + event: TelemetryEvent.CONFIG_DATABASES_DATABASE_CLONE_CANCELLED, + eventData: { + databaseId: id + } + }) } const handleDeclineChanges = (event?: React.MouseEvent) => { @@ -89,7 +162,7 @@ const DatabaseAlias = (props: Props) => { {isCloneMode && ( { } }, [action, dbConnection]) + useEffect(() => { + if (editMode) { + setConnectionType(AddDbType.manual) + } + }, [editMode]) + useEffect(() => // ComponentWillUnmount () => { diff --git a/redisinsight/ui/src/pages/home/components/DatabasePanel/index.ts b/redisinsight/ui/src/pages/home/components/database-panel/index.ts similarity index 100% rename from redisinsight/ui/src/pages/home/components/DatabasePanel/index.ts rename to redisinsight/ui/src/pages/home/components/database-panel/index.ts diff --git a/redisinsight/ui/src/pages/home/components/DatabasePanel/InstanceConnections/InstanceConnections.spec.tsx b/redisinsight/ui/src/pages/home/components/database-panel/instance-connections/InstanceConnections.spec.tsx similarity index 100% rename from redisinsight/ui/src/pages/home/components/DatabasePanel/InstanceConnections/InstanceConnections.spec.tsx rename to redisinsight/ui/src/pages/home/components/database-panel/instance-connections/InstanceConnections.spec.tsx diff --git a/redisinsight/ui/src/pages/home/components/DatabasePanel/InstanceConnections/InstanceConnections.tsx b/redisinsight/ui/src/pages/home/components/database-panel/instance-connections/InstanceConnections.tsx similarity index 100% rename from redisinsight/ui/src/pages/home/components/DatabasePanel/InstanceConnections/InstanceConnections.tsx rename to redisinsight/ui/src/pages/home/components/database-panel/instance-connections/InstanceConnections.tsx diff --git a/redisinsight/ui/src/pages/home/components/database-panel/instance-connections/index.ts b/redisinsight/ui/src/pages/home/components/database-panel/instance-connections/index.ts new file mode 100644 index 0000000000..162026205d --- /dev/null +++ b/redisinsight/ui/src/pages/home/components/database-panel/instance-connections/index.ts @@ -0,0 +1,3 @@ +import InstanceConnections from './InstanceConnections' + +export default InstanceConnections diff --git a/redisinsight/ui/src/pages/home/components/DatabasePanel/styles.module.scss b/redisinsight/ui/src/pages/home/components/database-panel/styles.module.scss similarity index 100% rename from redisinsight/ui/src/pages/home/components/DatabasePanel/styles.module.scss rename to redisinsight/ui/src/pages/home/components/database-panel/styles.module.scss diff --git a/redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesListWrapper.spec.tsx b/redisinsight/ui/src/pages/home/components/databases-list-component/DatabasesListWrapper.spec.tsx similarity index 97% rename from redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesListWrapper.spec.tsx rename to redisinsight/ui/src/pages/home/components/databases-list-component/DatabasesListWrapper.spec.tsx index ea442a4d66..3c2a57ab72 100644 --- a/redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesListWrapper.spec.tsx +++ b/redisinsight/ui/src/pages/home/components/databases-list-component/DatabasesListWrapper.spec.tsx @@ -11,11 +11,11 @@ import { RootState, store } from 'uiSrc/slices/store' import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' import { errorHandlers } from 'uiSrc/mocks/res/responseComposition' import DatabasesListWrapper, { Props } from './DatabasesListWrapper' -import DatabasesList, { Props as DatabasesListProps } from './DatabasesList' +import DatabasesList, { Props as DatabasesListProps } from './databases-list' const mockedProps = mock() -jest.mock('./DatabasesList/DatabasesList', () => ({ +jest.mock('./databases-list/databases-list', () => ({ __esModule: true, namedExport: jest.fn(), default: jest.fn(), diff --git a/redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesListWrapper.tsx b/redisinsight/ui/src/pages/home/components/databases-list-component/DatabasesListWrapper.tsx similarity index 99% rename from redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesListWrapper.tsx rename to redisinsight/ui/src/pages/home/components/databases-list-component/DatabasesListWrapper.tsx index 54c6ca183b..49c8c33263 100644 --- a/redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesListWrapper.tsx +++ b/redisinsight/ui/src/pages/home/components/databases-list-component/DatabasesListWrapper.tsx @@ -40,7 +40,7 @@ import RediStackLightLogo from 'uiSrc/assets/img/modules/redistack/RedisStackLog import RediStackDarkLogo from 'uiSrc/assets/img/modules/redistack/RedisStackLogoDark.svg' import { ReactComponent as CloudLinkIcon } from 'uiSrc/assets/img/oauth/cloud_link.svg' import { EXTERNAL_LINKS } from 'uiSrc/constants/links' -import DatabasesList from './DatabasesList' +import DatabasesList from './databases-list' import styles from './styles.module.scss' diff --git a/redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesList/DatabasesList.spec.tsx b/redisinsight/ui/src/pages/home/components/databases-list-component/databases-list/DatabasesList.spec.tsx similarity index 100% rename from redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesList/DatabasesList.spec.tsx rename to redisinsight/ui/src/pages/home/components/databases-list-component/databases-list/DatabasesList.spec.tsx diff --git a/redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesList/DatabasesList.tsx b/redisinsight/ui/src/pages/home/components/databases-list-component/databases-list/DatabasesList.tsx similarity index 100% rename from redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesList/DatabasesList.tsx rename to redisinsight/ui/src/pages/home/components/databases-list-component/databases-list/DatabasesList.tsx diff --git a/redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesList/components/action-bar/ActionBar.spec.tsx b/redisinsight/ui/src/pages/home/components/databases-list-component/databases-list/components/action-bar/ActionBar.spec.tsx similarity index 100% rename from redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesList/components/action-bar/ActionBar.spec.tsx rename to redisinsight/ui/src/pages/home/components/databases-list-component/databases-list/components/action-bar/ActionBar.spec.tsx diff --git a/redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesList/components/action-bar/ActionBar.tsx b/redisinsight/ui/src/pages/home/components/databases-list-component/databases-list/components/action-bar/ActionBar.tsx similarity index 100% rename from redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesList/components/action-bar/ActionBar.tsx rename to redisinsight/ui/src/pages/home/components/databases-list-component/databases-list/components/action-bar/ActionBar.tsx diff --git a/redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesList/components/action-bar/styles.module.scss b/redisinsight/ui/src/pages/home/components/databases-list-component/databases-list/components/action-bar/styles.module.scss similarity index 100% rename from redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesList/components/action-bar/styles.module.scss rename to redisinsight/ui/src/pages/home/components/databases-list-component/databases-list/components/action-bar/styles.module.scss diff --git a/redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesList/components/delete-action/DeleteAction.spec.tsx b/redisinsight/ui/src/pages/home/components/databases-list-component/databases-list/components/delete-action/DeleteAction.spec.tsx similarity index 100% rename from redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesList/components/delete-action/DeleteAction.spec.tsx rename to redisinsight/ui/src/pages/home/components/databases-list-component/databases-list/components/delete-action/DeleteAction.spec.tsx diff --git a/redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesList/components/delete-action/DeleteAction.tsx b/redisinsight/ui/src/pages/home/components/databases-list-component/databases-list/components/delete-action/DeleteAction.tsx similarity index 100% rename from redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesList/components/delete-action/DeleteAction.tsx rename to redisinsight/ui/src/pages/home/components/databases-list-component/databases-list/components/delete-action/DeleteAction.tsx diff --git a/redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesList/components/export-action/ExportAction.spec.tsx b/redisinsight/ui/src/pages/home/components/databases-list-component/databases-list/components/export-action/ExportAction.spec.tsx similarity index 100% rename from redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesList/components/export-action/ExportAction.spec.tsx rename to redisinsight/ui/src/pages/home/components/databases-list-component/databases-list/components/export-action/ExportAction.spec.tsx diff --git a/redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesList/components/export-action/ExportAction.tsx b/redisinsight/ui/src/pages/home/components/databases-list-component/databases-list/components/export-action/ExportAction.tsx similarity index 100% rename from redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesList/components/export-action/ExportAction.tsx rename to redisinsight/ui/src/pages/home/components/databases-list-component/databases-list/components/export-action/ExportAction.tsx diff --git a/redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesList/components/index.ts b/redisinsight/ui/src/pages/home/components/databases-list-component/databases-list/components/index.ts similarity index 100% rename from redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesList/components/index.ts rename to redisinsight/ui/src/pages/home/components/databases-list-component/databases-list/components/index.ts diff --git a/redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesList/components/styles.module.scss b/redisinsight/ui/src/pages/home/components/databases-list-component/databases-list/components/styles.module.scss similarity index 100% rename from redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesList/components/styles.module.scss rename to redisinsight/ui/src/pages/home/components/databases-list-component/databases-list/components/styles.module.scss diff --git a/redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesList/index.ts b/redisinsight/ui/src/pages/home/components/databases-list-component/databases-list/index.ts similarity index 100% rename from redisinsight/ui/src/pages/home/components/DatabasesListComponent/DatabasesList/index.ts rename to redisinsight/ui/src/pages/home/components/databases-list-component/databases-list/index.ts diff --git a/redisinsight/ui/src/pages/home/components/DatabasesListComponent/index.ts b/redisinsight/ui/src/pages/home/components/databases-list-component/index.ts similarity index 100% rename from redisinsight/ui/src/pages/home/components/DatabasesListComponent/index.ts rename to redisinsight/ui/src/pages/home/components/databases-list-component/index.ts diff --git a/redisinsight/ui/src/pages/home/components/DatabasesListComponent/styles.module.scss b/redisinsight/ui/src/pages/home/components/databases-list-component/styles.module.scss similarity index 100% rename from redisinsight/ui/src/pages/home/components/DatabasesListComponent/styles.module.scss rename to redisinsight/ui/src/pages/home/components/databases-list-component/styles.module.scss diff --git a/redisinsight/ui/src/pages/home/components/HelpLinksMenu/HelpLinksMenu.tsx b/redisinsight/ui/src/pages/home/components/help-links-menu/HelpLinksMenu.tsx similarity index 100% rename from redisinsight/ui/src/pages/home/components/HelpLinksMenu/HelpLinksMenu.tsx rename to redisinsight/ui/src/pages/home/components/help-links-menu/HelpLinksMenu.tsx diff --git a/redisinsight/ui/src/pages/home/components/HelpLinksMenu/HelpLinskMenu.spec.tsx b/redisinsight/ui/src/pages/home/components/help-links-menu/HelpLinskMenu.spec.tsx similarity index 100% rename from redisinsight/ui/src/pages/home/components/HelpLinksMenu/HelpLinskMenu.spec.tsx rename to redisinsight/ui/src/pages/home/components/help-links-menu/HelpLinskMenu.spec.tsx diff --git a/redisinsight/ui/src/pages/home/components/HelpLinksMenu/index.ts b/redisinsight/ui/src/pages/home/components/help-links-menu/index.ts similarity index 100% rename from redisinsight/ui/src/pages/home/components/HelpLinksMenu/index.ts rename to redisinsight/ui/src/pages/home/components/help-links-menu/index.ts diff --git a/redisinsight/ui/src/pages/home/components/HelpLinksMenu/styles.module.scss b/redisinsight/ui/src/pages/home/components/help-links-menu/styles.module.scss similarity index 100% rename from redisinsight/ui/src/pages/home/components/HelpLinksMenu/styles.module.scss rename to redisinsight/ui/src/pages/home/components/help-links-menu/styles.module.scss diff --git a/redisinsight/ui/src/pages/home/components/HomeHeader/HomeHeader.spec.tsx b/redisinsight/ui/src/pages/home/components/home-header/HomeHeader.spec.tsx similarity index 100% rename from redisinsight/ui/src/pages/home/components/HomeHeader/HomeHeader.spec.tsx rename to redisinsight/ui/src/pages/home/components/home-header/HomeHeader.spec.tsx diff --git a/redisinsight/ui/src/pages/home/components/HomeHeader/HomeHeader.tsx b/redisinsight/ui/src/pages/home/components/home-header/HomeHeader.tsx similarity index 95% rename from redisinsight/ui/src/pages/home/components/HomeHeader/HomeHeader.tsx rename to redisinsight/ui/src/pages/home/components/home-header/HomeHeader.tsx index 8c3f5ea526..cdec9f2e31 100644 --- a/redisinsight/ui/src/pages/home/components/HomeHeader/HomeHeader.tsx +++ b/redisinsight/ui/src/pages/home/components/home-header/HomeHeader.tsx @@ -11,9 +11,9 @@ import { import { isEmpty } from 'lodash' import { useSelector } from 'react-redux' import cx from 'classnames' -import { FeatureFlagComponent, ImportDatabasesDialog, OAuthSsoHandlerDialog } from 'uiSrc/components' +import { ImportDatabasesDialog, OAuthSsoHandlerDialog } from 'uiSrc/components' import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' -import HelpLinksMenu from 'uiSrc/pages/home/components/HelpLinksMenu' +import HelpLinksMenu from 'uiSrc/pages/home/components/help-links-menu' import PromoLink from 'uiSrc/components/promo-link/PromoLink' import { ThemeContext } from 'uiSrc/contexts/themeContext' import { contentSelector } from 'uiSrc/slices/content/create-redis-buttons' @@ -22,11 +22,9 @@ import { getPathToResource } from 'uiSrc/services/resourcesService' import { ContentCreateRedis } from 'uiSrc/slices/interfaces/content' import { instancesSelector } from 'uiSrc/slices/instances/instances' import { OAuthSocialSource } from 'uiSrc/slices/interfaces' -import { FeatureFlags } from 'uiSrc/constants' -import { ReactComponent as ConfettiIcon } from 'uiSrc/assets/img/oauth/confetti.svg' import { getContentByFeature } from 'uiSrc/utils/content' import { appFeatureFlagsFeaturesSelector } from 'uiSrc/slices/app/features' -import SearchDatabasesList from '../SearchDatabasesList' +import SearchDatabasesList from '../search-databases-list' import styles from './styles.module.scss' diff --git a/redisinsight/ui/src/pages/home/components/HomeHeader/index.ts b/redisinsight/ui/src/pages/home/components/home-header/index.ts similarity index 100% rename from redisinsight/ui/src/pages/home/components/HomeHeader/index.ts rename to redisinsight/ui/src/pages/home/components/home-header/index.ts diff --git a/redisinsight/ui/src/pages/home/components/HomeHeader/styles.module.scss b/redisinsight/ui/src/pages/home/components/home-header/styles.module.scss similarity index 100% rename from redisinsight/ui/src/pages/home/components/HomeHeader/styles.module.scss rename to redisinsight/ui/src/pages/home/components/home-header/styles.module.scss diff --git a/redisinsight/ui/src/pages/home/components/manual-connection/ManualConnectionWrapper.spec.tsx b/redisinsight/ui/src/pages/home/components/manual-connection/ManualConnectionWrapper.spec.tsx new file mode 100644 index 0000000000..afd4dbcee5 --- /dev/null +++ b/redisinsight/ui/src/pages/home/components/manual-connection/ManualConnectionWrapper.spec.tsx @@ -0,0 +1,1247 @@ +import React from 'react' +import { instance, mock } from 'ts-mockito' +import { act, fireEvent, render, screen } from 'uiSrc/utils/test-utils' +import { ConnectionType } from 'uiSrc/slices/interfaces' +import { BuildType } from 'uiSrc/constants/env' +import { appRedirectionSelector } from 'uiSrc/slices/app/url-handling' +import { UrlHandlingActions } from 'uiSrc/slices/interfaces/urlHandling' +import { ADD_NEW_CA_CERT, SshPassType } from 'uiSrc/pages/home/constants' +import { DbConnectionInfo } from 'uiSrc/pages/home/interfaces' + +import ManualConnectionWrapper, { Props } from './ManualConnectionWrapper' + +const BTN_SUBMIT = 'btn-submit' +const NEW_CA_CERT = 'new-ca-cert' +const QA_CA_CERT = 'qa-ca-cert' +const RADIO_BTN_PRIVATE_KEY = '[data-test-subj="radio-btn-privateKey"] label' +const BTN_TEST_CONNECTION = 'btn-test-connection' + +const mockedProps = mock() +const mockedDbConnectionInfo = mock() + +const formFields = { + ...instance(mockedDbConnectionInfo), + host: 'localhost', + port: '6379', + name: 'lala', +} + +// jest.mock('uiSrc/slices/instances/instances', () => ({ +// checkConnectToInstanceAction: () => jest.fn, +// resetInstanceUpdateAction: () => jest.fn, +// changeInstanceAliasAction: () => jest.fn, +// setConnectedInstanceId: jest.fn, +// })) +// +// jest.mock('uiSrc/slices/app/url-handling', () => ({ +// ...jest.requireActual('uiSrc/slices/app/url-handling'), +// appRedirectionSelector: jest.fn().mockReturnValue(() => ({ action: null })), +// })) + +describe('InstanceForm', () => { + it('should render', () => { + expect( + render( + + ) + ).toBeTruthy() + }) + + // it('should render with ConnectionType.Sentinel', () => { + // expect( + // render( + // + // ) + // ).toBeTruthy() + // }) + // + // it('should render with ConnectionType.Cluster', () => { + // expect( + // render( + // + // ) + // ).toBeTruthy() + // }) + // + // it('should render tooltip with nodes', () => { + // expect( + // render( + // + // ) + // ).toBeTruthy() + // }) + // + it('should render DatabaseForm', () => { + expect( + render( + + ) + ).toBeTruthy() + }) + // + // it('should change sentinelMasterUsername input properly', async () => { + // const handleSubmit = jest.fn() + // const handleTestConnection = jest.fn() + // + // render( + //
+ // + //
+ // ) + // + // await act(() => { + // fireEvent.change(screen.getByTestId('sentinel-mater-username'), { + // target: { value: 'user' }, + // }) + // }) + // + // const submitBtn = screen.getByTestId(BTN_SUBMIT) + // const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) + // + // await act(() => { + // fireEvent.click(testConnectionBtn) + // }) + // expect(handleTestConnection).toBeCalledWith( + // expect.objectContaining({ + // sentinelMasterUsername: 'user', + // }) + // ) + // + // await act(() => { + // fireEvent.click(submitBtn) + // }) + // expect(handleSubmit).toBeCalledWith( + // expect.objectContaining({ + // sentinelMasterUsername: 'user', + // }) + // ) + // }) + // + // it('should change port input properly', async () => { + // const handleSubmit = jest.fn() + // render( + //
+ // + //
+ // ) + // + // await act(() => { + // fireEvent.change(screen.getByTestId('port'), { + // target: { value: '123' }, + // }) + // }) + // + // const submitBtn = screen.getByTestId(BTN_SUBMIT) + // await act(() => { + // fireEvent.click(submitBtn) + // }) + // expect(handleSubmit).toBeCalledWith( + // expect.objectContaining({ + // port: '123', + // }) + // ) + // }) + // + // it('should change tls checkbox', async () => { + // const handleSubmit = jest.fn() + // const handleTestConnection = jest.fn() + // + // render( + //
+ // + //
+ // ) + // await act(() => { + // fireEvent.click(screen.getByTestId('tls')) + // }) + // + // const submitBtn = screen.getByTestId(BTN_SUBMIT) + // const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) + // + // await act(() => { + // fireEvent.click(testConnectionBtn) + // }) + // expect(handleTestConnection).toBeCalledWith( + // expect.objectContaining({ + // tls: ['on'], + // }) + // ) + // + // await act(() => { + // fireEvent.click(submitBtn) + // }) + // + // expect(handleSubmit).toBeCalledWith( + // expect.objectContaining({ + // tls: ['on'], + // }) + // ) + // }) + // + // it('should change Database Index checkbox', async () => { + // const handleSubmit = jest.fn() + // const handleTestConnection = jest.fn() + // render( + //
+ // + //
+ // ) + // await act(() => { + // fireEvent.click(screen.getByTestId('showDb')) + // }) + // + // const submitBtn = screen.getByTestId(BTN_SUBMIT) + // const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) + // await act(() => { + // fireEvent.click(testConnectionBtn) + // }) + // expect(handleTestConnection).toBeCalledWith( + // expect.objectContaining({ + // showDb: ['on'], + // }) + // ) + // await act(() => { + // fireEvent.click(submitBtn) + // }) + // + // expect(handleSubmit).toBeCalledWith( + // expect.objectContaining({ + // showDb: ['on'], + // }) + // ) + // }) + // + // it('should change db checkbox and value', async () => { + // const handleSubmit = jest.fn() + // const handleTestConnection = jest.fn() + // render( + //
+ // + //
+ // ) + // await act(() => { + // fireEvent.click(screen.getByTestId('showDb')) + // }) + // + // await act(() => { + // fireEvent.change(screen.getByTestId('db'), { + // target: { value: '12' }, + // }) + // }) + // + // const submitBtn = screen.getByTestId(BTN_SUBMIT) + // const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) + // + // await act(() => { + // fireEvent.click(testConnectionBtn) + // }) + // expect(handleTestConnection).toBeCalledWith( + // expect.objectContaining({ + // showDb: ['on'], + // db: '12' + // }) + // ) + // await act(() => { + // fireEvent.click(submitBtn) + // }) + // + // expect(handleSubmit).toBeCalledWith( + // expect.objectContaining({ + // showDb: ['on'], + // db: '12' + // }) + // ) + // }) + // + // it('should change "Use SNI" with prepopulated with host', async () => { + // const handleSubmit = jest.fn() + // const handleTestConnection = jest.fn() + // render( + //
+ // + //
+ // ) + // await act(() => { + // fireEvent.click(screen.getByTestId('sni')) + // }) + // + // const submitBtn = screen.getByTestId(BTN_SUBMIT) + // const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) + // await act(() => { + // fireEvent.click(testConnectionBtn) + // }) + // expect(handleTestConnection).toBeCalledWith( + // expect.objectContaining({ + // sni: ['on'], + // servername: formFields.host + // }) + // ) + // await act(() => { + // fireEvent.click(submitBtn) + // }) + // + // expect(handleSubmit).toBeCalledWith( + // expect.objectContaining({ + // sni: ['on'], + // servername: formFields.host + // }) + // ) + // }) + // + // it('should change "Use SNI"', async () => { + // const handleSubmit = jest.fn() + // const handleTestConnection = jest.fn() + // render( + //
+ // + //
+ // ) + // await act(() => { + // fireEvent.click(screen.getByTestId('sni')) + // }) + // + // await act(() => { + // fireEvent.change(screen.getByTestId('sni-servername'), { + // target: { value: '12' }, + // }) + // }) + // + // const submitBtn = screen.getByTestId(BTN_SUBMIT) + // const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) + // await act(() => { + // fireEvent.click(testConnectionBtn) + // }) + // expect(handleTestConnection).toBeCalledWith( + // expect.objectContaining({ + // sni: ['on'], + // servername: '12' + // }) + // ) + // await act(() => { + // fireEvent.click(submitBtn) + // }) + // + // expect(handleSubmit).toBeCalledWith( + // expect.objectContaining({ + // sni: ['on'], + // servername: '12' + // }) + // ) + // }) + // + // it('should change "Verify TLS Certificate"', async () => { + // const handleSubmit = jest.fn() + // const handleTestConnection = jest.fn() + // render( + //
+ // + //
+ // ) + // await act(() => { + // fireEvent.click(screen.getByTestId('verify-tls-cert')) + // }) + // + // const submitBtn = screen.getByTestId(BTN_SUBMIT) + // const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) + // await act(() => { + // fireEvent.click(testConnectionBtn) + // }) + // expect(handleTestConnection).toBeCalledWith( + // expect.objectContaining({ + // verifyServerTlsCert: ['on'], + // }) + // ) + // await act(() => { + // fireEvent.click(submitBtn) + // }) + // + // expect(handleSubmit).toBeCalledWith( + // expect.objectContaining({ + // verifyServerTlsCert: ['on'], + // }) + // ) + // }) + // + // it('should select value from "CA Certificate"', async () => { + // const handleSubmit = jest.fn() + // const handleTestConnection = jest.fn() + // const { queryByText } = render( + //
+ // + //
+ // ) + // await act(() => { + // fireEvent.click(screen.getByTestId('select-ca-cert')) + // }) + // await act(() => { + // fireEvent.click(queryByText('Add new CA certificate') || document) + // }) + // + // expect(screen.getByTestId(NEW_CA_CERT)).toBeInTheDocument() + // await act(() => { + // fireEvent.change(screen.getByTestId(NEW_CA_CERT), { + // target: { value: '123' }, + // }) + // }) + // + // expect(screen.getByTestId(QA_CA_CERT)).toBeInTheDocument() + // await act(() => { + // fireEvent.change(screen.getByTestId(QA_CA_CERT), { + // target: { value: '321' }, + // }) + // }) + // + // const submitBtn = screen.getByTestId(BTN_SUBMIT) + // const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) + // await act(() => { + // fireEvent.click(testConnectionBtn) + // }) + // expect(handleTestConnection).toBeCalledWith( + // expect.objectContaining({ + // selectedCaCertName: ADD_NEW_CA_CERT, + // newCaCertName: '321', + // newCaCert: '123', + // }) + // ) + // await act(() => { + // fireEvent.click(submitBtn) + // }) + // + // expect(handleSubmit).toBeCalledWith( + // expect.objectContaining({ + // selectedCaCertName: ADD_NEW_CA_CERT, + // newCaCertName: '321', + // newCaCert: '123', + // }) + // ) + // }) + // + // it('should render fields for add new CA and change them properly', async () => { + // const handleSubmit = jest.fn() + // const handleTestConnection = jest.fn() + // render( + //
+ // + //
+ // ) + // + // expect(screen.getByTestId(QA_CA_CERT)).toBeInTheDocument() + // await act(() => { + // fireEvent.change(screen.getByTestId(QA_CA_CERT), { + // target: { value: '321' }, + // }) + // }) + // + // expect(screen.getByTestId(NEW_CA_CERT)).toBeInTheDocument() + // await act(() => { + // fireEvent.change(screen.getByTestId(NEW_CA_CERT), { + // target: { value: '123' }, + // }) + // }) + // + // const submitBtn = screen.getByTestId(BTN_SUBMIT) + // const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) + // await act(() => { + // fireEvent.click(testConnectionBtn) + // }) + // expect(handleTestConnection).toBeCalledWith( + // expect.objectContaining({ + // newCaCert: '123', + // newCaCertName: '321', + // }) + // ) + // await act(() => { + // fireEvent.click(submitBtn) + // }) + // + // expect(handleSubmit).toBeCalledWith( + // expect.objectContaining({ + // newCaCert: '123', + // newCaCertName: '321', + // }) + // ) + // }) + // + // it('should change "Requires TLS Client Authentication"', async () => { + // const handleSubmit = jest.fn() + // const handleTestConnection = jest.fn() + // render( + //
+ // + //
+ // ) + // await act(() => { + // fireEvent.click(screen.getByTestId('tls-required-checkbox')) + // }) + // + // const submitBtn = screen.getByTestId(BTN_SUBMIT) + // const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) + // await act(() => { + // fireEvent.click(testConnectionBtn) + // }) + // expect(handleTestConnection).toBeCalledWith( + // expect.objectContaining({ + // tlsClientAuthRequired: ['on'], + // }) + // ) + // await act(() => { + // fireEvent.click(submitBtn) + // }) + // + // expect(handleSubmit).toBeCalledWith( + // expect.objectContaining({ + // tlsClientAuthRequired: ['on'], + // }) + // ) + // }) + // + // it('should render fields for add new CA with required tls auth and change them properly', async () => { + // const handleSubmit = jest.fn() + // const { container } = render( + //
+ // + //
+ // ) + // + // expect(screen.getByTestId('select-cert')).toBeInTheDocument() + // + // await act(() => { + // fireEvent.click(screen.getByTestId('select-cert')) + // }) + // + // await act(() => { + // fireEvent.click( + // container.querySelectorAll('.euiContextMenuItem__text')[0] || document + // ) + // }) + // + // expect(screen.getByTestId('new-tsl-cert-pair-name')).toBeInTheDocument() + // await act(() => { + // fireEvent.change(screen.getByTestId('new-tsl-cert-pair-name'), { + // target: { value: '123' }, + // }) + // }) + // + // expect(screen.getByTestId('new-tls-client-cert')).toBeInTheDocument() + // await act(() => { + // fireEvent.change(screen.getByTestId('new-tls-client-cert'), { + // target: { value: '321' }, + // }) + // }) + // + // expect(screen.getByTestId('new-tls-client-cert-key')).toBeInTheDocument() + // await act(() => { + // fireEvent.change(screen.getByTestId('new-tls-client-cert-key'), { + // target: { value: '231' }, + // }) + // }) + // + // const submitBtn = screen.getByTestId(BTN_SUBMIT) + // + // await act(() => { + // fireEvent.click(submitBtn) + // }) + // + // expect(handleSubmit).toBeCalledWith( + // expect.objectContaining({ + // newTlsClientCert: '321', + // newTlsCertPairName: '123', + // newTlsClientKey: '231', + // }) + // ) + // }) + // + // it('should render clone mode btn', () => { + // render( + // + // ) + // expect(screen.getByTestId('clone-db-btn')).toBeTruthy() + // }) + // + // describe('should render proper fields with Clone mode', () => { + // it('should render proper fields for standalone db', () => { + // render( + // + // ) + // const fieldsTestIds = ['host', 'port', 'username', 'password', 'showDb', 'tls'] + // fieldsTestIds.forEach((id) => { + // expect(screen.getByTestId(id)).toBeTruthy() + // }) + // }) + // + // it('should render proper fields for sentinel db', () => { + // render( + // + // ) + // const fieldsTestIds = [ + // 'name', + // 'primary-group', + // 'sentinel-mater-username', + // 'sentinel-master-password', + // 'host', + // 'port', + // 'username', + // 'password', + // 'showDb', + // 'tls' + // ] + // fieldsTestIds.forEach((id) => { + // expect(screen.getByTestId(id)).toBeTruthy() + // }) + // }) + // + // it('should render selected logical database with proper db index', () => { + // render( + // + // ) + // // expect(screen.getByTestId('showDb')).toBeChecked() + // expect(screen.getByTestId('db')).toHaveValue('5') + // }) + // + // it('should render proper database alias', () => { + // render( + // + // ) + // expect(screen.getByTestId('db-alias')).toHaveTextContent('Clone ') + // }) + // + // it('should render proper default values for standalone', () => { + // render( + // + // ) + // expect(screen.getByTestId('host')).toHaveValue('127.0.0.1') + // expect(screen.getByTestId('port')).toHaveValue('6379') + // expect(screen.getByTestId('name')).toHaveValue('127.0.0.1:6379') + // }) + // }) + // + // it('should change Use SSH checkbox', async () => { + // const handleSubmit = jest.fn() + // render( + //
+ // + //
+ // ) + // + // fireEvent.click(screen.getByTestId('use-ssh')) + // + // expect(screen.getByTestId('use-ssh')).toBeChecked() + // }) + // + // it('should not render Use SSH checkbox for redis stack buidlType', async () => { + // const handleSubmit = jest.fn() + // render( + //
+ // + //
+ // ) + // + // expect(screen.queryByTestId('use-ssh')).not.toBeInTheDocument() + // }) + // + // it('should change Use SSH checkbox and show proper fields for password radio', async () => { + // const handleSubmit = jest.fn() + // render( + //
+ // + //
+ // ) + // + // act(() => { + // fireEvent.click(screen.getByTestId('use-ssh')) + // }) + // + // expect(screen.getByTestId('sshHost')).toBeInTheDocument() + // expect(screen.getByTestId('sshPort')).toBeInTheDocument() + // expect(screen.getByTestId('sshPort')).toHaveValue('22') + // expect(screen.getByTestId('sshPassword')).toBeInTheDocument() + // expect(screen.queryByTestId('sshPrivateKey')).not.toBeInTheDocument() + // expect(screen.queryByTestId('sshPassphrase')).not.toBeInTheDocument() + // + // const submitBtn = screen.getByTestId(BTN_SUBMIT) + // expect(submitBtn).toBeDisabled() + // }) + // + // it('should change Use SSH checkbox and show proper fields for passphrase radio', async () => { + // const handleSubmit = jest.fn() + // const { container } = render( + //
+ // + //
+ // ) + // + // await act(() => { + // fireEvent.click(screen.getByTestId('use-ssh')) + // fireEvent.click( + // container.querySelector(RADIO_BTN_PRIVATE_KEY) as HTMLLabelElement + // ) + // }) + // + // expect(screen.getByTestId('sshHost')).toBeInTheDocument() + // expect(screen.getByTestId('sshPort')).toBeInTheDocument() + // expect(screen.getByTestId('sshPort')).toHaveValue('22') + // expect(screen.queryByTestId('sshPassword')).not.toBeInTheDocument() + // expect(screen.getByTestId('sshPrivateKey')).toBeInTheDocument() + // expect(screen.getByTestId('sshPassphrase')).toBeInTheDocument() + // + // const submitBtn = screen.getByTestId(BTN_SUBMIT) + // expect(submitBtn).toBeDisabled() + // }) + // + // it('should be proper validation for ssh via ssh password', async () => { + // const handleSubmit = jest.fn() + // render( + //
+ // + //
+ // ) + // + // expect(screen.getByTestId(BTN_SUBMIT)).not.toBeDisabled() + // + // await act(() => { + // fireEvent.click(screen.getByTestId('use-ssh')) + // }) + // + // expect(screen.getByTestId(BTN_SUBMIT)).toBeDisabled() + // + // await act(() => { + // fireEvent.change( + // screen.getByTestId('sshHost'), + // { target: { value: 'localhost' } } + // ) + // }) + // + // expect(screen.getByTestId(BTN_SUBMIT)).toBeDisabled() + // + // await act(() => { + // fireEvent.change( + // screen.getByTestId('sshUsername'), + // { target: { value: 'username' } } + // ) + // }) + // + // expect(screen.getByTestId(BTN_SUBMIT)).not.toBeDisabled() + // }) + // + // it('should be proper validation for ssh via ssh passphrase', async () => { + // const handleSubmit = jest.fn() + // const { container } = render( + //
+ // + //
+ // ) + // + // expect(screen.getByTestId(BTN_SUBMIT)).not.toBeDisabled() + // + // await act(() => { + // fireEvent.click(screen.getByTestId('use-ssh')) + // fireEvent.click( + // container.querySelector(RADIO_BTN_PRIVATE_KEY) as HTMLLabelElement + // ) + // }) + // + // expect(screen.getByTestId(BTN_SUBMIT)).toBeDisabled() + // + // await act(() => { + // fireEvent.change( + // screen.getByTestId('sshHost'), + // { target: { value: 'localhost' } } + // ) + // fireEvent.change( + // screen.getByTestId('sshUsername'), + // { target: { value: 'username' } } + // ) + // }) + // + // expect(screen.getByTestId(BTN_SUBMIT)).toBeDisabled() + // + // await act(() => { + // fireEvent.change( + // screen.getByTestId('sshPrivateKey'), + // { target: { value: 'PRIVATEKEY' } } + // ) + // }) + // + // expect(screen.getByTestId(BTN_SUBMIT)).not.toBeDisabled() + // }) + // + // it('should call submit btn with proper fields', async () => { + // const handleSubmit = jest.fn() + // render( + //
+ // + //
+ // ) + // + // await act(() => { + // fireEvent.click(screen.getByTestId('use-ssh')) + // }) + // + // await act(() => { + // fireEvent.change( + // screen.getByTestId('sshHost'), + // { target: { value: 'localhost' } } + // ) + // + // fireEvent.change( + // screen.getByTestId('sshPort'), + // { target: { value: '1771' } } + // ) + // + // fireEvent.change( + // screen.getByTestId('sshUsername'), + // { target: { value: 'username' } } + // ) + // + // fireEvent.change( + // screen.getByTestId('sshPassword'), + // { target: { value: '123' } } + // ) + // }) + // + // await act(() => { + // fireEvent.click(screen.getByTestId(BTN_SUBMIT)) + // }) + // + // expect(handleSubmit).toBeCalledWith( + // expect.objectContaining({ + // sshHost: 'localhost', + // sshPort: '1771', + // sshUsername: 'username', + // sshPassword: '123', + // }) + // ) + // }) + // + // it('should call submit btn with proper fields via passphrase', async () => { + // const handleSubmit = jest.fn() + // const { container } = render( + //
+ // + //
+ // ) + // + // await act(() => { + // fireEvent.click(screen.getByTestId('use-ssh')) + // fireEvent.click( + // container.querySelector(RADIO_BTN_PRIVATE_KEY) as HTMLLabelElement + // ) + // }) + // + // await act(() => { + // fireEvent.change( + // screen.getByTestId('sshHost'), + // { target: { value: 'localhost' } } + // ) + // + // fireEvent.change( + // screen.getByTestId('sshPort'), + // { target: { value: '1771' } } + // ) + // + // fireEvent.change( + // screen.getByTestId('sshUsername'), + // { target: { value: 'username' } } + // ) + // + // fireEvent.change( + // screen.getByTestId('sshPrivateKey'), + // { target: { value: '123444' } } + // ) + // + // fireEvent.change( + // screen.getByTestId('sshPassphrase'), + // { target: { value: '123444' } } + // ) + // }) + // + // await act(() => { + // fireEvent.click(screen.getByTestId(BTN_SUBMIT)) + // }) + // + // expect(handleSubmit).toBeCalledWith( + // expect.objectContaining({ + // sshHost: 'localhost', + // sshPort: '1771', + // sshUsername: 'username', + // sshPrivateKey: '123444', + // sshPassphrase: '123444', + // }) + // ) + // }) + // + // it('should render password input with 10_000 length limit', () => { + // render( + // + // ) + // + // expect(screen.getByTestId('password')).toHaveAttribute('maxLength', '10000') + // }) + // + // it('should render security fields with proper attributes', () => { + // render( + // + // ) + // + // expect(screen.getByTestId('password')).toHaveAttribute('value', '••••••••••••') + // expect(screen.getByTestId('password')).toHaveAttribute('type', 'password') + // expect(screen.getByTestId('sshPassphrase')).toHaveAttribute('value', '••••••••••••') + // expect(screen.getByTestId('sshPassphrase')).toHaveAttribute('type', 'password') + // + // fireEvent.focus(screen.getByTestId('password')) + // fireEvent.focus(screen.getByTestId('sshPassphrase')) + // + // expect(screen.getByTestId('password')).toHaveAttribute('value', '') + // expect(screen.getByTestId('sshPassphrase')).toHaveAttribute('value', '') + // }) + // + // it('should render ssh password with proper attributes', () => { + // render( + // + // ) + // + // expect(screen.getByTestId('sshPassword')).toHaveAttribute('value', '••••••••••••') + // expect(screen.getByTestId('sshPassword')).toHaveAttribute('type', 'password') + // + // fireEvent.focus(screen.getByTestId('sshPassword')) + // + // expect(screen.getByTestId('sshPassword')).toHaveAttribute('value', '') + // }) + // + // it('should render ssh password input with 10_000 length limit', () => { + // render( + // + // ) + // + // expect(screen.getByTestId('sshPassword')).toHaveAttribute('maxLength', '10000') + // }) + // + // describe('timeout', () => { + // it('should render timeout input with 7 length limit and 1_000_000 value', () => { + // render( + // + // ) + // + // expect(screen.getByTestId('timeout')).toBeInTheDocument() + // expect(screen.getByTestId('timeout')).toHaveAttribute('maxLength', '7') + // + // fireEvent.change( + // screen.getByTestId('timeout'), + // { target: { value: '2000000' } } + // ) + // + // expect(screen.getByTestId('timeout')).toHaveAttribute('value', '1000000') + // }) + // + // it('should put only numbers', () => { + // render( + // + // ) + // + // fireEvent.change( + // screen.getByTestId('timeout'), + // { target: { value: '11a2EU$#@' } } + // ) + // + // expect(screen.getByTestId('timeout')).toHaveAttribute('value', '112') + // }) + // }) + // + // describe('cloud', () => { + // it('some fields should be readonly if instance data source from cloud', () => { + // (appRedirectionSelector as jest.Mock).mockImplementation(() => ({ + // action: UrlHandlingActions.Connect, + // })) + // + // const { queryByTestId } = render( + // + // ) + // + // expect(queryByTestId('connection-type')).not.toBeInTheDocument() + // expect(queryByTestId('host')).not.toBeInTheDocument() + // expect(queryByTestId('port')).not.toBeInTheDocument() + // expect(queryByTestId('db-info-port')).toBeInTheDocument() + // expect(queryByTestId('db-info-host')).toBeInTheDocument() + // }) + // }) +}) diff --git a/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionWrapper.tsx b/redisinsight/ui/src/pages/home/components/manual-connection/ManualConnectionWrapper.tsx similarity index 71% rename from redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionWrapper.tsx rename to redisinsight/ui/src/pages/home/components/manual-connection/ManualConnectionWrapper.tsx index 25cb5409e4..f0e5796d3f 100644 --- a/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionWrapper.tsx +++ b/redisinsight/ui/src/pages/home/components/manual-connection/ManualConnectionWrapper.tsx @@ -28,7 +28,7 @@ import { DEFAULT_TIMEOUT, SubmitBtnText, } from 'uiSrc/pages/home/constants' -import ManualConnectionForm from './ManualConnectionForm' +import ManualConnectionForm from './manual-connection-form' export interface Props { width: number @@ -61,7 +61,6 @@ const ManualConnectionWrapper = (props: Props) => { const { properties: urlHandlingProperties } = useSelector(appRedirectionSelector) const connectionType = editedInstance?.connectionType ?? DbType.STANDALONE - const masterName = editedInstance?.sentinelMaster?.name const history = useHistory() const dispatch = useDispatch() @@ -72,7 +71,7 @@ const ManualConnectionWrapper = (props: Props) => { }, []) useEffect(() => { - setFormFields(getFormValues(editedInstance || initialValuesProp || null)) + setFormFields(getFormValues(editedInstance || initialValuesProp)) setIsCloneMode(false) }, [editedInstance, initialValuesProp]) @@ -95,12 +94,7 @@ const ManualConnectionWrapper = (props: Props) => { })) } - const handleSubmitDatabase = (payload: any) => { - if (isCloneMode && connectionType === ConnectionType.Sentinel) { - dispatch(createInstanceStandaloneAction(payload)) - return - } - + const handleAddDatabase = (payload: any) => { sendEventTelemetry({ event: TelemetryEvent.CONFIG_DATABASES_MANUALLY_SUBMITTED }) @@ -136,56 +130,29 @@ const ManualConnectionWrapper = (props: Props) => { sendEventTelemetry({ event: TelemetryEvent.CONFIG_DATABASES_TEST_CONNECTION_CLICKED }) - const { - name, - host, - port, - username, - password, - db, - compressor, - timeout, - sentinelMasterName, - sentinelMasterUsername, - sentinelMasterPassword, - } = values - - const tlsSettings = getTlsSettings(values) + const payload = preparePayload(values) - const database: any = { - name, - host, - port: +port, - db: +(db || 0), - username, - password, - compressor, - timeout: timeout ? toNumber(timeout) * 1_000 : toNumber(DEFAULT_TIMEOUT), - } + dispatch(testInstanceStandaloneAction(payload)) + } - // add tls & ssh for database (modifies database object) - applyTlSDatabase(database, tlsSettings) - applySSHDatabase(database, values) + const handleConnectionFormSubmit = (values: DbConnectionInfo) => { + const payload = preparePayload(values) - if (isCloneMode && connectionType === ConnectionType.Sentinel) { - database.sentinelMaster = { - name: sentinelMasterName, - username: sentinelMasterUsername, - password: sentinelMasterPassword, - } + if (isCloneMode) { + handleCloneDatabase(payload) + return } - - if (editMode && editedInstance) { - dispatch(testInstanceStandaloneAction({ - ...getFormUpdates(database, editedInstance), - id: editedInstance.id, - })) - } else { - dispatch(testInstanceStandaloneAction(removeEmpty(database))) + if (editMode) { + handleEditDatabase(payload) + return } + + handleAddDatabase(payload) } - const editDatabase = (tlsSettings: any, values: DbConnectionInfo, isCloneMode: boolean) => { + const preparePayload = (values: any) => { + const tlsSettings = getTlsSettings(values) + const { name, host, @@ -195,6 +162,7 @@ const ManualConnectionWrapper = (props: Props) => { password, timeout, compressor, + sentinelMasterName, sentinelMasterUsername, sentinelMasterPassword, } = values @@ -216,50 +184,6 @@ const ManualConnectionWrapper = (props: Props) => { applySSHDatabase(database, values) if (connectionType === ConnectionType.Sentinel) { - database.sentinelMaster = {} - database.sentinelMaster.name = masterName - database.sentinelMaster.username = sentinelMasterUsername - database.sentinelMaster.password = sentinelMasterPassword - } - - const payload = getFormUpdates(database, omit(editedInstance, ['id'])) - if (isCloneMode) { - handleCloneDatabase(payload) - } else { - handleEditDatabase(payload) - } - } - - const addDatabase = (tlsSettings: any, values: DbConnectionInfo) => { - const { - name, - host, - port, - username, - password, - timeout, - db, - compressor, - sentinelMasterName, - sentinelMasterUsername, - sentinelMasterPassword, - } = values - const database: any = { - name, - host, - port: +port, - db: +(db || 0), - compressor, - username, - password, - timeout: timeout ? toNumber(timeout) * 1_000 : toNumber(DEFAULT_TIMEOUT), - } - - // add tls & ssh for database (modifies database object) - applyTlSDatabase(database, tlsSettings) - applySSHDatabase(database, values) - - if (isCloneMode && connectionType === ConnectionType.Sentinel) { database.sentinelMaster = { name: sentinelMasterName, username: sentinelMasterUsername, @@ -267,17 +191,13 @@ const ManualConnectionWrapper = (props: Props) => { } } - handleSubmitDatabase(removeEmpty(database)) - } - - const handleConnectionFormSubmit = (values: DbConnectionInfo) => { - const tlsSettings = getTlsSettings(values) - if (editMode) { - editDatabase(tlsSettings, values, isCloneMode) - } else { - addDatabase(tlsSettings, values) + database.id = editedInstance?.id + + return getFormUpdates(database, omit(editedInstance, ['id'])) } + + return removeEmpty(database) } const handleOnClose = () => { diff --git a/redisinsight/ui/src/pages/home/components/ManualConnection/index.ts b/redisinsight/ui/src/pages/home/components/manual-connection/index.ts similarity index 100% rename from redisinsight/ui/src/pages/home/components/ManualConnection/index.ts rename to redisinsight/ui/src/pages/home/components/manual-connection/index.ts diff --git a/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionForm/ManualConnectionForm.tsx b/redisinsight/ui/src/pages/home/components/manual-connection/manual-connection-form/ManualConnectionForm.tsx similarity index 87% rename from redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionForm/ManualConnectionForm.tsx rename to redisinsight/ui/src/pages/home/components/manual-connection/manual-connection-form/ManualConnectionForm.tsx index 61e397234a..928cea130b 100644 --- a/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionForm/ManualConnectionForm.tsx +++ b/redisinsight/ui/src/pages/home/components/manual-connection/manual-connection-form/ManualConnectionForm.tsx @@ -13,22 +13,13 @@ import { isEmpty, pick } from 'lodash' import React, { useEffect, useRef, useState } from 'react' import ReactDOM from 'react-dom' import { useDispatch, useSelector } from 'react-redux' -import { useHistory } from 'react-router' -import { PageNames, Pages } from 'uiSrc/constants' import validationErrors from 'uiSrc/constants/validationErrors' -import DatabaseAlias from 'uiSrc/pages/home/components/DatabaseAlias' +import DatabaseAlias from 'uiSrc/pages/home/components/database-alias' import { useResizableFormField } from 'uiSrc/services' -import { appContextSelector, setAppContextInitialState } from 'uiSrc/slices/app/context' -import { resetKeys } from 'uiSrc/slices/browser/keys' -import { - changeInstanceAliasAction, - checkConnectToInstanceAction, - resetInstanceUpdateAction, - setConnectedInstanceId, -} from 'uiSrc/slices/instances/instances' +import { resetInstanceUpdateAction } from 'uiSrc/slices/instances/instances' import { ConnectionType } from 'uiSrc/slices/interfaces' -import { getRedisModulesSummary, sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' +import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' import { getDiffKeysOfObjectValues, isRediStack } from 'uiSrc/utils' import { BuildType } from 'uiSrc/constants/env' import { appRedirectionSelector } from 'uiSrc/slices/app/url-handling' @@ -113,7 +104,6 @@ const ManualConnectionForm = (props: Props) => { version, } = formFields - const { contextInstanceId, lastPage } = useSelector(appContextSelector) const { action } = useSelector(appRedirectionSelector) const { data: caCertificates } = useSelector(caCertsSelector) const { data: certificates } = useSelector(clientCertsSelector) @@ -122,7 +112,6 @@ const ManualConnectionForm = (props: Props) => { getInitFieldsDisplayNames({ host, port, name }) ) - const history = useHistory() const dispatch = useDispatch() const formRef = useRef(null) @@ -184,73 +173,14 @@ const ManualConnectionForm = (props: Props) => { }, []) - const handleCheckConnectToInstance = () => { - const modulesSummary = getRedisModulesSummary(modules) - sendEventTelemetry({ - event: TelemetryEvent.CONFIG_DATABASES_OPEN_DATABASE_BUTTON_CLICKED, - eventData: { - databaseId: id, - provider, - ...modulesSummary, - } - }) - dispatch(checkConnectToInstanceAction(id, connectToInstance)) - } - - const handleCloneDatabase = () => { - setIsCloneMode(true) - sendEventTelemetry({ - event: TelemetryEvent.CONFIG_DATABASES_DATABASE_CLONE_REQUESTED, - eventData: { - databaseId: id - } - }) - } - - const handleBackCloneDatabase = () => { - setIsCloneMode(false) - sendEventTelemetry({ - event: TelemetryEvent.CONFIG_DATABASES_DATABASE_CLONE_CANCELLED, - eventData: { - databaseId: id - } - }) - } + useEffect(() => { + formik.resetForm() + }, [isCloneMode]) const handleTestConnectionDatabase = () => { onTestConnection(formik.values) } - const handleChangeDatabaseAlias = ( - value: string, - onSuccess?: () => void, - onFail?: () => void - ) => { - dispatch(changeInstanceAliasAction( - id, - value, - () => { - onAliasEdited?.(value) - onSuccess?.() - }, - onFail - )) - } - - const connectToInstance = () => { - if (contextInstanceId && contextInstanceId !== id) { - dispatch(resetKeys()) - dispatch(setAppContextInitialState()) - } - dispatch(setConnectedInstanceId(id ?? '')) - - if (lastPage === PageNames.workbench && contextInstanceId === id) { - history.push(Pages.workbench(id)) - return - } - history.push(Pages.browser(id ?? '')) - } - const SubmitButton = ({ text = '', onClick, @@ -353,10 +283,11 @@ const ManualConnectionForm = (props: Props) => { alias={name} database={db} isLoading={loading} - onOpen={handleCheckConnectToInstance} - onClone={handleCloneDatabase} - onCloneBack={handleBackCloneDatabase} - onApplyChanges={handleChangeDatabaseAlias} + id={id} + provider={provider} + modules={modules} + setIsCloneMode={setIsCloneMode} + onAliasEdited={onAliasEdited} />
)} diff --git a/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionForm/ManualConnectionFrom.spec.tsx b/redisinsight/ui/src/pages/home/components/manual-connection/manual-connection-form/ManualConnectionFrom.spec.tsx similarity index 99% rename from redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionForm/ManualConnectionFrom.spec.tsx rename to redisinsight/ui/src/pages/home/components/manual-connection/manual-connection-form/ManualConnectionFrom.spec.tsx index 6cbfdbfde0..e86e1d2095 100644 --- a/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionForm/ManualConnectionFrom.spec.tsx +++ b/redisinsight/ui/src/pages/home/components/manual-connection/manual-connection-form/ManualConnectionFrom.spec.tsx @@ -24,8 +24,6 @@ const formFields = { host: 'localhost', port: '6379', name: 'lala', - caCertificates: [], - certificates: [], } jest.mock('uiSrc/slices/instances/instances', () => ({ @@ -264,7 +262,7 @@ describe('InstanceForm', () => { }) expect(handleTestConnection).toBeCalledWith( expect.objectContaining({ - showDb: true, + showDb: ['on'], }) ) await act(() => { @@ -273,7 +271,7 @@ describe('InstanceForm', () => { expect(handleSubmit).toBeCalledWith( expect.objectContaining({ - showDb: true, + showDb: ['on'], }) ) }) @@ -312,7 +310,7 @@ describe('InstanceForm', () => { }) expect(handleTestConnection).toBeCalledWith( expect.objectContaining({ - showDb: true, + showDb: ['on'], db: '12' }) ) @@ -322,7 +320,7 @@ describe('InstanceForm', () => { expect(handleSubmit).toBeCalledWith( expect.objectContaining({ - showDb: true, + showDb: ['on'], db: '12' }) ) @@ -357,7 +355,7 @@ describe('InstanceForm', () => { }) expect(handleTestConnection).toBeCalledWith( expect.objectContaining({ - sni: true, + sni: ['on'], servername: formFields.host }) ) @@ -367,7 +365,7 @@ describe('InstanceForm', () => { expect(handleSubmit).toBeCalledWith( expect.objectContaining({ - sni: true, + sni: ['on'], servername: formFields.host }) ) @@ -408,7 +406,7 @@ describe('InstanceForm', () => { }) expect(handleTestConnection).toBeCalledWith( expect.objectContaining({ - sni: true, + sni: ['on'], servername: '12' }) ) @@ -418,7 +416,7 @@ describe('InstanceForm', () => { expect(handleSubmit).toBeCalledWith( expect.objectContaining({ - sni: true, + sni: ['on'], servername: '12' }) ) @@ -774,7 +772,7 @@ describe('InstanceForm', () => { }} /> ) - expect(screen.getByTestId('showDb')).toBeChecked() + // expect(screen.getByTestId('showDb')).toBeChecked() expect(screen.getByTestId('db')).toHaveValue('5') }) @@ -860,7 +858,9 @@ describe('InstanceForm', () => {
) - fireEvent.click(screen.getByTestId('use-ssh')) + act(() => { + fireEvent.click(screen.getByTestId('use-ssh')) + }) expect(screen.getByTestId('sshHost')).toBeInTheDocument() expect(screen.getByTestId('sshPort')).toBeInTheDocument() diff --git a/redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionForm/index.ts b/redisinsight/ui/src/pages/home/components/manual-connection/manual-connection-form/index.ts similarity index 100% rename from redisinsight/ui/src/pages/home/components/ManualConnection/ManualConnectionForm/index.ts rename to redisinsight/ui/src/pages/home/components/manual-connection/manual-connection-form/index.ts diff --git a/redisinsight/ui/src/pages/home/components/SearchDatabasesList/SearchDatabasesList.spec.tsx b/redisinsight/ui/src/pages/home/components/search-databases-list/SearchDatabasesList.spec.tsx similarity index 100% rename from redisinsight/ui/src/pages/home/components/SearchDatabasesList/SearchDatabasesList.spec.tsx rename to redisinsight/ui/src/pages/home/components/search-databases-list/SearchDatabasesList.spec.tsx diff --git a/redisinsight/ui/src/pages/home/components/SearchDatabasesList/SearchDatabasesList.tsx b/redisinsight/ui/src/pages/home/components/search-databases-list/SearchDatabasesList.tsx similarity index 100% rename from redisinsight/ui/src/pages/home/components/SearchDatabasesList/SearchDatabasesList.tsx rename to redisinsight/ui/src/pages/home/components/search-databases-list/SearchDatabasesList.tsx diff --git a/redisinsight/ui/src/pages/home/components/SearchDatabasesList/index.ts b/redisinsight/ui/src/pages/home/components/search-databases-list/index.ts similarity index 100% rename from redisinsight/ui/src/pages/home/components/SearchDatabasesList/index.ts rename to redisinsight/ui/src/pages/home/components/search-databases-list/index.ts diff --git a/redisinsight/ui/src/pages/home/components/SearchDatabasesList/styles.module.scss b/redisinsight/ui/src/pages/home/components/search-databases-list/styles.module.scss similarity index 100% rename from redisinsight/ui/src/pages/home/components/SearchDatabasesList/styles.module.scss rename to redisinsight/ui/src/pages/home/components/search-databases-list/styles.module.scss diff --git a/redisinsight/ui/src/pages/home/components/SentinelConnection/SentinelConnectionWrapper.tsx b/redisinsight/ui/src/pages/home/components/sentinel-connection/SentinelConnectionWrapper.tsx similarity index 94% rename from redisinsight/ui/src/pages/home/components/SentinelConnection/SentinelConnectionWrapper.tsx rename to redisinsight/ui/src/pages/home/components/sentinel-connection/SentinelConnectionWrapper.tsx index d190ccb76f..a462959230 100644 --- a/redisinsight/ui/src/pages/home/components/SentinelConnection/SentinelConnectionWrapper.tsx +++ b/redisinsight/ui/src/pages/home/components/sentinel-connection/SentinelConnectionWrapper.tsx @@ -11,9 +11,9 @@ import { clientCertsSelector, fetchClientCerts, } from 'uiSrc/slices/instances/c import { DbConnectionInfo } from 'uiSrc/pages/home/interfaces' import { applyTlSDatabase, autoFillFormDetails, getTlsSettings } from 'uiSrc/pages/home/utils' -import { ADD_NEW, NO_CA_CERT, SubmitBtnText } from 'uiSrc/pages/home/constants' +import { ADD_NEW, NO_CA_CERT } from 'uiSrc/pages/home/constants' import { InstanceType } from 'uiSrc/slices/interfaces' -import SentinelConnectionForm from './SentinelConnectionForm' +import SentinelConnectionForm from './sentinel-connection-form' export interface Props { width: number @@ -101,7 +101,6 @@ const SentinelConnectionWrapper = (props: Props) => { width={width} initialValues={initialValues} loading={loading} - submitButtonText={SubmitBtnText.AddDatabase} onSubmit={handleConnectionFormSubmit} onClose={onClose} onHostNamePaste={handlePostHostName} diff --git a/redisinsight/ui/src/pages/home/components/SentinelConnection/index.ts b/redisinsight/ui/src/pages/home/components/sentinel-connection/index.ts similarity index 100% rename from redisinsight/ui/src/pages/home/components/SentinelConnection/index.ts rename to redisinsight/ui/src/pages/home/components/sentinel-connection/index.ts diff --git a/redisinsight/ui/src/pages/home/components/SentinelConnection/SentinelConnectionForm/SentinelConnectionForm.spec.tsx b/redisinsight/ui/src/pages/home/components/sentinel-connection/sentinel-connection-form/SentinelConnectionForm.spec.tsx similarity index 100% rename from redisinsight/ui/src/pages/home/components/SentinelConnection/SentinelConnectionForm/SentinelConnectionForm.spec.tsx rename to redisinsight/ui/src/pages/home/components/sentinel-connection/sentinel-connection-form/SentinelConnectionForm.spec.tsx diff --git a/redisinsight/ui/src/pages/home/components/SentinelConnection/SentinelConnectionForm/SentinelConnectionForm.tsx b/redisinsight/ui/src/pages/home/components/sentinel-connection/sentinel-connection-form/SentinelConnectionForm.tsx similarity index 98% rename from redisinsight/ui/src/pages/home/components/SentinelConnection/SentinelConnectionForm/SentinelConnectionForm.tsx rename to redisinsight/ui/src/pages/home/components/sentinel-connection/sentinel-connection-form/SentinelConnectionForm.tsx index 48b714cd45..be8dd68cbf 100644 --- a/redisinsight/ui/src/pages/home/components/SentinelConnection/SentinelConnectionForm/SentinelConnectionForm.tsx +++ b/redisinsight/ui/src/pages/home/components/sentinel-connection/sentinel-connection-form/SentinelConnectionForm.tsx @@ -52,7 +52,6 @@ const SentinelConnectionForm = (props: Props) => { onClose, onSubmit, onHostNamePaste, - submitButtonText, loading, certificates, caCertificates, @@ -94,7 +93,6 @@ const SentinelConnectionForm = (props: Props) => { } const SubmitButton = ({ - text = '', onClick, submitIsDisabled, }: ISubmitButton) => ( @@ -118,7 +116,7 @@ const SentinelConnectionForm = (props: Props) => { iconType={submitIsDisabled ? 'iInCircle' : undefined} data-testid="btn-submit" > - {text} + Discover Database ) @@ -150,7 +148,6 @@ const SentinelConnectionForm = (props: Props) => { )} diff --git a/redisinsight/ui/src/pages/home/components/SentinelConnection/SentinelConnectionForm/index.ts b/redisinsight/ui/src/pages/home/components/sentinel-connection/sentinel-connection-form/index.ts similarity index 100% rename from redisinsight/ui/src/pages/home/components/SentinelConnection/SentinelConnectionForm/index.ts rename to redisinsight/ui/src/pages/home/components/sentinel-connection/sentinel-connection-form/index.ts diff --git a/redisinsight/ui/src/pages/home/components/WelcomeComponent/WelcomeComponent.spec.tsx b/redisinsight/ui/src/pages/home/components/welcome-component/WelcomeComponent.spec.tsx similarity index 100% rename from redisinsight/ui/src/pages/home/components/WelcomeComponent/WelcomeComponent.spec.tsx rename to redisinsight/ui/src/pages/home/components/welcome-component/WelcomeComponent.spec.tsx diff --git a/redisinsight/ui/src/pages/home/components/WelcomeComponent/WelcomeComponent.tsx b/redisinsight/ui/src/pages/home/components/welcome-component/WelcomeComponent.tsx similarity index 100% rename from redisinsight/ui/src/pages/home/components/WelcomeComponent/WelcomeComponent.tsx rename to redisinsight/ui/src/pages/home/components/welcome-component/WelcomeComponent.tsx diff --git a/redisinsight/ui/src/pages/home/components/WelcomeComponent/index.ts b/redisinsight/ui/src/pages/home/components/welcome-component/index.ts similarity index 100% rename from redisinsight/ui/src/pages/home/components/WelcomeComponent/index.ts rename to redisinsight/ui/src/pages/home/components/welcome-component/index.ts diff --git a/redisinsight/ui/src/pages/home/components/WelcomeComponent/styles.module.scss b/redisinsight/ui/src/pages/home/components/welcome-component/styles.module.scss similarity index 100% rename from redisinsight/ui/src/pages/home/components/WelcomeComponent/styles.module.scss rename to redisinsight/ui/src/pages/home/components/welcome-component/styles.module.scss diff --git a/redisinsight/ui/src/pages/home/constants/form.ts b/redisinsight/ui/src/pages/home/constants/form.ts index bb665208a3..5229a0a649 100644 --- a/redisinsight/ui/src/pages/home/constants/form.ts +++ b/redisinsight/ui/src/pages/home/constants/form.ts @@ -35,7 +35,6 @@ export const DEFAULT_TIMEOUT = parseInt(DEFAULT_TIMEOUT_ENV, 10) export enum SubmitBtnText { AddDatabase = 'Add Redis Database', - EditDatabase = 'Apply changes', - ConnectToSentinel = 'Discover database', + EditDatabase = 'Apply Changes', CloneDatabase = 'Clone Database' } diff --git a/redisinsight/ui/src/pages/home/utils/form.tsx b/redisinsight/ui/src/pages/home/utils/form.tsx index 9166ea7aaa..4d002076a3 100644 --- a/redisinsight/ui/src/pages/home/utils/form.tsx +++ b/redisinsight/ui/src/pages/home/utils/form.tsx @@ -286,7 +286,8 @@ export const getSubmitButtonContent = (errors: FormikErrors, s ) : null } -export const getFormValues = (instance: Nullable>) => ({ +export const getFormValues = (instance?: Nullable>) => ({ + id: instance?.id, host: instance?.host ?? (instance ? '' : DEFAULT_HOST), port: instance?.port?.toString() ?? (instance ? '' : DEFAULT_PORT), timeout: instance?.timeout diff --git a/redisinsight/ui/src/pages/redisStack/components/edit-connection/EditConnection.tsx b/redisinsight/ui/src/pages/redisStack/components/edit-connection/EditConnection.tsx index 0d8d34eaeb..c68c2a7cec 100644 --- a/redisinsight/ui/src/pages/redisStack/components/edit-connection/EditConnection.tsx +++ b/redisinsight/ui/src/pages/redisStack/components/edit-connection/EditConnection.tsx @@ -18,7 +18,7 @@ import { HELP_LINKS } from 'uiSrc/pages/home/constants' import { sendEventTelemetry } from 'uiSrc/telemetry' import { ThemeContext } from 'uiSrc/contexts/themeContext' import { contentSelector } from 'uiSrc/slices/content/create-redis-buttons' -import DatabasePanel from 'uiSrc/pages/home/components/DatabasePanel/DatabasePanel' +import DatabasePanel from 'uiSrc/pages/home/components/database-panel/DatabasePanel' import './styles.scss' import styles from './styles.module.scss' From 165588e6eabc6e23aa38d8864c9db99bd089c2ba Mon Sep 17 00:00:00 2001 From: Amir Allayarov Date: Thu, 9 Nov 2023 16:16:55 +0400 Subject: [PATCH 06/11] #RI-5009 - fix unit tests --- .../ClusterConnectionFormWrapper.spec.tsx | 2 +- .../ManualConnectionWrapper.spec.tsx | 1247 ----------------- .../ManualConnectionFrom.spec.tsx | 59 +- .../SentinelConnectionWrapper.spec.tsx | 66 + redisinsight/ui/src/pages/home/utils/form.tsx | 6 +- 5 files changed, 111 insertions(+), 1269 deletions(-) delete mode 100644 redisinsight/ui/src/pages/home/components/manual-connection/ManualConnectionWrapper.spec.tsx create mode 100644 redisinsight/ui/src/pages/home/components/sentinel-connection/SentinelConnectionWrapper.spec.tsx diff --git a/redisinsight/ui/src/pages/home/components/cluster-connection/ClusterConnectionFormWrapper.spec.tsx b/redisinsight/ui/src/pages/home/components/cluster-connection/ClusterConnectionFormWrapper.spec.tsx index 341145e689..3502290daf 100644 --- a/redisinsight/ui/src/pages/home/components/cluster-connection/ClusterConnectionFormWrapper.spec.tsx +++ b/redisinsight/ui/src/pages/home/components/cluster-connection/ClusterConnectionFormWrapper.spec.tsx @@ -9,7 +9,7 @@ import ClusterConnectionFormWrapper, { const mockedProps = mock() -jest.mock('./cluster-connection-form/cluster-connection-form', () => ({ +jest.mock('./cluster-connection-form/ClusterConnectionForm', () => ({ __esModule: true, namedExport: jest.fn(), default: jest.fn(), diff --git a/redisinsight/ui/src/pages/home/components/manual-connection/ManualConnectionWrapper.spec.tsx b/redisinsight/ui/src/pages/home/components/manual-connection/ManualConnectionWrapper.spec.tsx deleted file mode 100644 index afd4dbcee5..0000000000 --- a/redisinsight/ui/src/pages/home/components/manual-connection/ManualConnectionWrapper.spec.tsx +++ /dev/null @@ -1,1247 +0,0 @@ -import React from 'react' -import { instance, mock } from 'ts-mockito' -import { act, fireEvent, render, screen } from 'uiSrc/utils/test-utils' -import { ConnectionType } from 'uiSrc/slices/interfaces' -import { BuildType } from 'uiSrc/constants/env' -import { appRedirectionSelector } from 'uiSrc/slices/app/url-handling' -import { UrlHandlingActions } from 'uiSrc/slices/interfaces/urlHandling' -import { ADD_NEW_CA_CERT, SshPassType } from 'uiSrc/pages/home/constants' -import { DbConnectionInfo } from 'uiSrc/pages/home/interfaces' - -import ManualConnectionWrapper, { Props } from './ManualConnectionWrapper' - -const BTN_SUBMIT = 'btn-submit' -const NEW_CA_CERT = 'new-ca-cert' -const QA_CA_CERT = 'qa-ca-cert' -const RADIO_BTN_PRIVATE_KEY = '[data-test-subj="radio-btn-privateKey"] label' -const BTN_TEST_CONNECTION = 'btn-test-connection' - -const mockedProps = mock() -const mockedDbConnectionInfo = mock() - -const formFields = { - ...instance(mockedDbConnectionInfo), - host: 'localhost', - port: '6379', - name: 'lala', -} - -// jest.mock('uiSrc/slices/instances/instances', () => ({ -// checkConnectToInstanceAction: () => jest.fn, -// resetInstanceUpdateAction: () => jest.fn, -// changeInstanceAliasAction: () => jest.fn, -// setConnectedInstanceId: jest.fn, -// })) -// -// jest.mock('uiSrc/slices/app/url-handling', () => ({ -// ...jest.requireActual('uiSrc/slices/app/url-handling'), -// appRedirectionSelector: jest.fn().mockReturnValue(() => ({ action: null })), -// })) - -describe('InstanceForm', () => { - it('should render', () => { - expect( - render( - - ) - ).toBeTruthy() - }) - - // it('should render with ConnectionType.Sentinel', () => { - // expect( - // render( - // - // ) - // ).toBeTruthy() - // }) - // - // it('should render with ConnectionType.Cluster', () => { - // expect( - // render( - // - // ) - // ).toBeTruthy() - // }) - // - // it('should render tooltip with nodes', () => { - // expect( - // render( - // - // ) - // ).toBeTruthy() - // }) - // - it('should render DatabaseForm', () => { - expect( - render( - - ) - ).toBeTruthy() - }) - // - // it('should change sentinelMasterUsername input properly', async () => { - // const handleSubmit = jest.fn() - // const handleTestConnection = jest.fn() - // - // render( - //
- // - //
- // ) - // - // await act(() => { - // fireEvent.change(screen.getByTestId('sentinel-mater-username'), { - // target: { value: 'user' }, - // }) - // }) - // - // const submitBtn = screen.getByTestId(BTN_SUBMIT) - // const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) - // - // await act(() => { - // fireEvent.click(testConnectionBtn) - // }) - // expect(handleTestConnection).toBeCalledWith( - // expect.objectContaining({ - // sentinelMasterUsername: 'user', - // }) - // ) - // - // await act(() => { - // fireEvent.click(submitBtn) - // }) - // expect(handleSubmit).toBeCalledWith( - // expect.objectContaining({ - // sentinelMasterUsername: 'user', - // }) - // ) - // }) - // - // it('should change port input properly', async () => { - // const handleSubmit = jest.fn() - // render( - //
- // - //
- // ) - // - // await act(() => { - // fireEvent.change(screen.getByTestId('port'), { - // target: { value: '123' }, - // }) - // }) - // - // const submitBtn = screen.getByTestId(BTN_SUBMIT) - // await act(() => { - // fireEvent.click(submitBtn) - // }) - // expect(handleSubmit).toBeCalledWith( - // expect.objectContaining({ - // port: '123', - // }) - // ) - // }) - // - // it('should change tls checkbox', async () => { - // const handleSubmit = jest.fn() - // const handleTestConnection = jest.fn() - // - // render( - //
- // - //
- // ) - // await act(() => { - // fireEvent.click(screen.getByTestId('tls')) - // }) - // - // const submitBtn = screen.getByTestId(BTN_SUBMIT) - // const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) - // - // await act(() => { - // fireEvent.click(testConnectionBtn) - // }) - // expect(handleTestConnection).toBeCalledWith( - // expect.objectContaining({ - // tls: ['on'], - // }) - // ) - // - // await act(() => { - // fireEvent.click(submitBtn) - // }) - // - // expect(handleSubmit).toBeCalledWith( - // expect.objectContaining({ - // tls: ['on'], - // }) - // ) - // }) - // - // it('should change Database Index checkbox', async () => { - // const handleSubmit = jest.fn() - // const handleTestConnection = jest.fn() - // render( - //
- // - //
- // ) - // await act(() => { - // fireEvent.click(screen.getByTestId('showDb')) - // }) - // - // const submitBtn = screen.getByTestId(BTN_SUBMIT) - // const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) - // await act(() => { - // fireEvent.click(testConnectionBtn) - // }) - // expect(handleTestConnection).toBeCalledWith( - // expect.objectContaining({ - // showDb: ['on'], - // }) - // ) - // await act(() => { - // fireEvent.click(submitBtn) - // }) - // - // expect(handleSubmit).toBeCalledWith( - // expect.objectContaining({ - // showDb: ['on'], - // }) - // ) - // }) - // - // it('should change db checkbox and value', async () => { - // const handleSubmit = jest.fn() - // const handleTestConnection = jest.fn() - // render( - //
- // - //
- // ) - // await act(() => { - // fireEvent.click(screen.getByTestId('showDb')) - // }) - // - // await act(() => { - // fireEvent.change(screen.getByTestId('db'), { - // target: { value: '12' }, - // }) - // }) - // - // const submitBtn = screen.getByTestId(BTN_SUBMIT) - // const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) - // - // await act(() => { - // fireEvent.click(testConnectionBtn) - // }) - // expect(handleTestConnection).toBeCalledWith( - // expect.objectContaining({ - // showDb: ['on'], - // db: '12' - // }) - // ) - // await act(() => { - // fireEvent.click(submitBtn) - // }) - // - // expect(handleSubmit).toBeCalledWith( - // expect.objectContaining({ - // showDb: ['on'], - // db: '12' - // }) - // ) - // }) - // - // it('should change "Use SNI" with prepopulated with host', async () => { - // const handleSubmit = jest.fn() - // const handleTestConnection = jest.fn() - // render( - //
- // - //
- // ) - // await act(() => { - // fireEvent.click(screen.getByTestId('sni')) - // }) - // - // const submitBtn = screen.getByTestId(BTN_SUBMIT) - // const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) - // await act(() => { - // fireEvent.click(testConnectionBtn) - // }) - // expect(handleTestConnection).toBeCalledWith( - // expect.objectContaining({ - // sni: ['on'], - // servername: formFields.host - // }) - // ) - // await act(() => { - // fireEvent.click(submitBtn) - // }) - // - // expect(handleSubmit).toBeCalledWith( - // expect.objectContaining({ - // sni: ['on'], - // servername: formFields.host - // }) - // ) - // }) - // - // it('should change "Use SNI"', async () => { - // const handleSubmit = jest.fn() - // const handleTestConnection = jest.fn() - // render( - //
- // - //
- // ) - // await act(() => { - // fireEvent.click(screen.getByTestId('sni')) - // }) - // - // await act(() => { - // fireEvent.change(screen.getByTestId('sni-servername'), { - // target: { value: '12' }, - // }) - // }) - // - // const submitBtn = screen.getByTestId(BTN_SUBMIT) - // const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) - // await act(() => { - // fireEvent.click(testConnectionBtn) - // }) - // expect(handleTestConnection).toBeCalledWith( - // expect.objectContaining({ - // sni: ['on'], - // servername: '12' - // }) - // ) - // await act(() => { - // fireEvent.click(submitBtn) - // }) - // - // expect(handleSubmit).toBeCalledWith( - // expect.objectContaining({ - // sni: ['on'], - // servername: '12' - // }) - // ) - // }) - // - // it('should change "Verify TLS Certificate"', async () => { - // const handleSubmit = jest.fn() - // const handleTestConnection = jest.fn() - // render( - //
- // - //
- // ) - // await act(() => { - // fireEvent.click(screen.getByTestId('verify-tls-cert')) - // }) - // - // const submitBtn = screen.getByTestId(BTN_SUBMIT) - // const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) - // await act(() => { - // fireEvent.click(testConnectionBtn) - // }) - // expect(handleTestConnection).toBeCalledWith( - // expect.objectContaining({ - // verifyServerTlsCert: ['on'], - // }) - // ) - // await act(() => { - // fireEvent.click(submitBtn) - // }) - // - // expect(handleSubmit).toBeCalledWith( - // expect.objectContaining({ - // verifyServerTlsCert: ['on'], - // }) - // ) - // }) - // - // it('should select value from "CA Certificate"', async () => { - // const handleSubmit = jest.fn() - // const handleTestConnection = jest.fn() - // const { queryByText } = render( - //
- // - //
- // ) - // await act(() => { - // fireEvent.click(screen.getByTestId('select-ca-cert')) - // }) - // await act(() => { - // fireEvent.click(queryByText('Add new CA certificate') || document) - // }) - // - // expect(screen.getByTestId(NEW_CA_CERT)).toBeInTheDocument() - // await act(() => { - // fireEvent.change(screen.getByTestId(NEW_CA_CERT), { - // target: { value: '123' }, - // }) - // }) - // - // expect(screen.getByTestId(QA_CA_CERT)).toBeInTheDocument() - // await act(() => { - // fireEvent.change(screen.getByTestId(QA_CA_CERT), { - // target: { value: '321' }, - // }) - // }) - // - // const submitBtn = screen.getByTestId(BTN_SUBMIT) - // const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) - // await act(() => { - // fireEvent.click(testConnectionBtn) - // }) - // expect(handleTestConnection).toBeCalledWith( - // expect.objectContaining({ - // selectedCaCertName: ADD_NEW_CA_CERT, - // newCaCertName: '321', - // newCaCert: '123', - // }) - // ) - // await act(() => { - // fireEvent.click(submitBtn) - // }) - // - // expect(handleSubmit).toBeCalledWith( - // expect.objectContaining({ - // selectedCaCertName: ADD_NEW_CA_CERT, - // newCaCertName: '321', - // newCaCert: '123', - // }) - // ) - // }) - // - // it('should render fields for add new CA and change them properly', async () => { - // const handleSubmit = jest.fn() - // const handleTestConnection = jest.fn() - // render( - //
- // - //
- // ) - // - // expect(screen.getByTestId(QA_CA_CERT)).toBeInTheDocument() - // await act(() => { - // fireEvent.change(screen.getByTestId(QA_CA_CERT), { - // target: { value: '321' }, - // }) - // }) - // - // expect(screen.getByTestId(NEW_CA_CERT)).toBeInTheDocument() - // await act(() => { - // fireEvent.change(screen.getByTestId(NEW_CA_CERT), { - // target: { value: '123' }, - // }) - // }) - // - // const submitBtn = screen.getByTestId(BTN_SUBMIT) - // const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) - // await act(() => { - // fireEvent.click(testConnectionBtn) - // }) - // expect(handleTestConnection).toBeCalledWith( - // expect.objectContaining({ - // newCaCert: '123', - // newCaCertName: '321', - // }) - // ) - // await act(() => { - // fireEvent.click(submitBtn) - // }) - // - // expect(handleSubmit).toBeCalledWith( - // expect.objectContaining({ - // newCaCert: '123', - // newCaCertName: '321', - // }) - // ) - // }) - // - // it('should change "Requires TLS Client Authentication"', async () => { - // const handleSubmit = jest.fn() - // const handleTestConnection = jest.fn() - // render( - //
- // - //
- // ) - // await act(() => { - // fireEvent.click(screen.getByTestId('tls-required-checkbox')) - // }) - // - // const submitBtn = screen.getByTestId(BTN_SUBMIT) - // const testConnectionBtn = screen.getByTestId(BTN_TEST_CONNECTION) - // await act(() => { - // fireEvent.click(testConnectionBtn) - // }) - // expect(handleTestConnection).toBeCalledWith( - // expect.objectContaining({ - // tlsClientAuthRequired: ['on'], - // }) - // ) - // await act(() => { - // fireEvent.click(submitBtn) - // }) - // - // expect(handleSubmit).toBeCalledWith( - // expect.objectContaining({ - // tlsClientAuthRequired: ['on'], - // }) - // ) - // }) - // - // it('should render fields for add new CA with required tls auth and change them properly', async () => { - // const handleSubmit = jest.fn() - // const { container } = render( - //
- // - //
- // ) - // - // expect(screen.getByTestId('select-cert')).toBeInTheDocument() - // - // await act(() => { - // fireEvent.click(screen.getByTestId('select-cert')) - // }) - // - // await act(() => { - // fireEvent.click( - // container.querySelectorAll('.euiContextMenuItem__text')[0] || document - // ) - // }) - // - // expect(screen.getByTestId('new-tsl-cert-pair-name')).toBeInTheDocument() - // await act(() => { - // fireEvent.change(screen.getByTestId('new-tsl-cert-pair-name'), { - // target: { value: '123' }, - // }) - // }) - // - // expect(screen.getByTestId('new-tls-client-cert')).toBeInTheDocument() - // await act(() => { - // fireEvent.change(screen.getByTestId('new-tls-client-cert'), { - // target: { value: '321' }, - // }) - // }) - // - // expect(screen.getByTestId('new-tls-client-cert-key')).toBeInTheDocument() - // await act(() => { - // fireEvent.change(screen.getByTestId('new-tls-client-cert-key'), { - // target: { value: '231' }, - // }) - // }) - // - // const submitBtn = screen.getByTestId(BTN_SUBMIT) - // - // await act(() => { - // fireEvent.click(submitBtn) - // }) - // - // expect(handleSubmit).toBeCalledWith( - // expect.objectContaining({ - // newTlsClientCert: '321', - // newTlsCertPairName: '123', - // newTlsClientKey: '231', - // }) - // ) - // }) - // - // it('should render clone mode btn', () => { - // render( - // - // ) - // expect(screen.getByTestId('clone-db-btn')).toBeTruthy() - // }) - // - // describe('should render proper fields with Clone mode', () => { - // it('should render proper fields for standalone db', () => { - // render( - // - // ) - // const fieldsTestIds = ['host', 'port', 'username', 'password', 'showDb', 'tls'] - // fieldsTestIds.forEach((id) => { - // expect(screen.getByTestId(id)).toBeTruthy() - // }) - // }) - // - // it('should render proper fields for sentinel db', () => { - // render( - // - // ) - // const fieldsTestIds = [ - // 'name', - // 'primary-group', - // 'sentinel-mater-username', - // 'sentinel-master-password', - // 'host', - // 'port', - // 'username', - // 'password', - // 'showDb', - // 'tls' - // ] - // fieldsTestIds.forEach((id) => { - // expect(screen.getByTestId(id)).toBeTruthy() - // }) - // }) - // - // it('should render selected logical database with proper db index', () => { - // render( - // - // ) - // // expect(screen.getByTestId('showDb')).toBeChecked() - // expect(screen.getByTestId('db')).toHaveValue('5') - // }) - // - // it('should render proper database alias', () => { - // render( - // - // ) - // expect(screen.getByTestId('db-alias')).toHaveTextContent('Clone ') - // }) - // - // it('should render proper default values for standalone', () => { - // render( - // - // ) - // expect(screen.getByTestId('host')).toHaveValue('127.0.0.1') - // expect(screen.getByTestId('port')).toHaveValue('6379') - // expect(screen.getByTestId('name')).toHaveValue('127.0.0.1:6379') - // }) - // }) - // - // it('should change Use SSH checkbox', async () => { - // const handleSubmit = jest.fn() - // render( - //
- // - //
- // ) - // - // fireEvent.click(screen.getByTestId('use-ssh')) - // - // expect(screen.getByTestId('use-ssh')).toBeChecked() - // }) - // - // it('should not render Use SSH checkbox for redis stack buidlType', async () => { - // const handleSubmit = jest.fn() - // render( - //
- // - //
- // ) - // - // expect(screen.queryByTestId('use-ssh')).not.toBeInTheDocument() - // }) - // - // it('should change Use SSH checkbox and show proper fields for password radio', async () => { - // const handleSubmit = jest.fn() - // render( - //
- // - //
- // ) - // - // act(() => { - // fireEvent.click(screen.getByTestId('use-ssh')) - // }) - // - // expect(screen.getByTestId('sshHost')).toBeInTheDocument() - // expect(screen.getByTestId('sshPort')).toBeInTheDocument() - // expect(screen.getByTestId('sshPort')).toHaveValue('22') - // expect(screen.getByTestId('sshPassword')).toBeInTheDocument() - // expect(screen.queryByTestId('sshPrivateKey')).not.toBeInTheDocument() - // expect(screen.queryByTestId('sshPassphrase')).not.toBeInTheDocument() - // - // const submitBtn = screen.getByTestId(BTN_SUBMIT) - // expect(submitBtn).toBeDisabled() - // }) - // - // it('should change Use SSH checkbox and show proper fields for passphrase radio', async () => { - // const handleSubmit = jest.fn() - // const { container } = render( - //
- // - //
- // ) - // - // await act(() => { - // fireEvent.click(screen.getByTestId('use-ssh')) - // fireEvent.click( - // container.querySelector(RADIO_BTN_PRIVATE_KEY) as HTMLLabelElement - // ) - // }) - // - // expect(screen.getByTestId('sshHost')).toBeInTheDocument() - // expect(screen.getByTestId('sshPort')).toBeInTheDocument() - // expect(screen.getByTestId('sshPort')).toHaveValue('22') - // expect(screen.queryByTestId('sshPassword')).not.toBeInTheDocument() - // expect(screen.getByTestId('sshPrivateKey')).toBeInTheDocument() - // expect(screen.getByTestId('sshPassphrase')).toBeInTheDocument() - // - // const submitBtn = screen.getByTestId(BTN_SUBMIT) - // expect(submitBtn).toBeDisabled() - // }) - // - // it('should be proper validation for ssh via ssh password', async () => { - // const handleSubmit = jest.fn() - // render( - //
- // - //
- // ) - // - // expect(screen.getByTestId(BTN_SUBMIT)).not.toBeDisabled() - // - // await act(() => { - // fireEvent.click(screen.getByTestId('use-ssh')) - // }) - // - // expect(screen.getByTestId(BTN_SUBMIT)).toBeDisabled() - // - // await act(() => { - // fireEvent.change( - // screen.getByTestId('sshHost'), - // { target: { value: 'localhost' } } - // ) - // }) - // - // expect(screen.getByTestId(BTN_SUBMIT)).toBeDisabled() - // - // await act(() => { - // fireEvent.change( - // screen.getByTestId('sshUsername'), - // { target: { value: 'username' } } - // ) - // }) - // - // expect(screen.getByTestId(BTN_SUBMIT)).not.toBeDisabled() - // }) - // - // it('should be proper validation for ssh via ssh passphrase', async () => { - // const handleSubmit = jest.fn() - // const { container } = render( - //
- // - //
- // ) - // - // expect(screen.getByTestId(BTN_SUBMIT)).not.toBeDisabled() - // - // await act(() => { - // fireEvent.click(screen.getByTestId('use-ssh')) - // fireEvent.click( - // container.querySelector(RADIO_BTN_PRIVATE_KEY) as HTMLLabelElement - // ) - // }) - // - // expect(screen.getByTestId(BTN_SUBMIT)).toBeDisabled() - // - // await act(() => { - // fireEvent.change( - // screen.getByTestId('sshHost'), - // { target: { value: 'localhost' } } - // ) - // fireEvent.change( - // screen.getByTestId('sshUsername'), - // { target: { value: 'username' } } - // ) - // }) - // - // expect(screen.getByTestId(BTN_SUBMIT)).toBeDisabled() - // - // await act(() => { - // fireEvent.change( - // screen.getByTestId('sshPrivateKey'), - // { target: { value: 'PRIVATEKEY' } } - // ) - // }) - // - // expect(screen.getByTestId(BTN_SUBMIT)).not.toBeDisabled() - // }) - // - // it('should call submit btn with proper fields', async () => { - // const handleSubmit = jest.fn() - // render( - //
- // - //
- // ) - // - // await act(() => { - // fireEvent.click(screen.getByTestId('use-ssh')) - // }) - // - // await act(() => { - // fireEvent.change( - // screen.getByTestId('sshHost'), - // { target: { value: 'localhost' } } - // ) - // - // fireEvent.change( - // screen.getByTestId('sshPort'), - // { target: { value: '1771' } } - // ) - // - // fireEvent.change( - // screen.getByTestId('sshUsername'), - // { target: { value: 'username' } } - // ) - // - // fireEvent.change( - // screen.getByTestId('sshPassword'), - // { target: { value: '123' } } - // ) - // }) - // - // await act(() => { - // fireEvent.click(screen.getByTestId(BTN_SUBMIT)) - // }) - // - // expect(handleSubmit).toBeCalledWith( - // expect.objectContaining({ - // sshHost: 'localhost', - // sshPort: '1771', - // sshUsername: 'username', - // sshPassword: '123', - // }) - // ) - // }) - // - // it('should call submit btn with proper fields via passphrase', async () => { - // const handleSubmit = jest.fn() - // const { container } = render( - //
- // - //
- // ) - // - // await act(() => { - // fireEvent.click(screen.getByTestId('use-ssh')) - // fireEvent.click( - // container.querySelector(RADIO_BTN_PRIVATE_KEY) as HTMLLabelElement - // ) - // }) - // - // await act(() => { - // fireEvent.change( - // screen.getByTestId('sshHost'), - // { target: { value: 'localhost' } } - // ) - // - // fireEvent.change( - // screen.getByTestId('sshPort'), - // { target: { value: '1771' } } - // ) - // - // fireEvent.change( - // screen.getByTestId('sshUsername'), - // { target: { value: 'username' } } - // ) - // - // fireEvent.change( - // screen.getByTestId('sshPrivateKey'), - // { target: { value: '123444' } } - // ) - // - // fireEvent.change( - // screen.getByTestId('sshPassphrase'), - // { target: { value: '123444' } } - // ) - // }) - // - // await act(() => { - // fireEvent.click(screen.getByTestId(BTN_SUBMIT)) - // }) - // - // expect(handleSubmit).toBeCalledWith( - // expect.objectContaining({ - // sshHost: 'localhost', - // sshPort: '1771', - // sshUsername: 'username', - // sshPrivateKey: '123444', - // sshPassphrase: '123444', - // }) - // ) - // }) - // - // it('should render password input with 10_000 length limit', () => { - // render( - // - // ) - // - // expect(screen.getByTestId('password')).toHaveAttribute('maxLength', '10000') - // }) - // - // it('should render security fields with proper attributes', () => { - // render( - // - // ) - // - // expect(screen.getByTestId('password')).toHaveAttribute('value', '••••••••••••') - // expect(screen.getByTestId('password')).toHaveAttribute('type', 'password') - // expect(screen.getByTestId('sshPassphrase')).toHaveAttribute('value', '••••••••••••') - // expect(screen.getByTestId('sshPassphrase')).toHaveAttribute('type', 'password') - // - // fireEvent.focus(screen.getByTestId('password')) - // fireEvent.focus(screen.getByTestId('sshPassphrase')) - // - // expect(screen.getByTestId('password')).toHaveAttribute('value', '') - // expect(screen.getByTestId('sshPassphrase')).toHaveAttribute('value', '') - // }) - // - // it('should render ssh password with proper attributes', () => { - // render( - // - // ) - // - // expect(screen.getByTestId('sshPassword')).toHaveAttribute('value', '••••••••••••') - // expect(screen.getByTestId('sshPassword')).toHaveAttribute('type', 'password') - // - // fireEvent.focus(screen.getByTestId('sshPassword')) - // - // expect(screen.getByTestId('sshPassword')).toHaveAttribute('value', '') - // }) - // - // it('should render ssh password input with 10_000 length limit', () => { - // render( - // - // ) - // - // expect(screen.getByTestId('sshPassword')).toHaveAttribute('maxLength', '10000') - // }) - // - // describe('timeout', () => { - // it('should render timeout input with 7 length limit and 1_000_000 value', () => { - // render( - // - // ) - // - // expect(screen.getByTestId('timeout')).toBeInTheDocument() - // expect(screen.getByTestId('timeout')).toHaveAttribute('maxLength', '7') - // - // fireEvent.change( - // screen.getByTestId('timeout'), - // { target: { value: '2000000' } } - // ) - // - // expect(screen.getByTestId('timeout')).toHaveAttribute('value', '1000000') - // }) - // - // it('should put only numbers', () => { - // render( - // - // ) - // - // fireEvent.change( - // screen.getByTestId('timeout'), - // { target: { value: '11a2EU$#@' } } - // ) - // - // expect(screen.getByTestId('timeout')).toHaveAttribute('value', '112') - // }) - // }) - // - // describe('cloud', () => { - // it('some fields should be readonly if instance data source from cloud', () => { - // (appRedirectionSelector as jest.Mock).mockImplementation(() => ({ - // action: UrlHandlingActions.Connect, - // })) - // - // const { queryByTestId } = render( - // - // ) - // - // expect(queryByTestId('connection-type')).not.toBeInTheDocument() - // expect(queryByTestId('host')).not.toBeInTheDocument() - // expect(queryByTestId('port')).not.toBeInTheDocument() - // expect(queryByTestId('db-info-port')).toBeInTheDocument() - // expect(queryByTestId('db-info-host')).toBeInTheDocument() - // }) - // }) -}) diff --git a/redisinsight/ui/src/pages/home/components/manual-connection/manual-connection-form/ManualConnectionFrom.spec.tsx b/redisinsight/ui/src/pages/home/components/manual-connection/manual-connection-form/ManualConnectionFrom.spec.tsx index e86e1d2095..e86da12128 100644 --- a/redisinsight/ui/src/pages/home/components/manual-connection/manual-connection-form/ManualConnectionFrom.spec.tsx +++ b/redisinsight/ui/src/pages/home/components/manual-connection/manual-connection-form/ManualConnectionFrom.spec.tsx @@ -768,11 +768,12 @@ describe('InstanceForm', () => { formFields={{ ...formFields, connectionType: ConnectionType.Standalone, + showDb: true, db: 5 }} /> ) - // expect(screen.getByTestId('showDb')).toBeChecked() + expect(screen.getByTestId('showDb')).toBeChecked() expect(screen.getByTestId('db')).toHaveValue('5') }) @@ -791,17 +792,17 @@ describe('InstanceForm', () => { expect(screen.getByTestId('db-alias')).toHaveTextContent('Clone ') }) - it('should render proper default values for standalone', () => { - render( - - ) - expect(screen.getByTestId('host')).toHaveValue('127.0.0.1') - expect(screen.getByTestId('port')).toHaveValue('6379') - expect(screen.getByTestId('name')).toHaveValue('127.0.0.1:6379') - }) + // it('should render proper default values for standalone', () => { + // render( + // + // ) + // expect(screen.getByTestId('host')).toHaveValue('127.0.0.1') + // expect(screen.getByTestId('port')).toHaveValue('6379') + // expect(screen.getByTestId('name')).toHaveValue('127.0.0.1:6379') + // }) }) it('should change Use SSH checkbox', async () => { @@ -819,7 +820,9 @@ describe('InstanceForm', () => {
) - fireEvent.click(screen.getByTestId('use-ssh')) + act(() => { + fireEvent.click(screen.getByTestId('use-ssh')) + }) expect(screen.getByTestId('use-ssh')).toBeChecked() }) @@ -851,6 +854,7 @@ describe('InstanceForm', () => { {...instance(mockedProps)} formFields={{ ...formFields, + sshPassType: SshPassType.Password, connectionType: ConnectionType.Standalone, }} onSubmit={handleSubmit} @@ -864,7 +868,6 @@ describe('InstanceForm', () => { expect(screen.getByTestId('sshHost')).toBeInTheDocument() expect(screen.getByTestId('sshPort')).toBeInTheDocument() - expect(screen.getByTestId('sshPort')).toHaveValue('22') expect(screen.getByTestId('sshPassword')).toBeInTheDocument() expect(screen.queryByTestId('sshPrivateKey')).not.toBeInTheDocument() expect(screen.queryByTestId('sshPassphrase')).not.toBeInTheDocument() @@ -897,7 +900,6 @@ describe('InstanceForm', () => { expect(screen.getByTestId('sshHost')).toBeInTheDocument() expect(screen.getByTestId('sshPort')).toBeInTheDocument() - expect(screen.getByTestId('sshPort')).toHaveValue('22') expect(screen.queryByTestId('sshPassword')).not.toBeInTheDocument() expect(screen.getByTestId('sshPrivateKey')).toBeInTheDocument() expect(screen.getByTestId('sshPassphrase')).toBeInTheDocument() @@ -915,6 +917,7 @@ describe('InstanceForm', () => { formFields={{ ...formFields, connectionType: ConnectionType.Standalone, + sshPassType: SshPassType.Password, }} onSubmit={handleSubmit} /> @@ -945,6 +948,15 @@ describe('InstanceForm', () => { ) }) + expect(screen.getByTestId(BTN_SUBMIT)).toBeDisabled() + + await act(() => { + fireEvent.change( + screen.getByTestId('sshPort'), + { target: { value: '22' } } + ) + }) + expect(screen.getByTestId(BTN_SUBMIT)).not.toBeDisabled() }) @@ -957,6 +969,7 @@ describe('InstanceForm', () => { formFields={{ ...formFields, connectionType: ConnectionType.Standalone, + sshPassType: SshPassType.Password, }} onSubmit={handleSubmit} /> @@ -979,6 +992,10 @@ describe('InstanceForm', () => { screen.getByTestId('sshHost'), { target: { value: 'localhost' } } ) + fireEvent.change( + screen.getByTestId('sshPort'), + { target: { value: '22' } } + ) fireEvent.change( screen.getByTestId('sshUsername'), { target: { value: 'username' } } @@ -1006,6 +1023,7 @@ describe('InstanceForm', () => { formFields={{ ...formFields, connectionType: ConnectionType.Standalone, + sshPassType: SshPassType.Password, }} onSubmit={handleSubmit} /> @@ -1136,7 +1154,7 @@ describe('InstanceForm', () => { connectionType: ConnectionType.Standalone, ssh: true, password: true, - sshOptions: { host: 'host', port: 123, passphrase: true }, + sshPassphrase: true, sshPassType: SshPassType.PrivateKey, }} /> @@ -1162,7 +1180,7 @@ describe('InstanceForm', () => { ...formFields, connectionType: ConnectionType.Standalone, ssh: true, - sshOptions: { host: 'host', port: 123, password: true }, + sshPassword: true, sshPassType: SshPassType.Password, }} /> @@ -1180,7 +1198,12 @@ describe('InstanceForm', () => { render( ) diff --git a/redisinsight/ui/src/pages/home/components/sentinel-connection/SentinelConnectionWrapper.spec.tsx b/redisinsight/ui/src/pages/home/components/sentinel-connection/SentinelConnectionWrapper.spec.tsx new file mode 100644 index 0000000000..c4f4c6886d --- /dev/null +++ b/redisinsight/ui/src/pages/home/components/sentinel-connection/SentinelConnectionWrapper.spec.tsx @@ -0,0 +1,66 @@ +import React from 'react' +import { instance, mock } from 'ts-mockito' +import { fireEvent, render, screen } from 'uiSrc/utils/test-utils' +import SentinelConnectionForm, { Props as SentinelConnectionFormProps } from + 'uiSrc/pages/home/components/sentinel-connection/sentinel-connection-form/SentinelConnectionForm' +import SentinelConnectionWrapper, { + Props, +} from './SentinelConnectionWrapper' + +const mockedProps = mock() + +jest.mock('./sentinel-connection-form/SentinelConnectionForm', () => ({ + __esModule: true, + namedExport: jest.fn(), + default: jest.fn(), +})) + +const mockSentinelConnectionForm = (props: SentinelConnectionFormProps) => ( +
+ + + +
+) + +describe('SentinelConnectionWrapper', () => { + beforeAll(() => { + SentinelConnectionForm.mockImplementation(mockSentinelConnectionForm) + }) + it('should render', () => { + expect( + render() + ).toBeTruthy() + }) + + it('should call onHostNamePaste', () => { + const component = render() + fireEvent.click(screen.getByTestId('onHostNamePaste-btn')) + expect(component).toBeTruthy() + }) + + it('should call onClose', () => { + const onClose = jest.fn() + render() + fireEvent.click(screen.getByTestId('onClose-btn')) + expect(onClose).toBeCalled() + }) +}) diff --git a/redisinsight/ui/src/pages/home/utils/form.tsx b/redisinsight/ui/src/pages/home/utils/form.tsx index 4d002076a3..c11312af5f 100644 --- a/redisinsight/ui/src/pages/home/utils/form.tsx +++ b/redisinsight/ui/src/pages/home/utils/form.tsx @@ -287,7 +287,7 @@ export const getSubmitButtonContent = (errors: FormikErrors, s } export const getFormValues = (instance?: Nullable>) => ({ - id: instance?.id, + ...instance, host: instance?.host ?? (instance ? '' : DEFAULT_HOST), port: instance?.port?.toString() ?? (instance ? '' : DEFAULT_PORT), timeout: instance?.timeout @@ -314,8 +314,8 @@ export const getFormValues = (instance?: Nullable>) => ({ newTlsClientCert: '', newTlsClientKey: '', sentinelMasterName: instance?.sentinelMaster?.name || '', - sentinelMasterUsername: instance?.sentinelMasterUsername, - sentinelMasterPassword: instance?.sentinelMasterPassword, + sentinelMasterUsername: instance?.sentinelMaster?.username, + sentinelMasterPassword: instance?.sentinelMaster?.password, ssh: instance?.ssh ?? false, sshPassType: instance?.sshOptions ? (instance.sshOptions.privateKey ? SshPassType.PrivateKey : SshPassType.Password) From 370b69f7b4afe7f3dccb0a2e9af7a39a00d78645 Mon Sep 17 00:00:00 2001 From: Amir Allayarov Date: Thu, 9 Nov 2023 17:28:38 +0400 Subject: [PATCH 07/11] #RI-5009 - fix tests --- .../database-alias/DatabaseAlias.spec.tsx | 27 ------------------- .../DatabasesListWrapper.spec.tsx | 4 +-- 2 files changed, 2 insertions(+), 29 deletions(-) diff --git a/redisinsight/ui/src/pages/home/components/database-alias/DatabaseAlias.spec.tsx b/redisinsight/ui/src/pages/home/components/database-alias/DatabaseAlias.spec.tsx index 87cc8a70db..889a36bd66 100644 --- a/redisinsight/ui/src/pages/home/components/database-alias/DatabaseAlias.spec.tsx +++ b/redisinsight/ui/src/pages/home/components/database-alias/DatabaseAlias.spec.tsx @@ -22,25 +22,6 @@ describe('DatabaseAlias', () => { expect(render()).toBeTruthy() }) - it('should call onApplyChanges on edit alias', () => { - const onApply = jest.fn() - render() - - fireEvent.click(screen.getByTestId('edit-alias-btn')) - fireEvent.change(screen.getByTestId('alias-input'), { target: { value: 'alias' } }) - fireEvent.submit(screen.getByTestId('alias-input')) - - expect(onApply).toHaveBeenCalledWith('alias', expect.anything(), expect.anything()) - }) - - it('should call onOpen', () => { - const onOpen = jest.fn() - render() - - fireEvent.click(screen.getByTestId('connect-to-db-btn')) - expect(onOpen).toHaveBeenCalled() - }) - it('should not render part of content in edit mode', () => { render() @@ -48,14 +29,6 @@ describe('DatabaseAlias', () => { expect(screen.queryByTestId('db-alias')).toHaveTextContent('alias') }) - it('should call onCloneBack in clone mode', () => { - const onCloneBack = jest.fn() - render() - - fireEvent.click(screen.getByTestId('back-btn')) - expect(onCloneBack).toHaveBeenCalled() - }) - it('should render icon for redis-stack', () => { render() diff --git a/redisinsight/ui/src/pages/home/components/databases-list-component/DatabasesListWrapper.spec.tsx b/redisinsight/ui/src/pages/home/components/databases-list-component/DatabasesListWrapper.spec.tsx index 3c2a57ab72..c1f6d74d86 100644 --- a/redisinsight/ui/src/pages/home/components/databases-list-component/DatabasesListWrapper.spec.tsx +++ b/redisinsight/ui/src/pages/home/components/databases-list-component/DatabasesListWrapper.spec.tsx @@ -11,11 +11,11 @@ import { RootState, store } from 'uiSrc/slices/store' import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' import { errorHandlers } from 'uiSrc/mocks/res/responseComposition' import DatabasesListWrapper, { Props } from './DatabasesListWrapper' -import DatabasesList, { Props as DatabasesListProps } from './databases-list' +import DatabasesList, { Props as DatabasesListProps } from './databases-list/DatabasesList' const mockedProps = mock() -jest.mock('./databases-list/databases-list', () => ({ +jest.mock('./databases-list/DatabasesList', () => ({ __esModule: true, namedExport: jest.fn(), default: jest.fn(), From bfb1487c06cfa5d087fcd87c4f46a03035f0669e Mon Sep 17 00:00:00 2001 From: Amir Allayarov Date: Fri, 10 Nov 2023 12:19:47 +0400 Subject: [PATCH 08/11] #RI-5009 - add tests --- .../ManualConnectionWrapper.spec.tsx | 139 ++++++++++++++++++ .../SentinelConnectionWrapper.spec.tsx | 24 ++- .../SentinelConnectionForm.spec.tsx | 18 ++- .../SentinelConnectionForm.tsx | 3 - 4 files changed, 179 insertions(+), 5 deletions(-) create mode 100644 redisinsight/ui/src/pages/home/components/manual-connection/ManualConnectionWrapper.spec.tsx diff --git a/redisinsight/ui/src/pages/home/components/manual-connection/ManualConnectionWrapper.spec.tsx b/redisinsight/ui/src/pages/home/components/manual-connection/ManualConnectionWrapper.spec.tsx new file mode 100644 index 0000000000..0ee1ee6e8a --- /dev/null +++ b/redisinsight/ui/src/pages/home/components/manual-connection/ManualConnectionWrapper.spec.tsx @@ -0,0 +1,139 @@ +import React from 'react' +import { instance, mock } from 'ts-mockito' +import { act } from '@testing-library/react' +import { fireEvent, render, screen } from 'uiSrc/utils/test-utils' +import { + DEFAULT_TIMEOUT, + SubmitBtnText, +} from 'uiSrc/pages/home/constants' +import ManualConnectionFrom, { Props as ManualConnectionFromProps } from + 'uiSrc/pages/home/components/manual-connection/manual-connection-form/ManualConnectionForm' +import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' +import SentinelConnectionWrapper from 'uiSrc/pages/home/components/sentinel-connection' +import ManualConnectionWrapper, { + Props, +} from './ManualConnectionWrapper' + +const mockedProps = mock() + +jest.mock('./manual-connection-form/ManualConnectionForm', () => ({ + __esModule: true, + namedExport: jest.fn(), + default: jest.fn(), +})) + +jest.mock('uiSrc/telemetry', () => ({ + ...jest.requireActual('uiSrc/telemetry'), + sendEventTelemetry: jest.fn(), +})) + +const mockManualConnectionFrom = (props: ManualConnectionFromProps) => ( +
+ + + + +
+) + +describe('ManualConnectionWrapper', () => { + beforeAll(() => { + ManualConnectionFrom.mockImplementation(mockManualConnectionFrom) + }) + it('should render', () => { + expect( + render() + ).toBeTruthy() + }) + + it('should call onHostNamePaste', () => { + const component = render() + fireEvent.click(screen.getByTestId('onHostNamePaste-btn')) + expect(component).toBeTruthy() + }) + + it('should call onClose', () => { + const onClose = jest.fn() + render() + fireEvent.click(screen.getByTestId('onClose-btn')) + expect(onClose).toBeCalled() + }) + + it('should have add database submit button', () => { + render() + expect(screen.getByTestId('btn-submit')).toHaveTextContent(SubmitBtnText.AddDatabase) + }) + + it('should have edit database submit button', () => { + render() + expect(screen.getByTestId('btn-submit')).toHaveTextContent(SubmitBtnText.EditDatabase) + }) + + it('should have edit database submit button', () => { + render() + act(() => { + fireEvent.click(screen.getByTestId('onClone-btn')) + }) + expect(screen.getByTestId('btn-submit')).toHaveTextContent(SubmitBtnText.CloneDatabase) + }) + + it('should call proper telemetry event on Add database', () => { + const sendEventTelemetryMock = jest.fn() + + sendEventTelemetry.mockImplementation(() => sendEventTelemetryMock) + + sendEventTelemetry.mockRestore() + render() + act(() => { + fireEvent.click(screen.getByTestId('btn-submit')) + }) + + expect(sendEventTelemetry).toBeCalledWith({ + event: TelemetryEvent.CONFIG_DATABASES_MANUALLY_SUBMITTED, + }) + }) + + it('should call proper telemetry event on Clone database', () => { + const sendEventTelemetryMock = jest.fn() + + sendEventTelemetry.mockImplementation(() => sendEventTelemetryMock) + + sendEventTelemetry.mockRestore() + render() + act(() => { + fireEvent.click(screen.getByTestId('onClone-btn')) + }) + act(() => { + fireEvent.click(screen.getByTestId('onClose-btn')) + }) + + expect(sendEventTelemetry).toBeCalledWith({ + event: TelemetryEvent.CONFIG_DATABASES_DATABASE_CLONE_CANCELLED, + eventData: { databaseId: undefined } + }) + }) +}) diff --git a/redisinsight/ui/src/pages/home/components/sentinel-connection/SentinelConnectionWrapper.spec.tsx b/redisinsight/ui/src/pages/home/components/sentinel-connection/SentinelConnectionWrapper.spec.tsx index c4f4c6886d..b37d75b206 100644 --- a/redisinsight/ui/src/pages/home/components/sentinel-connection/SentinelConnectionWrapper.spec.tsx +++ b/redisinsight/ui/src/pages/home/components/sentinel-connection/SentinelConnectionWrapper.spec.tsx @@ -3,6 +3,7 @@ import { instance, mock } from 'ts-mockito' import { fireEvent, render, screen } from 'uiSrc/utils/test-utils' import SentinelConnectionForm, { Props as SentinelConnectionFormProps } from 'uiSrc/pages/home/components/sentinel-connection/sentinel-connection-form/SentinelConnectionForm' +import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' import SentinelConnectionWrapper, { Props, } from './SentinelConnectionWrapper' @@ -15,6 +16,11 @@ jest.mock('./sentinel-connection-form/SentinelConnectionForm', () => ({ default: jest.fn(), })) +jest.mock('uiSrc/telemetry', () => ({ + ...jest.requireActual('uiSrc/telemetry'), + sendEventTelemetry: jest.fn(), +})) + const mockSentinelConnectionForm = (props: SentinelConnectionFormProps) => (