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
Original file line number Diff line number Diff line change
Expand Up @@ -435,8 +435,9 @@ const HashDetails = (props: Props) => {
maxWidth: 95,
render: function Actions(_act: any, { field: fieldItem, value: valueItem }: HashFieldDto, _, rowIndex?: number) {
const field = bufferToString(fieldItem, viewFormat)
const isEditable = !compressor && isFormatEditable(viewFormat)
const tooltipContent = compressor ? TEXT_DISABLED_COMPRESSED_VALUE : TEXT_DISABLED_FORMATTER_EDITING
const { isCompressed } = decompressingBuffer(valueItem, compressor)
const isEditable = !isCompressed && isFormatEditable(viewFormat)
const tooltipContent = isCompressed ? TEXT_DISABLED_COMPRESSED_VALUE : TEXT_DISABLED_FORMATTER_EDITING
return (
<StopPropagation>
<div className="value-table-actions">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
stringToBuffer,
validateTTLNumber
} from 'uiSrc/utils'
import { stringSelector } from 'uiSrc/slices/browser/string'
import KeyValueFormatter from './components/Formatter'
import AutoRefresh from '../auto-refresh'

Expand Down Expand Up @@ -94,7 +95,8 @@ const KeyDetailsHeader = ({
nameString: keyProp,
name: keyBuffer,
} = useSelector(selectedKeyDataSelector) ?? initialKeyInfo
const { id: instanceId, compressor = null } = useSelector(connectedInstanceSelector)
const { id: instanceId } = useSelector(connectedInstanceSelector)
const { isCompressed: isStringCompressed } = useSelector(stringSelector)
const { viewType } = useSelector(keysSelector)
const { viewType: streamViewType } = useSelector(streamSelector)
const { viewFormat: viewFormatProp } = useSelector(selectedKeySelector)
Expand Down Expand Up @@ -321,8 +323,8 @@ const KeyDetailsHeader = ({
)

const Actions = (width: number) => {
const isEditable = !compressor && isFormatEditable(viewFormatProp)
const noEditableText = compressor ? TEXT_DISABLED_COMPRESSED_VALUE : TEXT_DISABLED_FORMATTER_EDITING
const isEditable = !isStringCompressed && isFormatEditable(viewFormatProp)
const noEditableText = isStringCompressed ? TEXT_DISABLED_COMPRESSED_VALUE : TEXT_DISABLED_FORMATTER_EDITING
return (
<>
{KEY_TYPES_ACTIONS[keyType] && 'addItems' in KEY_TYPES_ACTIONS[keyType] && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -379,8 +379,9 @@ const ListDetails = (props: Props) => {
maxWidth: 60,
absoluteWidth: 60,
render: function Actions(_element: any, { index, element }: IListElement) {
const isEditable = !compressor && isFormatEditable(viewFormat)
const tooltipContent = compressor ? TEXT_DISABLED_COMPRESSED_VALUE : TEXT_DISABLED_FORMATTER_EDITING
const { isCompressed } = decompressingBuffer(element, compressor)
const isEditable = !isCompressed && isFormatEditable(viewFormat)
const tooltipContent = isCompressed ? TEXT_DISABLED_COMPRESSED_VALUE : TEXT_DISABLED_FORMATTER_EDITING
return (
<StopPropagation>
<div className="value-table-actions">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React from 'react'
import { instance, mock } from 'ts-mockito'
import { KeyValueCompressor } from 'uiSrc/constants'
import { stringDataSelector } from 'uiSrc/slices/browser/string'
import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances'
import { anyToBuffer, bufferToString } from 'uiSrc/utils'
import { render, screen, fireEvent, act } from 'uiSrc/utils/test-utils'
import { GZIP_COMPRESSED_VALUE_1, GZIP_COMPRESSED_VALUE_2, DECOMPRESSED_VALUE_STR_1, DECOMPRESSED_VALUE_STR_2 } from 'uiSrc/utils/tests/decompressors'
Expand All @@ -21,6 +23,13 @@ jest.mock('uiSrc/slices/browser/string', () => ({
}),
}))

jest.mock('uiSrc/slices/instances/instances', () => ({
...jest.requireActual('uiSrc/slices/instances/instances'),
connectedInstanceSelector: jest.fn().mockReturnValue({
compressor: null,
}),
}))

describe('StringDetails', () => {
it('should render', () => {
expect(
Expand Down Expand Up @@ -109,6 +118,10 @@ describe('StringDetails', () => {
})
stringDataSelector.mockImplementation(stringDataSelectorMock)

connectedInstanceSelector.mockImplementation(() => ({
compressor: KeyValueCompressor.GZIP,
}))

render(
<StringDetails
{...instance(mockedProps)}
Expand All @@ -127,6 +140,10 @@ describe('StringDetails', () => {
})
stringDataSelector.mockImplementation(stringDataSelectorMock)

connectedInstanceSelector.mockImplementation(() => ({
compressor: KeyValueCompressor.GZIP,
}))

render(
<StringDetails
{...instance(mockedProps)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ import {
isFormatEditable,
stringToBuffer,
stringToSerializedBufferFormat,
Nullable,
} from 'uiSrc/utils'
import {
resetStringValue,
setIsStringCompressed,
stringDataSelector,
stringSelector,
updateStringValueAction,
Expand Down Expand Up @@ -77,7 +77,7 @@ const StringDetails = (props: Props) => {
useEffect(() => {
if (!initialValue) return

const { value: decompressedValue } = decompressingBuffer(initialValue, compressor)
const { value: decompressedValue, isCompressed } = decompressingBuffer(initialValue, compressor)

const initialValueString = bufferToString(decompressedValue, viewFormat)
const { value: formattedValue, isValid } = formattingBuffer(decompressedValue, viewFormatProp, { expanded: true })
Expand All @@ -89,8 +89,10 @@ const StringDetails = (props: Props) => {
!isNonUnicodeFormatter(viewFormatProp, isValid)
&& !isEqualBuffers(initialValue, stringToBuffer(initialValueString))
)
setIsEditable(!compressor && isFormatEditable(viewFormatProp))
setNoEditableText(compressor ? TEXT_DISABLED_COMPRESSED_VALUE : TEXT_FAILED_CONVENT_FORMATTER(viewFormat))
setIsEditable(!isCompressed && isFormatEditable(viewFormatProp))
setNoEditableText(isCompressed ? TEXT_DISABLED_COMPRESSED_VALUE : TEXT_FAILED_CONVENT_FORMATTER(viewFormat))

dispatch(setIsStringCompressed(isCompressed))

if (viewFormat !== viewFormatProp) {
setViewFormat(viewFormatProp)
Expand Down
4 changes: 4 additions & 0 deletions redisinsight/ui/src/slices/browser/string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ const stringSlice = createSlice({
state.data.key = ''
state.data.value = null
},
setIsStringCompressed: (state, { payload }: PayloadAction<boolean>) => {
state.isCompressed = payload
},
},
})

Expand All @@ -70,6 +73,7 @@ export const {
updateValueSuccess,
updateValueFailure,
resetStringValue,
setIsStringCompressed,
} = stringSlice.actions

// A selector
Expand Down
1 change: 1 addition & 0 deletions redisinsight/ui/src/slices/interfaces/string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { RedisResponseBuffer } from './app'
export interface StringState {
loading: boolean
error: string
isCompressed: boolean
data: {
key: string
value: Nullable<RedisResponseBuffer>
Expand Down
26 changes: 25 additions & 1 deletion redisinsight/ui/src/slices/tests/browser/string.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import reducer, {
updateValueSuccess,
updateValueFailure,
resetStringValue,
updateStringValueAction
updateStringValueAction,
setIsStringCompressed,
} from '../../browser/string'

let store: typeof mockedStore
Expand Down Expand Up @@ -270,6 +271,29 @@ describe('string slice', () => {
})
})

describe('setIsStringCompressed', () => {
it('should properly set the state with isCompressed=true', () => {
// Arrange

const state = {
...initialState,
isCompressed: true,
}

// Act
const nextState = reducer(initialState, setIsStringCompressed(true))

// Assert
const rootState = Object.assign(initialStateDefault, {
browser: {
string: nextState,
},
})
expect(stringSelector(rootState)).toEqual(state)
expect(stringDataSelector(rootState)).toEqual(state.data)
})
})

describe('thunks', () => {
describe('fetchString', () => {
it('call both fetchString, getStringSuccess when fetch is successed', async () => {
Expand Down
19 changes: 13 additions & 6 deletions redisinsight/ui/src/utils/decompressors/decompressors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@ import { anyToBuffer, Nullable } from 'uiSrc/utils'
const decompressingBuffer = (
reply: RedisResponseBuffer,
compressorInit: Nullable<KeyValueCompressor> = null,
): { value: RedisString, compressor: Nullable<KeyValueCompressor> } => {
const compressor = compressorInit
): { value: RedisString, compressor: Nullable<KeyValueCompressor>, isCompressed: boolean } => {
const compressorByValue: Nullable<KeyValueCompressor> = getCompressor(reply)
const compressor = compressorInit === compressorByValue
|| (!compressorByValue && compressorInit === KeyValueCompressor.SNAPPY)
? compressorInit
: null

try {
switch (compressor) {
Expand All @@ -20,6 +24,7 @@ const decompressingBuffer = (

return {
compressor,
isCompressed: !!compressorByValue,
value: anyToBuffer(value),
}
}
Expand All @@ -28,35 +33,37 @@ const decompressingBuffer = (

return {
compressor,
isCompressed: !!compressorByValue,
value: anyToBuffer(value),
}
}
case KeyValueCompressor.LZ4: {
const value = decompressLz4(Buffer.from(reply))
return {
compressor,
isCompressed: !!compressorByValue,
value: anyToBuffer(value),
}
}
case KeyValueCompressor.SNAPPY: {
const value = decompressSnappy(Buffer.from(reply))
return {
compressor,
isCompressed: !!compressorByValue,
value: anyToBuffer(value),
}
}
default: {
return { value: reply, compressor: null }
return { value: reply, compressor: null, isCompressed: !!compressorByValue }
}
}
} catch (error) {
console.warn(`Error during decompressing data, compressor: ${compressor}`)
return { value: reply, compressor }
return { value: reply, compressor, isCompressed: false }
}
}

const getCompressor = (reply: RedisResponseBuffer): Nullable<KeyValueCompressor> => {
const replyStart = reply.data?.slice?.(0, 10)?.join?.(',') ?? ''
const replyStart = reply?.data?.slice?.(0, 10)?.join?.(',') ?? ''
let compressor: Nullable<KeyValueCompressor> = null

forIn<ICompressorMagicSymbols>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,78 +19,106 @@ import {
} from './constants'

const defaultValues = [
{ input: [49], compressor: null, output: [49], outputStr: '1' },
{ input: [49, 50], compressor: null, output: [49, 50], outputStr: '12' },
{ input: [49], compressor: null, output: [49], outputStr: '1', isCompressed: false },
{ input: [49, 50], compressor: null, output: [49, 50], outputStr: '12', isCompressed: false },
{
input: COMPRESSOR_MAGIC_SYMBOLS[KeyValueCompressor.GZIP].split(',').map((symbol) => toNumber(symbol)),
compressor: null,
output: [31, 139],
outputStr: '\\x1f\\x8b',
isCompressed: false,
},
{
input: COMPRESSOR_MAGIC_SYMBOLS[KeyValueCompressor.ZSTD].split(',').map((symbol) => toNumber(symbol)),
compressor: null,
output: [40, 181, 47, 253],
outputStr: '(\\xb5/\\xfd',
isCompressed: false,
},
{
input: GZIP_COMPRESSED_VALUE_1,
compressor: KeyValueCompressor.GZIP,
output: DECOMPRESSED_VALUE_1,
outputStr: DECOMPRESSED_VALUE_STR_1,
isCompressed: true,
},
{
input: GZIP_COMPRESSED_VALUE_2,
compressor: KeyValueCompressor.GZIP,
output: DECOMPRESSED_VALUE_2,
outputStr: DECOMPRESSED_VALUE_STR_2,
isCompressed: true,
},
{
input: ZSTD_COMPRESSED_VALUE_1,
compressor: KeyValueCompressor.ZSTD,
output: DECOMPRESSED_VALUE_1,
outputStr: DECOMPRESSED_VALUE_STR_1,
isCompressed: true,
},
{
input: ZSTD_COMPRESSED_VALUE_2,
compressor: KeyValueCompressor.ZSTD,
output: DECOMPRESSED_VALUE_2,
outputStr: DECOMPRESSED_VALUE_STR_2,
isCompressed: true,
},
{
input: LZ4_COMPRESSED_VALUE_1,
compressor: KeyValueCompressor.LZ4,
output: DECOMPRESSED_VALUE_1,
outputStr: DECOMPRESSED_VALUE_STR_1,
isCompressed: true,
},
{
input: LZ4_COMPRESSED_VALUE_2,
compressor: KeyValueCompressor.LZ4,
output: DECOMPRESSED_VALUE_2,
outputStr: DECOMPRESSED_VALUE_STR_2,
isCompressed: true,
},
{
input: SNAPPY_COMPRESSED_VALUE_1,
compressor: KeyValueCompressor.SNAPPY,
compressorInit: KeyValueCompressor.SNAPPY,
output: DECOMPRESSED_VALUE_1,
outputStr: DECOMPRESSED_VALUE_STR_1,
isCompressed: false,
},
{
input: SNAPPY_COMPRESSED_VALUE_2,
compressor: KeyValueCompressor.SNAPPY,
compressorInit: KeyValueCompressor.SNAPPY,
output: DECOMPRESSED_VALUE_2,
outputStr: DECOMPRESSED_VALUE_STR_2,
isCompressed: false,
},
{
input: GZIP_COMPRESSED_VALUE_1,
compressor: null,
output: GZIP_COMPRESSED_VALUE_1,
outputStr: DECOMPRESSED_VALUE_STR_1,
compressorInit: KeyValueCompressor.LZ4,
compressorByValue: KeyValueCompressor.GZIP,
isCompressed: true,
},
{
input: ZSTD_COMPRESSED_VALUE_1,
compressor: null,
compressorInit: KeyValueCompressor.LZ4,
compressorByValue: KeyValueCompressor.ZSTD,
output: ZSTD_COMPRESSED_VALUE_1,
outputStr: DECOMPRESSED_VALUE_STR_1,
isCompressed: true,
},
].map((value) => ({
...value,
input: anyToBuffer(value.input)
}))

describe('getCompressor', () => {
test.each(defaultValues)('%j', ({ input, compressor }) => {
let expected = compressor
test.each(defaultValues)('%j', ({ input, compressor, compressorByValue = null }) => {
let expected = compressorByValue || compressor

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

describe('decompressingBuffer', () => {
test.each(defaultValues)('%j', ({ input, compressor, output, compressorInit = null }) => {
const result = decompressingBuffer(input, compressorInit)
test.each(defaultValues)('%j', ({ input, compressor, output, compressorInit = null, isCompressed }) => {
const result = decompressingBuffer(input, compressorInit || compressor)
let value: UintArray = output

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

expect(result).toEqual({ value: anyToBuffer(value), compressor })
expect(result).toEqual({ value: anyToBuffer(value), compressor, isCompressed })
})
})