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
4 changes: 4 additions & 0 deletions redisinsight/ui/src/config/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ export const defaultConfig = {
'RI_DATABASE_OVERVIEW_MINIMUM_REFRESH_INTERVAL',
1,
),
rejsonMonacoEditorMaxThreshold: intEnv(
'RI_REJSON_MONACO_EDITOR_MAX_THRESHOLD',
10_000,
),
},
features: {
envDependent: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import React from 'react'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import ChangeEditorTypeButton, { ButtonMode } from './ChangeEditorTypeButton'

import ChangeEditorTypeButton from './ChangeEditorTypeButton'

const mockSwitchEditorType = jest.fn()
let mockIsTextEditorDisabled = false

jest.mock('./useChangeEditorType', () => ({
useChangeEditorType: () => ({
switchEditorType: mockSwitchEditorType,
isTextEditorDisabled: mockIsTextEditorDisabled,
}),
}))

Expand All @@ -16,32 +20,38 @@ describe('ChangeEditorTypeButton', () => {
})

it('should render an enabled button with default tooltip', async () => {
mockIsTextEditorDisabled = false

render(<ChangeEditorTypeButton />)

const button = screen.getByRole('button', { name: /change editor type/i })
expect(button).toBeEnabled()

await userEvent.hover(button)

expect(
await screen.findByText('Edit value in text editor'),
).toBeInTheDocument()
})

it('should render a disabled button with read-only tooltip', async () => {
render(<ChangeEditorTypeButton mode={ButtonMode.readOnly} />)
it('should render a disabled button with a tooltip', async () => {
mockIsTextEditorDisabled = true

render(<ChangeEditorTypeButton />)

const button = screen.getByRole('button', { name: /change editor type/i })
expect(button).toBeDisabled()

await userEvent.hover(button)

expect(
await screen.findByText('This JSON is too large to edit'),
await screen.findByText(
'This JSON document is too large to view or edit in full.',
),
).toBeInTheDocument()
})

it('should call switchEditorType on click when not disabled', async () => {
mockIsTextEditorDisabled = false

render(<ChangeEditorTypeButton />)

const button = screen.getByRole('button', { name: /change editor type/i })
Expand All @@ -51,7 +61,9 @@ describe('ChangeEditorTypeButton', () => {
})

it('should not call switchEditorType when disabled', async () => {
render(<ChangeEditorTypeButton mode={ButtonMode.readOnly} />)
mockIsTextEditorDisabled = true

render(<ChangeEditorTypeButton />)

const button = screen.getByRole('button', { name: /change editor type/i })
await userEvent.click(button)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,12 @@ import React from 'react'
import { EuiButtonIcon, EuiToolTip } from '@elastic/eui'
import { useChangeEditorType } from './useChangeEditorType'

export enum ButtonMode {
editable = 'editable',
readOnly = 'readOnly',
}

export type ChangeEditorTypeButtonProps = {
mode?: ButtonMode
}

const ChangeEditorTypeButton = ({
mode = ButtonMode.editable,
}: ChangeEditorTypeButtonProps) => {
const { switchEditorType } = useChangeEditorType()
const isReadMode = mode === ButtonMode.readOnly
const ChangeEditorTypeButton = () => {
const { switchEditorType, isTextEditorDisabled } = useChangeEditorType()

const isDisabled = isReadMode
const tooltip = isReadMode
? 'This JSON is too large to edit'
const isDisabled = isTextEditorDisabled
const tooltip = isTextEditorDisabled
? 'This JSON document is too large to view or edit in full.'
: 'Edit value in text editor'

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import ChangeEditorTypeButton from './ChangeEditorTypeButton'

export { ButtonMode as ChangeEditorTypeButtonMode } from './ChangeEditorTypeButton'
export { useChangeEditorType } from './useChangeEditorType'
export default ChangeEditorTypeButton
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import * as reactRedux from 'react-redux'
import { renderHook, act } from '@testing-library/react-hooks'
import { EditorType } from 'uiSrc/slices/interfaces'
import { FeatureFlags } from 'uiSrc/constants'

import { useChangeEditorType } from './useChangeEditorType'

jest.mock('react-redux', () => ({
Expand Down Expand Up @@ -56,4 +58,62 @@ describe('useChangeEditorType', () => {
payload: EditorType.Default,
})
})

describe('isTextEditorDisabled', () => {
it('should be false when isWithinThreshold is true', () => {
mockedUseSelector
.mockImplementationOnce(() => ({
editorType: EditorType.Default,
isWithinThreshold: true,
}))
.mockImplementationOnce(() => ({
[FeatureFlags.envDependent]: { flag: false },
}))

const { result } = renderHook(() => useChangeEditorType())
expect(result.current.isTextEditorDisabled).toBe(false)
})

it('should be false when not within threshold but feature flag is true', () => {
mockedUseSelector
.mockImplementationOnce(() => ({
editorType: EditorType.Default,
isWithinThreshold: false,
}))
.mockImplementationOnce(() => ({
[FeatureFlags.envDependent]: { flag: true },
}))

const { result } = renderHook(() => useChangeEditorType())
expect(result.current.isTextEditorDisabled).toBe(false)
})

it('should be true when not within threshold and feature flag is false', () => {
mockedUseSelector
.mockImplementationOnce(() => ({
editorType: EditorType.Default,
isWithinThreshold: false,
}))
.mockImplementationOnce(() => ({
[FeatureFlags.envDependent]: { flag: false },
}))

const { result } = renderHook(() => useChangeEditorType())
expect(result.current.isTextEditorDisabled).toBe(true)
})

it('should be true when envDependentFeature is undefined', () => {
mockedUseSelector
.mockImplementationOnce(() => ({
editorType: EditorType.Default,
isWithinThreshold: false,
}))
.mockImplementationOnce(() => ({
[FeatureFlags.envDependent]: undefined,
}))

const { result } = renderHook(() => useChangeEditorType())
expect(result.current.isTextEditorDisabled).toBe(true)
})
})
})
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
import { useCallback } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { FeatureFlags } from 'uiSrc/constants'
import { appFeatureFlagsFeaturesSelector } from 'uiSrc/slices/app/features'
import { rejsonSelector, setEditorType } from 'uiSrc/slices/browser/rejson'
import { EditorType } from 'uiSrc/slices/interfaces'

export const useChangeEditorType = () => {
const dispatch = useDispatch()
const { editorType } = useSelector(rejsonSelector)
const { editorType, isWithinThreshold } = useSelector(rejsonSelector)
const { [FeatureFlags.envDependent]: envDependentFeature } = useSelector(
appFeatureFlagsFeaturesSelector,
)

const isTextEditorDisabled = !isWithinThreshold && !envDependentFeature?.flag

const switchEditorType = useCallback(() => {
const opposite =
editorType === EditorType.Default ? EditorType.Text : EditorType.Default
dispatch(setEditorType(opposite))
}, [dispatch, editorType])

return { switchEditorType, editorType }
return { switchEditorType, editorType, isTextEditorDisabled }
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ const mockStore = configureStore({
browser: {
rejson: {},
},
app: {
features: {
featureFlags: { features: { envDependent: { flag: true } } },
},
},
}),
})

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { useDispatch } from 'react-redux'
import { EuiButtonIcon } from '@elastic/eui'
import cx from 'classnames'
import {
Expand All @@ -9,16 +9,12 @@ import {
setReJSONDataAction,
} from 'uiSrc/slices/browser/rejson'
import { RedisResponseBuffer } from 'uiSrc/slices/interfaces'
import { appFeatureFlagsFeaturesSelector } from 'uiSrc/slices/app/features'
import { FeatureFlags } from 'uiSrc/constants'

import { getBrackets, isRealArray, isRealObject, wrapPath } from '../utils'
import { BaseProps, ObjectTypes } from '../interfaces'
import RejsonDynamicTypes from '../rejson-dynamic-types'
import { AddItem } from '../components'
import ChangeEditorTypeButton, {
ChangeEditorTypeButtonMode,
} from '../../change-editor-type-button'
import ChangeEditorTypeButton from '../../change-editor-type-button'

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

Expand All @@ -36,10 +32,6 @@ const RejsonDetails = (props: BaseProps) => {

const [addRootKVPair, setAddRootKVPair] = useState<boolean>(false)

const { [FeatureFlags.envDependent]: envDependentFeature } = useSelector(
appFeatureFlagsFeaturesSelector,
)

const dispatch = useDispatch()

const handleFetchVisualisationResults = (
Expand Down Expand Up @@ -104,13 +96,7 @@ const RejsonDetails = (props: BaseProps) => {
'start',
)}
</span>
<ChangeEditorTypeButton
mode={
envDependentFeature?.flag
? ChangeEditorTypeButtonMode.editable
: ChangeEditorTypeButtonMode.readOnly
}
/>
<ChangeEditorTypeButton />
</div>
)}
<RejsonDynamicTypes
Expand Down
11 changes: 9 additions & 2 deletions redisinsight/ui/src/slices/browser/keys.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { cloneDeep, remove, get, isUndefined } from 'lodash'
import axios, { AxiosError, CancelTokenSource } from 'axios'
import { useSelector } from 'react-redux'
import { apiService, localStorageService } from 'uiSrc/services'
import { getConfig } from 'uiSrc/config'
import {
ApiEndpoints,
BrowserStorageItem,
Expand Down Expand Up @@ -62,7 +62,7 @@ import {
refreshZsetMembersAction,
} from './zset'
import { fetchSetMembers, refreshSetMembersAction } from './set'
import { fetchReJSON, setEditorType } from './rejson'
import { fetchReJSON, setEditorType, setIsWithinThreshold } from './rejson'
import {
setHashInitialState,
fetchHashFields,
Expand Down Expand Up @@ -104,6 +104,8 @@ import { AppDispatch, RootState } from '../store'
import { StreamViewType } from '../interfaces/stream'
import { EditorType, RedisResponseBuffer, RedisString } from '../interfaces'

const riConfig = getConfig()

const defaultViewFormat = KeyValueFormat.Unicode

export const initialState: KeysStore = {
Expand Down Expand Up @@ -808,6 +810,11 @@ export function fetchKeyInfo(
if (data.type === KeyTypes.ReJSON) {
dispatch<any>(fetchReJSON(key, '$', data.length, resetData))
dispatch<any>(setEditorType(EditorType.Default))
dispatch<any>(
setIsWithinThreshold(
data.size <= riConfig.browser.rejsonMonacoEditorMaxThreshold,
),
)
}
if (data.type === KeyTypes.Stream) {
const { viewType } = state.browser.stream
Expand Down
5 changes: 5 additions & 0 deletions redisinsight/ui/src/slices/browser/rejson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export const initialState: InitialStateRejson = {
type: '',
},
editorType: EditorType.Default,
isWithinThreshold: false,
}

// A slice for recipes
Expand Down Expand Up @@ -114,6 +115,9 @@ const rejsonSlice = createSlice({
setEditorType: (state, { payload }: PayloadAction<EditorType>) => {
state.editorType = payload
},
setIsWithinThreshold: (state, { payload }: PayloadAction<boolean>) => {
state.isWithinThreshold = payload
},
},
})

Expand All @@ -132,6 +136,7 @@ export const {
removeRejsonKeySuccess,
removeRejsonKeyFailure,
setEditorType,
setIsWithinThreshold,
} = rejsonSlice.actions

// A selector
Expand Down
1 change: 1 addition & 0 deletions redisinsight/ui/src/slices/interfaces/instances.ts
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,7 @@ export interface InitialStateRejson {
error: Nullable<string>
data: GetRejsonRlResponse
editorType: EditorType
isWithinThreshold: boolean
}

export interface ICredentialsRedisCluster {
Expand Down
Loading
Loading