Skip to content

Commit

Permalink
refactor(18077): Align the design of the charts with the Edge theme (#…
Browse files Browse the repository at this point in the history
…244)

* fix(18077): fix the group panels so that overview and observability b…

* refactor(18077): add char themes to the types

* refactor(18077): create a colorscheme for each grouped entity and ass…

* refactor(18077): add the chart theme to the props

* refactor(18077): apply the theme to the Stat chart

* refactor(18077): apply the theme to the line chart

* refactor(18077): apply the theme to the bar chart

* fix(18077): fix legend

* fix(18077): fix x-axis ticks snd labels

* refactor(18077): add a custom tooltip to all charts

* style(18077): fix style of stat chart

* fix(18077): fix translations

* refactor(18077): refactor the tooltip

* test(18077): add tests

* test(18077): fix tests

* fix(18077): hide the value from the bars

* test(18077): fix test

* fix(18077): add entity name into the series name
  • Loading branch information
vanch3d committed Dec 19, 2023
1 parent 3fc2f3c commit 307e083
Show file tree
Hide file tree
Showing 15 changed files with 257 additions and 115 deletions.
3 changes: 2 additions & 1 deletion hivemq-edge/src/frontend/src/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,7 @@
"property": {
"header_ADAPTER_NODE": "Adapter Overview",
"header_BRIDGE_NODE": "Bridge Overview",
"header_CLUSTER_NODE": "Group Overview",
"modify_ADAPTER_NODE": "Modify the adapter",
"modify_BRIDGE_NODE": "Modify the bridge",
"eventLog": {
Expand Down Expand Up @@ -703,7 +704,7 @@
"charts": {
"LineChart": {
"ariaLabel": {
"legend": "timestamp (s)"
"legend": "timestamp (seconds ago)"
}
}
},
Expand Down
16 changes: 16 additions & 0 deletions hivemq-edge/src/frontend/src/modules/Metrics/Metrics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
CardBody,
SimpleGrid,
useDisclosure,
useTheme,
} from '@chakra-ui/react'
import { useTranslation } from 'react-i18next'

Expand All @@ -19,6 +20,7 @@ import config from '@/config'

import { ChartType, MetricDefinition } from './types.ts'
import useMetricsStore from './hooks/useMetricsStore.ts'
import { extractMetricInfo } from './utils/metrics-name.utils.ts'
import MetricEditor from './components/editor/MetricEditor.tsx'
import ChartContainer from './components/container/ChartContainer.tsx'
import Sample from './components/container/Sample.tsx'
Expand All @@ -36,12 +38,21 @@ export interface MetricSpecStorage {
selectedChart?: ChartType
}

// TODO[NVL] Should go to some kind of reusable routine, with verification
const defaultColorSchemes = ['blue', 'green', 'orange', 'pink', 'purple', 'red', 'teal', 'yellow', 'cyan']

const Metrics: FC<MetricsProps> = ({ nodeId, adapterIDs, initMetrics, defaultChartType }) => {
const { t } = useTranslation()
const { isOpen, onOpen, onClose } = useDisclosure()
const { addMetrics, getMetricsFor, removeMetrics } = useMetricsStore()
const { colors } = useTheme()

const showEditor = config.features.METRICS_SHOW_EDITOR

// TODO[NVL] Should go to some kind of reusable routine, with verification
const colorKeys = Object.keys(colors)
const chartTheme = defaultColorSchemes.filter((c) => colorKeys.includes(c))

const handleCreateMetrics = (value: MetricDefinition) => {
const { selectedTopic, selectedChart } = value
addMetrics(nodeId, selectedTopic.value, selectedChart?.value)
Expand Down Expand Up @@ -96,11 +107,15 @@ const Metrics: FC<MetricsProps> = ({ nodeId, adapterIDs, initMetrics, defaultCha
<CardBody>
<SimpleGrid spacing={4} templateColumns="repeat(auto-fill, minmax(200px, 1fr))">
{metrics.map((e) => {
const { id } = extractMetricInfo(e.metrics)
const colorSchemeIndex = adapterIDs.indexOf(id as string)

if (!e.chart || e.chart === ChartType.SAMPLE)
return (
<Sample
key={e.metrics}
metricName={e.metrics}
chartTheme={{ colourScheme: chartTheme[colorSchemeIndex] }}
onClose={() => handleRemoveMetrics(e.metrics)}
canEdit={isOpen}
/>
Expand All @@ -111,6 +126,7 @@ const Metrics: FC<MetricsProps> = ({ nodeId, adapterIDs, initMetrics, defaultCha
key={e.metrics}
chartType={e.chart}
metricName={e.metrics}
chartTheme={{ colourScheme: chartTheme[colorSchemeIndex] }}
onClose={() => handleRemoveMetrics(e.metrics)}
canEdit={isOpen}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { Box } from '@chakra-ui/react'

import { MOCK_METRIC_SAMPLE_ARRAY, MOCK_METRICS } from '@/api/hooks/useGetMetrics/__handlers__'
import BarChart from './BarChart.tsx'
import { DateTime } from 'luxon'

const mockAriaLabel = 'aria-label'
const Wrapper: FC<PropsWithChildren> = ({ children }) => (
Expand Down Expand Up @@ -33,24 +32,10 @@ describe('BarChart', () => {
</Wrapper>
)

const formatShortDate = new Intl.DateTimeFormat(navigator.language, {
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
})

cy.get("[role='application']").should('have.attr', 'aria-label', mockAriaLabel)
cy.get('text').should('contain.text', 'timestamp (s)')
cy.get('text').should('contain.text', 'timestamp (seconds ago)')
cy.get('svg > g > g > rect').eq(4).click()
cy.getByTestId('bar-chart-tooltip')
.should('contain.text', '[Forward] Publish success (count)')
.should(
'contain.text',
formatShortDate.format(DateTime.fromISO(MOCK_METRIC_SAMPLE_ARRAY[5].sampleTime as string).toJSDate())
)
.should('contain.text', '55000')

cy.checkAccessibility()
cy.percySnapshot('Component: BarChart')
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,80 +2,51 @@ import { FC } from 'react'
import { BarDatum, ResponsiveBar } from '@nivo/bar'
import { DateTime } from 'luxon'
import { useTranslation } from 'react-i18next'
import { Badge, Box, Card, Text } from '@chakra-ui/react'
import { Box, useTheme } from '@chakra-ui/react'

import { ChartProps } from '../../types.ts'
import DateTimeRenderer from '@/components/DateTime/DateTimeRenderer.tsx'
import { extractMetricInfo } from '@/modules/Metrics/utils/metrics-name.utils.ts'
import { extractMetricInfo } from '../../utils/metrics-name.utils.ts'
import ChartTooltip from '../parts/ChartTooltip.tsx'

interface Datum extends BarDatum {
sampleTime: string
}

const BarChart: FC<ChartProps> = ({ data, metricName, 'aria-label': ariaLabel, ...props }) => {
const BarChart: FC<ChartProps> = ({ data, metricName, 'aria-label': ariaLabel, chartTheme, ...props }) => {
const { t } = useTranslation()
const { colors } = useTheme()

if (!metricName) return null

const { suffix, device } = extractMetricInfo(metricName)
const seriesName = t(`metrics.${device}.${suffix}`).replaceAll('.', ' ')
const { suffix, device, id } = extractMetricInfo(metricName)
let seriesName = t(`metrics.${device}.${suffix}`).replaceAll('.', ' ')
seriesName = `${seriesName} - ${id}`

const barSeries: Datum[] = [...data]
.reverse()
.map((e) => ({ sampleTime: e.sampleTime as string, [seriesName]: e.value as number }))

const colorScheme = chartTheme?.colourScheme || 'red'
const colorElement = colors[colorScheme][500]

return (
<Box w={'100%'} {...props}>
<ResponsiveBar
data={barSeries}
keys={[seriesName]}
indexBy="sampleTime"
margin={{ top: 10, right: 80, bottom: 40, left: 40 }}
padding={0.3}
valueScale={{ type: 'linear' }}
indexScale={{ type: 'band', round: true }}
colors={{ scheme: 'nivo' }}
tooltip={(d) => (
<Card p={1} data-testid={'bar-chart-tooltip'}>
<Badge backgroundColor={d.color}>{d.id}</Badge>
<DateTimeRenderer date={DateTime.fromISO(d.indexValue as string)} isShort />
<Text fontWeight={'bold'}>{d.formattedValue}</Text>
</Card>
)}
// defs={[
// {
// id: 'dots',
// type: 'patternDots',
// background: 'inherit',
// color: '#38bcb2',
// size: 4,
// padding: 1,
// stagger: true,
// },
// {
// id: 'lines',
// type: 'patternLines',
// background: 'inherit',
// color: '#eed312',
// rotation: -45,
// lineWidth: 6,
// spacing: 10,
// },
// ]}
// fill={[
// {
// match: {
// id: 'fries',
// },
// id: 'dots',
// },
// {
// match: {
// id: 'sandwich',
// },
// id: 'lines',
// },
// ]}
colors={[colorElement]}
// tooltip={(d) => (
// <Card p={1} data-testid={'bar-chart-tooltip'}>
// <Badge backgroundColor={d.color} color={'white'}>
// {d.id}
// </Badge>
// <DateTimeRenderer date={DateTime.fromISO(d.indexValue as string)} isShort />
// <Text fontWeight={'bold'}>{d.formattedValue}</Text>
// </Card>
// )}
borderColor={{
from: 'color',
modifiers: [['darker', 1.6]],
Expand All @@ -91,7 +62,12 @@ const BarChart: FC<ChartProps> = ({ data, metricName, 'aria-label': ariaLabel, .
legendOffset: 32,
truncateTickAt: 0,
format: (value) => {
return '+' + DateTime.fromISO(value).second
const duration = DateTime.fromISO(value).diffNow(['second'])
const rescaledDuration = duration
.negate()
.mapUnits((x) => Math.floor(x))
.rescale()
return rescaledDuration.as('seconds')
},
}}
axisLeft={{
Expand All @@ -107,22 +83,30 @@ const BarChart: FC<ChartProps> = ({ data, metricName, 'aria-label': ariaLabel, .
labelSkipHeight={12}
labelTextColor={{
from: 'color',
modifiers: [['darker', 1.6]],
modifiers: [['darker', 0]],
}}
margin={{ top: 5, right: 0, bottom: 70, left: 40 }}
padding={0.3}
tooltip={(d) => (
<ChartTooltip
color={d.color}
id={d.id}
date={DateTime.fromISO(d.indexValue as string)}
formattedValue={d.formattedValue.toString()}
/>
)}
legends={[
{
dataFrom: 'keys',
direction: 'row',
anchor: 'bottom-right',
direction: 'column',
justify: false,
translateX: 120,
translateY: 0,
itemsSpacing: 2,
itemWidth: 100,
dataFrom: 'keys',
itemDirection: 'right-to-left',
translateY: 70,

itemWidth: 0,
itemHeight: 20,
itemDirection: 'left-to-right',
itemOpacity: 0.85,
symbolSize: 20,
// itemOpacity: 0.85,
// symbolSize: 20,
effects: [
{
on: 'hover',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe('LineChart', () => {
)

cy.get("[role='application']").should('have.attr', 'aria-label', mockAriaLabel)
cy.get('text').should('contain.text', 'timestamp (s)')
cy.get('text').should('contain.text', 'timestamp (seconds ago)')

cy.checkAccessibility()
cy.percySnapshot('Component: LineChart')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import { FC } from 'react'
import { ResponsiveLine } from '@nivo/line'
import { DateTime } from 'luxon'
import { useTranslation } from 'react-i18next'
import { Badge, Box, Card, Text } from '@chakra-ui/react'
import { Box, useTheme } from '@chakra-ui/react'

import { ChartProps } from '../../types.ts'
import DateTimeRenderer from '@/components/DateTime/DateTimeRenderer.tsx'
import { extractMetricInfo } from '@/modules/Metrics/utils/metrics-name.utils.ts'
import { extractMetricInfo } from '../../utils/metrics-name.utils.ts'
import ChartTooltip from '../parts/ChartTooltip.tsx'

const LineChart: FC<ChartProps> = ({ data, metricName, 'aria-label': ariaLabel, ...props }) => {
const LineChart: FC<ChartProps> = ({ data, metricName, 'aria-label': ariaLabel, chartTheme, ...props }) => {
const { t } = useTranslation()
const { colors } = useTheme()

if (!metricName) return null

Expand All @@ -18,8 +19,12 @@ const LineChart: FC<ChartProps> = ({ data, metricName, 'aria-label': ariaLabel,
y: Math.max(...data.map((e) => e.value as number)),
}

const { suffix, device } = extractMetricInfo(metricName)
const seriesName = t(`metrics.${device}.${suffix}`).replaceAll('.', ' ')
const { suffix, device, id } = extractMetricInfo(metricName)
let seriesName = t(`metrics.${device}.${suffix}`).replaceAll('.', ' ')
seriesName = `${seriesName} - ${id}`

const colorScheme = chartTheme?.colourScheme || 'red'
const colorElement = colors[colorScheme][500]

return (
<Box w={'100%'} {...props} role={'application'} aria-label={ariaLabel}>
Expand All @@ -36,15 +41,9 @@ const LineChart: FC<ChartProps> = ({ data, metricName, 'aria-label': ariaLabel,
.map((e) => ({ x: DateTime.fromISO(e.sampleTime as string).toMillis(), y: e.value })),
},
]}
colors={{ scheme: 'nivo' }}
tooltip={(d) => (
<Card p={1} data-testid={'line-chart-tooltip'}>
<Badge backgroundColor={d.point.serieColor}>{d.point.serieId}</Badge>
<DateTimeRenderer date={DateTime.fromMillis(d.point.data.x as number)} isShort />
<Text fontWeight={'bold'}>{d.point.data.yFormatted}</Text>
</Card>
)}
margin={{ top: 0, right: 10, bottom: 50, left: 50 }}
// colors={{ scheme: 'nivo' }}
colors={[colorElement]}
margin={{ top: 0, right: 10, bottom: 70, left: 50 }}
yScale={{ type: 'linear', min: boundaries.x, max: boundaries.y, stacked: true }}
// xScale={{ type: 'linear' }}
// yScale={{ type: 'linear', stacked: true, min: 0, max: 2500 }}
Expand All @@ -67,7 +66,12 @@ const LineChart: FC<ChartProps> = ({ data, metricName, 'aria-label': ariaLabel,
// tickRotation: 0,
// // format: '.2f',
format: (value) => {
return '+' + DateTime.fromMillis(value).second
const duration = DateTime.fromMillis(value).diffNow(['second'])
const rescaledDuration = duration
.negate()
.mapUnits((x) => Math.floor(x))
.rescale()
return rescaledDuration.as('seconds')
},
legend: t('metrics.charts.LineChart.ariaLabel.legend'),
legendOffset: 36,
Expand Down Expand Up @@ -120,6 +124,36 @@ const LineChart: FC<ChartProps> = ({ data, metricName, 'aria-label': ariaLabel,
// ],
// },
// ]}

tooltip={(d) => (
<ChartTooltip
color={d.point.serieColor}
id={d.point.serieId}
date={DateTime.fromMillis(d.point.data.x as number)}
formattedValue={d.point.data.yFormatted.toString()}
/>
)}
legends={[
{
direction: 'row',
anchor: 'bottom-right',
itemDirection: 'right-to-left',
translateY: 70,

itemWidth: 0,
itemHeight: 20,
// itemOpacity: 0.85,
// symbolSize: 20,
effects: [
{
on: 'hover',
style: {
itemOpacity: 1,
},
},
],
},
]}
/>
</Box>
)
Expand Down
Loading

0 comments on commit 307e083

Please sign in to comment.