Skip to content

Commit 09aa6db

Browse files
committed
#RI-6338 - update add database form
1 parent 6fc015e commit 09aa6db

File tree

24 files changed

+807
-127
lines changed

24 files changed

+807
-127
lines changed

redisinsight/ui/src/components/oauth/oauth-sso/oauth-autodiscovery/OAuthAutodiscovery.tsx

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,22 @@ import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry'
1414

1515
import { Pages } from 'uiSrc/constants'
1616
import OAuthForm from 'uiSrc/components/oauth/shared/oauth-form'
17+
18+
import CloudIcon from 'uiSrc/assets/img/oauth/cloud_centered.svg?react'
19+
20+
import { OAuthSsoHandlerDialog } from 'uiSrc/components'
21+
import { getUtmExternalLink } from 'uiSrc/utils/links'
22+
import { EXTERNAL_LINKS } from 'uiSrc/constants/links'
1723
import styles from './styles.module.scss'
1824

1925
export interface Props {
2026
inline?: boolean
2127
source?: OAuthSocialSource
28+
onClose?: () => void
2229
}
2330

2431
const OAuthAutodiscovery = (props: Props) => {
25-
const { inline, source = OAuthSocialSource.Autodiscovery } = props
32+
const { inline, source = OAuthSocialSource.Autodiscovery, onClose } = props
2633
const { data } = useSelector(oauthCloudUserSelector)
2734

2835
const [isDiscoverDisabled, setIsDiscoverDisabled] = useState(false)
@@ -94,6 +101,32 @@ const OAuthAutodiscovery = (props: Props) => {
94101
})
95102
}
96103

104+
const CreateFreeDb = () => (
105+
<div className={styles.createDbSection}>
106+
<div className={styles.createDbTitle}><CloudIcon /><span>Start FREE with Redis Cloud</span></div>
107+
<OAuthSsoHandlerDialog>
108+
{(ssoCloudHandlerClick) => (
109+
<EuiButton
110+
fill
111+
color="secondary"
112+
size="s"
113+
href={getUtmExternalLink(EXTERNAL_LINKS.tryFree, { campaign: '' })}
114+
target="_blank"
115+
onClick={(e: React.MouseEvent) => {
116+
ssoCloudHandlerClick(e, {
117+
source: OAuthSocialSource.DiscoveryForm,
118+
action: OAuthSocialAction.Create
119+
})
120+
onClose?.()
121+
}}
122+
>
123+
Quick start
124+
</EuiButton>
125+
)}
126+
</OAuthSsoHandlerDialog>
127+
</div>
128+
)
129+
97130
return (
98131
<div
99132
className={styles.container}
@@ -111,6 +144,8 @@ const OAuthAutodiscovery = (props: Props) => {
111144
Discover subscriptions and add your databases.
112145
A new Redis Cloud account will be created for you if you don’t have one.
113146
</EuiText>
147+
<EuiSpacer size="m" />
148+
<CreateFreeDb />
114149
<EuiSpacer size="xl" />
115150
<EuiText>Get started with</EuiText>
116151
<EuiTitle className={styles.title} size="l"><h3>Redis Cloud account</h3></EuiTitle>

redisinsight/ui/src/components/oauth/oauth-sso/oauth-autodiscovery/styles.module.scss

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,26 @@
5555
font-size: 12px !important;
5656
}
5757
}
58+
59+
.createDbSection {
60+
display: flex;
61+
align-items: center;
62+
justify-content: space-between;
63+
width: 100%;
64+
65+
border: 1px solid var(--controlsBorderColor);
66+
border-radius: 8px;
67+
padding: 8px 16px;
68+
69+
.createDbTitle {
70+
display: flex;
71+
align-items: center;
72+
73+
> svg {
74+
margin-right: 8px;
75+
}
76+
}
77+
}
5878
}
5979

6080
.withAdvantagesWrapper {

redisinsight/ui/src/constants/links.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const UTM_CAMPAINGS: Record<any, string> = {
2424
[OAuthSocialSource.Workbench]: 'redisinsight_workbench',
2525
[CloudSsoUtmCampaign.BrowserFilter]: 'browser_filter',
2626
[OAuthSocialSource.EmptyDatabasesList]: 'empty_db_list',
27+
[OAuthSocialSource.AddDbForm]: 'add_db_form',
2728
PubSub: 'pub_sub',
2829
Main: 'main',
2930
}

redisinsight/ui/src/contexts/ModalTitleProvider.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Nullable } from 'uiSrc/utils'
33

