Skip to content

Commit 98d8f64

Browse files
Merge pull request #1856 from RedisInsight/fe/bugfix/RI-4274_Can_not_edit_key
#RI-4274 - Can not edit key even it is not compressed
2 parents dca2bb8 + 3118414 commit 98d8f64

File tree

10 files changed

+112
-25
lines changed

10 files changed

+112
-25
lines changed

redisinsight/ui/src/pages/browser/components/hash-details/HashDetails.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -435,8 +435,9 @@ const HashDetails = (props: Props) => {
435435
maxWidth: 95,
436436
render: function Actions(_act: any, { field: fieldItem, value: valueItem }: HashFieldDto, _, rowIndex?: number) {
437437
const field = bufferToString(fieldItem, viewFormat)
438-
const isEditable = !compressor && isFormatEditable(viewFormat)
439-
const tooltipContent = compressor ? TEXT_DISABLED_COMPRESSED_VALUE : TEXT_DISABLED_FORMATTER_EDITING
438+
const { isCompressed } = decompressingBuffer(valueItem, compressor)
439+
const isEditable = !isCompressed && isFormatEditable(viewFormat)
440+
const tooltipContent = isCompressed ? TEXT_DISABLED_COMPRESSED_VALUE : TEXT_DISABLED_FORMATTER_EDITING
440441
return (
441442
<StopPropagation>
442443
<div className="value-table-actions">

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import {
4545
stringToBuffer,
4646
validateTTLNumber
4747
} from 'uiSrc/utils'
48+
import { stringSelector } from 'uiSrc/slices/browser/string'
4849
import KeyValueFormatter from './components/Formatter'
4950
import AutoRefresh from '../auto-refresh'
5051

@@ -94,7 +95,8 @@ const KeyDetailsHeader = ({
9495
nameString: keyProp,
9596
name: keyBuffer,
9697
} = useSelector(selectedKeyDataSelector) ?? initialKeyInfo
97-
const { id: instanceId, compressor = null } = useSelector(connectedInstanceSelector)
98+
const { id: instanceId } = useSelector(connectedInstanceSelector)
99+
const { isCompressed: isStringCompressed } = useSelector(stringSelector)
98100
const { viewType } = useSelector(keysSelector)
99101
const { viewType: streamViewType } = useSelector(streamSelector)
100102
const { viewFormat: viewFormatProp } = useSelector(selectedKeySelector)
@@ -321,8 +323,8 @@ const KeyDetailsHeader = ({
321323
)
322324

323325
const Actions = (width: number) => {
324-
const isEditable = !compressor && isFormatEditable(viewFormatProp)
325-
const noEditableText = compressor ? TEXT_DISABLED_COMPRESSED_VALUE : TEXT_DISABLED_FORMATTER_EDITING
326+
const isEditable = !isStringCompressed && isFormatEditable(viewFormatProp)
327+
const noEditableText = isStringCompressed ? TEXT_DISABLED_COMPRESSED_VALUE : TEXT_DISABLED_FORMATTER_EDITING
326328
return (
327329
<>
328330
{KEY_TYPES_ACTIONS[keyType] && 'addItems' in KEY_TYPES_ACTIONS[keyType] && (

redisinsight/ui/src/pages/browser/components/list-details/ListDetails.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -379,8 +379,9 @@ const ListDetails = (props: Props) => {
379379
maxWidth: 60,
380380
absoluteWidth: 60,
381381
render: function Actions(_element: any, { index, element }: IListElement) {
382-
const isEditable = !compressor && isFormatEditable(viewFormat)
383-
const tooltipContent = compressor ? TEXT_DISABLED_COMPRESSED_VALUE : TEXT_DISABLED_FORMATTER_EDITING
382+
const { isCompressed } = decompressingBuffer(element, compressor)
383+
const isEditable = !isCompressed && isFormatEditable(viewFormat)
384+
const tooltipContent = isCompressed ? TEXT_DISABLED_COMPRESSED_VALUE : TEXT_DISABLED_FORMATTER_EDITING
384385
return (
385386
<StopPropagation>
386387
<div className="value-table-actions">

redisinsight/ui/src/pages/browser/components/string-details/StringDetails.spec.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import React from 'react'
22
import { instance, mock } from 'ts-mockito'
3+
import { KeyValueCompressor } from 'uiSrc/constants'
34
import { stringDataSelector } from 'uiSrc/slices/browser/string'
5+
import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances'
46
import { anyToBuffer, bufferToString } from 'uiSrc/utils'
57
import { render, screen, fireEvent, act } from 'uiSrc/utils/test-utils'
68
import { GZIP_COMPRESSED_VALUE_1, GZIP_COMPRESSED_VALUE_2, DECOMPRESSED_VALUE_STR_1, DECOMPRESSED_VALUE_STR_2 } from 'uiSrc/utils/tests/decompressors'
@@ -21,6 +23,13 @@ jest.mock('uiSrc/slices/browser/string', () => ({
2123
}),
2224
}))
2325

26+
jest.mock('uiSrc/slices/instances/instances', () => ({
27+
...jest.requireActual('uiSrc/slices/instances/instances'),
28+
connectedInstanceSelector: jest.fn().mockReturnValue({
29+
compressor: null,
30+
}),
31+
}))
32+
2433
describe('StringDetails', () => {
2534
it('should render', () => {
2635
expect(
@@ -109,6 +118,10 @@ describe('StringDetails', () => {
109118
})
110119
stringDataSelector.mockImplementation(stringDataSelectorMock)
111120

121+
connectedInstanceSelector.mockImplementation(() => ({
122+
compressor: KeyValueCompressor.GZIP,
123+
}))
124+
112125
render(
113126
<StringDetails
114127
{...instance(mockedProps)}
@@ -127,6 +140,10 @@ describe('StringDetails', () => {
127140
})
128141
stringDataSelector.mockImplementation(stringDataSelectorMock)
129142

143+
connectedInstanceSelector.mockImplementation(() => ({
144+
compressor: KeyValueCompressor.GZIP,
145+
}))
146+
130147
render(
131148
<StringDetails
132149
{...instance(mockedProps)}

redisinsight/ui/src/pages/browser/components/string-details/StringDetails.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ import {
2020
isFormatEditable,
2121
stringToBuffer,
2222
stringToSerializedBufferFormat,
23-
Nullable,
2423
} from 'uiSrc/utils'
2524
import {
2625
resetStringValue,
26+
setIsStringCompressed,
2727
stringDataSelector,
2828
stringSelector,
2929
updateStringValueAction,
@@ -77,7 +77,7 @@ const StringDetails = (props: Props) => {
7777
useEffect(() => {
7878
if (!initialValue) return
7979

80-
const { value: decompressedValue } = decompressingBuffer(initialValue, compressor)
80+
const { value: decompressedValue, isCompressed } = decompressingBuffer(initialValue, compressor)
8181

8282
const initialValueString = bufferToString(decompressedValue, viewFormat)
8383
const { value: formattedValue, isValid } = formattingBuffer(decompressedValue, viewFormatProp, { expanded: true })
@@ -89,8 +89,10 @@ const StringDetails = (props: Props) => {
8989
!isNonUnicodeFormatter(viewFormatProp, isValid)
9090
&& !isEqualBuffers(initialValue, stringToBuffer(initialValueString))
9191
)
92-
setIsEditable(!compressor && isFormatEditable(viewFormatProp))
93-
setNoEditableText(compressor ? TEXT_DISABLED_COMPRESSED_VALUE : TEXT_FAILED_CONVENT_FORMATTER(viewFormat))
92+
setIsEditable(!isCompressed && isFormatEditable(viewFormatProp))
93+
setNoEditableText(isCompressed ? TEXT_DISABLED_COMPRESSED_VALUE : TEXT_FAILED_CONVENT_FORMATTER(viewFormat))
94+
95+
dispatch(setIsStringCompressed(isCompressed))
9496

9597
if (viewFormat !== viewFormatProp) {
9698
setViewFormat(viewFormatProp)

redisinsight/ui/src/slices/browser/string.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ const stringSlice = createSlice({
5858
state.data.key = ''
5959
state.data.value = null
6060
},
61+
setIsStringCompressed: (state, { payload }: PayloadAction<boolean>) => {
62+
state.isCompressed = payload
63+
},
6164
},
6265
})
6366

@@ -70,6 +73,7 @@ export const {
7073
updateValueSuccess,
7174
updateValueFailure,
7275
resetStringValue,
76+
setIsStringCompressed,
7377
} = stringSlice.actions
7478

7579
// A selector

redisinsight/ui/src/slices/interfaces/string.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { RedisResponseBuffer } from './app'
44
export interface StringState {
55
loading: boolean
66
error: string
7+
isCompressed: boolean
78
data: {
89
key: string
910
value: Nullable<RedisResponseBuffer>

redisinsight/ui/src/slices/tests/browser/string.spec.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import reducer, {
1616
updateValueSuccess,
1717
updateValueFailure,
1818
resetStringValue,
19-
updateStringValueAction
19+
updateStringValueAction,
20+
setIsStringCompressed,
2021
} from '../../browser/string'
2122

2223
let store: typeof mockedStore
@@ -270,6 +271,29 @@ describe('string slice', () => {
270271
})
271272
})
272273

274+
describe('setIsStringCompressed', () => {
275+
it('should properly set the state with isCompressed=true', () => {
276+
// Arrange
277+
278+
const state = {
279+
...initialState,
280+
isCompressed: true,
281+
}
282+
283+
// Act
284+
const nextState = reducer(initialState, setIsStringCompressed(true))
285+
286+
// Assert
287+
const rootState = Object.assign(initialStateDefault, {
288+
browser: {
289+
string: nextState,
290+
},
291+
})
292+
expect(stringSelector(rootState)).toEqual(state)
293+
expect(stringDataSelector(rootState)).toEqual(state.data)
294+
})
295+
})
296+
273297
describe('thunks', () => {
274298
describe('fetchString', () => {
275299
it('call both fetchString, getStringSuccess when fetch is successed', async () => {

redisinsight/ui/src/utils/decompressors/decompressors.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,12 @@ import { anyToBuffer, Nullable } from 'uiSrc/utils'
1010
const decompressingBuffer = (
1111
reply: RedisResponseBuffer,
1212
compressorInit: Nullable<KeyValueCompressor> = null,
13-
): { value: RedisString, compressor: Nullable<KeyValueCompressor> } => {
14-
const compressor = compressorInit
13+
): { value: RedisString, compressor: Nullable<KeyValueCompressor>, isCompressed: boolean } => {
14+
const compressorByValue: Nullable<KeyValueCompressor> = getCompressor(reply)
15+
const compressor = compressorInit === compressorByValue
16+
|| (!compressorByValue && compressorInit === KeyValueCompressor.SNAPPY)
17+
? compressorInit
18+
: null
1519

1620
try {
1721
switch (compressor) {
@@ -20,6 +24,7 @@ const decompressingBuffer = (
2024

2125
return {
2226
compressor,
27+
isCompressed: !!compressorByValue,
2328
value: anyToBuffer(value),
2429
}
2530
}
@@ -28,35 +33,37 @@ const decompressingBuffer = (
2833

2934
return {
3035
compressor,
36+
isCompressed: !!compressorByValue,
3137
value: anyToBuffer(value),
3238
}
3339
}
3440
case KeyValueCompressor.LZ4: {
3541
const value = decompressLz4(Buffer.from(reply))
3642
return {
3743
compressor,
44+
isCompressed: !!compressorByValue,
3845
value: anyToBuffer(value),
3946
}
4047
}
4148
case KeyValueCompressor.SNAPPY: {
4249
const value = decompressSnappy(Buffer.from(reply))
4350
return {
4451
compressor,
52+
isCompressed: !!compressorByValue,
4553
value: anyToBuffer(value),
4654
}
4755
}
4856
default: {
49-
return { value: reply, compressor: null }
57+
return { value: reply, compressor: null, isCompressed: !!compressorByValue }
5058
}
5159
}
5260
} catch (error) {
53-
console.warn(`Error during decompressing data, compressor: ${compressor}`)
54-
return { value: reply, compressor }
61+
return { value: reply, compressor, isCompressed: false }
5562
}
5663
}
5764

5865
const getCompressor = (reply: RedisResponseBuffer): Nullable<KeyValueCompressor> => {
59-
const replyStart = reply.data?.slice?.(0, 10)?.join?.(',') ?? ''
66+
const replyStart = reply?.data?.slice?.(0, 10)?.join?.(',') ?? ''
6067
let compressor: Nullable<KeyValueCompressor> = null
6168

6269
forIn<ICompressorMagicSymbols>(

redisinsight/ui/src/utils/tests/decompressors/decompressors.spec.ts

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,78 +19,106 @@ import {
1919
} from './constants'
2020

2121
const defaultValues = [
22-
{ input: [49], compressor: null, output: [49], outputStr: '1' },
23-
{ input: [49, 50], compressor: null, output: [49, 50], outputStr: '12' },
22+
{ input: [49], compressor: null, output: [49], outputStr: '1', isCompressed: false },
23+
{ input: [49, 50], compressor: null, output: [49, 50], outputStr: '12', isCompressed: false },
2424
{
2525
input: COMPRESSOR_MAGIC_SYMBOLS[KeyValueCompressor.GZIP].split(',').map((symbol) => toNumber(symbol)),
2626
compressor: null,
2727
output: [31, 139],
2828
outputStr: '\\x1f\\x8b',
29+
isCompressed: false,
2930
},
3031
{
3132
input: COMPRESSOR_MAGIC_SYMBOLS[KeyValueCompressor.ZSTD].split(',').map((symbol) => toNumber(symbol)),
3233
compressor: null,
3334
output: [40, 181, 47, 253],
3435
outputStr: '(\\xb5/\\xfd',
36+
isCompressed: false,
3537
},
3638
{
3739
input: GZIP_COMPRESSED_VALUE_1,
3840
compressor: KeyValueCompressor.GZIP,
3941
output: DECOMPRESSED_VALUE_1,
4042
outputStr: DECOMPRESSED_VALUE_STR_1,
43+
isCompressed: true,
4144
},
4245
{
4346
input: GZIP_COMPRESSED_VALUE_2,
4447
compressor: KeyValueCompressor.GZIP,
4548
output: DECOMPRESSED_VALUE_2,
4649
outputStr: DECOMPRESSED_VALUE_STR_2,
50+
isCompressed: true,
4751
},
4852
{
4953
input: ZSTD_COMPRESSED_VALUE_1,
5054
compressor: KeyValueCompressor.ZSTD,
5155
output: DECOMPRESSED_VALUE_1,
5256
outputStr: DECOMPRESSED_VALUE_STR_1,
57+
isCompressed: true,
5358
},
5459
{
5560
input: ZSTD_COMPRESSED_VALUE_2,
5661
compressor: KeyValueCompressor.ZSTD,
5762
output: DECOMPRESSED_VALUE_2,
5863
outputStr: DECOMPRESSED_VALUE_STR_2,
64+
isCompressed: true,
5965
},
6066
{
6167
input: LZ4_COMPRESSED_VALUE_1,
6268
compressor: KeyValueCompressor.LZ4,
6369
output: DECOMPRESSED_VALUE_1,
6470
outputStr: DECOMPRESSED_VALUE_STR_1,
71+
isCompressed: true,
6572
},
6673
{
6774
input: LZ4_COMPRESSED_VALUE_2,
6875
compressor: KeyValueCompressor.LZ4,
6976
output: DECOMPRESSED_VALUE_2,
7077
outputStr: DECOMPRESSED_VALUE_STR_2,
78+
isCompressed: true,
7179
},
7280
{
7381
input: SNAPPY_COMPRESSED_VALUE_1,
7482
compressor: KeyValueCompressor.SNAPPY,
7583
compressorInit: KeyValueCompressor.SNAPPY,
7684
output: DECOMPRESSED_VALUE_1,
7785
outputStr: DECOMPRESSED_VALUE_STR_1,
86+
isCompressed: false,
7887
},
7988
{
8089
input: SNAPPY_COMPRESSED_VALUE_2,
8190
compressor: KeyValueCompressor.SNAPPY,
8291
compressorInit: KeyValueCompressor.SNAPPY,
8392
output: DECOMPRESSED_VALUE_2,
8493
outputStr: DECOMPRESSED_VALUE_STR_2,
94+
isCompressed: false,
95+
},
96+
{
97+
input: GZIP_COMPRESSED_VALUE_1,
98+
compressor: null,
99+
output: GZIP_COMPRESSED_VALUE_1,
100+
outputStr: DECOMPRESSED_VALUE_STR_1,
101+
compressorInit: KeyValueCompressor.LZ4,
102+
compressorByValue: KeyValueCompressor.GZIP,
103+
isCompressed: true,
104+
},
105+
{
106+
input: ZSTD_COMPRESSED_VALUE_1,
107+
compressor: null,
108+
compressorInit: KeyValueCompressor.LZ4,
109+
compressorByValue: KeyValueCompressor.ZSTD,
110+
output: ZSTD_COMPRESSED_VALUE_1,
111+
outputStr: DECOMPRESSED_VALUE_STR_1,
112+
isCompressed: true,
85113
},
86114
].map((value) => ({
87115
...value,
88116
input: anyToBuffer(value.input)
89117
}))
90118

91119
describe('getCompressor', () => {
92-
test.each(defaultValues)('%j', ({ input, compressor }) => {
93-
let expected = compressor
120+
test.each(defaultValues)('%j', ({ input, compressor, compressorByValue = null }) => {
121+
let expected = compressorByValue || compressor
94122

95123
// SNAPPY doesn't have magic symbols
96124
if (compressor === KeyValueCompressor.SNAPPY) {
@@ -103,14 +131,14 @@ describe('getCompressor', () => {
103131
})
104132

105133
describe('decompressingBuffer', () => {
106-
test.each(defaultValues)('%j', ({ input, compressor, output, compressorInit = null }) => {
107-
const result = decompressingBuffer(input, compressorInit)
134+
test.each(defaultValues)('%j', ({ input, compressor, output, compressorInit = null, isCompressed }) => {
135+
const result = decompressingBuffer(input, compressorInit || compressor)
108136
let value: UintArray = output
109137

110138
if (compressor && compressor !== KeyValueCompressor.GZIP) {
111139
value = new Uint8Array(output)
112140
}
113141

114-
expect(result).toEqual({ value: anyToBuffer(value), compressor })
142+
expect(result).toEqual({ value: anyToBuffer(value), compressor, isCompressed })
115143
})
116144
})

0 commit comments

Comments
 (0)