Skip to content

Commit 49c04bb

Browse files
Merge pull request #2686 from RedisInsight/fe/feature/RI-4815-portion-of-string
#RI-4815 get a portion of the string (FE)
2 parents 48c97de + 6370b61 commit 49c04bb

File tree

20 files changed

+609
-104
lines changed

20 files changed

+609
-104
lines changed

redisinsight/ui/src/constants/api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ enum ApiEndpoints {
3838

3939
STRING = 'string',
4040
STRING_VALUE = 'string/get-value',
41+
STRING_VALUE_DOWNLOAD = 'string/download-value',
4142

4243
HASH = 'hash',
4344
HASH_FIELDS = 'hash/fields',

redisinsight/ui/src/constants/browser.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ export const TEXT_UNPRINTABLE_CHARACTERS = {
88
text: 'Use Workbench or CLI to edit without data loss.',
99
}
1010
export const TEXT_DISABLED_FORMATTER_EDITING = 'Cannot edit the value in this format'
11+
export const TEXT_DISABLED_STRING_EDITING = 'Load the entire value to edit it'
12+
export const TEXT_DISABLED_STRING_FORMATTING = 'Load the entire value to select a format'
1113

1214
export const TEXT_INVALID_VALUE = {
1315
title: 'Value will be saved as Unicode',

redisinsight/ui/src/constants/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export * from './mocks/mock-tutorials'
2121
export * from './mocks/mock-custom-tutorials'
2222
export * from './socketErrors'
2323
export * from './browser'
24+
export * from './string'
2425
export * from './durationUnits'
2526
export * from './streamViews'
2627
export * from './bulkActions'

redisinsight/ui/src/constants/prop-types/keys.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,9 @@ export interface IKeyPropTypes {
99
size: number
1010
length: number
1111
}
12+
13+
export interface IFetchKeyArgs {
14+
resetData?: boolean
15+
start?: number
16+
end?: number
17+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const STRING_MAX_LENGTH = 4999

redisinsight/ui/src/pages/browser/BrowserPage.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ describe('KeyDetailsWrapper', () => {
179179
key: unprintableStringBuffer,
180180
value: {
181181
type: 'Buffer',
182-
data: [172, 237, 0]
182+
data: [172, 237, 0, 5, 115, 114, 0]
183183
},
184184
}
185185
}

redisinsight/ui/src/pages/browser/components/key-details-header/KeyDetailsHeader.spec.tsx

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,26 @@
11
import React from 'react'
22
import { mock } from 'ts-mockito'
33
import { render, screen, fireEvent } from 'uiSrc/utils/test-utils'
4+
import { stringDataSelector } from 'uiSrc/slices/browser/string'
5+
import { KeyTypes } from 'uiSrc/constants'
46
import KeyDetailsHeader, { Props } from './KeyDetailsHeader'
57

68
const mockedProps = mock<Props>()
79

810
const KEY_INPUT_TEST_ID = 'edit-key-input'
911
const KEY_BTN_TEST_ID = 'edit-key-btn'
1012
const TTL_INPUT_TEST_ID = 'edit-ttl-input'
13+
const EDIT_VALUE_BTN_TEST_ID = 'edit-key-value-btn'
14+
15+
jest.mock('uiSrc/slices/browser/string', () => ({
16+
...jest.requireActual('uiSrc/slices/browser/string'),
17+
stringDataSelector: jest.fn().mockReturnValue({
18+
value: {
19+
type: 'Buffer',
20+
data: [49, 50, 51, 52],
21+
}
22+
}),
23+
}))
1124

1225
jest.mock('uiSrc/slices/browser/keys', () => ({
1326
...jest.requireActual('uiSrc/slices/browser/keys'),
@@ -16,7 +29,8 @@ jest.mock('uiSrc/slices/browser/keys', () => ({
1629
type: 'Buffer',
1730
data: [116, 101, 115, 116]
1831
},
19-
nameString: 'test'
32+
nameString: 'test',
33+
length: 4
2034
}),
2135
}))
2236

@@ -92,4 +106,36 @@ describe('KeyDetailsHeader', () => {
92106

93107
expect(screen.getByTestId(TTL_INPUT_TEST_ID)).toHaveValue('100')
94108
})
109+
110+
it('should be able to change value (long string fully load)', () => {
111+
render(
112+
<KeyDetailsHeader
113+
{...mockedProps}
114+
keyType={KeyTypes.String}
115+
/>
116+
)
117+
118+
const editValueBtn = screen.getByTestId(`${EDIT_VALUE_BTN_TEST_ID}`)
119+
expect(editValueBtn).toHaveProperty('disabled', false)
120+
})
121+
122+
it('should not be able to change value (long string not fully load)', () => {
123+
const stringDataSelectorMock = jest.fn().mockReturnValue({
124+
value: {
125+
type: 'Buffer',
126+
data: [49, 50, 51],
127+
}
128+
})
129+
stringDataSelector.mockImplementation(stringDataSelectorMock)
130+
131+
render(
132+
<KeyDetailsHeader
133+
{...mockedProps}
134+
keyType={KeyTypes.String}
135+
/>
136+
)
137+
138+
const editValueBtn = screen.getByTestId(`${EDIT_VALUE_BTN_TEST_ID}`)
139+
expect(editValueBtn).toHaveProperty('disabled', true)
140+
})
95141
})

redisinsight/ui/src/pages/browser/components/key-details-header/KeyDetailsHeader.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ import {
2525
LENGTH_NAMING_BY_TYPE,
2626
ModulesKeyTypes,
2727
STREAM_ADD_ACTION,
28+
TEXT_DISABLED_COMPRESSED_VALUE,
2829
TEXT_DISABLED_FORMATTER_EDITING,
2930
TEXT_UNPRINTABLE_CHARACTERS,
30-
TEXT_DISABLED_COMPRESSED_VALUE,
31+
TEXT_DISABLED_STRING_EDITING,
3132
} from 'uiSrc/constants'
3233
import { AddCommonFieldsFormConfig } from 'uiSrc/pages/browser/components/add-key/constants/fields-config'
3334
import { initialKeyInfo, keysSelector, selectedKeyDataSelector, selectedKeySelector } from 'uiSrc/slices/browser/keys'
@@ -40,12 +41,13 @@ import {
4041
formatLongName,
4142
isEqualBuffers,
4243
isFormatEditable,
44+
isFullStringLoaded,
4345
MAX_TTL_NUMBER,
4446
replaceSpaces,
4547
stringToBuffer,
4648
validateTTLNumber
4749
} from 'uiSrc/utils'
48-
import { stringSelector } from 'uiSrc/slices/browser/string'
50+
import { stringDataSelector, stringSelector } from 'uiSrc/slices/browser/string'
4951
import KeyValueFormatter from './components/Formatter'
5052
import AutoRefresh from '../auto-refresh'
5153

@@ -95,6 +97,7 @@ const KeyDetailsHeader = ({
9597
nameString: keyProp,
9698
name: keyBuffer,
9799
} = useSelector(selectedKeyDataSelector) ?? initialKeyInfo
100+
const { value: keyValue } = useSelector(stringDataSelector)
98101
const { id: instanceId } = useSelector(connectedInstanceSelector)
99102
const { isCompressed: isStringCompressed } = useSelector(stringSelector)
100103
const { viewType } = useSelector(keysSelector)
@@ -307,7 +310,10 @@ const KeyDetailsHeader = ({
307310

308311
const Actions = (width: number) => {
309312
const isEditable = !isStringCompressed && isFormatEditable(viewFormatProp)
313+
const isStringEditable = keyType === KeyTypes.String ? isFullStringLoaded(keyValue?.data?.length, length) : true
310314
const noEditableText = isStringCompressed ? TEXT_DISABLED_COMPRESSED_VALUE : TEXT_DISABLED_FORMATTER_EDITING
315+
const editToolTip = !isEditable ? noEditableText : (!isStringEditable ? TEXT_DISABLED_STRING_EDITING : null)
316+
311317
return (
312318
<>
313319
{KEY_TYPES_ACTIONS[keyType] && 'addItems' in KEY_TYPES_ACTIONS[keyType] && (
@@ -388,11 +394,11 @@ const KeyDetailsHeader = ({
388394
{KEY_TYPES_ACTIONS[keyType] && 'editItem' in KEY_TYPES_ACTIONS[keyType] && (
389395
<div className={styles.actionBtn}>
390396
<EuiToolTip
391-
content={!isEditable ? noEditableText : null}
397+
content={editToolTip}
392398
data-testid="edit-key-value-tooltip"
393399
>
394400
<EuiButtonIcon
395-
disabled={!isEditable}
401+
disabled={!isEditable || !isStringEditable}
396402
iconType="pencil"
397403
color="primary"
398404
aria-label={KEY_TYPES_ACTIONS[keyType].editItem?.name}

redisinsight/ui/src/pages/browser/components/key-details-header/components/Formatter/KeyValueFormatter.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ import { EuiIcon, EuiSuperSelect, EuiSuperSelectOption, EuiText, EuiTextColor, E
44
import { useDispatch, useSelector } from 'react-redux'
55
import { useParams } from 'react-router-dom'
66

7-
import { KeyValueFormat, Theme } from 'uiSrc/constants'
7+
import { KeyTypes, KeyValueFormat, TEXT_DISABLED_STRING_FORMATTING, Theme } from 'uiSrc/constants'
88
import { ThemeContext } from 'uiSrc/contexts/themeContext'
99
import { keysSelector, selectedKeyDataSelector, selectedKeySelector, setViewFormat } from 'uiSrc/slices/browser/keys'
1010
import { getBasedOnViewTypeEvent, sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry'
1111
import FormattersLight from 'uiSrc/assets/img/icons/formatter_light.svg'
1212
import FormattersDark from 'uiSrc/assets/img/icons/formatter_dark.svg'
13+
import { stringDataSelector } from 'uiSrc/slices/browser/string'
14+
import { isFullStringLoaded } from 'uiSrc/utils'
1315
import { getKeyValueFormatterOptions } from './constants'
1416
import { MIDDLE_SCREEN_RESOLUTION } from '../../KeyDetailsHeader'
1517
import styles from './styles.module.scss'
@@ -24,21 +26,26 @@ const KeyValueFormatter = (props: Props) => {
2426
const { theme } = useContext(ThemeContext)
2527
const { viewType } = useSelector(keysSelector)
2628
const { viewFormat } = useSelector(selectedKeySelector)
27-
const { type: keyType } = useSelector(selectedKeyDataSelector) ?? {}
29+
const { type: keyType, length } = useSelector(selectedKeyDataSelector) ?? {}
30+
const { value: keyValue } = useSelector(stringDataSelector)
2831

2932
const [isSelectOpen, setIsSelectOpen] = useState<boolean>(false)
3033
const [typeSelected, setTypeSelected] = useState<KeyValueFormat>(viewFormat)
3134
const [options, setOptions] = useState<EuiSuperSelectOption<KeyValueFormat>[]>([])
3235

3336
const dispatch = useDispatch()
3437

38+
const isStringFormattingEnabled = keyType === KeyTypes.String
39+
? isFullStringLoaded(keyValue?.data?.length, length)
40+
: true
41+
3542
useEffect(() => {
3643
const newOptions: EuiSuperSelectOption<KeyValueFormat>[] = getKeyValueFormatterOptions(keyType).map(
3744
({ value, text }) => ({
3845
value,
3946
inputDisplay: (
4047
<EuiToolTip
41-
content={typeSelected}
48+
content={!isStringFormattingEnabled ? TEXT_DISABLED_STRING_FORMATTING : typeSelected}
4249
position="top"
4350
display="inlineBlock"
4451
anchorClassName="flex-row"
@@ -63,7 +70,7 @@ const KeyValueFormatter = (props: Props) => {
6370
)
6471

6572
setOptions(newOptions)
66-
}, [viewFormat, keyType, width])
73+
}, [viewFormat, keyType, width, isStringFormattingEnabled])
6774

6875
const onChangeType = (value: KeyValueFormat) => {
6976
sendEventTelemetry({
@@ -92,6 +99,7 @@ const KeyValueFormatter = (props: Props) => {
9299
<div className={cx(styles.container, { [styles.fullWidth]: width > MIDDLE_SCREEN_RESOLUTION })}>
93100
<div className={styles.selectWrapper}>
94101
<EuiSuperSelect
102+
disabled={!isStringFormattingEnabled}
95103
fullWidth
96104
isOpen={isSelectOpen}
97105
options={options}

redisinsight/ui/src/pages/browser/components/key-details/KeyDetails/KeyDetails.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { RedisResponseBuffer } from 'uiSrc/slices/interfaces'
3131

3232
import ExploreGuides from 'uiSrc/components/explore-guides'
3333
import { Nullable } from 'uiSrc/utils'
34+
import { IFetchKeyArgs } from 'uiSrc/constants/prop-types/keys'
3435
import KeyDetailsHeader from '../../key-details-header/KeyDetailsHeader'
3536
import ZSetDetails from '../../zset-details/ZSetDetails'
3637
import StringDetails from '../../string-details/StringDetails'
@@ -51,7 +52,7 @@ export interface Props {
5152
onToggleFullScreen: () => void
5253
onClose: (key: RedisResponseBuffer) => void
5354
onClosePanel: () => void
54-
onRefresh: (key: RedisResponseBuffer, type: KeyTypes | ModulesKeyTypes) => void
55+
onRefresh: (key: RedisResponseBuffer, type: KeyTypes | ModulesKeyTypes, args: IFetchKeyArgs) => void
5556
onDelete: (key: RedisResponseBuffer, type: string) => void
5657
onRemoveKey: () => void
5758
onEditTTL: (key: RedisResponseBuffer, ttl: number) => void
@@ -61,7 +62,7 @@ export interface Props {
6162
}
6263

6364
const KeyDetails = ({ ...props }: Props) => {
64-
const { onClosePanel, onRemoveKey, totalKeys, keysLastRefreshTime } = props
65+
const { onRefresh, onClosePanel, onRemoveKey, totalKeys, keysLastRefreshTime } = props
6566
const { loading, error = '', data } = useSelector(selectedKeySelector)
6667
const { type: selectedKeyType, name: selectedKey } = useSelector(selectedKeyDataSelector) ?? {
6768
type: KeyTypes.String,
@@ -133,6 +134,7 @@ const KeyDetails = ({ ...props }: Props) => {
133134
<StringDetails
134135
isEditItem={editItem}
135136
setIsEdit={(isEdit) => setEditItem(isEdit)}
137+
onRefresh={onRefresh}
136138
/>
137139
),
138140
[KeyTypes.Hash]: <HashDetails isFooterOpen={isAddItemPanelOpen} onRemoveKey={onRemoveKey} />,

0 commit comments

Comments
 (0)