Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 22 additions & 5 deletions redisinsight/ui/src/constants/keys.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { StreamViewType } from 'uiSrc/slices/interfaces/stream'
import { CommandGroup } from './commands'

export enum KeyTypes {
Expand Down Expand Up @@ -114,11 +115,27 @@ export const KEY_TYPES_ACTIONS: KeyTypesActions = Object.freeze({
name: 'Edit Value',
},
},
[KeyTypes.ReJSON]: {},
[KeyTypes.Stream]: {
addItems: {
name: 'New Entry',
},
[KeyTypes.ReJSON]: {}
})

export const STREAM_ADD_GROUP_VIEW_TYPES = [
StreamViewType.Groups,
StreamViewType.Consumers,
StreamViewType.Messages
]

export const STREAM_ADD_ACTION = Object.freeze({
[StreamViewType.Data]: {
name: 'New Entry'
},
[StreamViewType.Groups]: {
name: 'New Group'
},
[StreamViewType.Consumers]: {
name: 'New Group'
},
[StreamViewType.Messages]: {
name: 'New Group'
}
})

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import React from 'react'
import { fireEvent, render, screen } from 'uiSrc/utils/test-utils'
import { instance, mock } from 'ts-mockito'
import AddStreamGroup, { Props } from './AddStreamGroup'

const GROUP_NAME_FIELD = 'group-name-field'
const ID_FIELD = 'id-field'

const mockedProps = mock<Props>()

