Skip to content

Commit db9e590

Browse files
committed
1 parent 6fd34ed commit db9e590

File tree

11 files changed

+165
-38
lines changed

11 files changed

+165
-38
lines changed

src/webviews/src/modules/add-key/components/AddKeyList/AddKeyList.spec.tsx

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ import { render, screen, fireEvent } from 'testSrc/helpers'
44
import { AddKeyList, Props } from './AddKeyList'
55

66
const mockedProps = mock<Props>()
7+
const elementFindingRegex = /^element-\d+$/
78

89
describe('AddKeyList', () => {
910
it('should render', () => {
1011
expect(render(<AddKeyList {...instance(mockedProps)} />)).toBeTruthy()
1112
})
1213
it('should set value properly', () => {
1314
render(<AddKeyList {...instance(mockedProps)} />)
14-
const valueInput = screen.getByTestId('element')
15+
const valueInput = screen.getByTestId(elementFindingRegex)
1516
const value = 'list list list list list list '
1617
fireEvent.change(valueInput, { target: { value } })
1718
expect(valueInput).toHaveValue(value)
@@ -28,4 +29,28 @@ describe('AddKeyList', () => {
2829
const button = screen.getByTestId('btn-add') as HTMLButtonElement
2930
expect(button.disabled).toBe(false)
3031
})
32+
33+
it('should render add button', () => {
34+
render(<AddKeyList {...instance(mockedProps)} />)
35+
expect(screen.getByTestId('add-new-item')).toBeTruthy()
36+
})
37+
38+
it('should render one more element input after click add item', () => {
39+
render(<AddKeyList {...instance(mockedProps)} />)
40+
fireEvent.click(screen.getByTestId('add-new-item'))
41+
42+
expect(screen.getAllByTestId(elementFindingRegex)).toHaveLength(2)
43+
})
44+
45+
it('should clear the element after click clear button', () => {
46+
render(<AddKeyList {...instance(mockedProps)} />)
47+
const fieldName = screen.getByTestId('element-0')
48+
fireEvent.input(
49+
fieldName,
50+
{ target: { value: 'name' } },
51+
)
52+
fireEvent.click(screen.getByLabelText(/clear item/i))
53+
54+
expect(fieldName).toHaveValue('')
55+
})
3156
})

src/webviews/src/modules/add-key/components/AddKeyList/AddKeyList.tsx

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { Maybe } from 'uiSrc/interfaces'
88
import { useKeysApi, useKeysInContext } from 'uiSrc/modules/keys-tree/hooks/useKeys'
99
import { InputText, Tooltip } from 'uiSrc/ui'
1010
import { CreateListWithExpireDto } from 'uiSrc/modules/keys-tree/hooks/interface'
11+
import { AddItemsActions } from 'uiSrc/components'
1112

1213
export interface Props {
1314
keyName: string
@@ -17,7 +18,7 @@ export interface Props {
1718

1819
export const AddKeyList = (props: Props) => {
1920
const { keyName = '', keyTTL, onClose } = props
20-
const [element, setElement] = useState<string>('')
21+
const [elements, setElements] = useState<string[]>([''])
2122
const [isFormValid, setIsFormValid] = useState<boolean>(false)
2223

2324
const keysApi = useKeysApi()
@@ -27,6 +28,28 @@ export const AddKeyList = (props: Props) => {
2728
setIsFormValid(keyName.length > 0)
2829
}, [keyName])
2930

31+
const addElement = () => {
32+
setElements([...elements, ''])
33+
}
34+
35+
const removeElement = (index: number) => {
36+
setElements(elements.filter((_el, i) => i !== index))
37+
}
38+
39+
const clearElement = (index: number) => {
40+
const newElements = [...elements]
41+
newElements[index] = ''
42+
setElements(newElements)
43+
}
44+
45+
const handleElementChange = (value: string, index: number) => {
46+
const newElements = [...elements]
47+
newElements[index] = value
48+
setElements(newElements)
49+
}
50+
51+
const isClearDisabled = (item:string) => elements.length === 1 && !item.length
52+
3053
const onFormSubmit = (event: FormEvent<HTMLFormElement>): void => {
3154
event.preventDefault()
3255
if (isFormValid) {
@@ -37,7 +60,7 @@ export const AddKeyList = (props: Props) => {
3760
const submitData = (): void => {
3861
const data: CreateListWithExpireDto = {
3962
keyName: stringToBuffer(keyName),
40-
element: stringToBuffer(element),
63+
elements: elements.map((el) => stringToBuffer(el)),
4164
}
4265
if (keyTTL !== undefined) {
4366
data.expire = keyTTL
@@ -51,16 +74,32 @@ export const AddKeyList = (props: Props) => {
5174
<>
5275
<form onSubmit={onFormSubmit} className="key-footer-items-container pl-0 h-full">
5376
<h3 className="font-bold uppercase pb-3">{l10n.t('Element')}</h3>
54-
<InputText
55-
name="element"
56-
id="element"
57-
placeholder={config.element.placeholder}
58-
value={element}
59-
onChange={(e: ChangeEvent<HTMLInputElement>) =>
60-
setElement(e.target.value)}
61-
disabled={loading}
62-
data-testid="element"
63-
/>
77+
{elements.map((item, index) => (
78+
<div key={index}>
79+
<div className="flex items-center mb-3">
80+
<InputText
81+
name={`element-${index}`}
82+
id={`element-${index}`}
83+
placeholder={config.element.placeholder}
84+
value={item}
85+
disabled={loading}
86+
onChange={(e: ChangeEvent<HTMLInputElement>) =>
87+
handleElementChange(e.target.value, index)}
88+
data-testid={`element-${index}`}
89+
/>
90+
<AddItemsActions
91+
id={index}
92+
index={index}
93+
length={elements.length}
94+
addItem={addElement}
95+
removeItem={removeElement}
96+
clearItemValues={clearElement}
97+
clearIsDisabled={isClearDisabled(item)}
98+
disabled={loading}
99+
/>
100+
</div>
101+
</div>
102+
))}
64103
<button type="submit" className="hidden">
65104
{l10n.t('Submit')}
66105
</button>

src/webviews/src/modules/cli/hooks/interface.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ export interface CreateSetWithExpireDto extends KeyWithExpireDto {
112112
}
113113

114114
export interface CreateListWithExpireDto extends KeyWithExpireDto {
115-
element: RedisString
115+
elements: RedisString[]
116116
}
117117

118118
export interface HashFieldDto {

src/webviews/src/modules/key-details/components/list-details/add-list-elements/AddListElements.spec.tsx

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { fireEvent, render, screen } from 'testSrc/helpers'
55
import { AddListElements, Props } from './AddListElements'
66

77
const mockedProps = mock<Props>()
8+
const elementFindingRegex = /^element-\d+$/
89

910
describe('AddListElements', () => {
1011
it('should render', () => {
@@ -13,7 +14,7 @@ describe('AddListElements', () => {
1314

1415
it('should set elements input properly', () => {
1516
render(<AddListElements {...instance(mockedProps)} />)
16-
const elementsInput = screen.getByTestId('elements-input')
17+
const elementsInput = screen.getByTestId(elementFindingRegex)
1718
fireEvent.change(
1819
elementsInput,
1920
{ target: { value: '123' } },
@@ -27,4 +28,28 @@ describe('AddListElements', () => {
2728
expect(screen.queryByTestId(HEAD_DESTINATION)).toBeInTheDocument()
2829
expect(screen.queryByTestId(TAIL_DESTINATION)).toBeInTheDocument()
2930
})
31+
32+
it('should render add button', () => {
33+
render(<AddListElements {...instance(mockedProps)} />)
34+
expect(screen.getByTestId('add-new-item')).toBeTruthy()
35+
})
36+
37+
it('should render one more element input after click add item', () => {
38+
render(<AddListElements {...instance(mockedProps)} />)
39+
fireEvent.click(screen.getByTestId('add-new-item'))
40+
41+
expect(screen.getAllByTestId(elementFindingRegex)).toHaveLength(2)
42+
})
43+
44+
it('should clear the element after click clear button', () => {
45+
render(<AddListElements {...instance(mockedProps)} />)
46+
const fieldName = screen.getByTestId('element-0')
47+
fireEvent.input(
48+
fieldName,
49+
{ target: { value: 'name' } },
50+
)
51+
fireEvent.click(screen.getByLabelText(/clear item/i))
52+
53+
expect(fieldName).toHaveValue('')
54+
})
3055
})

src/webviews/src/modules/key-details/components/list-details/add-list-elements/AddListElements.tsx

Lines changed: 55 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { TelemetryEvent, sendEventTelemetry, stringToBuffer } from 'uiSrc/utils'
1313

1414
import { useDatabasesStore, useSelectedKeyStore } from 'uiSrc/store'
1515
import { InputText, Select, SelectOption } from 'uiSrc/ui'
16+
import { AddItemsActions } from 'uiSrc/components'
1617
import { PushElementToListDto } from '../hooks/interface'
1718
import { insertListElementsAction, useListStore } from '../hooks/useListStore'
1819
import styles from '../styles.module.scss'
@@ -37,7 +38,7 @@ const optionsDestinations: SelectOption[] = [
3738
const AddListElements = (props: Props) => {
3839
const { closePanel } = props
3940

40-
const [element, setElement] = useState<string>('')
41+
const [elements, setElements] = useState<string[]>([''])
4142
const [destination, setDestination] = useState<ListElementDestination>(TAIL_DESTINATION)
4243
const databaseId = useDatabasesStore((state) => state.connectedDatabase?.id)
4344

@@ -58,15 +59,37 @@ const AddListElements = (props: Props) => {
5859
eventData: {
5960
databaseId,
6061
keyType: KeyTypes.List,
61-
numberOfAdded: 1,
62+
numberOfAdded: elements.length,
6263
},
6364
})
6465
}
6566

67+
const addElement = () => {
68+
setElements([...elements, ''])
69+
}
70+
71+
const removeElement = (index: number) => {
72+
setElements(elements.filter((_el, i) => i !== index))
73+
}
74+
75+
const clearElement = (index: number) => {
76+
const newElements = [...elements]
77+
newElements[index] = ''
78+
setElements(newElements)
79+
}
80+
81+
const handleElementChange = (value: string, index: number) => {
82+
const newElements = [...elements]
83+
newElements[index] = value
84+
setElements(newElements)
85+
}
86+
87+
const isClearDisabled = (item:string) => elements.length === 1 && !item.length
88+
6689
const submitData = (): void => {
6790
const data: PushElementToListDto = {
6891
keyName: selectedKey!,
69-
element: stringToBuffer(element),
92+
elements: elements.map((el) => stringToBuffer(el)),
7093
destination,
7194
}
7295
insertListElementsAction(data, onSuccessAdded)
@@ -76,8 +99,8 @@ const AddListElements = (props: Props) => {
7699
<>
77100
<div className="key-footer-items-container">
78101
<div className="flex items-center mb-3">
79-
<div className="flex grow">
80-
<div className="w-1/3 mr-2">
102+
<div className="flex-column grow">
103+
<div className="w-1/3 mr-2 mb-3">
81104
<Select
82105
position="below"
83106
options={optionsDestinations}
@@ -88,18 +111,33 @@ const AddListElements = (props: Props) => {
88111
testid="destination-select"
89112
/>
90113
</div>
91-
<div className="w-2/3">
92-
<InputText
93-
name={config.element.name}
94-
id={config.element.name}
95-
placeholder={config.element.placeholder}
96-
value={element}
97-
autoComplete="off"
98-
onChange={(e: ChangeEvent<HTMLInputElement>) => setElement(e.target.value)}
99-
data-testid="elements-input"
100-
inputRef={elementInput}
101-
/>
102-
</div>
114+
{elements.map((item, index) => (
115+
<div key={index}>
116+
<div className="flex items-center mb-3">
117+
<InputText
118+
name={`element-${index}`}
119+
id={`element-${index}`}
120+
placeholder={config.element.placeholder}
121+
value={item}
122+
disabled={loading}
123+
onChange={(e: ChangeEvent<HTMLInputElement>) =>
124+
handleElementChange(e.target.value, index)}
125+
inputRef={index === elements.length - 1 ? elementInput : null}
126+
data-testid={`element-${index}`}
127+
/>
128+
<AddItemsActions
129+
id={index}
130+
index={index}
131+
length={elements.length}
132+
addItem={addElement}
133+
removeItem={removeElement}
134+
clearItemValues={clearElement}
135+
clearIsDisabled={isClearDisabled(item)}
136+
disabled={loading}
137+
/>
138+
</div>
139+
</div>
140+
))}
103141
</div>
104142
</div>
105143
</div>

src/webviews/src/modules/key-details/components/list-details/hooks/interface.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export interface DeleteListElementsDto extends KeyDto {
6161
}
6262

6363
export interface PushElementToListDto extends KeyDto {
64-
element: RedisString
64+
elements: RedisString[]
6565
destination: ListElementDestination
6666
}
6767

src/webviews/src/modules/key-details/components/list-details/hooks/tests/useListStore.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ describe('async', () => {
165165

166166
insertListElementsAction({
167167
keyName: constants.KEY_NAME_4,
168-
element: constants.KEY_4_ELEMENT,
168+
elements: [constants.KEY_4_ELEMENT],
169169
destination: ListElementDestination.Head,
170170
})
171171
await waitForStack()

src/webviews/src/modules/keys-tree/hooks/interface.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ export interface CreateSetWithExpireDto extends KeyWithExpireDto {
105105
}
106106

107107
export interface CreateListWithExpireDto extends KeyWithExpireDto {
108-
element: RedisString
108+
elements: RedisString[]
109109
}
110110

111111
export interface HashFieldDto {

src/webviews/src/modules/keys-tree/hooks/tests/useKeys.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ describe('useKeys', () => {
447447
// Arrange
448448
const data: CreateListWithExpireDto = {
449449
keyName: 'keyName',
450-
element: 'element',
450+
elements: ['element'],
451451
}
452452
const responsePayload = { data, status: 200 }
453453

src/webviews/src/utils/telemetry/telemetryUtils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export const getLengthByKeyType = (type: KeyTypes, data: any) => {
8585
case KeyTypes.String:
8686
return data.value?.length
8787
case KeyTypes.List:
88-
return 1
88+
return data.elements?.length
8989
case KeyTypes.Stream:
9090
return 1
9191
case KeyTypes.ReJSON:

0 commit comments

Comments
 (0)