Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e891e6d
RI-5917-ShowTTL-checkbox-for-hash-fields
kchepikava Jul 18, 2024
24ab8a4
5917 comments resolve, show TTl checkbox only when necessary
kchepikava Jul 22, 2024
383e17a
5917 fix tests
kchepikava Jul 22, 2024
a178747
Merge branch 'main' into fe/feature/RI-5917-hide-ttl-for-individual-h…
mariasergeenko Jul 26, 2024
7e81886
Merge remote-tracking branch 'origin/release/2.54.0' into fe/feature/…
mariasergeenko Jul 26, 2024
c9d4023
RI-5959 fixed showttl not working
kchepikava Jul 26, 2024
ee2e973
RI-5960 fixed borders are not aligned
kchepikava Jul 26, 2024
9332b5d
RI-5960-fix-style
kchepikava Jul 26, 2024
c7fc9a8
Merge pull request #3650 from RedisInsight/fe/bugfix/RI-5960-borders-…
mariasergeenko Jul 29, 2024
7b302b7
Merge pull request #3649 from RedisInsight/fe/bugfix/RI-5959-show-ttl…
mariasergeenko Jul 29, 2024
934e6d7
Merge remote-tracking branch 'origin/fe/feature/RI-5917-hide-ttl-for-…
mariasergeenko Jul 29, 2024
2426d8d
Verify add show ttl
mariasergeenko Jul 29, 2024
02be7e3
Merge remote-tracking branch 'origin' into fe/feature/RI-5917-hide-tt…
kchepikava Jul 29, 2024
bb5121d
Merge branch 'main' into fe/feature/RI-5917-hide-ttl-for-individual-h…
kchepikava Jul 29, 2024
5252acf
Merge branch 'release/2.54.0' into fe/feature/RI-5917-hide-ttl-for-in…
kchepikava Jul 29, 2024
9a9ea6b
Merge branch 'fe/feature/RI-5917-hide-ttl-for-individual-hash-fields'…
mariasergeenko Jul 29, 2024
1a94b1c
remove only
mariasergeenko Jul 29, 2024
667590a
remove config
mariasergeenko Jul 29, 2024
1dad4ba
Merge pull request #3655 from RedisInsight/e2e/feature/RI-5917-hide-t…
mariasergeenko Jul 29, 2024
c1132af
RI-5917 add subheader for all fields
kchepikava Jul 29, 2024
3c2a64c
RI-5917 move show ttl checkbox in hash actions
kchepikava Jul 30, 2024
822eac7
RI 5917 returned margin to key-details-header-formatter
kchepikava Jul 30, 2024
4a69cc2
Merge branch 'main' into fe/feature/RI-5917-hide-ttl-for-individual-h…
kchepikava Aug 6, 2024
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: 3 additions & 1 deletion redisinsight/ui/src/components/divider/Divider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,18 @@ export interface Props {
orientation?: 'horizontal' | 'vertical',
variant? : 'fullWidth' | 'middle' | 'half';
className?: string;
style?: any
}

