Skip to content

Commit 79fee74

Browse files
authored
RI-7399: DatabasesList (v2) (#5146)
* RI-7399: adjust existing db list screens * RI-7399: set db name overflow * RI-7399: add redis-ui table for DatabasesList * 7399: add resetRowSelection * 7399: add unit tests * 7399: fix pagination * 7399: fix name overflow when indicator is missing * 7399: add missing test id * RI-7399: memoize db list components * RI-7399: address review comments * RI-7399: address review comments part 2
1 parent fca1393 commit 79fee74

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1789
-154
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import styled from 'styled-components'
2+
3+
import { Row } from 'uiSrc/components/base/layout/flex'
4+
5+
export const Container = styled(Row)`
6+
position: fixed;
7+
z-index: 1;
8+
left: 50%;
9+
transform: translate(-50%, -50%);
10+
11+
width: 462px;
12+
height: 50px;
13+
bottom: calc(9vh + 9px);
14+
background-color: ${({ theme }) =>
15+
theme.semantic.color.background.neutral500};
16+
border-radius: ${({ theme }) => theme.core.space.space250};
17+
padding-left: ${({ theme }) => theme.core.space.space050};
18+
`
Lines changed: 18 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,32 @@
11
import React from 'react'
22

3-
import { FlexItem, Row } from 'uiSrc/components/base/layout/flex'
3+
import { FlexItem } from 'uiSrc/components/base/layout/flex'
44
import { IconButton } from 'uiSrc/components/base/forms/buttons'
55
import { CancelSlimIcon } from 'uiSrc/components/base/icons'
6-
import styles from './styles.module.scss'
6+
import { Container } from './ActionBar.styles'
77

88
export interface Props {
9-
width: number
9+
width?: number
1010
selectionCount: number
1111
actions: (JSX.Element | null)[]
1212
onCloseActionBar: () => void
1313
}
1414

15-
const ActionBar = ({
16-
width,
17-
selectionCount,
18-
actions,
19-
onCloseActionBar,
20-
}: Props) => (
21-
<div className={styles.inner}>
22-
<Row
23-
centered
24-
className={styles.container}
25-
gap="l"
26-
style={{
27-
left: `calc(${width / 2}px - 156px)`,
28-
}}
29-
>
30-
<FlexItem className={styles.text}>
31-
{`You selected: ${selectionCount} items`}
32-
</FlexItem>
33-
{actions?.map((action, index) => (
34-
<FlexItem className={styles.actions} key={`action-${index + 1}`}>
35-
{action}
36-
</FlexItem>
37-
))}
38-
<FlexItem className={styles.cross}>
39-
<IconButton
40-
icon={CancelSlimIcon}
41-
aria-label="Cancel selecting"
42-
onClick={() => onCloseActionBar()}
43-
data-testid="cancel-selecting"
44-
/>
45-
</FlexItem>
46-
</Row>
47-
</div>
15+
const ActionBar = ({ selectionCount, actions, onCloseActionBar }: Props) => (
16+
<Container centered gap="l">
17+
<FlexItem>{`You selected: ${selectionCount} items`}</FlexItem>
18+
{actions?.map((action, index) => (
19+
<FlexItem key={`action-${index + 1}`}>{action}</FlexItem>
20+
))}
21+
<FlexItem>
22+
<IconButton
23+
icon={CancelSlimIcon}
24+
aria-label="Cancel selecting"
25+
onClick={() => onCloseActionBar()}
26+
data-testid="cancel-selecting"
27+
/>
28+
</FlexItem>
29+
</Container>
4830
)
4931

5032
export default ActionBar

redisinsight/ui/src/components/item-list/components/action-bar/styles.module.scss

Lines changed: 0 additions & 41 deletions
This file was deleted.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { Factory } from 'fishery'
2+
import { faker } from '@faker-js/faker'
3+
import { ConnectionType, Instance } from 'uiSrc/slices/interfaces'
4+
5+
export const DBInstanceFactory = Factory.define<Instance>(() => ({
6+
id: faker.string.uuid(),
7+
name: faker.company.name(),
8+
host: faker.internet.ip(),
9+
port: faker.internet.port(),
10+
username: faker.internet.userName(),
11+
password: faker.internet.password(),
12+
visible: faker.datatype.boolean(),
13+
connectionType: faker.helpers.arrayElement([
14+
ConnectionType.Standalone,
15+
ConnectionType.Cluster,
16+
ConnectionType.Sentinel,
17+
]),
18+
nameFromProvider: faker.company.name(),
19+
modules: [],
20+
version: faker.system.semver(),
21+
lastConnection: faker.date.past(),
22+
provider: faker.company.name(),
23+
}))

redisinsight/ui/src/pages/home/HomePage.tsx

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import React, { useEffect, useState } from 'react'
1+
import React, { useEffect } from 'react'
22
import { useDispatch, useSelector } from 'react-redux'
33
import {
44
clusterSelector,
55
resetDataRedisCluster,
66
resetInstancesRedisCluster,
77
} from 'uiSrc/slices/instances/cluster'
8-
import { Nullable, setTitle } from 'uiSrc/utils'
8+
import { setTitle } from 'uiSrc/utils'
99
import { HomePageTemplate } from 'uiSrc/templates'
1010
import { BrowserStorageItem, FeatureFlags } from 'uiSrc/constants'
1111
import { resetKeys } from 'uiSrc/slices/browser/keys'
@@ -58,10 +58,15 @@ import { appFeatureFlagsFeaturesSelector } from 'uiSrc/slices/app/features'
5858
import { Page, PageBody } from 'uiSrc/components/base/layout/page'
5959
import { Card } from 'uiSrc/components/base/layout'
6060
import DatabasesList from './components/database-list-component'
61+
import DatabasesListV2 from './components/databases-list/DatabasesList'
6162
import DatabaseListHeader from './components/database-list-header'
6263
import EmptyMessage from './components/empty-message/EmptyMessage'
6364
import DatabasePanelDialog from './components/database-panel-dialog'
6465
import { ManageTagsModal } from './components/database-manage-tags-modal/ManageTagsModal'
66+
import {
67+
HomePageDataProviderProvider,
68+
useHomePageDataProvider,
69+
} from './contexts/HomePageDataProvider'
6570

6671
import './styles.scss'
6772
import styles from './styles.module.scss'
@@ -73,7 +78,7 @@ enum OpenDialogName {
7378
}
7479

7580
const HomePage = () => {
76-
const [openDialog, setOpenDialog] = useState<Nullable<OpenDialogName>>(null)
81+
const { openDialog, setOpenDialog } = useHomePageDataProvider()
7782

7883
const dispatch = useDispatch()
7984

@@ -83,6 +88,7 @@ const HomePage = () => {
8388
const { action, dbConnection } = useSelector(appRedirectionSelector)
8489
const { data: createDbContent } = useSelector(contentSelector)
8590
const {
91+
[FeatureFlags.databasesListV2]: databasesListV2Feature,
8692
[FeatureFlags.enhancedCloudUI]: enhancedCloudUIFeature,
8793
[FeatureFlags.cloudAds]: cloudAdsFeature,
8894
} = useSelector(appFeatureFlagsFeaturesSelector)
@@ -112,6 +118,7 @@ const HomePage = () => {
112118
: []
113119
const isInstanceExists =
114120
instances.length > 0 || predefinedInstances.length > 0
121+
const hideDbList = !isInstanceExists && !loading && !loadingChanging
115122

116123
useEffect(() => {
117124
setTitle('Redis databases')
@@ -279,11 +286,12 @@ const HomePage = () => {
279286
/>
280287
)}
281288
<div key="homePage" className="homePage">
282-
{!isInstanceExists && !loading && !loadingChanging ? (
289+
{hideDbList && (
283290
<Card>
284291
<EmptyMessage onAddInstanceClick={handleAddInstance} />
285292
</Card>
286-
) : (
293+
)}
294+
{!hideDbList && !databasesListV2Feature?.flag && (
287295
<DatabasesList
288296
loading={loading}
289297
instances={instances}
@@ -294,6 +302,9 @@ const HomePage = () => {
294302
onManageInstanceTags={handleManageInstanceTags}
295303
/>
296304
)}
305+
{!hideDbList && databasesListV2Feature?.flag && (
306+
<DatabasesListV2 />
307+
)}
297308
</div>
298309
</PageBody>
299310
</Page>
@@ -302,4 +313,10 @@ const HomePage = () => {
302313
)
303314
}
304315

305-
export default HomePage
316+
const HomePageWithProvider = () => (
317+
<HomePageDataProviderProvider>
318+
<HomePage />
319+
</HomePageDataProviderProvider>
320+
)
321+
322+
export default HomePageWithProvider

redisinsight/ui/src/pages/home/components/database-list-component/DatabasesListWrapper.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ import { RiPopover, RiTooltip } from 'uiSrc/components/base'
8484
import { EmptyButton, IconButton } from 'uiSrc/components/base/forms/buttons'
8585
import { Link } from 'uiSrc/components/base/link/Link'
8686
import { RIResizeObserver } from 'uiSrc/components/base/utils'
87+
import { Row } from 'uiSrc/components/base/layout/flex'
8788

8889
import DbStatus from '../db-status'
8990
import { TagsCell } from '../tags-cell/TagsCell'
@@ -394,7 +395,7 @@ const DatabasesListWrapper = (props: Props) => {
394395
const cellContent = replaceSpaces(name.substring(0, 200))
395396

396397
return (
397-
<div role="presentation">
398+
<Row role="presentation" align="center" gap="xs">
398399
<DbStatus
399400
id={id}
400401
isNew={newStatus}
@@ -428,7 +429,7 @@ const DatabasesListWrapper = (props: Props) => {
428429
<ColorText>{` ${getDbIndex(db)}`}</ColorText>
429430
</Text>
430431
</RiTooltip>
431-
</div>
432+
</Row>
432433
)
433434
},
434435
},
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import React from 'react'
2+
3+
import { Instance } from 'uiSrc/slices/interfaces'
4+
import {
5+
ColumnDef,
6+
SortingState,
7+
Table,
8+
} from 'uiSrc/components/base/layout/table'
9+
import {
10+
BrowserStorageItem,
11+
COLUMN_FIELD_NAME_MAP,
12+
DatabaseListColumn,
13+
DEFAULT_SORT,
14+
} from 'uiSrc/constants'
15+
import { localStorageService } from 'uiSrc/services'
16+
17+
import DatabasesListCellName from './components/DatabasesListCellName/DatabasesListCellName'
18+
import DatabasesListCellHost from './components/DatabasesListCellHost/DatabasesListCellHost'
19+
import DatabasesListCellConnectionType from './components/DatabasesListCellConnectionType/DatabasesListCellConnectionType'
20+
import DatabasesListCellLastConnection from './components/DatabasesListCellLastConnection/DatabasesListCellLastConnection'
21+
import DatabasesListCellModules from './components/DatabasesListCellModules/DatabasesListCellModules'
22+
import DatabasesListCellControls from './components/DatabasesListCellControls/DatabasesListCellControls'
23+
import DatabasesListCellTags from './components/DatabasesListCellTags/DatabasesListCellTags'
24+
import { TagsCellHeader } from '../tags-cell/TagsCellHeader'
25+
26+
export const SELECT_COL_ID = 'select-col-db'
27+
28+
export const ENABLE_PAGINATION_COUNT = 15
29+
30+
export const DEFAULT_SORTING: SortingState = [
31+
{
32+
id: (
33+
localStorageService.get(BrowserStorageItem.instancesSorting) ??
34+
DEFAULT_SORT
35+
).field,
36+
desc:
37+
(
38+
localStorageService.get(BrowserStorageItem.instancesSorting) ??
39+
DEFAULT_SORT
40+
).direction === 'desc',
41+
},
42+
]
43+
44+
export const BASE_COLUMNS: ColumnDef<Instance>[] = [
45+
{
46+
id: SELECT_COL_ID,
47+
size: 40,
48+
isHeaderCustom: true,
49+
enableSorting: false,
50+
header: Table.HeaderMultiRowSelectionButton,
51+
cell: (props) => (
52+
<Table.RowSelectionButton
53+
{...props}
54+
onClick={(e: any) => e.stopPropagation()}
55+
/>
56+
),
57+
},
58+
{
59+
id: DatabaseListColumn.Name,
60+
accessorKey: DatabaseListColumn.Name,
61+
header: COLUMN_FIELD_NAME_MAP.get(DatabaseListColumn.Name),
62+
enableSorting: true,
63+
cell: DatabasesListCellName,
64+
sortingFn: (rowA, rowB) => {
65+
return `${rowA.original.name?.toLowerCase()}`.localeCompare(
66+
`${rowB.original.name?.toLowerCase()}`,
67+
)
68+
},
69+
},
70+
{
71+
id: DatabaseListColumn.Host,
72+
accessorKey: DatabaseListColumn.Host,
73+
header: COLUMN_FIELD_NAME_MAP.get(DatabaseListColumn.Host),
74+
enableSorting: true,
75+
cell: DatabasesListCellHost,
76+
sortingFn: (rowA, rowB) => {
77+
return `${rowA.original.host?.toLowerCase()}:${rowA.original.port}`.localeCompare(
78+
`${rowB.original.host?.toLowerCase()}:${rowB.original.port}`,
79+
)
80+
},
81+
},
82+
{
83+
id: DatabaseListColumn.ConnectionType,
84+
accessorKey: DatabaseListColumn.ConnectionType,
85+
header: COLUMN_FIELD_NAME_MAP.get(DatabaseListColumn.ConnectionType),
86+
enableSorting: true,
87+
cell: DatabasesListCellConnectionType,
88+
},
89+
{
90+
id: DatabaseListColumn.Modules,
91+
accessorKey: DatabaseListColumn.Modules,
92+
header: COLUMN_FIELD_NAME_MAP.get(DatabaseListColumn.Modules),
93+
enableSorting: false,
94+
cell: DatabasesListCellModules,
95+
},
96+
{
97+
id: DatabaseListColumn.LastConnection,
98+
accessorKey: DatabaseListColumn.LastConnection,
99+
header: COLUMN_FIELD_NAME_MAP.get(DatabaseListColumn.LastConnection),
100+
enableSorting: true,
101+
cell: DatabasesListCellLastConnection,
102+
},
103+
{
104+
id: DatabaseListColumn.Tags,
105+
accessorKey: DatabaseListColumn.Tags,
106+
isHeaderCustom: true,
107+
header: TagsCellHeader,
108+
enableSorting: true,
109+
cell: DatabasesListCellTags,
110+
sortingFn: (rowA, rowB) => {
111+
// compare value of first tag only
112+
const tagA = rowA.original.tags?.[0]
113+
const tagB = rowB.original.tags?.[0]
114+
115+
return `${tagA?.key || ''}:${tagA?.value || ''}`
116+
.toLowerCase()
117+
.localeCompare(`${tagB?.key || ''}:${tagB?.value || ''}`.toLowerCase())
118+
},
119+
},
120+
{
121+
id: DatabaseListColumn.Controls,
122+
accessorKey: DatabaseListColumn.Controls,
123+
header: '',
124+
enableSorting: false,
125+
cell: DatabasesListCellControls,
126+
},
127+
]

0 commit comments

Comments
 (0)