Skip to content

Commit c23a86d

Browse files
Merge pull request #2869 from RedisInsight/fe/feature/RI-5229_deeplink_tls
#RI-5229 - add deeplink tls support
2 parents 6728207 + 1a68a80 commit c23a86d

File tree

5 files changed

+233
-8
lines changed

5 files changed

+233
-8
lines changed

redisinsight/ui/src/components/global-url-handler/GlobalUrlHandler.spec.tsx

Lines changed: 147 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { userSettingsSelector } from 'uiSrc/slices/user/user-settings'
1313
import { addInfiniteNotification } from 'uiSrc/slices/app/notifications'
1414
import { INFINITE_MESSAGES } from 'uiSrc/components/notifications/components'
1515
import { Pages } from 'uiSrc/constants'
16+
import { ADD_NEW, ADD_NEW_CA_CERT } from 'uiSrc/pages/home/constants'
1617
import { UrlHandlingActions } from 'uiSrc/slices/interfaces/urlHandling'
1718
import GlobalUrlHandler from './GlobalUrlHandler'
1819

@@ -131,7 +132,9 @@ describe('GlobalUrlHandler', () => {
131132
password: 'password',
132133
port: 6379,
133134
tls: false,
134-
username: undefined
135+
username: undefined,
136+
caCert: undefined,
137+
clientCert: undefined,
135138
}
136139
}),
137140
]
@@ -141,7 +144,147 @@ describe('GlobalUrlHandler', () => {
141144
expect(pushMock).toBeCalledWith(Pages.home)
142145
})
143146

