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
@@ -1,4 +1,6 @@
import { sumBy } from 'lodash'
import React from 'react'
import { getPercentage } from 'uiSrc/utils/numbers'
import { render, screen, fireEvent } from 'uiSrc/utils/test-utils'

import DonutChart, { ChartData } from './DonutChart'
Expand All @@ -12,6 +14,8 @@ const mockData: ChartData[] = [
{ value: 15, name: 'F', color: [50, 50, 50] },
]

const sum = sumBy(mockData, 'value')

describe('DonutChart', () => {
it('should render with empty data', () => {
expect(render(<DonutChart data={[]} />)).toBeTruthy()
Expand Down Expand Up @@ -71,11 +75,27 @@ describe('DonutChart', () => {
it('should call render tooltip and label methods', () => {
const renderLabel = jest.fn()
const renderTooltip = jest.fn()
render(<DonutChart data={mockData} renderLabel={renderLabel} renderTooltip={renderTooltip} />)
expect(renderLabel).toBeCalled()
render(
<DonutChart
data={mockData}
renderLabel={renderLabel}
renderTooltip={renderTooltip}
config={{ percentToShowLabel: 0 }}
/>
)
expect(renderLabel).toBeCalledTimes(mockData.length)

fireEvent.mouseEnter(screen.getByTestId('arc-A-1'))
expect(renderTooltip).toBeCalledWith(mockData[0])
})

it('should render provided tooltip', () => {
const renderTooltip = () => (<span data-testid="label" />)

render(<DonutChart data={mockData} renderTooltip={renderTooltip} />)

fireEvent.mouseEnter(screen.getByTestId('arc-A-1'))
expect(renderTooltip).toBeCalled()
expect(screen.getByTestId('label')).toBeInTheDocument()
})

it('should set tooltip as visible on hover and hidden on leave', () => {
Expand All @@ -87,4 +107,12 @@ describe('DonutChart', () => {
fireEvent.mouseLeave(screen.getByTestId('arc-A-1'))
expect(screen.getByTestId('chart-value-tooltip')).not.toBeVisible()
})

it('should display values with percentage', () => {
render(<DonutChart data={mockData} labelAs="percentage" config={{ percentToShowLabel: 0 }} />)

mockData.forEach(({ value, name }) => {
expect(screen.getByTestId(`label-${name}-${value}`)).toHaveTextContent(`: ${getPercentage(value, sum)}%`)
})
})
})
12 changes: 6 additions & 6 deletions redisinsight/ui/src/components/charts/donut-chart/DonutChart.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import cx from 'classnames'
import * as d3 from 'd3'
import { sumBy } from 'lodash'
import { isString, sumBy } from 'lodash'
import React, { useEffect, useRef, useState } from 'react'
import { flushSync } from 'react-dom'
import { Nullable, truncateNumberToRange } from 'uiSrc/utils'
Expand All @@ -12,7 +12,7 @@ import styles from './styles.module.scss'
export interface ChartData {
value: number
name: string
color: RGBColor
color: RGBColor | string
meta?: {
[key: string]: any
}
Expand Down Expand Up @@ -61,7 +61,7 @@ const DonutChart = (props: IProps) => {
const margin = config?.margin || 98
const radius = config?.radius || (width / 2 - margin)
const arcWidth = config?.arcWidth || 8
const percentToShowLabel = config?.percentToShowLabel || 5
const percentToShowLabel = config?.percentToShowLabel ?? 5

const [hoveredData, setHoveredData] = useState<Nullable<ChartData>>(null)
const svgRef = useRef<SVGSVGElement>(null)
Expand Down Expand Up @@ -113,12 +113,12 @@ const DonutChart = (props: IProps) => {
}

const isShowLabel = (d: d3.PieArcDatum<ChartData>) =>
d.endAngle - d.startAngle > (Math.PI * 2) / (100 / percentToShowLabel)
(percentToShowLabel > 0 ? d.endAngle - d.startAngle > (Math.PI * 2) / (100 / percentToShowLabel) : true)

const getLabelPosition = (d: d3.PieArcDatum<ChartData>) => {
const [x, y] = arc.centroid(d)
const h = Math.sqrt(x * x + y * y)
return `translate(${(x / h) * (radius + 16)}, ${((y + 4) / h) * (radius + 16)})`
return `translate(${(x / h) * (radius + 12)}, ${((y + 4) / h) * (radius + 12)})`
}

useEffect(() => {
Expand Down Expand Up @@ -147,7 +147,7 @@ const DonutChart = (props: IProps) => {
.append('path')
.attr('data-testid', (d) => `arc-${d.data.name}-${d.data.value}`)
.attr('d', arc)
.attr('fill', (d) => rgb(d.data.color))
.attr('fill', (d) => (isString(d.data.color) ? d.data.color : rgb(d.data.color)))
.attr('class', cx(styles.arc, classNames?.arc))
.on('mouseenter mousemove', onMouseEnterSlice)
.on('mouseleave', onMouseLeaveSlice)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
fill: var(--euiTextSubduedColor);
font-size: 12px;
font-weight: bold;
letter-spacing: -0.12px !important;

.chartLabelValue {
font-weight: normal;
Expand Down
2 changes: 1 addition & 1 deletion redisinsight/ui/src/components/group-badge/GroupBadge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export interface Props {

const GroupBadge = ({ type, name = '', className = '', onDelete, compressed }: Props) => (
<EuiBadge
style={{ backgroundColor: GROUP_TYPES_COLORS[type] ?? '#14708D' }}
style={{ backgroundColor: GROUP_TYPES_COLORS[type] ?? 'var(--defaultTypeColor)' }}
className={cx(
styles.badgeWrapper,
className,
Expand Down
4 changes: 4 additions & 0 deletions redisinsight/ui/src/constants/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ export const GROUP_TYPES_DISPLAY = Object.freeze({
[CommandGroup.CuckooFilter]: 'Cuckoo Filter',
})

export type GroupTypesDisplay = keyof (typeof GROUP_TYPES_DISPLAY)

// Enums don't allow to use dynamic key
export const GROUP_TYPES_COLORS = Object.freeze({
[KeyTypes.Hash]: 'var(--typeHashColor)',
Expand All @@ -72,6 +74,8 @@ export const GROUP_TYPES_COLORS = Object.freeze({
[CommandGroup.HyperLogLog]: 'var(--groupHyperLolLogColor)',
})

export type GroupTypesColors = keyof (typeof GROUP_TYPES_COLORS)

export type KeyTypesActions = {
[key: string]: {
addItems?: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { emptyMessageContent } from 'uiSrc/constants'

import EmptyAnalysisMessage from '../empty-analysis-message'
import TopNamespaceView from '../top-namespace-view'
import SummaryPerData from '../summary-per-data'
import styles from '../../styles.module.scss'

export interface Props {
Expand Down Expand Up @@ -34,7 +35,10 @@ const AnalysisDataView = (props: Props) => {
/>
)}
<div className={cx(styles.grid, styles.content)}>
<TopNamespaceView data={data} loading={loading} />
<div>
<SummaryPerData data={data} loading={loading} />
<TopNamespaceView data={data} loading={loading} />
</div>
</div>
</>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import React from 'react'
import { GROUP_TYPES_DISPLAY } from 'uiSrc/constants'
import { render, screen } from 'uiSrc/utils/test-utils'
import { DatabaseAnalysis, SimpleTypeSummary } from 'apiSrc/modules/database-analysis/models'

import SummaryPerData from './SummaryPerData'

const mockData = {
totalMemory: {
total: 75,
types: [
{
type: 'hash',
total: 18
},
{
type: 'TSDB-TYPE',
total: 11
},
{
type: 'string',
total: 10
},
{
type: 'list',
total: 9
},
{
type: 'stream',
total: 8
},
{
type: 'zset',
total: 8
},
{
type: 'set',
total: 7
},
{
type: 'graphdata',
total: 2
},
{
type: 'ReJSON-RL',
total: 1
},
{
type: 'MBbloom--',
total: 1
}
]
},
totalKeys: {
total: 1168424,
types: [
{
type: 'hash',
total: 572813
},
{
type: 'zset',
total: 233571
},
{
type: 'set',
total: 138184
},
{
type: 'list',
total: 95886
},
{
type: 'stream',
total: 79532
},
{
type: 'TSDB-TYPE',
total: 47143
},
{
type: 'string',
total: 891
},
{
type: 'MBbloom--',
total: 272
},
{
type: 'graphdata',
total: 72
},
{
type: 'ReJSON-RL',
total: 60
}
]
}
} as DatabaseAnalysis

const getName = (t: SimpleTypeSummary) =>
(t.type in GROUP_TYPES_DISPLAY ? (GROUP_TYPES_DISPLAY as any)[t.type] : t.type)

describe('SummaryPerData', () => {
it('should render', () => {
expect(render(<SummaryPerData data={mockData} loading={false} />)).toBeTruthy()
})

it('should render nothing without data', () => {
render(<SummaryPerData data={null} loading={false} />)

expect(screen.queryByTestId('summary-per-data-loading')).not.toBeInTheDocument()
expect(screen.queryByTestId('summary-per-data')).not.toBeInTheDocument()
})

it('should render loading', () => {
render(<SummaryPerData data={null} loading />)

expect(screen.getByTestId('summary-per-data-loading')).toBeInTheDocument()
})

it('should render charts', () => {
render(<SummaryPerData data={mockData} loading={false} />)
expect(screen.getByTestId('donut-memory')).toBeInTheDocument()
expect(screen.queryByTestId('donut-keys')).toBeInTheDocument()
})

it('should render chart arcs', () => {
render(<SummaryPerData data={mockData} loading={false} />)

mockData.totalKeys.types.forEach((t) => {
expect(screen.getByTestId(`arc-${getName(t)}-${t.total}`)).toBeInTheDocument()
})

mockData.totalMemory.types.forEach((t) => {
expect(screen.getByTestId(`arc-${getName(t)}-${t.total}`)).toBeInTheDocument()
})
})

it('should render chart labels', () => {
render(<SummaryPerData data={mockData} loading={false} />)

mockData.totalKeys.types.forEach((t) => {
expect(screen.getByTestId(`label-${getName(t)}-${t.total}`)).toBeInTheDocument()
})

mockData.totalMemory.types.forEach((t) => {
expect(screen.getByTestId(`label-${getName(t)}-${t.total}`)).toBeInTheDocument()
})
})
})
Loading