const Divider = ({ orientation, variant, className, color, colorVariable }: Props) => (
const Divider = ({ orientation, variant, className, color, colorVariable, ...props }: Props) => (
<div
className={cx(
styles.divider,
styles[`divider-${variant || 'fullWidth'}`],
styles[`divider-${orientation || 'horizontal'}`],
className,
)}
{...props}
>
<hr style={(color || colorVariable) ? { backgroundColor: color ?? `var(--${colorVariable})` } : {}} />
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ import AutoSizer from 'react-virtualized-auto-sizer'
import { GroupBadge, AutoRefresh, FullScreen } from 'uiSrc/components'
import {
HIDE_LAST_REFRESH,
KeyTypes,
ModulesKeyTypes,
} from 'uiSrc/constants'
import {
deleteSelectedKeyAction,
Expand All @@ -30,7 +28,6 @@ import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances'
import { RedisResponseBuffer } from 'uiSrc/slices/interfaces'
import { getBasedOnViewTypeEvent, sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry'

import { KeyDetailsHeaderFormatter } from './components/key-details-header-formatter'
import { KeyDetailsHeaderName } from './components/key-details-header-name'
import { KeyDetailsHeaderTTL } from './components/key-details-header-ttl'
import { KeyDetailsHeaderDelete } from './components/key-details-header-delete'
Expand All @@ -39,7 +36,6 @@ import { KeyDetailsHeaderSizeLength } from './components/key-details-header-size
import styles from './styles.module.scss'

export interface KeyDetailsHeaderProps {
keyType: KeyTypes | ModulesKeyTypes
onCloseKey: (key: RedisResponseBuffer) => void
onRemoveKey: () => void
onEditKey: (key: RedisResponseBuffer, newKey: RedisResponseBuffer, onFailure?: () => void) => void
Expand All @@ -56,7 +52,6 @@ const KeyDetailsHeader = ({
onCloseKey,
onRemoveKey,
onEditKey,
keyType,
Actions,
}: KeyDetailsHeaderProps) => {
const { refreshing, loading, lastRefreshTime, isRefreshDisabled } = useSelector(selectedKeySelector)
Expand Down Expand Up @@ -177,9 +172,6 @@ const KeyDetailsHeader = ({
onChangeAutoRefreshRate={handleChangeAutoRefreshRate}
testid="key"
/>
{Object.values(KeyTypes).includes(keyType as KeyTypes) && (
<KeyDetailsHeaderFormatter width={width} />
)}
{!isUndefined(Actions) && <Actions width={width} />}
<KeyDetailsHeaderDelete onDelete={handleDeleteKey} />
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { StreamDetails } from '../stream-details'
export interface Props extends KeyDetailsHeaderProps {
onOpenAddItemPanel: () => void
onCloseAddItemPanel: () => void
keyType: KeyTypes | ModulesKeyTypes
}

const DynamicTypeDetails = (props: Props) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,97 @@
import React from 'react'
import { instance, mock } from 'ts-mockito'
import { render } from 'uiSrc/utils/test-utils'
import { render, screen, fireEvent } from 'uiSrc/utils/test-utils'
import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry'
import { INSTANCE_ID_MOCK } from 'uiSrc/mocks/handlers/instances/instancesHandlers'
import { Props, HashDetails } from './HashDetails'

const mockedProps = mock<Props>()

jest.mock('uiSrc/telemetry', () => ({
...jest.requireActual('uiSrc/telemetry'),
sendEventTelemetry: jest.fn(),
}))

jest.mock('uiSrc/slices/instances/instances', () => ({
...jest.requireActual('uiSrc/slices/instances/instances'),
connectedInstanceOverviewSelector: jest.fn().mockReturnValue({
version: '7.4.2',
}),
}))

jest.mock('uiSrc/slices/app/features', () => ({
...jest.requireActual('uiSrc/slices/app/features'),
appFeatureFlagsFeaturesSelector: jest.fn().mockReturnValue({
hashFieldExpiration: { flag: true },
}),
}))

describe('HashDetails', () => {
beforeEach(() => {
jest.clearAllMocks()
})
it('should render', () => {
expect(render(<HashDetails {...instance(mockedProps)} />)).toBeTruthy()
})

it('should render subheader', () => {
render(<HashDetails {...instance(mockedProps)} />)
expect(screen.getByTestId('select-format-key-value')).toBeInTheDocument()
})

it('opens and closes the add item panel', () => {
render(
<HashDetails
{...instance(mockedProps)}
onOpenAddItemPanel={() => {}}
onCloseAddItemPanel={() => {}}
/>
)
fireEvent.click(screen.getByTestId('add-key-value-items-btn'))
expect(screen.getByText('Save')).toBeInTheDocument()
fireEvent.click(screen.getByText('Cancel'))
expect(screen.queryByText('Save')).not.toBeInTheDocument()
})

describe('when hashFieldFeatureFlag and version higher 7.3', () => {
it('renders subheader with checkbox', () => {
render(<HashDetails {...instance(mockedProps)} />)
expect(screen.getByText('Show TTL')).toBeInTheDocument()
})

it('toggles the show TTL button', () => {
render(<HashDetails {...instance(mockedProps)} />)
const el = screen.getByTestId('test-check-ttl') as HTMLInputElement
expect(el.checked).toBe(true)
fireEvent.click(el)
expect(el.checked).toBe(false)
})

it('should call proper telemetry event after click on showTtl', () => {
const sendEventTelemetryMock = jest.fn();
(sendEventTelemetry as jest.Mock).mockImplementation(() => sendEventTelemetryMock)

render(<HashDetails {...instance(mockedProps)} />)

fireEvent.click(screen.getByTestId('test-check-ttl'))

expect(sendEventTelemetry).toBeCalledWith({
event: TelemetryEvent.SHOW_HASH_TTL_CLICKED,
eventData: {
databaseId: INSTANCE_ID_MOCK,
action: 'hide'
}
})

fireEvent.click(screen.getByTestId('test-check-ttl'))

expect(sendEventTelemetry).toBeCalledWith({
event: TelemetryEvent.SHOW_HASH_TTL_CLICKED,
eventData: {
databaseId: INSTANCE_ID_MOCK,
action: 'show'
}
})
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import React, { useState } from 'react'
import { useSelector } from 'react-redux'
import cx from 'classnames'

import { useParams } from 'react-router-dom'
import { EuiCheckbox, EuiFlexItem } from '@elastic/eui'
import {
selectedKeySelector,
} from 'uiSrc/slices/browser/keys'
Expand All @@ -12,9 +14,13 @@ import { isVersionHigherOrEquals } from 'uiSrc/utils'
import { CommandsVersions } from 'uiSrc/constants/commandsVersions'
import { connectedInstanceOverviewSelector } from 'uiSrc/slices/instances/instances'
import { appFeatureFlagsFeaturesSelector } from 'uiSrc/slices/app/features'
import { TelemetryEvent, sendEventTelemetry } from 'uiSrc/telemetry'
import Divider from 'uiSrc/components/divider/Divider'
import AddHashFields from './add-hash-fields/AddHashFields'
import { HashDetailsTable } from './hash-details-table'
import { KeyDetailsSubheader } from '../key-details-subheader/KeyDetailsSubheader'
import { AddItemsAction } from '../key-details-actions'
import styles from './styles.module.scss'

export interface Props extends KeyDetailsHeaderProps {
onRemoveKey: () => void
Expand All @@ -28,11 +34,13 @@ const HashDetails = (props: Props) => {

const { loading } = useSelector(selectedKeySelector)
const { version } = useSelector(connectedInstanceOverviewSelector)
const { instanceId } = useParams<{ instanceId: string }>()
const {
[FeatureFlags.hashFieldExpiration]: hashFieldExpirationFeature
} = useSelector(appFeatureFlagsFeaturesSelector)

const [isAddItemPanelOpen, setIsAddItemPanelOpen] = useState<boolean>(false)
const [showTtl, setShowTtl] = useState<boolean>(true)

const isExpireFieldsAvailable = hashFieldExpirationFeature?.flag
&& isVersionHigherOrEquals(version, CommandsVersions.HASH_TTL.since)
Expand All @@ -50,21 +58,55 @@ const HashDetails = (props: Props) => {
}

const Actions = ({ width }: { width: number }) => (
<AddItemsAction title="Add Fields" width={width} openAddItemPanel={openAddItemPanel} />
<>
{isExpireFieldsAvailable && (
<>
<EuiCheckbox
id="showTtl"
name="showTtl"
label="Show TTL"
className={styles.showTtlCheckbox}
checked={showTtl}
onChange={(e) => handleSelectShow(e.target.checked)}
data-testId="test-check-ttl"
/>
<Divider
className={styles.divider}
colorVariable="separatorColor"
orientation="vertical"
/>
</>
)}
<AddItemsAction title="Add Fields" width={width} openAddItemPanel={openAddItemPanel} />
</>
)

const handleSelectShow = (show: boolean) => {
setShowTtl(show)

sendEventTelemetry({
event: TelemetryEvent.SHOW_HASH_TTL_CLICKED,
eventData: {
databaseId: instanceId,
action: show ? 'show' : 'hide'
}
})
}

return (
<div className="fluid flex-column relative">
<KeyDetailsHeader
{...props}
key="key-details-header"
/>
<KeyDetailsSubheader
keyType={keyType}
Actions={Actions}
/>
<div className="key-details-body" key="key-details-body">
{!loading && (
<div className="flex-column" style={{ flex: '1', height: '100%' }}>
<HashDetailsTable isExpireFieldsAvailable={isExpireFieldsAvailable} onRemoveKey={onRemoveKey} />
<HashDetailsTable isExpireFieldsAvailable={isExpireFieldsAvailable && showTtl} onRemoveKey={onRemoveKey} />
</div>
)}
{isAddItemPanelOpen && (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
.divider {
margin: 0 14px;
height: 20px;
width: 1px;
}


.showTtlCheckbox {
font-weight: 400;
font-size: 14px;

:global(.euiCheckbox__input) {
color: var(--controlsBorderColor) !important;
}

:global(.euiCheckbox__square) {
width: 18px !important;
height: 18px !important;
border: 1px solid var(--controlsBorderColor) !important;
border-radius: 4px !important;
box-shadow: none !important;
}

:global(.euiCheckbox__label) {
color: var(--controlsLabelColor) !important;
}
}

Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
.actionBtn {
margin-right: 12px;
position: relative;
z-index: 2;

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

const mockedProps = mock<Props>()

describe('KeyDetailsSubheader', () => {
it('should render', () => {
expect(render(<KeyDetailsSubheader {...instance(mockedProps)} />)).toBeTruthy()
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React, { ReactElement } from 'react'

import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'
import AutoSizer from 'react-virtualized-auto-sizer'

import { isUndefined } from 'lodash'
import Divider from 'uiSrc/components/divider/Divider'
import { KeyTypes, ModulesKeyTypes } from 'uiSrc/constants'
import { KeyDetailsHeaderFormatter } from '../../../key-details-header/components/key-details-header-formatter'
import styles from './styles.module.scss'

export interface Props {
keyType: KeyTypes | ModulesKeyTypes
Actions?: (props: { width: number }) => ReactElement
}

export const KeyDetailsSubheader = ({
keyType,
Actions,
}: Props) => (
<div className={styles.subheaderContainer}>
<AutoSizer disableHeight>
{({ width = 0 }) => (
<div style={{ width }}>
<EuiFlexGroup justifyContent="flexEnd" alignItems="center" gutterSize="none">
{Object.values(KeyTypes).includes(keyType as KeyTypes) && (
<>
<EuiFlexItem className={styles.keyFormatterItem} grow={false}>
<KeyDetailsHeaderFormatter width={width} />
</EuiFlexItem>
<Divider
className={styles.divider}
colorVariable="separatorColor"
orientation="vertical"
/>
</>
)}
{!isUndefined(Actions) && <Actions width={width} />}
</EuiFlexGroup>
</div>
)}
</AutoSizer>
</div>
)

export default KeyDetailsSubheader
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.subheaderContainer {
padding: 12px 18px 0px 18px;

.divider {
margin: 0 14px;
height: 20px;
width: 1px;
}

.actionItem {
margin-left: 12px;
}
}

Loading