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
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Factory } from 'fishery'
import { faker } from '@faker-js/faker'
import { ClusterNodeDetails } from 'src/modules/cluster-monitor/models'

enum NodeRole {
Primary = 'primary',
Replica = 'replica',
}

enum HealthStatus {
Online = 'online',
Offline = 'offline',
Loading = 'loading',
}

export const ClusterNodeDetailsFactory = Factory.define<ClusterNodeDetails>(
() => ({
id: faker.string.uuid(),
version: faker.system.semver(),
mode: faker.helpers.arrayElement(['standalone', 'cluster', 'sentinel']),
host: faker.internet.ip(),
port: faker.internet.port(),
role: faker.helpers.arrayElement([NodeRole.Primary, NodeRole.Replica]),
health: faker.helpers.arrayElement([
HealthStatus.Online,
HealthStatus.Offline,
HealthStatus.Loading,
]),
slots: ['0-5460'],
totalKeys: faker.number.int({ min: 0, max: 1000000 }),
usedMemory: faker.number.int({ min: 1000000, max: 100000000 }),
opsPerSecond: faker.number.int({ min: 0, max: 10000 }),
connectionsReceived: faker.number.int({ min: 0, max: 10000 }),
connectedClients: faker.number.int({ min: 0, max: 100 }),
commandsProcessed: faker.number.int({ min: 0, max: 1000000000 }),
networkInKbps: faker.number.float({
min: 0,
max: 10000,
fractionDigits: 2,
}),
networkOutKbps: faker.number.float({
min: 0,
max: 10000,
fractionDigits: 2,
}),
cacheHitRatio: faker.number.float({ min: 0, max: 1, fractionDigits: 2 }),
replicationOffset: faker.number.int({ min: 0, max: 1000000 }),
uptimeSec: faker.number.int({ min: 0, max: 10000000 }),
replicas: [],
}),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import styled from 'styled-components'

export const ClusterDetailsPageWrapper = styled.div<
React.HTMLAttributes<HTMLDivElement>
>`
height: 100%;
padding: 0 1.6rem;
`
26 changes: 12 additions & 14 deletions redisinsight/ui/src/pages/cluster-details/ClusterDetailsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { orderBy } from 'lodash'
import React, { useContext, useEffect, useState } from 'react'
import React, { useContext, useEffect, useState, useMemo } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { useParams } from 'react-router-dom'
import { ClusterNodeDetails } from 'src/modules/cluster-monitor/models'
Expand All @@ -21,7 +21,6 @@ import {
formatLongName,
getDbIndex,
getLetterByIndex,
Nullable,
setTitle,
} from 'uiSrc/utils'
import { ColorScheme, getRGBColorByScheme, RGBColor } from 'uiSrc/utils/colors'
Expand All @@ -33,7 +32,7 @@ import {
ClusterNodesTable,
} from './components'

import styles from './styles.module.scss'
import * as S from './ClusterDetailsPage.styles'

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

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

const dispatch = useDispatch()
const { theme } = useContext(ThemeContext)
Expand Down Expand Up @@ -103,18 +101,20 @@ const ClusterDetailsPage = () => {
return () => clearInterval(interval)
}, [instanceId, loading])

