Skip to content

Commit c9194ef

Browse files
committed
- Add unit tests for DashboardChartPanel to ensure re-rendering does not throw errors with changing datasets.
- Refactor chart options to use a constant for text color in dashboard components. - Update DashboardPage to utilize new chart text color constant. - Enhance date input field in dashboardDayPersonSections with max attribute for better date handling. - Adjust monthly sections to ensure consistent prop usage across components. - Improve chart options for monthly team distribution to handle compact view based on data length. #deploy-test-dolly-frontend
1 parent dc6279e commit c9194ef

11 files changed

Lines changed: 165 additions & 61 deletions
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { render } from '@testing-library/react'
2+
import { describe, expect, it } from 'vitest'
3+
import { DashboardChartPanel } from '@/pages/adminPages/Dashboard/dashboardSharedComponents'
4+
import { createPersonTrendChartOptions } from '@/pages/adminPages/Dashboard/dashboardTrendChartOptions'
5+
import type { PersonTrendPoint } from '@/pages/adminPages/Dashboard/dashboardUtils'
6+
7+
const makePoints = (count: number, base: number): PersonTrendPoint[] =>
8+
Array.from({ length: count }, (_, index) => ({
9+
dato: `2026-01-${String(index + 1).padStart(2, '0')}`,
10+
datoVisning: `${index + 1}. jan`,
11+
personerTotalt: base * 10 + index,
12+
nye: base + index,
13+
gjenopprettede: base + index + 1,
14+
pdlFeil: index,
15+
andreFeil: index + 1,
16+
}))
17+
18+
const visibilityOptions = {
19+
personerTotaltVisible: false,
20+
feilTotaltVisible: false,
21+
onPersonerTotaltVisibilityChange: () => undefined,
22+
onFeilTotaltVisibilityChange: () => undefined,
23+
}
24+
25+
const optionsFor = (count: number, base: number) =>
26+
createPersonTrendChartOptions(makePoints(count, base), visibilityOptions)
27+
28+
describe('DashboardChartPanel', () => {
29+
it('should re-render across changing datasets without throwing (Highcharts 13 setData regression)', () => {
30+
const { rerender } = render(
31+
<DashboardChartPanel options={optionsFor(7, 0)} ariaLabel="Persontrend" />,
32+
)
33+
34+
const transitions = [optionsFor(7, 9), optionsFor(20, 5), optionsFor(5, 3), optionsFor(31, 2)]
35+
transitions.forEach((nextOptions) => {
36+
expect(() =>
37+
rerender(<DashboardChartPanel options={nextOptions} ariaLabel="Persontrend" />),
38+
).not.toThrow()
39+
})
40+
})
41+
42+
it('should not throw when re-rendered with structurally unchanged options', () => {
43+
const points = makePoints(12, 5)
44+
const { rerender } = render(
45+
<DashboardChartPanel
46+
options={createPersonTrendChartOptions(points, visibilityOptions)}
47+
ariaLabel="Persontrend"
48+
/>,
49+
)
50+
51+
expect(() =>
52+
rerender(
53+
<DashboardChartPanel
54+
options={createPersonTrendChartOptions(points, visibilityOptions)}
55+
ariaLabel="Persontrend"
56+
/>,
57+
),
58+
).not.toThrow()
59+
})
60+
})

apps/dolly-frontend/src/main/js/src/pages/adminPages/Dashboard/DashboardPage.tsx

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { useErDollyAdmin } from '@/utils/DollyAdmin'
1212
import { Alert, Box, Loader, VStack } from '@navikt/ds-react'
1313
import Highcharts from 'highcharts'
1414
import HighchartsAccessibility from 'highcharts/modules/accessibility'
15-
import { MONTH_SCOPE_ALL } from './dashboardUtils'
15+
import { CHART_TEXT_COLOR } from '@/pages/adminPages/Dashboard/dashboardChartBase'
1616
import { QUICK_RANGE_OPTIONS, useDashboardData } from './useDashboardData'
1717

1818
const initAccessibilityModule =
@@ -21,6 +21,23 @@ const initAccessibilityModule =
2121
: (HighchartsAccessibility as { default?: (chartInstance: typeof Highcharts) => void }).default
2222
initAccessibilityModule?.(Highcharts)
2323