144-
it('should call proper actions only after consents popup is accepted and open form to add db', async () => {
147+
it('should call proper actions only after consents popup is accepted and open form to add db with caCert', async () => {
148+
const pushMock = jest.fn()
149+
reactRouterDom.useHistory = jest.fn().mockReturnValueOnce({ push: pushMock });
150+
(userSettingsSelector as jest.Mock).mockReturnValueOnce({
151+
config: {},
152+
isShowConsents: false
153+
})
154+
155+
const url = `${fromUrl}&requiredCaCert=true`;
156+
157+
(appRedirectionSelector as jest.Mock).mockReturnValueOnce({
158+
fromUrl: url
159+
})
160+
161+
await act(() => {
162+
render(<GlobalUrlHandler />)
163+
})
164+
165+
const actionUrl = new URL(url)
166+
const fromParams = new URLSearchParams(actionUrl.search)
167+
// @ts-ignore
168+
const urlProperties = Object.fromEntries(fromParams) || {}
169+
urlProperties.cloudId = urlProperties.cloudBdbId
170+
delete urlProperties.cloudBdbId
171+
172+
const expectedActions = [
173+
setUrlProperties(urlProperties),
174+
setFromUrl(null),
175+
setUrlDbConnection({
176+
action: UrlHandlingActions.Connect,
177+
dbConnection: {
178+
host: 'localhost',
179+
name: 'My Name',
180+
password: 'password',
181+
port: 6379,
182+
tls: true,
183+
caCert: { id: ADD_NEW_CA_CERT },
184+
username: 'default',
185+
}
186+
})
187+
]
188+
189+
expect(store.getActions().slice(0, expectedActions.length)).toEqual(expectedActions)
190+
expect(pushMock).toBeCalledWith(Pages.home)
191+
})
192+
193+
it('should call proper actions only after consents popup is accepted and open form to add db with client cert', async () => {
194+
const pushMock = jest.fn()
195+
reactRouterDom.useHistory = jest.fn().mockReturnValueOnce({ push: pushMock });
196+
(userSettingsSelector as jest.Mock).mockReturnValueOnce({
197+
config: {},
198+
isShowConsents: false
199+
})
200+
201+
const url = `${fromUrl}&requiredClientCert=true`;
202+
203+
(appRedirectionSelector as jest.Mock).mockReturnValueOnce({
204+
fromUrl: url
205+
})
206+
207+
await act(() => {
208+
render(<GlobalUrlHandler />)
209+
})
210+
211+
const actionUrl = new URL(url)
212+
const fromParams = new URLSearchParams(actionUrl.search)
213+
// @ts-ignore
214+
const urlProperties = Object.fromEntries(fromParams) || {}
215+
urlProperties.cloudId = urlProperties.cloudBdbId
216+
delete urlProperties.cloudBdbId
217+
218+
const expectedActions = [
219+
setUrlProperties(urlProperties),
220+
setFromUrl(null),
221+
setUrlDbConnection({
222+
action: UrlHandlingActions.Connect,
223+
dbConnection: {
224+
host: 'localhost',
225+
name: 'My Name',
226+
password: 'password',
227+
port: 6379,
228+
tls: true,
229+
caCert: undefined,
230+
clientCert: { id: ADD_NEW },
231+
username: 'default',
232+
}
233+
})
234+
]
235+
236+
expect(store.getActions().slice(0, expectedActions.length)).toEqual(expectedActions)
237+
expect(pushMock).toBeCalledWith(Pages.home)
238+
})
239+
240+
it('should call proper actions only after consents popup is accepted and open form to add db with tls certs', async () => {
241+
const pushMock = jest.fn()
242+
reactRouterDom.useHistory = jest.fn().mockReturnValueOnce({ push: pushMock });
243+
(userSettingsSelector as jest.Mock).mockReturnValueOnce({
244+
config: {},
245+
isShowConsents: false
246+
})
247+
248+
const url = `${fromUrl}&requiredCaCert=true&requiredClientCert=true`;
249+
250+
(appRedirectionSelector as jest.Mock).mockReturnValueOnce({
251+
fromUrl: url
252+
})
253+
254+
await act(() => {
255+
render(<GlobalUrlHandler />)
256+
})
257+
258+
const actionUrl = new URL(url)
259+
const fromParams = new URLSearchParams(actionUrl.search)
260+
// @ts-ignore
261+
const urlProperties = Object.fromEntries(fromParams) || {}
262+
urlProperties.cloudId = urlProperties.cloudBdbId
263+
delete urlProperties.cloudBdbId
264+
265+
const expectedActions = [
266+
setUrlProperties(urlProperties),
267+
setFromUrl(null),
268+
setUrlDbConnection({
269+
action: UrlHandlingActions.Connect,
270+
dbConnection: {
271+
host: 'localhost',
272+
name: 'My Name',
273+
password: 'password',
274+
port: 6379,
275+
tls: true,
276+
caCert: { id: ADD_NEW_CA_CERT },
277+
clientCert: { id: ADD_NEW },
278+
username: 'default',
279+
}
280+
})
281+
]
282+
283+
expect(store.getActions().slice(0, expectedActions.length)).toEqual(expectedActions)
284+
expect(pushMock).toBeCalledWith(Pages.home)
285+
})
286+
287+
it('should call proper actions only after consents popup is accepted and open form to add db with tls', async () => {
145288
const pushMock = jest.fn()
146289
reactRouterDom.useHistory = jest.fn().mockReturnValueOnce({ push: pushMock });
147290
(userSettingsSelector as jest.Mock).mockReturnValueOnce({
@@ -177,6 +320,8 @@ describe('GlobalUrlHandler', () => {
177320
password: 'password',
178321
port: 6379,
179322
tls: true,
323+
caCert: undefined,
324+
clientCert: undefined,
180325
username: 'default',
181326
}
182327
})

redisinsight/ui/src/components/global-url-handler/GlobalUrlHandler.tsx

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import { useHistory, useLocation } from 'react-router-dom'
22
import { useDispatch, useSelector } from 'react-redux'
33
import { useEffect } from 'react'
44
import { ConnectionString } from 'connection-string'
5-
import { isNull, isNumber, every, values, pick } from 'lodash'
5+
import { isNull, isNumber, every, values, pick, some } from 'lodash'
66
import { Pages, REDIS_URI_SCHEMES } from 'uiSrc/constants'
7+
import { ADD_NEW_CA_CERT, ADD_NEW } from 'uiSrc/pages/home/constants'
78
import {
89
appRedirectionSelector,
910
setFromUrl,
@@ -87,8 +88,10 @@ const GlobalUrlHandler = () => {
8788
const {
8889
redisUrl,
8990
databaseAlias,
90-
requiredTls,
9191
redirect,
92+
requiredTls,
93+
requiredCaCert,
94+
requiredClientCert,
9295
} = properties
9396

9497
const cloudDetails = transformQueryParamsObject(
@@ -107,17 +110,24 @@ const GlobalUrlHandler = () => {
107110
host: url.hostname,
108111
port: url.port,
109112
username: url.user,
110-
password: url.password
113+
password: url.password,
114+
}
115+
116+
const tlsFields = {
117+
requiredTls,
118+
requiredCaCert,
119+
requiredClientCert,
111120
}
112121

113122
const isAllObligatoryProvided = every(values(obligatoryForAutoConnectFields), (value) => value || isNumber(value))
123+
const isTlsProvided = some(values(tlsFields), (value) => value === 'true')
114124

115125
const db = {
116126
...obligatoryForAutoConnectFields,
117127
name: databaseAlias || url.host,
118128
} as any
119129

120-
if (isAllObligatoryProvided && requiredTls !== 'true') {
130+
if (isAllObligatoryProvided && !isTlsProvided) {
121131
if (cloudDetails?.cloudId) {
122132
db.cloudDetails = cloudDetails
123133
}
@@ -131,7 +141,10 @@ const GlobalUrlHandler = () => {
131141
action: UrlHandlingActions.Connect,
132142
dbConnection: {
133143
...db,
134-
tls: requiredTls === 'true',
144+
// set tls with new cert option
145+
tls: isTlsProvided,
146+
caCert: requiredCaCert === 'true' ? { id: ADD_NEW_CA_CERT } : undefined,
147+
clientCert: requiredClientCert === 'true' ? { id: ADD_NEW } : undefined,
135148
}
136149
}))
137150

redisinsight/ui/src/pages/home/components/manual-connection/manual-connection-form/ManualConnectionForm.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ const ManualConnectionForm = (props: Props) => {
137137
initialValues: formFields,
138138
validate,
139139
enableReinitialize: true,
140+
validateOnMount: true,
140141
onSubmit: (values: any) => {
141142
onSubmit(values)
142143
},

tests/e2e/pageObjects/browser-page.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ export class BrowserPage extends InstancePage {
206206
listElementsList = Selector('[data-testid^=list-element-value-]');
207207
jsonKeyValue = Selector('[data-testid=json-data]');
208208
jsonError = Selector('[data-testid=edit-json-error]');
209-
tooltip = Selector('[role=tooltip]');
209+
tooltip = Selector('[role=tooltip]', { timeout: 500 });
210210
noResultsFound = Selector('[data-test-subj=no-result-found]');
211211
noResultsFoundOnly = Selector('[data-testid=no-result-found-only]');
212212
searchAdvices = Selector('[data-test-subj=search-advices]');
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { commonUrl, ossStandaloneRedisGears } from '../../../../helpers/conf';
2+
import { rte } from '../../../../helpers/constants';
3+
import { DatabaseAPIRequests } from '../../../../helpers/api/api-database';
4+
import { Common } from '../../../../helpers/common';
5+
import { MyRedisDatabasePage } from '../../../../pageObjects';
6+
import { DatabaseHelper } from '../../../../helpers/database';
7+
import { BrowserActions } from '../../../../common-actions/browser-actions';
8+
9+
const myRedisDatabasePage = new MyRedisDatabasePage();
10+
const browserActions = new BrowserActions();
11+
12+
const databaseAPIRequests = new DatabaseAPIRequests();
13+
const databaseHelper = new DatabaseHelper();
14+
15+
let { host, port, databaseName, databaseUsername = '', databasePassword = '' } = ossStandaloneRedisGears;
16+
17+
function generateLink(params: Record<string, any>): string {
18+
const params1 = Common.generateUrlTParams(params);
19+
const from = encodeURIComponent(`${redisConnect}?${params1}`);
20+
return (new URL(`?from=${from}`, commonUrl)).toString();
21+
}
22+
23+
const redisConnect = 'redisinsight://databases/connect';
24+
25+
fixture `Add DB from SM`
26+
.meta({ type: 'critical_path', rte: rte.none })
27+
.afterEach(async() => {
28+
// Delete all existing connections
29+
await databaseAPIRequests.deleteAllDatabasesApi();
30+
})
31+
.beforeEach(async() => {
32+
await databaseHelper.acceptLicenseTerms();
33+
});
34+
test
35+
.page(commonUrl)('Tls dropdown', async t => {
36+
const connectUrlParams = {
37+
redisUrl: `redis://${databaseUsername}:${databasePassword}@${host}:${port}`,
38+
databaseAlias: databaseName,
39+
redirect: 'workbench',
40+
requiredCaCert: 'true',
41+
requiredClientCert: 'true'
42+
};
43+
44+
const tooltipText = [
45+
'CA Certificate Name',
46+
'CA certificate',
47+
'Client Certificate Name',
48+
'Client Certificate',
49+
'Private Key'
50+
];
51+
52+
await t.navigateTo(generateLink(connectUrlParams));
53+
await t.expect(myRedisDatabasePage.AddRedisDatabase.caCertField.textContent).contains('Add new CA certificate', 'add CA certificate is not shown');
54+
await t.expect(myRedisDatabasePage.AddRedisDatabase.clientCertField.textContent).contains('Add new certificate', 'add client certificate is not shown');
55+
await t.hover(myRedisDatabasePage.AddRedisDatabase.addRedisDatabaseButton);
56+
57+
for (const text of tooltipText) {
58+
await browserActions.verifyTooltipContainsText(text, true);
59+
}
60+
61+
// Verify that user can see the Test Connection button enabled/disabled with the same rules as the button to add/apply the changes
62+
await t.hover(myRedisDatabasePage.AddRedisDatabase.testConnectionBtn);
63+
for (const text of tooltipText) {
64+
await browserActions.verifyTooltipContainsText(text, true);
65+
}
66+
});

0 commit comments

Comments
 (0)