useEffect(() => {
const nodes = useMemo(() => {
if (data) {
const nodes = orderBy(data.nodes, ['asc', 'host'])
const shift = colorScheme.cHueRange / nodes.length
const modifiedNodes = nodes.map((d, index) => ({

return nodes.map((d, index) => ({
...d,
letter: getLetterByIndex(index),
index,
color: getRGBColorByScheme(index, shift, colorScheme),
}))
setNodes(modifiedNodes)
})) as ModifiedClusterNodes[]
}

return [] as ModifiedClusterNodes[]
}, [data])

useEffect(() => {
Expand All @@ -134,13 +134,11 @@ const ClusterDetailsPage = () => {
}

return (
<div className={styles.main} data-testid="cluster-details-page">
<S.ClusterDetailsPageWrapper data-testid="cluster-details-page">
<ClusterDetailsHeader />
<div className={styles.wrapper}>
<ClusterDetailsGraphics nodes={nodes} loading={loading} />
<ClusterNodesTable nodes={nodes} loading={loading} />
</div>
</div>
<ClusterDetailsGraphics nodes={nodes} loading={loading} />
<ClusterNodesTable nodes={nodes} />
</S.ClusterDetailsPageWrapper>
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { ColumnDef, SortingState } from 'uiSrc/components/base/layout/table'

import { ModifiedClusterNodes } from '../../ClusterDetailsPage'
import { ClusterNodesHostCell } from './components/ClusterNodesHostCell/ClusterNodesHostCell'
import { ClusterNodesNumericCell } from './components/ClusterNodesNumericCell/ClusterNodesNumericCell'

export const DEFAULT_SORTING: SortingState = [
{
id: 'host',
desc: false,
},
]

export const DEFAULT_CLUSTER_NODES_COLUMNS: ColumnDef<ModifiedClusterNodes>[] =
[
{
header: ({ table }) => `${table.options.data.length} Primary nodes`,
isHeaderCustom: true,
id: 'host',
accessorKey: 'host',
enableSorting: true,
cell: ClusterNodesHostCell,
},
{
header: 'Commands/s',
id: 'opsPerSecond',
accessorKey: 'opsPerSecond',
enableSorting: true,
cell: ClusterNodesNumericCell,
},
{
header: 'Network Input',
id: 'networkInKbps',
accessorKey: 'networkInKbps',
enableSorting: true,
cell: ClusterNodesNumericCell,
},
{
header: 'Network Output',
id: 'networkOutKbps',
accessorKey: 'networkOutKbps',
enableSorting: true,
cell: ClusterNodesNumericCell,
},
{
header: 'Total Memory',
id: 'usedMemory',
accessorKey: 'usedMemory',
enableSorting: true,
cell: ClusterNodesNumericCell,
},
{
header: 'Total Keys',
id: 'totalKeys',
accessorKey: 'totalKeys',
enableSorting: true,
cell: ClusterNodesNumericCell,
},
{
header: 'Clients',
id: 'connectedClients',
accessorKey: 'connectedClients',
enableSorting: true,
cell: ClusterNodesNumericCell,
},
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React from 'react'
import { getLetterByIndex } from 'uiSrc/utils'
import { rgb } from 'uiSrc/utils/colors'
import { render, screen } from 'uiSrc/utils/test-utils'

import ClusterNodesTable from './ClusterNodesTable'
import { ModifiedClusterNodes } from '../../ClusterDetailsPage'
import { ClusterNodeDetailsFactory } from 'uiSrc/mocks/factories/cluster/ClusterNodeDetails.factory'

const mockNodes = [
ClusterNodeDetailsFactory.build({
totalKeys: 1,
opsPerSecond: 1,
}),
ClusterNodeDetailsFactory.build({
totalKeys: 4,
opsPerSecond: 1,
}),
ClusterNodeDetailsFactory.build({
totalKeys: 10,
opsPerSecond: 0,
}),
].map((d, index) => ({
...d,
letter: getLetterByIndex(index),
index,
color: [0, 0, 0],
})) as ModifiedClusterNodes[]

describe('ClusterNodesTable', () => {
it('should render', () => {
expect(render(<ClusterNodesTable nodes={mockNodes} />)).toBeTruthy()
})

it('should render loading content', () => {
const { container } = render(<ClusterNodesTable nodes={[]} />)
expect(container).toBeInTheDocument()
})

it('should render table', () => {
const { container } = render(<ClusterNodesTable nodes={mockNodes} />)
expect(container).toBeInTheDocument()
expect(
screen.queryByTestId('primary-nodes-table-loading'),
).not.toBeInTheDocument()
})

it('should render table with 3 items', () => {
render(<ClusterNodesTable nodes={mockNodes} />)
expect(screen.getAllByTestId('node-letter')).toHaveLength(3)
})

it('should highlight max value for total keys', () => {
render(<ClusterNodesTable nodes={mockNodes} />)
expect(screen.getByTestId('totalKeys-value-max')).toHaveTextContent(
mockNodes[2].totalKeys.toString(),
)
})

it('should not highlight max value for opsPerSecond with equals values', () => {
render(<ClusterNodesTable nodes={mockNodes} />)
expect(
screen.queryByTestId('opsPerSecond-value-max'),
).not.toBeInTheDocument()
})

it('should render background color for each node', () => {
render(<ClusterNodesTable nodes={mockNodes} />)
mockNodes.forEach(({ letter, color }) => {
expect(screen.getByTestId(`node-color-${letter}`)).toHaveStyle({
'background-color': rgb(color),
})
})
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react'

import { Table } from 'uiSrc/components/base/layout/table'

import {
DEFAULT_CLUSTER_NODES_COLUMNS,
DEFAULT_SORTING,
} from './ClusterNodesTable.constants'
import { ClusterNodesEmptyState } from './components/ClusterNodesEmptyState/ClusterNodesEmptyState'
import { ClusterNodesTableProps } from './ClusterNodesTable.types'

const ClusterNodesTable = ({ nodes }: ClusterNodesTableProps) => (
<Table
columns={DEFAULT_CLUSTER_NODES_COLUMNS}
data={nodes}
defaultSorting={DEFAULT_SORTING}
emptyState={ClusterNodesEmptyState}
maxHeight="20rem"
/>
)

export default ClusterNodesTable
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { CellContext } from 'uiSrc/components/base/layout/table'
import { ModifiedClusterNodes } from '../../ClusterDetailsPage'

export type ClusterNodesTableProps = {
nodes: ModifiedClusterNodes[]
}

export type ClusterNodesTableCell = (
props: CellContext<ModifiedClusterNodes, unknown>,
) => React.ReactElement<any, any> | null
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import styled from 'styled-components'

export const EmptyStateWrapper = styled.div<
React.HTMLAttributes<HTMLDivElement>
>`
margin-top: 40px;
width: 100%;
`
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react'

import { LoadingContent } from 'uiSrc/components'

import * as S from './ClusterNodesEmptyState.styles'

export const ClusterNodesEmptyState = () => (
<S.EmptyStateWrapper data-testid="primary-nodes-table-loading">
<LoadingContent lines={4} />
</S.EmptyStateWrapper>
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import styled from 'styled-components'

export const LineIndicator = styled.div<{ $backgroundColor: string }>`
position: absolute;
left: 0;
top: 1px;
bottom: 1px;
width: 3px;
background-color: ${({ $backgroundColor }) => $backgroundColor};
`
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react'

import { rgb } from 'uiSrc/utils/colors'
import { Text } from 'uiSrc/components/base/text'
import { Row } from 'uiSrc/components/base/layout/flex'
import { ClusterNodesTableCell } from 'uiSrc/pages/cluster-details/components/ClusterNodesTable/ClusterNodesTable.types'

import * as S from './ClusterNodesHostCell.styles'

export const ClusterNodesHostCell: ClusterNodesTableCell = ({
row: {
original: { letter, port, color, host },
},
}) => (
<>
<S.LineIndicator
data-testid={`node-color-${letter}`}
$backgroundColor={rgb(color)}
/>
<Row justify="between">
<Text variant="semiBold" data-testid="node-letter">
{letter}
</Text>
<Text variant="regular">
{host}:{port}
</Text>
</Row>
</>
)
Loading
Loading