Skip to content

Commit a7f4efb

Browse files
committed
Merge branch 'main' into fe/bugfix/RI-7785/fix-analysis-pages-padding
2 parents 5760f3c + 79f21b7 commit a7f4efb

21 files changed

+724
-471
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { Factory } from 'fishery'
2+
import { faker } from '@faker-js/faker'
3+
import { ClusterNodeDetails } from 'src/modules/cluster-monitor/models'
4+
5+
enum NodeRole {
6+
Primary = 'primary',
7+
Replica = 'replica',
8+
}
9+
10+
enum HealthStatus {
11+
Online = 'online',
12+
Offline = 'offline',
13+
Loading = 'loading',
14+
}
15+
16+
export const ClusterNodeDetailsFactory = Factory.define<ClusterNodeDetails>(
17+
() => ({
18+
id: faker.string.uuid(),
19+
version: faker.system.semver(),
20+
mode: faker.helpers.arrayElement(['standalone', 'cluster', 'sentinel']),
21+
host: faker.internet.ip(),
22+
port: faker.internet.port(),
23+
role: faker.helpers.arrayElement([NodeRole.Primary, NodeRole.Replica]),
24+
health: faker.helpers.arrayElement([
25+
HealthStatus.Online,
26+
HealthStatus.Offline,
27+
HealthStatus.Loading,
28+
]),
29+
slots: ['0-5460'],
30+
totalKeys: faker.number.int({ min: 0, max: 1000000 }),
31+
usedMemory: faker.number.int({ min: 1000000, max: 100000000 }),
32+
opsPerSecond: faker.number.int({ min: 0, max: 10000 }),
33+
connectionsReceived: faker.number.int({ min: 0, max: 10000 }),
34+
connectedClients: faker.number.int({ min: 0, max: 100 }),
35+
commandsProcessed: faker.number.int({ min: 0, max: 1000000000 }),
36+
networkInKbps: faker.number.float({
37+
min: 0,
38+
max: 10000,
39+
fractionDigits: 2,
40+
}),
41+
networkOutKbps: faker.number.float({
42+
min: 0,
43+
max: 10000,
44+
fractionDigits: 2,
45+
}),
46+
cacheHitRatio: faker.number.float({ min: 0, max: 1, fractionDigits: 2 }),
47+
replicationOffset: faker.number.int({ min: 0, max: 1000000 }),
48+
uptimeSec: faker.number.int({ min: 0, max: 10000000 }),
49+
replicas: [],
50+
}),
51+
)
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
import { Theme } from '@redis-ui/styles'
12
import styled from 'styled-components'
23
import { scrollbarStyles } from 'uiSrc/styles/mixins'
34