44
interface ModalHeaderContextType {
55
modalHeader: Nullable<React.ReactNode>
6-
setModalHeader: (content: Nullable<React.ReactNode>) => void
6+
setModalHeader: (content: Nullable<React.ReactNode>, withBack?: boolean) => void
77
}
88

99
// Create a context

redisinsight/ui/src/pages/home/HomePage.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ enum OpenDialogName {
5050

5151
const HomePage = () => {
5252
const [openDialog, setOpenDialog] = useState<Nullable<OpenDialogName>>(null)
53-
const initialDbTypeRef = useRef<AddDbType>(AddDbType.cloud)
5453

5554
const dispatch = useDispatch()
5655

@@ -176,7 +175,6 @@ const HomePage = () => {
176175
}
177176

178177
const handleAddInstance = (addDbType = AddDbType.manual) => {
179-
initialDbTypeRef.current = addDbType
180178
setOpenDialog(OpenDialogName.AddDatabase)
181179
dispatch(setEditedInstance(null))
182180
}
@@ -226,7 +224,6 @@ const HomePage = () => {
226224
: handleClose
227225
}
228226
onDbEdited={onDbEdited}
229-
initConnectionType={initialDbTypeRef.current}
230227
/>
231228
<div key="homePage" className="homePage">
232229
{(!isInstanceExists && !loading && !loadingChanging ? (

redisinsight/ui/src/pages/home/components/cloud-connection/CloudConnectionFormWrapper.tsx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ import React, { useEffect } from 'react'
22
import { useDispatch, useSelector } from 'react-redux'
33
import { useHistory } from 'react-router-dom'
44

5+
import { EuiTitle } from '@elastic/eui'
56
import { Pages } from 'uiSrc/constants'
67
import { cloudSelector, fetchSubscriptionsRedisCloud, setSSOFlow } from 'uiSrc/slices/instances/cloud'
78
import { resetErrors } from 'uiSrc/slices/app/notifications'
89
import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry'
910

11+
import { useModalHeader } from 'uiSrc/contexts/ModalTitleProvider'
1012
import CloudConnectionForm from './cloud-connection-form'
1113

1214
export interface Props {
@@ -24,12 +26,19 @@ const CloudConnectionFormWrapper = ({ onClose }: Props) => {
2426
const history = useHistory()
2527
const { loading, credentials } = useSelector(cloudSelector)
2628

27-
useEffect(
28-
() => () => {
29+
const { setModalHeader } = useModalHeader()
30+
31+
useEffect(() => {
32+
setModalHeader(
33+
<EuiTitle size="s"><h4>Discover Cloud databases</h4></EuiTitle>,
34+
true
35+
)
36+
37+
return () => {
38+
setModalHeader(null)
2939
dispatch(resetErrors())
30-
},
31-
[]
32-
)
40+
}
41+
}, [])
3342

3443
const formSubmit = (credentials: ICloudConnectionSubmit) => {
3544
sendEventTelemetry({

redisinsight/ui/src/pages/home/components/cloud-connection/cloud-connection-form/CloudConnectionForm.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,9 +234,11 @@ const CloudConnectionForm = (props: Props) => {
234234
/>
235235
</EuiFlexItem>
236236
</EuiFlexGroup>
237-
<EuiSpacer />
237+
<EuiSpacer size="s" />
238238
</FeatureFlagComponent>
239-
{type === CloudConnectionOptions.Account && (<OAuthAutodiscovery source={OAuthSocialSource.DiscoveryForm} />)}
239+
{type === CloudConnectionOptions.Account && (
240+
<OAuthAutodiscovery source={OAuthSocialSource.DiscoveryForm} onClose={onClose} />
241+
)}
240242
{type === CloudConnectionOptions.ApiKeys && CloudApiForm}
241243
</div>
242244
)

redisinsight/ui/src/pages/home/components/cluster-connection/ClusterConnectionFormWrapper.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React, { useEffect, useRef, useState } from 'react'
22
import { useDispatch, useSelector } from 'react-redux'
33
import { useHistory } from 'react-router-dom'
44

5+
import { EuiTitle } from '@elastic/eui'
56
import {
67
clusterSelector,
78
fetchInstancesRedisCluster,
@@ -12,6 +13,7 @@ import { ICredentialsRedisCluster, InstanceType } from 'uiSrc/slices/interfaces'
1213
import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry'
1314
import { autoFillFormDetails } from 'uiSrc/pages/home/utils'
1415

16+
import { useModalHeader } from 'uiSrc/contexts/ModalTitleProvider'
1517
import ClusterConnectionForm from './cluster-connection-form/ClusterConnectionForm'
1618

1719
export interface Props {
@@ -28,17 +30,23 @@ const ClusterConnectionFormWrapper = ({ onClose }: Props) => {
2830

2931
const history = useHistory()
3032
const dispatch = useDispatch()
33+
const { setModalHeader } = useModalHeader()
3134

3235
const formRef = useRef<HTMLDivElement>(null)
3336

3437
const { loading, credentials } = useSelector(clusterSelector)
3538

36-
useEffect(
37-
() => () => {
39+
useEffect(() => {
40+
setModalHeader(
41+
<EuiTitle size="s"><h4>Redis Software</h4></EuiTitle>,
42+
true
43+
)
44+
45+
return () => {
46+
setModalHeader(null)
3847
dispatch(resetErrors())
39-
},
40-
[]
41-
)
48+
}
49+
}, [])
4250

4351
useEffect(() => {
4452
if (credentials) {
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import React from 'react'
2+
import { mock } from 'ts-mockito'
3+
import { cloneDeep } from 'lodash'
4+
import { render, screen, fireEvent, mockedStore, cleanup, act } from 'uiSrc/utils/test-utils'
5+
6+
import { defaultInstanceChanging } from 'uiSrc/slices/instances/instances'
7+
import { AddDbType } from 'uiSrc/pages/home/constants'
8+
import ConnectionUrl, { Props } from './ConnectionUrl'
9+
10+
const mockedProps = mock<Props>()
11+
12+
let store: typeof mockedStore
13+
beforeEach(() => {
14+
cleanup()
15+
store = cloneDeep(mockedStore)
16+
store.clearActions()
17+
})
18+
19+
describe('ConnectionUrl', () => {
20+
it('should render', () => {
21+
expect(render(<ConnectionUrl {...mockedProps} />)).toBeTruthy()
22+
})
23+
24+
it('should call proper actions with empty connection url', async () => {
25+
render(<ConnectionUrl {...mockedProps} />)
26+
27+
await act(async () => {
28+
fireEvent.click(screen.getByTestId('btn-submit'))
29+
})
30+
31+
expect(store.getActions()).toEqual([defaultInstanceChanging()])
32+
})
33+
34+
it('should disable test connection and submit buttons when connection url is invalid', async () => {
35+
render(<ConnectionUrl {...mockedProps} />)
36+
37+
await act(async () => {
38+
fireEvent.change(
39+
screen.getByTestId('connection-url'),
40+
{ target: { value: 'q' } }
41+
)
42+
})
43+
44+
expect(screen.getByTestId('btn-submit')).toBeDisabled()
45+
expect(screen.getByTestId('btn-test-connection')).toBeDisabled()
46+
})
47+
48+
it('should not disable buttons with proper connection url', async () => {
49+
render(<ConnectionUrl {...mockedProps} />)
50+
51+
await act(async () => {
52+
fireEvent.change(
53+
screen.getByTestId('connection-url'),
54+
{ target: { value: 'redis://localhost:6322' } }
55+
)
56+
})
57+
58+
expect(screen.getByTestId('btn-submit')).not.toBeDisabled()
59+
expect(screen.getByTestId('btn-test-connection')).not.toBeDisabled()
60+
})
61+
62+
it('should call proper actions after click manual settings', async () => {
63+
const onSelectOptionMock = jest.fn()
64+
render(<ConnectionUrl {...mockedProps} onSelectOption={onSelectOptionMock} />)
65+
66+
await act(async () => {
67+
fireEvent.change(
68+
screen.getByTestId('connection-url'),
69+
{ target: { value: 'redis://localhost:6322' } }
70+
)
71+
})
72+
73+
await act(async () => {
74+
fireEvent.click(screen.getByTestId('btn-connection-settings'))
75+
})
76+
77+
expect(onSelectOptionMock).toBeCalledWith(
78+
AddDbType.manual,
79+
{
80+
db: undefined,
81+
host: 'localhost',
82+
name: 'localhost:6322',
83+
password: undefined,
84+
port: 6322,
85+
tls: false,
86+
username: undefined
87+
}
88+
)
89+
})
90+
91+
it('should call proper actions after click connectivity option', async () => {
92+
const onSelectOptionMock = jest.fn()
93+
render(<ConnectionUrl {...mockedProps} onSelectOption={onSelectOptionMock} />)
94+
95+
await act(async () => {
96+
fireEvent.click(screen.getByTestId('option-btn-sentinel'))
97+
})
98+
99+
expect(onSelectOptionMock).toBeCalledWith(AddDbType.sentinel, expect.any(Object))
100+
})
101+
})

0 commit comments

Comments
 (0)