Skip to content

Commit 5b2b3f3

Browse files
committed
#RIVS-299 - Add the changes from the main project
1 parent 73460be commit 5b2b3f3

File tree

39 files changed

+769
-92
lines changed

39 files changed

+769
-92
lines changed

l10n/bundle.l10n.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
"Key Name": "Key Name",
4242
" will be deleted.": " will be deleted.",
4343
"Delete": "Delete",
44-
"will be deleted from Redis for VS Code.": "will be deleted from Redis for VS Code.",
44+
"will be removed from Redis for VS Code.": "will be removed from Redis for VS Code.",
4545
"Key Size": "Key Size",
4646
"Key Size: ": "Key Size: ",
4747
"Length": "Length",
@@ -295,9 +295,6 @@
295295
"Host:": "Host:",
296296
"Database Index:": "Database Index:",
297297
"Modules:": "Modules:",
298-
"Select Logical Database": "Select Logical Database",
299-
"Database Index": "Database Index",
300-
"Enter Database Index": "Enter Database Index",
301298
"No decompression": "No decompression",
302299
"Enable automatic data decompression": "Enable automatic data decompression",
303300
"Decompression format": "Decompression format",

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@
143143
"download:backend": "tsc ./scripts/downloadBackend.ts && node ./scripts/downloadBackend.js",
144144
"dev": "vite dev",
145145
"dev:key": "cross-env RI_DATA_ROUTE=main/key vite dev",
146+
"dev:database": "cross-env RI_DATA_ROUTE=main/add_database vite dev",
146147
"dev:sidebar": "cross-env RI_DATA_ROUTE=sidebar vite dev",
147148
"l10n:collect": "npx @vscode/l10n-dev export -o ./l10n ./src",
148149
"watch": "tsc -watch -p ./",
@@ -286,7 +287,7 @@
286287
"react-inlinesvg": "^4.1.1",
287288
"react-monaco-editor": "^0.55.0",
288289
"react-router-dom": "^6.17.0",
289-
"react-select": "^5.8.0",
290+
"react-select": "^5.8.3",
290291
"react-spinners": "^0.13.8",
291292
"react-virtualized": "^9.22.5",
292293
"react-virtualized-auto-sizer": "^1.0.20",

src/webviews/src/components/database-form/TlsDetails.tsx

Lines changed: 67 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,61 @@ import React, { ChangeEvent, useId } from 'react'
22
import cx from 'classnames'
33
import { FormikProps } from 'formik'
44
import * as l10n from '@vscode/l10n'
5-
import { VSCodeDivider } from '@vscode/webview-ui-toolkit/react'
65
import { CheckboxChangeEvent } from 'rc-checkbox'
6+
import { find } from 'lodash'
7+
import { MenuListProps } from 'react-select'
78

8-
import { validateCertName, validateField } from 'uiSrc/utils'
9+
import { sendEventTelemetry, TelemetryEvent, validateCertName, validateField } from 'uiSrc/utils'
910
import {
1011
ADD_NEW,
1112
ADD_NEW_CA_CERT,
12-
ADD_NEW_CA_CERT_LABEL,
13-
ADD_NEW_LABEL,
13+
ApiEndpoints,
1414
NO_CA_CERT,
15-
NO_CA_CERT_LABEL,
1615
} from 'uiSrc/constants'
17-
import { DbConnectionInfo } from 'uiSrc/interfaces'
18-
import { Checkbox, InputText, Select, TextArea } from 'uiSrc/ui'
16+
import { DbConnectionInfo, RedisString } from 'uiSrc/interfaces'
17+
import { Checkbox, InputText, TextArea } from 'uiSrc/ui'
18+
import { removeCertAction } from 'uiSrc/store'
19+
import { SuperSelectRemovableOption, SuperSelect, SuperSelectOption } from 'uiSrc/components'
1920
import styles from './styles.module.scss'
2021

