{column.isSortable && !searching && (
)}
+ {column.isResizable && (
+
onDragColumnStart(e, column)}
+ data-testid={`resize-trigger-${column.id}`}
+ role="presentation"
+ />
+ )}
)
}
@@ -363,8 +487,12 @@ const VirtualTable = (props: IProps) => {
{(resizeRef) => (
{loading && !hideProgress && (
@@ -381,7 +509,7 @@ const VirtualTable = (props: IProps) => {
minimumBatchSize={SCAN_COUNT_DEFAULT}
threshold={threshold}
loadMoreRows={loadMoreRows}
- rowCount={totalItemsCount}
+ rowCount={totalItemsCount || undefined}
>
{({ onRowsRendered, registerChild }) => (
{
maxWidth={column.maxWidth}
label={column.label}
dataKey={column.id}
- width={
- column.absoluteWidth || column.relativeWidth
- ? column.relativeWidth ?? 0
- : 20
- }
+ width={columnWidthSizes?.[column.id]?.abs || (
+ column.absoluteWidth || column.relativeWidth ? column.relativeWidth ?? 0 : 20
+ )}
flexGrow={!column.absoluteWidth && !column.relativeWidth ? 1 : 0}
headerRenderer={(headerProps) =>
headerRenderer({
@@ -448,7 +574,7 @@ const VirtualTable = (props: IProps) => {
void
onRowsRendered?: (info: IndexRange & OverscanIndexRange) => void
+ onColResizeEnd?: (cols: RelativeWidthSizes) => void
}
export interface ISortedColumn {
column: string
order: SortOrder
}
+
+export interface RelativeWidthSizes {
+ [key: string]: number
+}
+
+export interface AbsoluteWidthSizes {
+ [key: string]: {
+ abs: number
+ }
+}
+
+export type ColumnWidthSizes = AbsoluteWidthSizes & {
+ [key: string]: {
+ relative: number
+ }
+}
+
+export interface ResizableState {
+ column: Nullable
+ active: boolean
+ x: number
+}
diff --git a/redisinsight/ui/src/components/virtual-table/styles.module.scss b/redisinsight/ui/src/components/virtual-table/styles.module.scss
index 80d7dd49b4..17a0e80347 100644
--- a/redisinsight/ui/src/components/virtual-table/styles.module.scss
+++ b/redisinsight/ui/src/components/virtual-table/styles.module.scss
@@ -26,6 +26,11 @@ $footerHeight: 38px;
height: 100%;
width: 100%;
padding-bottom: $footerHeight;
+
+ &.isResizing {
+ user-select: none;
+ cursor: col-resize;
+ }
}
:global(.keys-tree__count) {
@@ -240,6 +245,29 @@ $footerHeight: 38px;
animation: dots 1s steps(5, end) infinite;
}
+.resizeTrigger {
+ position: absolute;
+ height: 100%;
+ right: -4px;
+ width: 7px;
+
+ cursor: col-resize;
+ z-index: 2;
+
+ &:before {
+ content: '';
+ display: block;
+ width: 7px;
+ height: 8px;
+ border-left: 1px solid var(--tableLightestBorderColor);
+ border-right: 1px solid var(--tableLightestBorderColor);
+
+ position: absolute;
+ top: 50%;
+ transform: translateY(-50%);
+ }
+}
+
@keyframes dots {
0%,
20% {
diff --git a/redisinsight/ui/src/constants/storage.ts b/redisinsight/ui/src/constants/storage.ts
index e0c696b677..060adb42c9 100644
--- a/redisinsight/ui/src/constants/storage.ts
+++ b/redisinsight/ui/src/constants/storage.ts
@@ -19,7 +19,8 @@ enum BrowserStorageItem {
RunQueryMode = 'RunQueryMode',
wbCleanUp = 'wbCleanUp',
viewFormat = 'viewFormat',
- wbGroupMode = 'wbGroupMode'
+ wbGroupMode = 'wbGroupMode',
+ keyDetailSizes = 'keyDetailSizes'
}
export default BrowserStorageItem
diff --git a/redisinsight/ui/src/pages/browser/BrowserPage.tsx b/redisinsight/ui/src/pages/browser/BrowserPage.tsx
index e9a726a27a..d1155d6c1d 100644
--- a/redisinsight/ui/src/pages/browser/BrowserPage.tsx
+++ b/redisinsight/ui/src/pages/browser/BrowserPage.tsx
@@ -187,11 +187,9 @@ const BrowserPage = () => {
@@ -222,15 +220,13 @@ const BrowserPage = () => {
arePanelsCollapsed={arePanelsCollapsed}
setSelectedKey={setSelectedKey}
selectedKey={selectedKey}
- panelsState={{
- isAddKeyPanelOpen,
- isCreateIndexPanelOpen,
- isBulkActionsPanelOpen,
- handleAddKeyPanel,
- handleBulkActionsPanel,
- handleCreateIndexPanel,
- closeRightPanels
- }}
+ isAddKeyPanelOpen={isAddKeyPanelOpen}
+ isCreateIndexPanelOpen={isCreateIndexPanelOpen}
+ isBulkActionsPanelOpen={isBulkActionsPanelOpen}
+ handleAddKeyPanel={handleAddKeyPanel}
+ handleBulkActionsPanel={handleBulkActionsPanel}
+ handleCreateIndexPanel={handleCreateIndexPanel}
+ closeRightPanels={closeRightPanels}
/>
>
diff --git a/redisinsight/ui/src/pages/browser/components/browser-left-panel/BrowserLeftPanel.tsx b/redisinsight/ui/src/pages/browser/components/browser-left-panel/BrowserLeftPanel.tsx
index 73d87fee15..a725a38cc4 100644
--- a/redisinsight/ui/src/pages/browser/components/browser-left-panel/BrowserLeftPanel.tsx
+++ b/redisinsight/ui/src/pages/browser/components/browser-left-panel/BrowserLeftPanel.tsx
@@ -28,24 +28,18 @@ import styles from './styles.module.scss'
export interface Props {
arePanelsCollapsed: boolean
selectKey: ({ rowData }: { rowData: any }) => void
- panelsState: {
- handleAddKeyPanel: (value: boolean) => void
- handleBulkActionsPanel: (value: boolean) => void
- handleCreateIndexPanel: (value: boolean) => void
- }
+ handleAddKeyPanel: (value: boolean) => void
+ handleBulkActionsPanel: (value: boolean) => void
+ handleCreateIndexPanel: (value: boolean) => void
}
const BrowserLeftPanel = (props: Props) => {
const {
selectKey,
- panelsState,
- } = props
-
- const {
handleAddKeyPanel,
handleBulkActionsPanel,
handleCreateIndexPanel,
- } = panelsState
+ } = props
const { instanceId } = useParams<{ instanceId: string }>()
const patternKeysState = useSelector(keysDataSelector)
diff --git a/redisinsight/ui/src/pages/browser/components/browser-right-panel/BrowserRightPanel.tsx b/redisinsight/ui/src/pages/browser/components/browser-right-panel/BrowserRightPanel.tsx
index 2cfa20c678..8d1f9be261 100644
--- a/redisinsight/ui/src/pages/browser/components/browser-right-panel/BrowserRightPanel.tsx
+++ b/redisinsight/ui/src/pages/browser/components/browser-right-panel/BrowserRightPanel.tsx
@@ -23,15 +23,13 @@ export interface Props {
selectedKey: Nullable
setSelectedKey: (keyName: Nullable) => void
arePanelsCollapsed: boolean
- panelsState: {
- isAddKeyPanelOpen: boolean
- handleAddKeyPanel: (value: boolean) => void
- isBulkActionsPanelOpen: boolean
- handleBulkActionsPanel: (value: boolean) => void
- isCreateIndexPanelOpen: boolean
- handleCreateIndexPanel?: (value: boolean) => void
- closeRightPanels: () => void
- }
+ isAddKeyPanelOpen: boolean
+ handleAddKeyPanel: (value: boolean) => void
+ isBulkActionsPanelOpen: boolean
+ handleBulkActionsPanel: (value: boolean) => void
+ isCreateIndexPanelOpen: boolean
+ handleCreateIndexPanel?: (value: boolean) => void
+ closeRightPanels: () => void
}
const BrowserRightPanel = (props: Props) => {
@@ -39,17 +37,13 @@ const BrowserRightPanel = (props: Props) => {
selectedKey,
arePanelsCollapsed,
setSelectedKey,
- panelsState
- } = props
-
- const {
isAddKeyPanelOpen,
handleAddKeyPanel,
isBulkActionsPanelOpen,
handleBulkActionsPanel,
isCreateIndexPanelOpen,
closeRightPanels
- } = panelsState
+ } = props
const { isBrowserFullScreen, viewType } = useSelector(keysSelector)
const { type, length } = useSelector(selectedKeyDataSelector) ?? { type: '', length: 0 }
diff --git a/redisinsight/ui/src/pages/browser/components/hash-details/HashDetails.spec.tsx b/redisinsight/ui/src/pages/browser/components/hash-details/HashDetails.spec.tsx
index 0e00dd8252..bca2fe9caa 100644
--- a/redisinsight/ui/src/pages/browser/components/hash-details/HashDetails.spec.tsx
+++ b/redisinsight/ui/src/pages/browser/components/hash-details/HashDetails.spec.tsx
@@ -1,11 +1,12 @@
import React from 'react'
import { instance, mock } from 'ts-mockito'
+import { RedisResponseBufferType } from 'uiSrc/slices/interfaces'
import { bufferToString } from 'uiSrc/utils'
import { fireEvent, render, screen } from 'uiSrc/utils/test-utils'
import HashDetails, { Props } from './HashDetails'
const mockedProps = mock()
-const fields = [
+const fields: Array<{ field: RedisResponseBufferType, value: RedisResponseBufferType }> = [
{ field: { type: 'Buffer', data: [49] }, value: { type: 'Buffer', data: [49, 65] } },
{ field: { type: 'Buffer', data: [49, 50, 51] }, value: { type: 'Buffer', data: [49, 11] } },
{ field: { type: 'Buffer', data: [50] }, value: { type: 'Buffer', data: [49, 234, 453] } },
@@ -64,4 +65,9 @@ describe('HashDetails', () => {
fireEvent.click(screen.getAllByTestId(/edit-hash-button/)[0])
expect(screen.getByTestId('hash-value-editor')).toBeInTheDocument()
})
+
+ it('should render resize trigger for field column', () => {
+ render()
+ expect(screen.getByTestId('resize-trigger-field')).toBeInTheDocument()
+ })
})
diff --git a/redisinsight/ui/src/pages/browser/components/hash-details/HashDetails.tsx b/redisinsight/ui/src/pages/browser/components/hash-details/HashDetails.tsx
index 3f2294116b..b50bd15008 100644
--- a/redisinsight/ui/src/pages/browser/components/hash-details/HashDetails.tsx
+++ b/redisinsight/ui/src/pages/browser/components/hash-details/HashDetails.tsx
@@ -4,58 +4,56 @@ import React, { ChangeEvent, Ref, useCallback, useEffect, useRef, useState } fro
import { useDispatch, useSelector } from 'react-redux'
import { CellMeasurerCache } from 'react-virtualized'
import AutoSizer from 'react-virtualized-auto-sizer'
+import InlineItemEditor from 'uiSrc/components/inline-item-editor/InlineItemEditor'
+import { getColumnWidth } from 'uiSrc/components/virtual-grid'
+import { StopPropagation } from 'uiSrc/components/virtual-table'
+import {
+ IColumnSearchState,
+ ITableColumn,
+ RelativeWidthSizes,
+} from 'uiSrc/components/virtual-table/interfaces'
+import VirtualTable from 'uiSrc/components/virtual-table/VirtualTable'
+import {
+ KeyTypes,
+ OVER_RENDER_BUFFER_COUNT,
+ TableCellAlignment,
+ TEXT_DISABLED_FORMATTER_EDITING,
+ TEXT_INVALID_VALUE,
+ TEXT_UNPRINTABLE_CHARACTERS
+} from 'uiSrc/constants'
+import { SCAN_COUNT_DEFAULT } from 'uiSrc/constants/api'
+import HelpTexts from 'uiSrc/constants/help-texts'
+import { NoResultsFoundText } from 'uiSrc/constants/texts'
+import { appContextBrowserKeyDetails, updateKeyDetailsSizes } from 'uiSrc/slices/app/context'
import {
- hashSelector,
- hashDataSelector,
deleteHashFields,
fetchHashFields,
fetchMoreHashFields,
- updateHashValueStateSelector,
+ hashDataSelector,
+ hashSelector,
updateHashFieldsAction,
+ updateHashValueStateSelector,
} from 'uiSrc/slices/browser/hash'
+import { keysSelector, selectedKeyDataSelector, selectedKeySelector } from 'uiSrc/slices/browser/keys'
+import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances'
import { RedisResponseBuffer } from 'uiSrc/slices/interfaces'
+import { getBasedOnViewTypeEvent, getMatchType, sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry'
import {
- formatLongName,
+ bufferToSerializedFormat,
+ bufferToString,
createDeleteFieldHeader,
createDeleteFieldMessage,
- Nullable,
+ formatLongName,
formattingBuffer,
- bufferToString,
- bufferToSerializedFormat,
- stringToSerializedBufferFormat,
- isNonUnicodeFormatter,
+ isEqualBuffers,
isFormatEditable,
- isEqualBuffers
+ isNonUnicodeFormatter,
+ Nullable,
+ stringToSerializedBufferFormat
} from 'uiSrc/utils'
-import { sendEventTelemetry, TelemetryEvent, getBasedOnViewTypeEvent, getMatchType } from 'uiSrc/telemetry'
-import VirtualTable from 'uiSrc/components/virtual-table/VirtualTable'
-import InlineItemEditor from 'uiSrc/components/inline-item-editor/InlineItemEditor'
-import {
- IColumnSearchState,
- ITableColumn,
-} from 'uiSrc/components/virtual-table/interfaces'
-import { NoResultsFoundText } from 'uiSrc/constants/texts'
-import { selectedKeyDataSelector, keysSelector, selectedKeySelector } from 'uiSrc/slices/browser/keys'
-import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances'
-import { SCAN_COUNT_DEFAULT } from 'uiSrc/constants/api'
-import HelpTexts from 'uiSrc/constants/help-texts'
-import {
- KeyTypes,
- OVER_RENDER_BUFFER_COUNT,
- TableCellAlignment,
- TEXT_INVALID_VALUE,
- TEXT_DISABLED_FORMATTER_EDITING,
- TEXT_UNPRINTABLE_CHARACTERS
-} from 'uiSrc/constants'
-import { getColumnWidth } from 'uiSrc/components/virtual-grid'
-import { StopPropagation } from 'uiSrc/components/virtual-table'
import { stringToBuffer } from 'uiSrc/utils/formatters/bufferFormatters'
-import {
- GetHashFieldsResponse,
- AddFieldsToHashDto,
- HashFieldDto,
-} from 'apiSrc/modules/browser/dto/hash.dto'
+import { AddFieldsToHashDto, GetHashFieldsResponse, HashFieldDto, } from 'apiSrc/modules/browser/dto/hash.dto'
import PopoverDelete from '../popover-delete/PopoverDelete'
import styles from './styles.module.scss'
@@ -91,6 +89,7 @@ const HashDetails = (props: Props) => {
const { viewFormat: viewFormatProp } = useSelector(selectedKeySelector)
const { name: key, length } = useSelector(selectedKeyDataSelector) ?? { name: '' }
const { loading: updateLoading } = useSelector(updateHashValueStateSelector)
+ const { [KeyTypes.Hash]: hashSizes } = useSelector(appContextBrowserKeyDetails)
const [match, setMatch] = useState>(matchAllValue)
const [deleting, setDeleting] = useState('')
@@ -271,11 +270,21 @@ const HashDetails = (props: Props) => {
}
}
+ const onColResizeEnd = (sizes: RelativeWidthSizes) => {
+ dispatch(updateKeyDetailsSizes({
+ type: KeyTypes.Hash,
+ sizes
+ }))
+ }
+
const columns: ITableColumn[] = [
{
id: 'field',
label: 'Field',
isSearchable: true,
+ isResizable: true,
+ minWidth: 120,
+ relativeWidth: hashSizes?.field || 40,
prependSearchName: 'Field:',
initialSearchValue: '',
truncateText: true,
@@ -311,6 +320,7 @@ const HashDetails = (props: Props) => {
{
id: 'value',
label: 'Value',
+ minWidth: 120,
truncateText: true,
alignment: TableCellAlignment.Left,
render: function Value(
@@ -476,7 +486,7 @@ const HashDetails = (props: Props) => {
{
onRowToggleViewClick={handleRowToggleViewClick}
expandedRows={expandedRows}
setExpandedRows={setExpandedRows}
+ onColResizeEnd={onColResizeEnd}
/>
>
diff --git a/redisinsight/ui/src/pages/browser/components/list-details/ListDetails.spec.tsx b/redisinsight/ui/src/pages/browser/components/list-details/ListDetails.spec.tsx
index e6f4f20650..56f789276c 100644
--- a/redisinsight/ui/src/pages/browser/components/list-details/ListDetails.spec.tsx
+++ b/redisinsight/ui/src/pages/browser/components/list-details/ListDetails.spec.tsx
@@ -1,6 +1,9 @@
import React from 'react'
+import { mock } from 'ts-mockito'
import { act, fireEvent, render, screen } from 'uiSrc/utils/test-utils'
-import ListDetails from './ListDetails'
+import ListDetails, { Props } from './ListDetails'
+
+const mockedProps = mock()
const elements = [
{ element: { type: 'Buffer', data: [49] }, index: 0 },
@@ -30,11 +33,11 @@ jest.mock('uiSrc/slices/browser/list', () => {
describe('ListDetails', () => {
it('should render', () => {
- expect(render()).toBeTruthy()
+ expect(render()).toBeTruthy()
})
it('should render rows properly', () => {
- const { container } = render()
+ const { container } = render()
const rows = container.querySelectorAll(
'.ReactVirtualized__Table__row[role="row"]'
)
@@ -42,22 +45,27 @@ describe('ListDetails', () => {
})
it('should render search input', () => {
- render()
+ render()
expect(screen.getByTestId('search')).toBeTruthy()
})
it('should call search', () => {
- render()
+ render()
const searchInput = screen.getByTestId('search')
fireEvent.change(searchInput, { target: { value: '111' } })
expect(searchInput).toHaveValue('111')
})
it('should render editor after click edit button', async () => {
- render()
+ render()
await act(() => {
fireEvent.click(screen.getAllByTestId(/edit-list-button/)[0])
})
expect(screen.getByTestId('element-value-editor')).toBeInTheDocument()
})
+
+ it('should render resize trigger for index column', () => {
+ render()
+ expect(screen.getByTestId('resize-trigger-index')).toBeInTheDocument()
+ })
})
diff --git a/redisinsight/ui/src/pages/browser/components/list-details/ListDetails.tsx b/redisinsight/ui/src/pages/browser/components/list-details/ListDetails.tsx
index 53c790b861..354b81a7d5 100644
--- a/redisinsight/ui/src/pages/browser/components/list-details/ListDetails.tsx
+++ b/redisinsight/ui/src/pages/browser/components/list-details/ListDetails.tsx
@@ -5,6 +5,7 @@ import cx from 'classnames'
import { isNull } from 'lodash'
import { CellMeasurerCache } from 'react-virtualized'
import AutoSizer from 'react-virtualized-auto-sizer'
+import { appContextBrowserKeyDetails, updateKeyDetailsSizes } from 'uiSrc/slices/app/context'
import {
listSelector,
@@ -18,6 +19,7 @@ import {
import {
ITableColumn,
IColumnSearchState,
+ RelativeWidthSizes,
} from 'uiSrc/components/virtual-table/interfaces'
import { SCAN_COUNT_DEFAULT } from 'uiSrc/constants/api'
import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances'
@@ -69,7 +71,7 @@ const cellCache = new CellMeasurerCache({
interface IListElement extends SetListElementResponse {}
-interface Props {
+export interface Props {
isFooterOpen: boolean
}
@@ -84,6 +86,7 @@ const ListDetails = (props: Props) => {
const { id: instanceId } = useSelector(connectedInstanceSelector)
const { viewType } = useSelector(keysSelector)
const { viewFormat: viewFormatProp } = useSelector(selectedKeySelector)
+ const { [KeyTypes.List]: listSizes } = useSelector(appContextBrowserKeyDetails)
const [elements, setElements] = useState([])
const [width, setWidth] = useState(100)
@@ -219,15 +222,22 @@ const ListDetails = (props: Props) => {
}
}
+ const onColResizeEnd = (sizes: RelativeWidthSizes) => {
+ dispatch(updateKeyDetailsSizes({
+ type: KeyTypes.List,
+ sizes
+ }))
+ }
+
const columns: ITableColumn[] = [
{
id: 'index',
label: 'Index',
- minWidth: 220,
- maxWidth: 220,
- absoluteWidth: 220,
+ minWidth: 120,
+ relativeWidth: listSizes?.index || 30,
truncateText: true,
isSearchable: true,
+ isResizable: true,
prependSearchName: 'Index:',
initialSearchValue: '',
searchValidation: validateListIndex,
@@ -257,6 +267,7 @@ const ListDetails = (props: Props) => {
{
id: 'element',
label: 'Element',
+ minWidth: 150,
truncateText: true,
alignment: TableCellAlignment.Left,
render: function Element(
@@ -435,6 +446,7 @@ const ListDetails = (props: Props) => {
onRowToggleViewClick={handleRowToggleViewClick}
expandedRows={expandedRows}
setExpandedRows={setExpandedRows}
+ onColResizeEnd={onColResizeEnd}
/>
)
diff --git a/redisinsight/ui/src/pages/browser/components/zset-details/ZSetDetails.spec.tsx b/redisinsight/ui/src/pages/browser/components/zset-details/ZSetDetails.spec.tsx
index 7d8e9af815..31a7c32d09 100644
--- a/redisinsight/ui/src/pages/browser/components/zset-details/ZSetDetails.spec.tsx
+++ b/redisinsight/ui/src/pages/browser/components/zset-details/ZSetDetails.spec.tsx
@@ -70,4 +70,9 @@ describe('ZSetDetails', () => {
fireEvent.change(screen.getByTestId('inline-item-editor'), { target: { value: '123' } })
expect(screen.getByTestId('inline-item-editor')).toHaveValue('123')
})
+
+ it('should render resize trigger for name column', () => {
+ render()
+ expect(screen.getByTestId('resize-trigger-name')).toBeInTheDocument()
+ })
})
diff --git a/redisinsight/ui/src/pages/browser/components/zset-details/ZSetDetails.tsx b/redisinsight/ui/src/pages/browser/components/zset-details/ZSetDetails.tsx
index 62d87bf419..345d423704 100644
--- a/redisinsight/ui/src/pages/browser/components/zset-details/ZSetDetails.tsx
+++ b/redisinsight/ui/src/pages/browser/components/zset-details/ZSetDetails.tsx
@@ -4,6 +4,7 @@ import { toNumber, isNumber } from 'lodash'
import cx from 'classnames'
import { EuiButtonIcon, EuiProgress, EuiText, EuiToolTip } from '@elastic/eui'
import { CellMeasurerCache } from 'react-virtualized'
+import { appContextBrowserKeyDetails, updateKeyDetailsSizes } from 'uiSrc/slices/app/context'
import {
zsetSelector,
@@ -36,7 +37,7 @@ import { sendEventTelemetry, TelemetryEvent, getBasedOnViewTypeEvent, getMatchTy
import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances'
import VirtualTable from 'uiSrc/components/virtual-table/VirtualTable'
import InlineItemEditor from 'uiSrc/components/inline-item-editor/InlineItemEditor'
-import { IColumnSearchState, ITableColumn } from 'uiSrc/components/virtual-table/interfaces'
+import { IColumnSearchState, ITableColumn, RelativeWidthSizes } from 'uiSrc/components/virtual-table/interfaces'
import { StopPropagation } from 'uiSrc/components/virtual-table'
import { getColumnWidth } from 'uiSrc/components/virtual-grid'
import { stringToBuffer } from 'uiSrc/utils/formatters/bufferFormatters'
@@ -73,6 +74,7 @@ const ZSetDetails = (props: Props) => {
const { id: instanceId } = useSelector(connectedInstanceSelector)
const { viewType } = useSelector(keysSelector)
const { viewFormat: viewFormatProp } = useSelector(selectedKeySelector)
+ const { [KeyTypes.ZSet]: ZSetSizes } = useSelector(appContextBrowserKeyDetails)
const [match, setMatch] = useState('')
const [deleting, setDeleting] = useState('')
@@ -231,6 +233,13 @@ const ZSetDetails = (props: Props) => {
cellCache.clearAll()
}
+ const onColResizeEnd = (sizes: RelativeWidthSizes) => {
+ dispatch(updateKeyDetailsSizes({
+ type: KeyTypes.ZSet,
+ sizes
+ }))
+ }
+
const columns:ITableColumn[] = [
{
id: 'name',
@@ -239,6 +248,9 @@ const ZSetDetails = (props: Props) => {
prependSearchName: 'Member:',
initialSearchValue: '',
truncateText: true,
+ isResizable: true,
+ minWidth: 140,
+ relativeWidth: ZSetSizes?.name || 60,
alignment: TableCellAlignment.Left,
className: 'value-table-separate-border',
headerClassName: 'value-table-separate-border',
@@ -274,6 +286,7 @@ const ZSetDetails = (props: Props) => {
{
id: 'score',
label: 'Score',
+ minWidth: 100,
isSortable: true,
truncateText: true,
render: function Score(_name: string, { name: nameItem, score, editing }: IZsetMember, expanded?: boolean) {
@@ -443,6 +456,7 @@ const ZSetDetails = (props: Props) => {
onRowToggleViewClick={handleRowToggleViewClick}
expandedRows={expandedRows}
setExpandedRows={setExpandedRows}
+ onColResizeEnd={onColResizeEnd}
/>
>
diff --git a/redisinsight/ui/src/slices/app/context.ts b/redisinsight/ui/src/slices/app/context.ts
index 819b11b19b..3bf65167ed 100644
--- a/redisinsight/ui/src/slices/app/context.ts
+++ b/redisinsight/ui/src/slices/app/context.ts
@@ -1,6 +1,7 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
+import { RelativeWidthSizes } from 'uiSrc/components/virtual-table/interfaces'
import { getTreeLeafField, Nullable } from 'uiSrc/utils'
-import { BrowserStorageItem, DEFAULT_DELIMITER } from 'uiSrc/constants'
+import { BrowserStorageItem, DEFAULT_DELIMITER, KeyTypes } from 'uiSrc/constants'
import { localStorageService } from 'uiSrc/services'
import { RootState } from '../store'
import { RedisResponseBuffer, StateAppContext } from '../interfaces'
@@ -25,6 +26,11 @@ export const initialState: StateAppContext = {
},
bulkActions: {
opened: false,
+ },
+ keyDetailsSizes: {
+ [KeyTypes.Hash]: localStorageService?.get(BrowserStorageItem.keyDetailSizes)?.hash ?? null,
+ [KeyTypes.List]: localStorageService?.get(BrowserStorageItem.keyDetailSizes)?.list ?? null,
+ [KeyTypes.ZSet]: localStorageService?.get(BrowserStorageItem.keyDetailSizes)?.zset ?? null,
}
},
workbench: {
@@ -54,6 +60,10 @@ const appContextSlice = createSlice({
// don't need to reset instanceId
setAppContextInitialState: (state) => ({
...initialState,
+ browser: {
+ ...initialState.browser,
+ keyDetailsSizes: state.browser.keyDetailsSizes
+ },
contextInstanceId: state.contextInstanceId
}),
// set connected instance
@@ -151,6 +161,14 @@ const appContextSlice = createSlice({
setLastAnalyticsPage: (state, { payload }: { payload: string }) => {
state.analytics.lastViewedPage = payload
},
+ updateKeyDetailsSizes: (
+ state,
+ { payload }: { payload: { type: KeyTypes, sizes: RelativeWidthSizes } }
+ ) => {
+ const { type, sizes } = payload
+ state.browser.keyDetailsSizes[type] = sizes
+ localStorageService?.set(BrowserStorageItem.keyDetailSizes, state.browser.keyDetailsSizes)
+ }
},
})
@@ -179,6 +197,7 @@ export const {
setPubSubFieldsContext,
setBrowserBulkActionOpen,
setLastAnalyticsPage,
+ updateKeyDetailsSizes
} = appContextSlice.actions
// Selectors
@@ -188,6 +207,8 @@ export const appContextBrowser = (state: RootState) =>
state.app.context.browser
export const appContextBrowserTree = (state: RootState) =>
state.app.context.browser.tree
+export const appContextBrowserKeyDetails = (state: RootState) =>
+ state.app.context.browser.keyDetailsSizes
export const appContextWorkbench = (state: RootState) =>
state.app.context.workbench
export const appContextSelectedKey = (state: RootState) =>
diff --git a/redisinsight/ui/src/slices/interfaces/app.ts b/redisinsight/ui/src/slices/interfaces/app.ts
index 308a5fb70b..bf88afa8da 100644
--- a/redisinsight/ui/src/slices/interfaces/app.ts
+++ b/redisinsight/ui/src/slices/interfaces/app.ts
@@ -1,4 +1,5 @@
import { AxiosError } from 'axios'
+import { RelativeWidthSizes } from 'uiSrc/components/virtual-table/interfaces'
import { Nullable } from 'uiSrc/utils'
import { ICommands } from 'uiSrc/constants'
import { IKeyPropTypes } from 'uiSrc/constants/prop-types/keys'
@@ -64,6 +65,9 @@ export interface StateAppContext {
},
bulkActions: {
opened: boolean
+ },
+ keyDetailsSizes: {
+ [key: string]: Nullable
}
},
workbench: {
diff --git a/redisinsight/ui/src/slices/tests/app/context.spec.ts b/redisinsight/ui/src/slices/tests/app/context.spec.ts
index 29d241a020..040a05c701 100644
--- a/redisinsight/ui/src/slices/tests/app/context.spec.ts
+++ b/redisinsight/ui/src/slices/tests/app/context.spec.ts
@@ -33,7 +33,7 @@ import reducer, {
updateBrowserTreeSelectedLeaf,
setBrowserTreeDelimiter,
setBrowserIsNotRendered,
- setBrowserRedisearchScrollPosition,
+ setBrowserRedisearchScrollPosition, updateKeyDetailsSizes, appContextBrowserKeyDetails,
} from '../../app/context'
jest.mock('uiSrc/services', () => ({
@@ -64,6 +64,7 @@ describe('slices', () => {
...initialState,
contextInstanceId,
browser: {
+ ...initialState.browser,
keyList: {
isDataLoaded: true,
scrollTopPosition: 100,
@@ -74,7 +75,7 @@ describe('slices', () => {
},
bulkActions: {
opened: true,
- }
+ },
},
workbench: {
script: '123123',
@@ -703,4 +704,31 @@ describe('slices', () => {
expect(appContextBrowserTree(rootState)).toEqual(state)
})
})
+
+ describe('updateKeyDetailsSizes', () => {
+ it('should properly update sizes', () => {
+ // Arrange
+ const payload = {
+ type: KeyTypes.Hash,
+ sizes: {
+ field: 50
+ }
+ }
+
+ const state = {
+ ...initialState.browser.keyDetailsSizes,
+ [KeyTypes.Hash]: { ...payload.sizes }
+ }
+
+ // Act
+ const nextState = reducer(initialState, updateKeyDetailsSizes(payload))
+
+ // Assert
+ const rootState = Object.assign(initialStateDefault, {
+ app: { context: nextState },
+ })
+
+ expect(appContextBrowserKeyDetails(rootState)).toEqual(state)
+ })
+ })
})
diff --git a/tests/e2e/helpers/api/api-database.ts b/tests/e2e/helpers/api/api-database.ts
index 4f87be5e4c..be3a5c09a6 100644
--- a/tests/e2e/helpers/api/api-database.ts
+++ b/tests/e2e/helpers/api/api-database.ts
@@ -21,7 +21,6 @@ export async function addNewStandaloneDatabaseApi(databaseParameters: AddNewData
'password': databaseParameters.databasePassword
})
.set('Accept', 'application/json');
-
await t
.expect(response.status).eql(201, 'The creation of new standalone database request failed')
.expect(await response.body.name).eql(databaseParameters.databaseName, `Database Name is not equal to ${databaseParameters.databaseName} in response`);
diff --git a/tests/e2e/pageObjects/browser-page.ts b/tests/e2e/pageObjects/browser-page.ts
index f98b721302..01ac2218e0 100644
--- a/tests/e2e/pageObjects/browser-page.ts
+++ b/tests/e2e/pageObjects/browser-page.ts
@@ -10,6 +10,7 @@ export class BrowserPage {
cssSelectorKey = '[data-testid^=key-]';
cssFilteringLabel = '[data-testid=multi-search]';
cssJsonValue = '[data-tesid=value-as-json]';
+ cssRowInVirtualizedTable = '[role=gridcell]';
cssVirtualTableRow = '[aria-label=row]';
cssKeyBadge = '[data-testid^=badge-]';
cssKeyTtl = '[data-testid^=ttl-]';
@@ -135,6 +136,7 @@ export class BrowserPage {
createIndexBtn = Selector('[data-testid=create-index-btn]');
cancelIndexCreationBtn = Selector('[data-testid=create-index-cancel-btn]');
confirmIndexCreationBtn = Selector('[data-testid=create-index-btn]');
+ resizeTrigger = Selector('[data-testid^=resize-trigger-]');
//TABS
streamTabGroups = Selector('[data-testid=stream-tab-Groups]');
streamTabConsumers = Selector('[data-testid=stream-tab-Consumers]');
@@ -560,6 +562,16 @@ export class BrowserPage {
await t.click(this.confirmDeleteKeyButton);
}
+ /**
+ * Delete keys by their Names
+ * @param keyNames The names of the key array
+ */
+ async deleteKeysByNames(keyNames: string[]): Promise {
+ for(const name of keyNames) {
+ await this.deleteKeyByName(name);
+ }
+ }
+
/**
* Edit key name from details
* @param keyName The name of the key
diff --git a/tests/e2e/tests/regression/browser/resize-columns.e2e.ts b/tests/e2e/tests/regression/browser/resize-columns.e2e.ts
new file mode 100644
index 0000000000..70302f8a3f
--- /dev/null
+++ b/tests/e2e/tests/regression/browser/resize-columns.e2e.ts
@@ -0,0 +1,96 @@
+import { acceptLicenseTerms } from '../../../helpers/database';
+import {
+ MyRedisDatabasePage,
+ BrowserPage
+} from '../../../pageObjects';
+import { rte } from '../../../helpers/constants';
+import { commonUrl, ossStandaloneConfig } from '../../../helpers/conf';
+import { addNewStandaloneDatabasesApi, deleteStandaloneDatabasesApi } from '../../../helpers/api/api-database';
+import { Common } from '../../../helpers/common';
+
+const myRedisDatabasePage = new MyRedisDatabasePage();
+const browserPage = new BrowserPage();
+const common = new Common();
+
+const keyName = common.generateWord(10);
+const longFieldName = common.generateSentence(20);
+const keys = [
+ { type: 'Hash',
+ name: `${keyName}:1`,
+ offsetX: 100,
+ fieldWidthStart: 0,
+ fieldWidthEnd: 0
+ },
+ {
+ type: 'List',
+ name: `${keyName}:2`,
+ offsetX: 80,
+ fieldWidthStart: 0,
+ fieldWidthEnd: 0
+ },
+ {
+ type: 'Zset',
+ name: `${keyName}:3`,
+ offsetX: 50,
+ fieldWidthStart: 0,
+ fieldWidthEnd: 0
+ }
+];
+const keyNames: string[] = [];
+keys.forEach(key => keyNames.push(key.name));
+
+const databasesForAdding = [
+ { host: ossStandaloneConfig.host, port: ossStandaloneConfig.port, databaseName: 'testDB1' },
+ { host: ossStandaloneConfig.host, port: ossStandaloneConfig.port, databaseName: 'testDB2' }
+];
+
+fixture `Resize columns in Key details`
+ .meta({type: 'regression', rte: rte.standalone})
+ .page(commonUrl)
+ .beforeEach(async() => {
+ // Add new databases using API
+ await acceptLicenseTerms();
+ await addNewStandaloneDatabasesApi(databasesForAdding);
+ // Reload Page
+ await common.reloadPage();
+ await myRedisDatabasePage.clickOnDBByName(databasesForAdding[0].databaseName);
+ await browserPage.addHashKey(keys[0].name, '2147476121', longFieldName, longFieldName);
+ await browserPage.addListKey(keys[1].name, '2147476121', 'element');
+ await browserPage.addZSetKey(keys[2].name, '1', '2147476121', 'member');
+ })
+ .afterEach(async() => {
+ // Clear and delete database
+ await browserPage.deleteKeysByNames(keyNames);
+ await deleteStandaloneDatabasesApi(databasesForAdding);
+ });
+test('Resize of columns in Hash, List, Zset Key details', async t => {
+ const field = browserPage.keyDetailsTable.find(browserPage.cssRowInVirtualizedTable);
+ const tableHeaderResizeTrigger = browserPage.resizeTrigger;
+
+ for(const key of keys) {
+ await browserPage.openKeyDetails(key.name);
+ // Remember initial column width
+ key.fieldWidthStart = await field.clientWidth;
+ await t.hover(tableHeaderResizeTrigger);
+ await t.drag(tableHeaderResizeTrigger, -key.offsetX, 0, { speed: 0.5 });
+ // Remember last column width
+ key.fieldWidthEnd = await field.clientWidth;
+ // Verify that user can resize columns for Hash, List, Zset Keys
+ await t.expect(key.fieldWidthEnd).eql(key.fieldWidthStart - key.offsetX, `Field is not resized for ${key.type} key`);
+ }
+
+ // Verify that resize saved when switching between pages
+ await t.click(myRedisDatabasePage.workbenchButton);
+ await t.click(myRedisDatabasePage.browserButton);
+ await browserPage.openKeyDetails(keys[0].name);
+ await t.expect(field.clientWidth).eql(keys[0].fieldWidthEnd, 'Resize context not saved for key when switching between pages');
+
+ // Verify that resize saved when switching between databases
+ await t.click(myRedisDatabasePage.myRedisDBButton);
+ await myRedisDatabasePage.clickOnDBByName(databasesForAdding[1].databaseName);
+ // Verify that resize saved for specific data type
+ for(const key of keys) {
+ await browserPage.openKeyDetails(key.name);
+ await t.expect(field.clientWidth).eql(key.fieldWidthEnd, `Resize context not saved for ${key.type} key when switching between databases`);
+ }
+});