24+
Highcharts.setOptions({
25+
xAxis: {
26+
labels: { style: { color: CHART_TEXT_COLOR, fontSize: '12px' } },
27+
title: { style: { color: CHART_TEXT_COLOR } },
28+
},
29+
yAxis: {
30+
labels: { style: { color: CHART_TEXT_COLOR, fontSize: '12px' } },
31+
title: { style: { color: CHART_TEXT_COLOR } },
32+
},
33+
legend: {
34+
itemStyle: { color: CHART_TEXT_COLOR },
35+
itemHoverStyle: { color: CHART_TEXT_COLOR },
36+
},
37+
title: { style: { color: CHART_TEXT_COLOR } },
38+
subtitle: { style: { color: CHART_TEXT_COLOR } },
39+
})
40+
2441
export default () => {
2542
const isAdmin = useErDollyAdmin()
2643
const d = useDashboardData()
@@ -115,10 +132,10 @@ export default () => {
115132
<MonthlyTeamDistributionSection
116133
yearOptions={d.teamYearOptions}
117134
selectedYear={d.selectedTeamYear}
118-
monthOptions={d.teamMonthOptions}
119135
onSelectedYearChange={d.onSelectedTeamYearChange}
120-
selectedInterval={d.selectedTeamInterval}
136+
monthOptions={d.teamMonthOptions}
121137
onSelectedIntervalChange={d.onSelectedTeamIntervalChange}
138+
selectedInterval={d.selectedTeamInterval}
122139
selectedMonthlyPoint={d.selectedMonthlyPoint}
123140
teamDistribution={d.teamDistribution}
124141
monthlyDistributionChartOptions={d.monthlyDistributionChartOptions}
@@ -139,21 +156,20 @@ export default () => {
139156
secondaryTotalLabel="Antall organisasjoner totalt"
140157
yearOptions={d.organisasjonYearOptions}
141158
selectedYear={d.selectedOrganisasjonYear}
142-
monthOptions={d.organisasjonMonthOptions}
143159
onSelectedYearChange={d.onSelectedOrganisasjonYearChange}
144-
selectedInterval={d.selectedOrganisasjonInterval}
160+
monthOptions={d.organisasjonMonthOptions}
145161
onSelectedIntervalChange={d.onSelectedOrganisasjonIntervalChange}
162+
selectedInterval={d.selectedOrganisasjonInterval}
146163
selectedMonthlyPoint={d.selectedOrganisasjonPoint}
147164
teamDistribution={d.organisasjonDistribution}
148165
monthlyDistributionChartOptions={d.organisasjonDistributionChartOptions}
149166
/>
150167

151168
<MonthlyTeamTrendSection
152169
title="Dolly-team statistikk"
153-
showScopeToggle={false}
154170
filteredMonthlyTeamPointsLength={d.filteredDollyTeamsPointsLength}
155-
monthScope={MONTH_SCOPE_ALL}
156-
onMonthScopeChange={() => undefined}
171+
monthScope={d.dollyTeamsMonthScope}
172+
onMonthScopeChange={d.onDollyTeamsMonthScopeChange}
157173
monthlyTrendChartOptions={d.dollyTeamsMonthlyTrendChartOptions}
158174
/>
159175

@@ -164,10 +180,10 @@ export default () => {
164180
secondaryTotalLabel="Antall teams totalt"
165181
yearOptions={d.dollyTeamsYearOptions}
166182
selectedYear={d.selectedDollyTeamsYear}
167-
monthOptions={d.dollyTeamsMonthOptions}
168183
onSelectedYearChange={d.onSelectedDollyTeamsYearChange}
169-
selectedInterval={d.selectedDollyTeamsInterval}
184+
monthOptions={d.dollyTeamsMonthOptions}
170185
onSelectedIntervalChange={d.onSelectedDollyTeamsIntervalChange}
186+
selectedInterval={d.selectedDollyTeamsInterval}
171187
selectedMonthlyPoint={d.selectedDollyTeamsPoint}
172188
teamDistribution={d.dollyTeamsDistribution}
173189
monthlyDistributionChartOptions={d.dollyTeamsDistributionChartOptions}

apps/dolly-frontend/src/main/js/src/pages/adminPages/Dashboard/dashboardChartBase.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { type Options } from 'highcharts'
22

3+
export const CHART_TEXT_COLOR = '#23262A'
4+
35
export const CHART_COLORS = [
46
'var(--ax-accent-700)',
57
'var(--ax-success-700)',
@@ -75,7 +77,7 @@ export const TOOLTIP_OPTIONS: Options['tooltip'] = {
7577
padding: 12,
7678
shadow: false,
7779
style: {
78-
color: '#23262A',
80+
color: CHART_TEXT_COLOR,
7981
fontSize: '13px',
8082
},
8183
outside: true,

apps/dolly-frontend/src/main/js/src/pages/adminPages/Dashboard/dashboardDayPersonSections.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ export const PersonAnalysisSection = ({
187187
<TextField
188188
label="Fra dato"
189189
type="date"
190+
max={tilDatoMax}
190191
value={fraDato}
191192
onChange={(event) => onFraDatoChange(event.target.value)}
192193
/>

apps/dolly-frontend/src/main/js/src/pages/adminPages/Dashboard/dashboardMonthlySections.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,10 @@ export const MonthlyTeamDistributionSection = ({
6767
secondaryTotalLabel = 'Antall teams totalt',
6868
yearOptions,
6969
selectedYear,
70-
monthOptions,
7170
onSelectedYearChange,
72-
selectedInterval,
71+
monthOptions,
7372
onSelectedIntervalChange,
73+
selectedInterval,
7474
selectedMonthlyPoint,
7575
teamDistribution,
7676
monthlyDistributionChartOptions,
@@ -81,10 +81,10 @@ export const MonthlyTeamDistributionSection = ({
8181
secondaryTotalLabel?: string
8282
yearOptions: string[]
8383
selectedYear: string
84-
monthOptions: { value: string; label: string }[]
8584
onSelectedYearChange: (value: string) => void
86-
selectedInterval: string
85+
monthOptions: { value: string; label: string }[]
8786
onSelectedIntervalChange: (value: string) => void
87+
selectedInterval: string
8888
selectedMonthlyPoint: {
8989
interval: string
9090
intervalVisning: string

apps/dolly-frontend/src/main/js/src/pages/adminPages/Dashboard/dashboardMonthlyTeamChartOptions.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,21 @@ import { type Options } from 'highcharts'
22
import { BAR_COLUMN_PLOT_OPTIONS, TOOLTIP_OPTIONS, withBaseChart } from './dashboardChartBase'
33
import { TeamDistributionPoint } from './dashboardUtils'
44

5+
const COMPACT_THRESHOLD = 10
56
const BAR_HEIGHT_PER_CATEGORY = 28
7+
const BAR_HEIGHT_PER_CATEGORY_COMPACT = 20
68
const BAR_CHART_VERTICAL_PADDING = 80
79
const BAR_CHART_MIN_HEIGHT = 320
810

911
export const createMonthlyTeamDistributionChartOptions = (
1012
teamDistributionData: TeamDistributionPoint[],
1113
seriesName = 'Unike brukere',
1214
): Options => {
15+
const isCompact = teamDistributionData.length >= COMPACT_THRESHOLD
16+
const heightPerCategory = isCompact ? BAR_HEIGHT_PER_CATEGORY_COMPACT : BAR_HEIGHT_PER_CATEGORY
1317
const chartHeight = Math.max(
1418
BAR_CHART_MIN_HEIGHT,
15-
teamDistributionData.length * BAR_HEIGHT_PER_CATEGORY + BAR_CHART_VERTICAL_PADDING,
19+
teamDistributionData.length * heightPerCategory + BAR_CHART_VERTICAL_PADDING,
1620
)
1721
return {
1822
...withBaseChart('Stolpediagram med fordeling per team i valgt måned.', {
@@ -30,7 +34,11 @@ export const createMonthlyTeamDistributionChartOptions = (
3034
},
3135
legend: { enabled: false },
3236
tooltip: { ...TOOLTIP_OPTIONS, pointFormat: '<b>{point.y}</b>' },
33-
plotOptions: { bar: BAR_COLUMN_PLOT_OPTIONS },
37+
plotOptions: {
38+
bar: isCompact
39+
? { ...BAR_COLUMN_PLOT_OPTIONS, pointPadding: 0.05, groupPadding: 0.07 }
40+
: BAR_COLUMN_PLOT_OPTIONS,
41+
},
3442
series: [
3543
{
3644
type: 'bar',

apps/dolly-frontend/src/main/js/src/pages/adminPages/Dashboard/dashboardPreviousDayChartOptions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ export const createPreviousDayErrorBreakdownChartOptions = (
9696
series: [
9797
{
9898
type: 'pie',
99+
borderColor: 'white',
99100
name: 'Feil',
100101
data: [
101102
{

apps/dolly-frontend/src/main/js/src/pages/adminPages/Dashboard/dashboardSharedComponents.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import Highcharts, { type Options } from 'highcharts'
22
import { Alert, Box, Button, Heading, HGrid, HStack, Label, VStack } from '@navikt/ds-react'
33
import { HighchartsReact } from 'highcharts-react-official'
4-
import { type ReactNode, useEffect, useRef } from 'react'
4+
import { type ReactNode, useEffect, useMemo, useRef } from 'react'
55

66
export type DashboardSelectOption = {
77
value: string
@@ -99,6 +99,9 @@ export const DashboardChartPanel = ({
9999
? chartHeightOption
100100
: '320px'
101101

102+
const optionsSignature = JSON.stringify(options)
103+
const stableOptions = useMemo(() => options, [optionsSignature])
104+
102105
useEffect(() => {
103106
const chart = chartRef.current?.chart
104107
if (!chart) return
@@ -115,7 +118,8 @@ export const DashboardChartPanel = ({
115118
<HighchartsReact
116119
ref={chartRef}
117120
highcharts={Highcharts}
118-
options={options}
121+
options={stableOptions}
122+
immutable
119123
containerProps={{ style: { width: '100%', height: containerHeight } }}
120124
/>
121125
</Box>

apps/dolly-frontend/src/main/js/src/pages/adminPages/Dashboard/dashboardTrendChartOptions.ts

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -51,24 +51,6 @@ export const createPersonTrendChartOptions = (
5151
itemMarginBottom: 4,
5252
},
5353
series: [
54-
{ type: 'line', name: 'Nye', data: personTrendData.map((point) => point.nye) },
55-
{
56-
type: 'line',
57-
name: 'Gjenopprettede',
58-
data: personTrendData.map((point) => point.gjenopprettede),
59-
},
60-
{
61-
type: 'line',
62-
name: 'PDL-feil',
63-
data: personTrendData.map((point) => point.pdlFeil),
64-
color: ERROR_PRIMARY_COLOR,
65-
},
66-
{
67-
type: 'line',
68-
name: 'Andre feil',
69-
data: personTrendData.map((point) => point.andreFeil),
70-
color: ERROR_SECONDARY_COLOR,
71-
},
7254
{
7355
type: 'line',
7456
name: 'Personer totalt',
@@ -79,6 +61,12 @@ export const createPersonTrendChartOptions = (
7961
hide: () => visibilityOptions?.onPersonerTotaltVisibilityChange?.(false),
8062
},
8163
},
64+
{ type: 'line', name: 'Nye', data: personTrendData.map((point) => point.nye) },
65+
{
66+
type: 'line',
67+
name: 'Gjenopprettede',
68+
data: personTrendData.map((point) => point.gjenopprettede),
69+
},
8270
{
8371
type: 'line',
8472
name: 'Feil totalt',
@@ -89,6 +77,18 @@ export const createPersonTrendChartOptions = (
8977
hide: () => visibilityOptions?.onFeilTotaltVisibilityChange?.(false),
9078
},
9179
},
80+
{
81+
type: 'line',
82+
name: 'PDL-feil',
83+
data: personTrendData.map((point) => point.pdlFeil),
84+
color: ERROR_PRIMARY_COLOR,
85+
},
86+
{
87+
type: 'line',
88+
name: 'Andre feil',
89+
data: personTrendData.map((point) => point.andreFeil),
90+
color: ERROR_SECONDARY_COLOR,
91+
},
9292
],
9393
})
9494

0 commit comments

Comments
 (0)