4-
export const Wrapper = styled.div`
5-
${scrollbarStyles()}
5+
export const ClusterDetailsPageWrapper = styled.div`
6+
${scrollbarStyles()};
7+
height: 100%;
8+
padding: 0 ${({ theme }: { theme: Theme }) => theme.core.space.space200};
69
`

redisinsight/ui/src/pages/cluster-details/ClusterDetailsPage.tsx

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { orderBy } from 'lodash'
2-
import React, { useContext, useEffect, useState } from 'react'
2+
import React, { useContext, useEffect, useState, useMemo } from 'react'
33
import { useSelector, useDispatch } from 'react-redux'
44
import { useParams } from 'react-router-dom'
55
import { ClusterNodeDetails } from 'src/modules/cluster-monitor/models'
@@ -21,19 +21,18 @@ import {
2121
formatLongName,
2222
getDbIndex,
2323
getLetterByIndex,
24-
Nullable,
2524
setTitle,
2625
} from 'uiSrc/utils'
2726
import { ColorScheme, getRGBColorByScheme, RGBColor } from 'uiSrc/utils/colors'
2827

2928
import { ConnectionType } from 'uiSrc/slices/interfaces'
30-
import { AnalysisPageContainer } from 'uiSrc/pages/database-analysis/components/analysis-page-container'
3129
import {
3230
ClusterDetailsHeader,
3331
ClusterDetailsGraphics,
3432
ClusterNodesTable,
3533
} from './components'
36-
import { Wrapper } from './ClusterDetailsPage.styles'
34+
35+
import * as S from './ClusterDetailsPage.styles'
3736

3837
export interface ModifiedClusterNodes extends ClusterNodeDetails {
3938
letter: string
@@ -55,7 +54,6 @@ const ClusterDetailsPage = () => {
5554
const { loading, data } = useSelector(clusterDetailsSelector)
5655

5756
const [isPageViewSent, setIsPageViewSent] = useState(false)
58-
const [nodes, setNodes] = useState<Nullable<ModifiedClusterNodes[]>>(null)
5957

6058
const dispatch = useDispatch()
6159
const { theme } = useContext(ThemeContext)
@@ -103,18 +101,20 @@ const ClusterDetailsPage = () => {
103101
return () => clearInterval(interval)
104102
}, [instanceId, loading])
105103

106-
useEffect(() => {
104+
const nodes = useMemo(() => {
107105
if (data) {
108106
const nodes = orderBy(data.nodes, ['asc', 'host'])
109107
const shift = colorScheme.cHueRange / nodes.length
110-
const modifiedNodes = nodes.map((d, index) => ({
108+
109+
return nodes.map((d, index) => ({
111110
...d,
112111
letter: getLetterByIndex(index),
113112
index,
114113
color: getRGBColorByScheme(index, shift, colorScheme),
115-
}))
116-
setNodes(modifiedNodes)
114+
})) as ModifiedClusterNodes[]
117115
}
116+
117+
return [] as ModifiedClusterNodes[]
118118
}, [data])
119119

120120
useEffect(() => {
@@ -134,13 +134,11 @@ const ClusterDetailsPage = () => {
134134
}
135135

136136
return (
137-
<AnalysisPageContainer data-testid="cluster-details-page">
137+
<S.ClusterDetailsPageWrapper as="div" data-testid="cluster-details-page">
138138
<ClusterDetailsHeader />
139-
<Wrapper>
140-
<ClusterDetailsGraphics nodes={nodes} loading={loading} />
141-
<ClusterNodesTable nodes={nodes} loading={loading} />
142-
</Wrapper>
143-
</AnalysisPageContainer>
139+
<ClusterDetailsGraphics nodes={nodes} loading={loading} />
140+
<ClusterNodesTable nodes={nodes} />
141+
</S.ClusterDetailsPageWrapper>
144142
)
145143
}
146144

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { ColumnDef, SortingState } from 'uiSrc/components/base/layout/table'
2+
3+
import { ModifiedClusterNodes } from '../../ClusterDetailsPage'
4+
import { ClusterNodesHostCell } from './components/ClusterNodesHostCell/ClusterNodesHostCell'
5+
import { ClusterNodesNumericCell } from './components/ClusterNodesNumericCell/ClusterNodesNumericCell'
6+
7+
export const DEFAULT_SORTING: SortingState = [
8+
{
9+
id: 'host',
10+
desc: false,
11+
},
12+
]
13+
14+
export const DEFAULT_CLUSTER_NODES_COLUMNS: ColumnDef<ModifiedClusterNodes>[] =
15+
[
16+
{
17+
header: ({ table }) => `${table.options.data.length} Primary nodes`,
18+
isHeaderCustom: true,
19+
id: 'host',
20+
accessorKey: 'host',
21+
enableSorting: true,
22+
cell: ClusterNodesHostCell,
23+
},
24+
{
25+
header: 'Commands/s',
26+
id: 'opsPerSecond',
27+
accessorKey: 'opsPerSecond',
28+
enableSorting: true,
29+
cell: ClusterNodesNumericCell,
30+
},
31+
{
32+
header: 'Network Input',
33+
id: 'networkInKbps',
34+
accessorKey: 'networkInKbps',
35+
enableSorting: true,
36+
cell: ClusterNodesNumericCell,
37+
},
38+
{
39+
header: 'Network Output',
40+
id: 'networkOutKbps',
41+
accessorKey: 'networkOutKbps',
42+
enableSorting: true,
43+
cell: ClusterNodesNumericCell,
44+
},
45+
{
46+
header: 'Total Memory',
47+
id: 'usedMemory',
48+
accessorKey: 'usedMemory',
49+
enableSorting: true,
50+
cell: ClusterNodesNumericCell,
51+
},
52+
{
53+
header: 'Total Keys',
54+
id: 'totalKeys',
55+
accessorKey: 'totalKeys',
56+
enableSorting: true,
57+
cell: ClusterNodesNumericCell,
58+
},
59+
{
60+
header: 'Clients',
61+
id: 'connectedClients',
62+
accessorKey: 'connectedClients',
63+
enableSorting: true,
64+
cell: ClusterNodesNumericCell,
65+
},
66+
]
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import React from 'react'
2+
import { getLetterByIndex } from 'uiSrc/utils'
3+
import { rgb } from 'uiSrc/utils/colors'
4+
import { render, screen } from 'uiSrc/utils/test-utils'
5+
6+
import ClusterNodesTable from './ClusterNodesTable'
7+
import { ModifiedClusterNodes } from '../../ClusterDetailsPage'
8+
import { ClusterNodeDetailsFactory } from 'uiSrc/mocks/factories/cluster/ClusterNodeDetails.factory'
9+
10+
const mockNodes = [
11+
ClusterNodeDetailsFactory.build({
12+
totalKeys: 1,
13+
opsPerSecond: 1,
14+
}),
15+
ClusterNodeDetailsFactory.build({
16+
totalKeys: 4,
17+
opsPerSecond: 1,
18+
}),
19+
ClusterNodeDetailsFactory.build({
20+
totalKeys: 10,
21+
opsPerSecond: 0,
22+
}),
23+
].map((d, index) => ({
24+
...d,
25+
letter: getLetterByIndex(index),
26+
index,
27+
color: [0, 0, 0],
28+
})) as ModifiedClusterNodes[]
29+
30+
describe('ClusterNodesTable', () => {
31+
it('should render', () => {
32+
expect(render(<ClusterNodesTable nodes={mockNodes} />)).toBeTruthy()
33+
})
34+
35+
it('should render loading content', () => {
36+
const { container } = render(<ClusterNodesTable nodes={[]} />)
37+
expect(container).toBeInTheDocument()
38+
})
39+
40+
it('should render table', () => {
41+
const { container } = render(<ClusterNodesTable nodes={mockNodes} />)
42+
expect(container).toBeInTheDocument()
43+
expect(
44+
screen.queryByTestId('primary-nodes-table-loading'),
45+
).not.toBeInTheDocument()
46+
})
47+
48+
it('should render table with 3 items', () => {
49+
render(<ClusterNodesTable nodes={mockNodes} />)
50+
expect(screen.getAllByTestId('node-letter')).toHaveLength(3)
51+
})
52+
53+
it('should highlight max value for total keys', () => {
54+
render(<ClusterNodesTable nodes={mockNodes} />)
55+
expect(screen.getByTestId('totalKeys-value-max')).toHaveTextContent(
56+
mockNodes[2].totalKeys.toString(),
57+
)
58+
})
59+
60+
it('should not highlight max value for opsPerSecond with equals values', () => {
61+
render(<ClusterNodesTable nodes={mockNodes} />)
62+
expect(
63+
screen.queryByTestId('opsPerSecond-value-max'),
64+
).not.toBeInTheDocument()
65+
})
66+
67+
it('should render background color for each node', () => {
68+
render(<ClusterNodesTable nodes={mockNodes} />)
69+
mockNodes.forEach(({ letter, color }) => {
70+
expect(screen.getByTestId(`node-color-${letter}`)).toHaveStyle({
71+
'background-color': rgb(color),
72+
})
73+
})
74+
})
75+
})
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React from 'react'
2+
3+
import { Table } from 'uiSrc/components/base/layout/table'
4+
5+
import {
6+
DEFAULT_CLUSTER_NODES_COLUMNS,
7+
DEFAULT_SORTING,
8+
} from './ClusterNodesTable.constants'
9+
import { ClusterNodesEmptyState } from './components/ClusterNodesEmptyState/ClusterNodesEmptyState'
10+
import { ClusterNodesTableProps } from './ClusterNodesTable.types'
11+
12+
const ClusterNodesTable = ({ nodes }: ClusterNodesTableProps) => (
13+
<Table
14+
columns={DEFAULT_CLUSTER_NODES_COLUMNS}
15+
data={nodes}
16+
defaultSorting={DEFAULT_SORTING}
17+
emptyState={ClusterNodesEmptyState}
18+
maxHeight="20rem"
19+
/>
20+
)
21+
22+
export default ClusterNodesTable
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { CellContext } from 'uiSrc/components/base/layout/table'
2+
import { ModifiedClusterNodes } from '../../ClusterDetailsPage'
3+
4+
export type ClusterNodesTableProps = {
5+
nodes: ModifiedClusterNodes[]
6+
}
7+
8+
export type ClusterNodesTableCell = (
9+
props: CellContext<ModifiedClusterNodes, unknown>,
10+
) => React.ReactElement<any, any> | null
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import styled from 'styled-components'
2+
3+
export const EmptyStateWrapper = styled.div<
4+
React.HTMLAttributes<HTMLDivElement>
5+
>`
6+
margin-top: 40px;
7+
width: 100%;
8+
`
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import React from 'react'
2+
3+
import { LoadingContent } from 'uiSrc/components'
4+
5+
import * as S from './ClusterNodesEmptyState.styles'
6+
7+
export const ClusterNodesEmptyState = () => (
8+
<S.EmptyStateWrapper data-testid="primary-nodes-table-loading">
9+
<LoadingContent lines={4} />
10+
</S.EmptyStateWrapper>
11+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import styled from 'styled-components'
2+
3+
export const LineIndicator = styled.div<{ $backgroundColor: string }>`
4+
position: absolute;
5+
left: 0;
6+
top: 1px;
7+
bottom: 1px;
8+
width: 3px;
9+
background-color: ${({ $backgroundColor }) => $backgroundColor};
10+
`

0 commit comments

Comments
 (0)