Skip to content

Commit ffe5194

Browse files
authored
Merge pull request #683 from RedisInsight/feature/RI-810_create_consumer_groups
#RI-810 - initial implementation of add consumer groups
2 parents e9f81c9 + 8b542bc commit ffe5194

File tree

11 files changed

+421
-24
lines changed

11 files changed

+421
-24
lines changed

redisinsight/ui/src/constants/keys.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { StreamViewType } from 'uiSrc/slices/interfaces/stream'
12
import { CommandGroup } from './commands'
23

34
export enum KeyTypes {
@@ -114,11 +115,27 @@ export const KEY_TYPES_ACTIONS: KeyTypesActions = Object.freeze({
114115
name: 'Edit Value',
115116
},
116117
},
117-
[KeyTypes.ReJSON]: {},
118-
[KeyTypes.Stream]: {
119-
addItems: {
120-
name: 'New Entry',
121-
},
118+
[KeyTypes.ReJSON]: {}
119+
})
120+
121+
export const STREAM_ADD_GROUP_VIEW_TYPES = [
122+
StreamViewType.Groups,
123+
StreamViewType.Consumers,
124+
StreamViewType.Messages
125+
]
126+
127+
export const STREAM_ADD_ACTION = Object.freeze({
128+
[StreamViewType.Data]: {
129+
name: 'New Entry'
130+
},
131+
[StreamViewType.Groups]: {
132+
name: 'New Group'
133+
},
134+
[StreamViewType.Consumers]: {
135+
name: 'New Group'
136+
},
137+
[StreamViewType.Messages]: {
138+
name: 'New Group'
122139
}
123140
})
124141

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import React from 'react'
2+
import { fireEvent, render, screen } from 'uiSrc/utils/test-utils'
3+
import { instance, mock } from 'ts-mockito'
4+
import AddStreamGroup, { Props } from './AddStreamGroup'
5+
6+
const GROUP_NAME_FIELD = 'group-name-field'
7+
const ID_FIELD = 'id-field'
8+
9+
const mockedProps = mock<Props>()
10+
11+
describe('AddStreamGroup', () => {
12+
it('should render', () => {
13+
expect(render(<AddStreamGroup {...instance(mockedProps)} />)).toBeTruthy()
14+
})
15+
16+
it('should set member value properly', () => {
17+
render(<AddStreamGroup {...instance(mockedProps)} />)
18+
const groupNameInput = screen.getByTestId(GROUP_NAME_FIELD)
19+
fireEvent.change(
20+
groupNameInput,
21+
{ target: { value: 'group name' } }
22+
)
23+
expect(groupNameInput).toHaveValue('group name')
24+
})
25+
26+
it('should set score value properly if input wrong value', () => {
27+
render(<AddStreamGroup {...instance(mockedProps)} />)
28+
const idInput = screen.getByTestId(ID_FIELD)
29+
fireEvent.change(
30+
idInput,
31+
{ target: { value: 'aa1x-5' } }
32+
)
33+
expect(idInput).toHaveValue('1-5')
34+
})
35+
36+
it('should able to save with valid data', () => {
37+
render(<AddStreamGroup {...instance(mockedProps)} />)
38+
const groupNameInput = screen.getByTestId(GROUP_NAME_FIELD)
39+
const idInput = screen.getByTestId(ID_FIELD)
40+
fireEvent.change(
41+
groupNameInput,
42+
{ target: { value: 'name' } }
43+
)
44+
fireEvent.change(
45+
idInput,
46+
{ target: { value: '11111-3' } }
47+
)
48+
expect(screen.getByTestId('save-groups-btn')).not.toBeDisabled()
49+
})
50+
51+
it('should not able to save with valid data', () => {
52+
render(<AddStreamGroup {...instance(mockedProps)} />)
53+
const groupNameInput = screen.getByTestId(GROUP_NAME_FIELD)
54+
const idInput = screen.getByTestId(ID_FIELD)
55+
fireEvent.change(
56+
groupNameInput,
57+
{ target: { value: 'name' } }
58+
)
59+
fireEvent.change(
60+
idInput,
61+
{ target: { value: '11111----' } }
62+
)
63+
expect(screen.getByTestId('save-groups-btn')).toBeDisabled()
64+
})
65+
})
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import {
2+
EuiButton,
3+
EuiFieldText,
4+
EuiFlexGroup,
5+
EuiFlexItem,
6+
EuiFormRow,
7+
EuiIcon,
8+
EuiPanel,
9+
EuiSpacer,
10+
EuiTextColor,
11+
EuiToolTip
12+
} from '@elastic/eui'
13+
import cx from 'classnames'
14+
import React, { ChangeEvent, useEffect, useState } from 'react'
15+
import { useDispatch, useSelector } from 'react-redux'
16+
17+
import { selectedKeyDataSelector } from 'uiSrc/slices/browser/keys'
18+
import { addNewGroupAction } from 'uiSrc/slices/browser/stream'
19+
import { consumerGroupIdRegex, validateConsumerGroupId } from 'uiSrc/utils'
20+
import { CreateConsumerGroupsDto } from 'apiSrc/modules/browser/dto/stream.dto'
21+
22+
import styles from './styles.module.scss'
23+
24+
export interface Props {
25+
onCancel: (isCancelled?: boolean) => void
26+
}
27+
28+
const AddStreamGroup = (props: Props) => {
29+
const { onCancel } = props
30+
const { name: keyName = '' } = useSelector(selectedKeyDataSelector) ?? { name: undefined }
31+
32+
const [isFormValid, setIsFormValid] = useState<boolean>(false)
33+
const [groupName, setGroupName] = useState<string>('')
34+
const [id, setId] = useState<string>('$')
35+
const [idError, setIdError] = useState<string>('')
36+
const [isIdFocused, setIsIdFocused] = useState<boolean>(false)
37+
38+
const dispatch = useDispatch()
39+
40+
useEffect(() => {
41+
const isValid = !!groupName.length && !idError
42+
setIsFormValid(isValid)
43+
}, [groupName, idError])
44+
45+
useEffect(() => {
46+
if (!consumerGroupIdRegex.test(id)) {
47+
setIdError('ID format is not correct')
48+
return
49+
}
50+
setIdError('')
51+
}, [id])
52+
53+
const submitData = () => {
54+
if (isFormValid) {
55+
const data: CreateConsumerGroupsDto = {
56+
keyName,
57+
consumerGroups: [{
58+
name: groupName,
59+
lastDeliveredId: id,
60+
}],
61+
}
62+
dispatch(addNewGroupAction(data, onCancel))
63+
}
64+
}
65+
66+
const showIdError = !isIdFocused && idError
67+
68+
return (
69+
<>
70+
<EuiPanel
71+
color="transparent"
72+
hasShadow={false}
73+
borderRadius="none"
74+
className={cx(styles.content, 'eui-yScroll', 'flexItemNoFullWidth', 'inlineFieldsNoSpace')}
75+
>
76+
<EuiFlexItem
77+
className={cx('flexItemNoFullWidth', 'inlineFieldsNoSpace')}
78+
grow
79+
>
80+
<EuiFlexGroup gutterSize="none" responsive={false}>
81+
<EuiFlexItem grow>
82+
<EuiFlexGroup gutterSize="none" alignItems="flexStart" responsive={false}>
83+
<EuiFlexItem className={styles.groupNameWrapper} grow>
84+
<EuiFormRow fullWidth>
85+
<EuiFieldText
86+
fullWidth
87+
name="group-name"
88+
id="group-name"
89+
placeholder="Group Name*"
90+
value={groupName}
91+
onChange={(e: ChangeEvent<HTMLInputElement>) => setGroupName(e.target.value)}
92+
autoComplete="off"
93+
data-testid="group-name-field"
94+
/>
95+
</EuiFormRow>
96+
</EuiFlexItem>
97+
<EuiFlexItem className={styles.timestampWrapper} grow>
98+
<EuiFormRow fullWidth>
99+
<EuiFieldText
100+
fullWidth
101+
name="id"
102+
id="id"
103+
placeholder="ID*"
104+
value={id}
105+
onChange={(e: ChangeEvent<HTMLInputElement>) => setId(validateConsumerGroupId(e.target.value))}
106+
onBlur={() => setIsIdFocused(false)}
107+
onFocus={() => setIsIdFocused(true)}
108+
append={(
109+
<EuiToolTip
110+
anchorClassName="inputAppendIcon"
111+
className={styles.entryIdTooltip}
112+
position="left"
113+
title="Enter Valid ID, 0 or $"
114+
content={(
115+
<>
116+
Specify the ID of the last delivered entry in the stream from the new group's perspective.
117+
<EuiSpacer size="xs" />
118+
Otherwise, <b>$</b> represents the ID of the last entry in the stream,&nbsp;
119+
<b>0</b> fetches the entire stream from the beginning.
120+
</>
121+
)}
122+
>
123+
<EuiIcon type="iInCircle" style={{ cursor: 'pointer' }} />
124+
</EuiToolTip>
125+
)}
126+
autoComplete="off"
127+
data-testid="id-field"
128+
/>
129+
</EuiFormRow>
130+
{!showIdError && <span className={styles.idText} data-testid="id-help-text">Timestamp - Sequence Number or $</span>}
131+
{showIdError && <span className={styles.error} data-testid="id-error">{idError}</span>}
132+
</EuiFlexItem>
133+
</EuiFlexGroup>
134+
</EuiFlexItem>
135+
</EuiFlexGroup>
136+
</EuiFlexItem>
137+
</EuiPanel>
138+
<EuiPanel
139+
style={{ border: 'none' }}
140+
color="transparent"
141+
hasShadow={false}
142+
className="flexItemNoFullWidth"
143+
>
144+
<EuiFlexGroup justifyContent="flexEnd" gutterSize="l">
145+
<EuiFlexItem grow={false}>
146+
<div>
147+
<EuiButton color="secondary" onClick={() => onCancel(true)} data-testid="cancel-stream-groups-btn">
148+
<EuiTextColor color="default">Cancel</EuiTextColor>
149+
</EuiButton>
150+
</div>
151+
</EuiFlexItem>
152+
<EuiFlexItem grow={false}>
153+
<div>
154+
<EuiButton
155+
fill
156+
size="m"
157+
color="secondary"
158+
onClick={submitData}
159+
disabled={!isFormValid}
160+
data-testid="save-groups-btn"
161+
>
162+
Save
163+
</EuiButton>
164+
</div>
165+
</EuiFlexItem>
166+
</EuiFlexGroup>
167+
</EuiPanel>
168+
</>
169+
)
170+
}
171+
172+
export default AddStreamGroup
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import AddStreamGroup from './AddStreamGroup'
2+
3+
export default AddStreamGroup
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
.content {
2+
display: flex;
3+
flex-direction: column;
4+
width: 100%;
5+
border: none !important;
6+
border-top: 1px solid var(--euiColorPrimary);
7+
padding: 12px 20px;
8+
max-height: 234px;
9+
scroll-padding-bottom: 30px;
10+
11+
.groupNameWrapper {
12+
flex-grow: 2 !important;
13+
}
14+
15+
.timestampWrapper {
16+
min-width: 215px;
17+
}
18+
19+
.idText, .error {
20+
display: inline-block;
21+
color: var(--euiColorMediumShade);
22+
font: normal normal normal 12px/18px Graphik;
23+
margin-top: 6px;
24+
padding-right: 6px;
25+
}
26+
27+
.error {
28+
color: var(--euiColorDangerText);
29+
}
30+
}

redisinsight/ui/src/pages/browser/components/key-details-add-items/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import AddHashFields from './add-hash-fields/AddHashFields'
22
import AddListElements from './add-list-elements/AddListElements'
33
import AddSetMembers from './add-set-members/AddSetMembers'
44
import AddStreamEntries, { StreamEntryFields } from './add-stream-entity'
5+
import AddStreamGroup from './add-stream-group'
56
import AddZsetMembers from './add-zset-members/AddZsetMembers'
67

78
export {
@@ -10,5 +11,6 @@ export {
1011
AddSetMembers,
1112
AddStreamEntries,
1213
StreamEntryFields,
13-
AddZsetMembers
14+
AddZsetMembers,
15+
AddStreamGroup
1416
}

0 commit comments

Comments
 (0)