describe('AddStreamGroup', () => {
it('should render', () => {
expect(render(<AddStreamGroup {...instance(mockedProps)} />)).toBeTruthy()
})

it('should set member value properly', () => {
render(<AddStreamGroup {...instance(mockedProps)} />)
const groupNameInput = screen.getByTestId(GROUP_NAME_FIELD)
fireEvent.change(
groupNameInput,
{ target: { value: 'group name' } }
)
expect(groupNameInput).toHaveValue('group name')
})

it('should set score value properly if input wrong value', () => {
render(<AddStreamGroup {...instance(mockedProps)} />)
const idInput = screen.getByTestId(ID_FIELD)
fireEvent.change(
idInput,
{ target: { value: 'aa1x-5' } }
)
expect(idInput).toHaveValue('1-5')
})

it('should able to save with valid data', () => {
render(<AddStreamGroup {...instance(mockedProps)} />)
const groupNameInput = screen.getByTestId(GROUP_NAME_FIELD)
const idInput = screen.getByTestId(ID_FIELD)
fireEvent.change(
groupNameInput,
{ target: { value: 'name' } }
)
fireEvent.change(
idInput,
{ target: { value: '11111-3' } }
)
expect(screen.getByTestId('save-groups-btn')).not.toBeDisabled()
})

it('should not able to save with valid data', () => {
render(<AddStreamGroup {...instance(mockedProps)} />)
const groupNameInput = screen.getByTestId(GROUP_NAME_FIELD)
const idInput = screen.getByTestId(ID_FIELD)
fireEvent.change(
groupNameInput,
{ target: { value: 'name' } }
)
fireEvent.change(
idInput,
{ target: { value: '11111----' } }
)
expect(screen.getByTestId('save-groups-btn')).toBeDisabled()
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import {
EuiButton,
EuiFieldText,
EuiFlexGroup,
EuiFlexItem,
EuiFormRow,
EuiIcon,
EuiPanel,
EuiSpacer,
EuiTextColor,
EuiToolTip
} from '@elastic/eui'
import cx from 'classnames'
import React, { ChangeEvent, useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'

import { selectedKeyDataSelector } from 'uiSrc/slices/browser/keys'
import { addNewGroupAction } from 'uiSrc/slices/browser/stream'
import { consumerGroupIdRegex, validateConsumerGroupId } from 'uiSrc/utils'
import { CreateConsumerGroupsDto } from 'apiSrc/modules/browser/dto/stream.dto'

import styles from './styles.module.scss'

export interface Props {
onCancel: (isCancelled?: boolean) => void
}

const AddStreamGroup = (props: Props) => {
const { onCancel } = props
const { name: keyName = '' } = useSelector(selectedKeyDataSelector) ?? { name: undefined }

const [isFormValid, setIsFormValid] = useState<boolean>(false)
const [groupName, setGroupName] = useState<string>('')
const [id, setId] = useState<string>('$')
const [idError, setIdError] = useState<string>('')
const [isIdFocused, setIsIdFocused] = useState<boolean>(false)

const dispatch = useDispatch()

useEffect(() => {
const isValid = !!groupName.length && !idError
setIsFormValid(isValid)
}, [groupName, idError])

useEffect(() => {
if (!consumerGroupIdRegex.test(id)) {
setIdError('ID format is not correct')
return
}
setIdError('')
}, [id])

const submitData = () => {
if (isFormValid) {
const data: CreateConsumerGroupsDto = {
keyName,
consumerGroups: [{
name: groupName,
lastDeliveredId: id,
}],
}
dispatch(addNewGroupAction(data, onCancel))
}
}

const showIdError = !isIdFocused && idError

return (
<>
<EuiPanel
color="transparent"
hasShadow={false}
borderRadius="none"
className={cx(styles.content, 'eui-yScroll', 'flexItemNoFullWidth', 'inlineFieldsNoSpace')}
>
<EuiFlexItem
className={cx('flexItemNoFullWidth', 'inlineFieldsNoSpace')}
grow
>
<EuiFlexGroup gutterSize="none" responsive={false}>
<EuiFlexItem grow>
<EuiFlexGroup gutterSize="none" alignItems="flexStart" responsive={false}>
<EuiFlexItem className={styles.groupNameWrapper} grow>
<EuiFormRow fullWidth>
<EuiFieldText
fullWidth
name="group-name"
id="group-name"
placeholder="Group Name*"
value={groupName}
onChange={(e: ChangeEvent<HTMLInputElement>) => setGroupName(e.target.value)}
autoComplete="off"
data-testid="group-name-field"
/>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem className={styles.timestampWrapper} grow>
<EuiFormRow fullWidth>
<EuiFieldText
fullWidth
name="id"
id="id"
placeholder="ID*"
value={id}
onChange={(e: ChangeEvent<HTMLInputElement>) => setId(validateConsumerGroupId(e.target.value))}
onBlur={() => setIsIdFocused(false)}
onFocus={() => setIsIdFocused(true)}
append={(
<EuiToolTip
anchorClassName="inputAppendIcon"
className={styles.entryIdTooltip}
position="left"
title="Enter Valid ID, 0 or $"
content={(
<>
Specify the ID of the last delivered entry in the stream from the new group's perspective.
<EuiSpacer size="xs" />
Otherwise, <b>$</b> represents the ID of the last entry in the stream,&nbsp;
<b>0</b> fetches the entire stream from the beginning.
</>
)}
>
<EuiIcon type="iInCircle" style={{ cursor: 'pointer' }} />
</EuiToolTip>
)}
autoComplete="off"
data-testid="id-field"
/>
</EuiFormRow>
{!showIdError && <span className={styles.idText} data-testid="id-help-text">Timestamp - Sequence Number or $</span>}
{showIdError && <span className={styles.error} data-testid="id-error">{idError}</span>}
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiPanel>
<EuiPanel
style={{ border: 'none' }}
color="transparent"
hasShadow={false}
className="flexItemNoFullWidth"
>
<EuiFlexGroup justifyContent="flexEnd" gutterSize="l">
<EuiFlexItem grow={false}>
<div>
<EuiButton color="secondary" onClick={() => onCancel(true)} data-testid="cancel-stream-groups-btn">
<EuiTextColor color="default">Cancel</EuiTextColor>
</EuiButton>
</div>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<div>
<EuiButton
fill
size="m"
color="secondary"
onClick={submitData}
disabled={!isFormValid}
data-testid="save-groups-btn"
>
Save
</EuiButton>
</div>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
</>
)
}

export default AddStreamGroup
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import AddStreamGroup from './AddStreamGroup'

export default AddStreamGroup
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
.content {
display: flex;
flex-direction: column;
width: 100%;
border: none !important;
border-top: 1px solid var(--euiColorPrimary);
padding: 12px 20px;
max-height: 234px;
scroll-padding-bottom: 30px;

.groupNameWrapper {
flex-grow: 2 !important;
}

.timestampWrapper {
min-width: 215px;
}

.idText, .error {
display: inline-block;
color: var(--euiColorMediumShade);
font: normal normal normal 12px/18px Graphik;
margin-top: 6px;
padding-right: 6px;
}

.error {
color: var(--euiColorDangerText);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import AddHashFields from './add-hash-fields/AddHashFields'
import AddListElements from './add-list-elements/AddListElements'
import AddSetMembers from './add-set-members/AddSetMembers'
import AddStreamEntries, { StreamEntryFields } from './add-stream-entity'
import AddStreamGroup from './add-stream-group'
import AddZsetMembers from './add-zset-members/AddZsetMembers'

export {
Expand All @@ -10,5 +11,6 @@ export {
AddSetMembers,
AddStreamEntries,
StreamEntryFields,
AddZsetMembers
AddZsetMembers,
AddStreamGroup
}
Loading