22+
const suffix = '_tls_details'
23+
2124
export interface Props {
2225
formik: FormikProps<DbConnectionInfo>
2326
caCertificates?: { id: string, name: string }[]
2427
certificates?: { id: string, name: string }[]
2528
}
29+
2630
const TlsDetails = (props: Props) => {
2731
const { formik, caCertificates, certificates } = props
2832
const id = useId()
2933

30-
const optionsCertsCA = [
31-
{
32-
value: NO_CA_CERT,
33-
label: NO_CA_CERT_LABEL,
34-
},
35-
{
36-
value: ADD_NEW_CA_CERT,
37-
label: ADD_NEW_CA_CERT_LABEL,
38-
},
34+
const handleDeleteCaCert = (id: RedisString, onSuccess?: () => void) => {
35+
removeCertAction(id, ApiEndpoints.CA_CERTIFICATES, () => {
36+
onSuccess?.()
37+
handleClickDeleteCert('CA')
38+
})
39+
}
40+
41+
const handleDeleteClientCert = (id: RedisString, onSuccess?: () => void) => {
42+
removeCertAction(id, ApiEndpoints.CLIENT_CERTIFICATES, () => {
43+
onSuccess?.()
44+
handleClickDeleteCert('Client')
45+
})
46+
}
47+
48+
const handleClickDeleteCert = (certificateType: 'Client' | 'CA') => {
49+
sendEventTelemetry({
50+
event: TelemetryEvent.CONFIG_DATABASES_CERTIFICATE_REMOVED,
51+
eventData: {
52+
certificateType,
53+
},
54+
})
55+
}
56+
57+
const optionsCertsCA: SuperSelectOption[] = [
58+
NO_CA_CERT,
59+
ADD_NEW_CA_CERT,
3960
]
4061

4162
caCertificates?.forEach((cert) => {
@@ -45,12 +66,7 @@ const TlsDetails = (props: Props) => {
4566
})
4667
})
4768

48-
const optionsCertsClient = [
49-
{
50-
value: ADD_NEW,
51-
label: ADD_NEW_LABEL,
52-
},
53-
]
69+
const optionsCertsClient: SuperSelectOption[] = [ADD_NEW]
5470

5571
certificates?.forEach((cert) => {
5672
optionsCertsClient.push({
@@ -130,25 +146,30 @@ const TlsDetails = (props: Props) => {
130146
<div className="w-[100px]">
131147
{`${l10n.t('CA Certificate')}${formik.values.verifyServerTlsCert ? '*' : ''}`}
132148
</div>
133-
<Select
134-
// name="selectedCaCertName"
135-
// placeholder="Select CA certificate"
149+
<SuperSelect
136150
containerClassName="database-form-select w-[256px]"
137-
itemClassName="database-form-select__option"
138-
idSelected={formik.values.selectedCaCertName ?? NO_CA_CERT}
151+
selectedOption={find(optionsCertsCA, { value: formik.values.selectedCaCertName }) as SuperSelectOption ?? NO_CA_CERT}
139152
options={optionsCertsCA}
140-
onChange={(value) => {
153+
components={{ MenuList: (props: MenuListProps<SuperSelectOption, false>) => (
154+
<SuperSelectRemovableOption
155+
{...props}
156+
suffix={suffix}
157+
countDefaultOptions={2}
158+
onDeleteOption={handleDeleteCaCert}
159+
/>
160+
) }}
161+
onChange={(option) => {
141162
formik.setFieldValue(
142163
'selectedCaCertName',
143-
value || NO_CA_CERT,
164+
option?.value || NO_CA_CERT?.value,
144165
)
145166
}}
146167
testid="select-ca-cert"
147168
/>
148169
</div>
149170

150171
{formik.values.tls
151-
&& formik.values.selectedCaCertName === ADD_NEW_CA_CERT && (
172+
&& formik.values.selectedCaCertName === ADD_NEW_CA_CERT.value && (
152173
<div className="mb-3">
153174
<InputText
154175
name="newCaCertName"
@@ -170,7 +191,7 @@ const TlsDetails = (props: Props) => {
170191
</div>
171192

172193
{formik.values.tls
173-
&& formik.values.selectedCaCertName === ADD_NEW_CA_CERT && (
194+
&& formik.values.selectedCaCertName === ADD_NEW_CA_CERT.value && (
174195
<div>
175196
<TextArea
176197
name="newCaCert"
@@ -203,13 +224,23 @@ const TlsDetails = (props: Props) => {
203224
<div>
204225
<div className="flex items-center pb-3">
205226
<div className="w-[100px]">{l10n.t('Client Certificate*')}</div>
206-
<Select
227+
<SuperSelect
207228
containerClassName="database-form-select w-[256px]"
208-
itemClassName="database-form-select__option"
229+
selectedOption={find(optionsCertsClient, { value: formik.values.selectedTlsClientCertId }) as SuperSelectOption ?? ADD_NEW}
209230
options={optionsCertsClient}
210-
idSelected={formik.values.selectedTlsClientCertId ?? ADD_NEW}
211-
onChange={(value) => {
212-
formik.setFieldValue('selectedTlsClientCertId', value)
231+
components={{ MenuList: (props: MenuListProps<SuperSelectOption, false>) => (
232+
<SuperSelectRemovableOption
233+
{...props}
234+
countDefaultOptions={1}
235+
suffix={suffix}
236+
onDeleteOption={handleDeleteClientCert}
237+
/>
238+
) }}
239+
onChange={(option) => {
240+
formik.setFieldValue(
241+
'selectedTlsClientCertId',
242+
option?.value || ADD_NEW?.value,
243+
)
213244
}}
214245
testid="select-cert"
215246
/>

src/webviews/src/components/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ export { FieldMessage } from './field-message/FieldMessage'
1010
export { NoDatabases } from './no-databases/NoDatabases'
1111
export { MonacoLanguages } from './monaco-languages/MonacoLanguages'
1212
export { UploadFile } from './upload-file/UploadFile'
13+
export { SuperSelect } from './super-select/SuperSelect'
14+
export { SuperSelectRemovableOption } from './super-select/components/removable-option/RemovableOption'
1315
export * from './database-form'
1416
export * from './consents-option'
1517
export * from './consents-privacy'
18+
19+
export type { SuperSelectOption } from './super-select/SuperSelect'

src/webviews/src/components/popover-delete/PopoverDelete.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ const PopoverDelete = (props: Props) => {
6666
<Popup
6767
key={item}
6868
ref={ref}
69+
nested={false}
6970
closeOnEscape
7071
closeOnDocumentClick
7172
repositionOnResize
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import React from 'react'
2+
import { instance, mock } from 'ts-mockito'
3+
4+
import { render, constants, fireEvent, waitFor } from 'testSrc/helpers'
5+
import { SuperSelect, Props } from './SuperSelect'
6+
7+
const mockedProps = mock<Props>()
8+
const testIdMock = 'my-select-component'
9+
10+
describe('SuperSelect', () => {
11+
it('should render', async () => {
12+
expect(render(
13+
<SuperSelect {...instance(mockedProps)} options={constants.SUPER_SELECT_OPTIONS} />),
14+
).toBeTruthy()
15+
})
16+
17+
it('should call onChange when the first option is selected', async () => {
18+
const mockedOnChange = vi.fn()
19+
const mockedLabel = constants.SUPER_SELECT_OPTIONS?.[0].label ?? ''
20+
const mockedValue = constants.SUPER_SELECT_OPTIONS?.[0].value ?? ''
21+
22+
const { getByText, queryByTestId } = render(<SuperSelect
23+
options={constants.SUPER_SELECT_OPTIONS}
24+
onChange={mockedOnChange}
25+
testid={testIdMock}
26+
/>)
27+
28+
const mySelectComponent = queryByTestId('my-select-component')
29+
30+
expect(mySelectComponent).toBeDefined()
31+
expect(mySelectComponent).not.toBeNull()
32+
expect(mockedOnChange).toHaveBeenCalledTimes(0)
33+
34+
fireEvent.keyDown(mySelectComponent?.firstChild!, { key: 'ArrowDown' })
35+
await waitFor(() => getByText(mockedLabel))
36+
fireEvent.click(getByText(mockedLabel))
37+
38+
expect(mockedOnChange).toHaveBeenCalledTimes(1)
39+
expect(mockedOnChange).toHaveBeenCalledWith(
40+
{ label: mockedLabel, value: mockedValue },
41+
{ action: 'select-option', name: undefined, option: undefined })
42+
})
43+
})
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import React, { FC } from 'react'
2+
import cx from 'classnames'
3+
import Select, { Props as SelectProps } from 'react-select'
4+
5+
import { Maybe } from 'uiSrc/interfaces'
6+
import styles from './styles.module.scss'
7+
8+
export interface SuperSelectOption {
9+
value: string
10+
label: string
11+
testid?: string
12+
}
13+
14+
export interface Props extends SelectProps<SuperSelectOption, false> {
15+
selectedOption?: Maybe<SuperSelectOption>
16+
containerClassName?: string
17+
itemClassName?: string
18+
testid?: string
19+
}
20+
21+
const SuperSelect: FC<Props> = (props) => {
22+
const {
23+
selectedOption,
24+
containerClassName,
25+
testid,
26+
} = props
27+
28+
return (
29+
<div className={cx(styles.container, containerClassName)} data-testid={testid}>
30+
<Select<SuperSelectOption>
31+
{...props}
32+
closeMenuOnSelect
33+
closeMenuOnScroll
34+
isSearchable={false}
35+
isMulti={false}
36+
value={selectedOption}
37+
classNames={{
38+
container: () => styles.selectContainer,
39+
control: () => styles.control,
40+
option: ({ isSelected }) => cx(styles.option, { [styles.optionSelected]: isSelected }),
41+
singleValue: () => styles.singleValue,
42+
menu: () => styles.menu,
43+
indicatorsContainer: () => styles.indicatorsContainer,
44+
indicatorSeparator: () => 'hidden',
45+
}}
46+
/>
47+
</div>
48+
)
49+
}
50+
51+
export { SuperSelect }
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import React from 'react'
2+
import { instance, mock } from 'ts-mockito'
3+
4+
import { render, constants } from 'testSrc/helpers'
5+
import { SuperSelectRemovableOption, Props } from './RemovableOption'
6+
7+
const mockedProps = mock<Props>()
8+
9+
describe('SuperSelectRemovableOption', () => {
10+
it('should render', async () => {
11+
expect(render(<SuperSelectRemovableOption {...instance(mockedProps)} options={constants.SUPER_SELECT_OPTIONS} />)).toBeTruthy()
12+
})
13+
})
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import React, { Children, useCallback, useState } from 'react'
2+
import { MenuListProps } from 'react-select'
3+
import cx from 'classnames'
4+
import * as l10n from '@vscode/l10n'
5+
6+
import { PopoverDelete, SuperSelectOption } from 'uiSrc/components'
7+
import { Maybe, RedisString } from 'uiSrc/interfaces'
8+
9+
import styles from '../../styles.module.scss'
10+
11+
export interface Props extends MenuListProps<SuperSelectOption, false> {
12+
suffix: string
13+
countDefaultOptions: number
14+
onDeleteOption: (id: RedisString, onSuccess: () => void) => void
15+
}
16+
17+
const SuperSelectRemovableOption = (props: Props) => {
18+
const { suffix, countDefaultOptions = 0, options, children, getValue, selectOption, onDeleteOption } = props
19+
20+
const [activeOptionId, setActiveOptionId] = useState<Maybe<string>>(undefined)
21+
22+
const showPopover = useCallback((id = '') => {
23+
setActiveOptionId(`${id}${suffix}`)
24+
}, [])
25+
26+
const handleRemoveOption = (id: RedisString) => {
27+
onDeleteOption(id, () => {
28+
const selectedValue = getValue()?.[0]
29+
setActiveOptionId(undefined)
30+
31+
// reset selected option if removed value is selected
32+
if (selectedValue?.value === id) {
33+
selectOption(options?.[0] as SuperSelectOption)
34+
} else {
35+
selectOption(selectedValue)
36+
}
37+
})
38+
}
39+
40+
return (
41+
<div>
42+
{Children.map(children, (child, i) => (
43+
<div key={(options[i] as SuperSelectOption).value} className={cx(styles.option, 'flex justify-between items-center relative')}>
44+
{child}
45+
{i + 1 > countDefaultOptions && <PopoverDelete
46+
header={`${(options[i] as SuperSelectOption).label}`}
47+
text={l10n.t('will be removed from Redis for VS Code.')}
48+
item={(options[i] as SuperSelectOption).value}
49+
suffix={suffix}
50+
triggerClassName='absolute right-2.5'
51+
position='right center'
52+
deleting={activeOptionId}
53+
showPopover={showPopover}
54+
handleDeleteItem={handleRemoveOption}
55+
testid={`delete-option-${(options[i] as SuperSelectOption).value}`}
56+
/>}
57+
</div>
58+
))}
59+
</div>
60+
)
61+
}
62+
63+
export { SuperSelectRemovableOption }

0 commit comments

Comments
 (0)