From bdb1aec9b168a3018e19bd18375a2d71642dcf7d Mon Sep 17 00:00:00 2001 From: Andrew Baldwin Date: Tue, 13 Aug 2024 16:50:05 -0400 Subject: [PATCH 1/4] Update echarts to use time axis --- locust/stats.py | 15 ++++--- locust/webui/report.html | 2 +- locust/webui/src/assets/Logo.tsx | 8 ++-- locust/webui/src/components/Form/Select.tsx | 2 + .../src/components/LineChart/LineChart.tsx | 1 - .../components/LineChart/LineChart.types.ts | 2 +- .../components/LineChart/LineChart.utils.ts | 43 ++++++++++++++++--- .../LineChart/tests/LineChart.mocks.ts | 3 -- .../LineChart/tests/LineChartUtils.test.tsx | 7 +-- .../components/SwarmCharts/SwarmCharts.tsx | 2 +- .../webui/src/hooks/tests/useSwarmUi.test.tsx | 7 +-- locust/webui/src/hooks/useSwarmUi.ts | 21 ++++++--- locust/webui/src/lib.tsx | 1 + .../webui/src/test/mocks/statsRequest.mock.ts | 13 +++--- locust/webui/src/utils/date.ts | 5 +-- locust/webui/src/utils/number.ts | 3 ++ 16 files changed, 85 insertions(+), 50 deletions(-) diff --git a/locust/stats.py b/locust/stats.py index 45b06d0f76..16e6df91b3 100644 --- a/locust/stats.py +++ b/locust/stats.py @@ -904,18 +904,21 @@ def sort_stats(stats: dict[Any, S]) -> list[S]: def update_stats_history(runner: Runner) -> None: stats = runner.stats + timestamp = format_utc_timestamp(time.time()) current_response_time_percentiles = { - f"response_time_percentile_{percentile}": stats.total.get_current_response_time_percentile(percentile) or 0 + f"response_time_percentile_{percentile}": [ + timestamp, + stats.total.get_current_response_time_percentile(percentile) or 0, + ] for percentile in PERCENTILES_TO_CHART } r = { **current_response_time_percentiles, - "time": format_utc_timestamp(time.time()), - "current_rps": stats.total.current_rps or 0, - "current_fail_per_sec": stats.total.current_fail_per_sec or 0, - "total_avg_response_time": proper_round(stats.total.avg_response_time, digits=2), - "user_count": runner.user_count or 0, + "current_rps": [timestamp, stats.total.current_rps or 0], + "current_fail_per_sec": [timestamp, stats.total.current_fail_per_sec or 0], + "total_avg_response_time": [timestamp, proper_round(stats.total.avg_response_time, digits=2)], + "user_count": [timestamp, runner.user_count or 0], } stats.history.append(r) diff --git a/locust/webui/report.html b/locust/webui/report.html index bb9bd280ff..1da7b296ca 100644 --- a/locust/webui/report.html +++ b/locust/webui/report.html @@ -3,7 +3,7 @@ {% raw %} - + diff --git a/locust/webui/src/assets/Logo.tsx b/locust/webui/src/assets/Logo.tsx index cda5efb899..a992a51c7a 100644 --- a/locust/webui/src/assets/Logo.tsx +++ b/locust/webui/src/assets/Logo.tsx @@ -16,19 +16,19 @@ export default function Logo({ xmlns='http://www.w3.org/2000/svg' > @@ -23,6 +24,7 @@ export default function Select({ {label} ({ const isChartDataDefined = lines.every(({ key }) => !!charts[key]); if (chart && isChartDataDefined) { chart.setOption({ - xAxis: { data: charts.time }, series: lines.map(({ key, yAxisIndex, ...echartsOptions }, index) => ({ ...echartsOptions, data: charts[key], diff --git a/locust/webui/src/components/LineChart/LineChart.types.ts b/locust/webui/src/components/LineChart/LineChart.types.ts index 5f86db5cc8..6b188822d2 100644 --- a/locust/webui/src/components/LineChart/LineChart.types.ts +++ b/locust/webui/src/components/LineChart/LineChart.types.ts @@ -33,5 +33,5 @@ export interface ILineChartTooltipFormatterParams { axisValue: string; color: string; seriesName: string; - value: number; + value: string | number | string[]; } diff --git a/locust/webui/src/components/LineChart/LineChart.utils.ts b/locust/webui/src/components/LineChart/LineChart.utils.ts index 61fa295809..ea3a03e498 100644 --- a/locust/webui/src/components/LineChart/LineChart.utils.ts +++ b/locust/webui/src/components/LineChart/LineChart.utils.ts @@ -13,8 +13,10 @@ import { ILineChartZoomEvent, ILineChartTooltipFormatterParams, } from 'components/LineChart/LineChart.types'; +import { swarmTemplateArgs } from 'constants/swarm'; import { ICharts } from 'types/ui.types'; -import { formatLocaleString, formatLocaleTime } from 'utils/date'; +import { formatLocaleString } from 'utils/date'; +import { padStart } from 'utils/number'; export const getSeriesData = ({ charts, @@ -56,6 +58,30 @@ const createYAxis = ({ } as YAXisComponentOption; }; +const formatTimeAxis = (value: string) => { + const date = new Date(value); + + return [ + padStart(date.getHours(), 2), + padStart(date.getMinutes(), 2), + padStart(date.getSeconds(), 2), + ].join(':'); +}; + +const renderChartTooltipValue = ({ + chartValueFormatter, + value, +}: { + chartValueFormatter: ILineChart['chartValueFormatter']; + value: string | number | string[]; +}) => { + if (chartValueFormatter) { + return chartValueFormatter(value); + } + + return Array.isArray(value) ? value[1] : value; +}; + export const createOptions = ({ charts, title, @@ -85,7 +111,10 @@ export const createOptions = ({ ${tooltipText}
- ${seriesName}: ${chartValueFormatter ? chartValueFormatter(value) : value} + ${seriesName}: ${renderChartTooltipValue({ + chartValueFormatter, + value, + })} `, '', @@ -97,15 +126,19 @@ export const createOptions = ({ borderWidth: 0, }, xAxis: { - type: 'category', - axisLabel: { formatter: formatLocaleTime }, + type: 'time', + startValue: swarmTemplateArgs.startTime, + axisLabel: { + formatter: formatTimeAxis, + }, data: charts.time, }, - grid: { left: 40, right: splitAxis ? 40 : 10 }, + grid: { left: 50, right: 10 }, yAxis: createYAxis({ splitAxis, yAxisLabels }), series: getSeriesData({ charts, lines, scatterplot }), color: colors, toolbox: { + right: 10, feature: { dataZoom: { show: true, diff --git a/locust/webui/src/components/LineChart/tests/LineChart.mocks.ts b/locust/webui/src/components/LineChart/tests/LineChart.mocks.ts index 29ce4070bf..9317d722eb 100644 --- a/locust/webui/src/components/LineChart/tests/LineChart.mocks.ts +++ b/locust/webui/src/components/LineChart/tests/LineChart.mocks.ts @@ -1,5 +1,4 @@ import { ICharts } from 'types/ui.types'; -import { formatLocaleTime } from 'utils/date'; export type MockChartType = Pick; @@ -20,8 +19,6 @@ export const mockCharts = { ], }; -export const mockFormattedTimeAxis = mockCharts.time.map(formatLocaleTime); - export const mockSeriesData = [ { type: 'line', name: 'RPS', data: mockCharts.currentRps, symbolSize: 4 }, { type: 'line', name: 'Failures/s', data: mockCharts.currentFailPerSec, symbolSize: 4 }, diff --git a/locust/webui/src/components/LineChart/tests/LineChartUtils.test.tsx b/locust/webui/src/components/LineChart/tests/LineChartUtils.test.tsx index cfc490e1b1..3615700978 100644 --- a/locust/webui/src/components/LineChart/tests/LineChartUtils.test.tsx +++ b/locust/webui/src/components/LineChart/tests/LineChartUtils.test.tsx @@ -17,7 +17,7 @@ import { mockSeriesData, mockTooltipParams, } from 'components/LineChart/tests/LineChart.mocks'; -import { formatLocaleString, formatLocaleTime } from 'utils/date'; +import { formatLocaleString } from 'utils/date'; const removeWhitespace = (string: string) => string.replace(/\s+/g, ''); @@ -78,10 +78,7 @@ describe('createOptions', () => { test('xAxis should be formatted as expected', () => { const options = createOptions(createOptionsDefaultProps); - expect(options.xAxis.axisLabel.formatter(mockCharts.time[0])).toBe( - formatLocaleTime(mockCharts.time[0]), - ); - expect(options.xAxis.axisLabel.formatter('undefined')).toBe(''); + expect(options.xAxis.axisLabel.formatter(mockCharts.time[0])).toBe('07:33:02'); }); test('should format the tooltip as expected', () => { diff --git a/locust/webui/src/components/SwarmCharts/SwarmCharts.tsx b/locust/webui/src/components/SwarmCharts/SwarmCharts.tsx index bde2b5c6fd..5dc5db0025 100644 --- a/locust/webui/src/components/SwarmCharts/SwarmCharts.tsx +++ b/locust/webui/src/components/SwarmCharts/SwarmCharts.tsx @@ -14,7 +14,7 @@ const percentilesToChartLines = swarmTemplateArgs.percentilesToChart : []; const percentileColors = [ - '#eeff00', + '#ff9f00', '#9966CC', '#8A2BE2', '#8E4585', diff --git a/locust/webui/src/hooks/tests/useSwarmUi.test.tsx b/locust/webui/src/hooks/tests/useSwarmUi.test.tsx index 902ef95e91..042c173588 100644 --- a/locust/webui/src/hooks/tests/useSwarmUi.test.tsx +++ b/locust/webui/src/hooks/tests/useSwarmUi.test.tsx @@ -37,17 +37,14 @@ describe('useSwarmUi', () => { test('should fetch request stats, ratios, and exceptions and update UI accordingly', async () => { vi.useFakeTimers(); - vi.setSystemTime(mockDate); const { store } = renderWithProvider(); - await act(async () => { - await vi.runAllTimersAsync(); + waitFor(() => { + expect(store.getState().ui).toEqual(statsResponseTransformed); }); - expect(store.getState().ui).toEqual(statsResponseTransformed); - vi.useRealTimers(); }); diff --git a/locust/webui/src/hooks/useSwarmUi.ts b/locust/webui/src/hooks/useSwarmUi.ts index fb4fe8f8f9..a0f37d07ba 100644 --- a/locust/webui/src/hooks/useSwarmUi.ts +++ b/locust/webui/src/hooks/useSwarmUi.ts @@ -45,7 +45,7 @@ export default function useSwarmUi() { totalAvgResponseTime, } = statsData; - const time = new Date().toUTCString(); + const time = new Date().toISOString(); if (shouldAddMarker) { setShouldAddMarker(false); @@ -56,13 +56,20 @@ export default function useSwarmUi() { const totalFailPerSecRounded = roundToDecimalPlaces(totalFailPerSec, 2); const totalFailureRatioRounded = roundToDecimalPlaces(failRatio * 100); + const percentilesWithTime = Object.entries(currentResponseTimePercentiles).reduce( + (percentiles, [key, value]) => ({ + ...percentiles, + [key]: [time, value], + }), + {}, + ); + const newChartEntry = { - ...currentResponseTimePercentiles, - currentRps: totalRpsRounded, - currentFailPerSec: totalFailPerSecRounded, - totalAvgResponseTime: roundToDecimalPlaces(totalAvgResponseTime, 2), - userCount: userCount, - time, + ...percentilesWithTime, + currentRps: [time, totalRpsRounded], + currentFailPerSec: [time, totalFailPerSecRounded], + totalAvgResponseTime: [time, roundToDecimalPlaces(totalAvgResponseTime, 2)], + userCount: [time, userCount], }; setUi({ diff --git a/locust/webui/src/lib.tsx b/locust/webui/src/lib.tsx index 90132e76da..a983409348 100644 --- a/locust/webui/src/lib.tsx +++ b/locust/webui/src/lib.tsx @@ -13,6 +13,7 @@ export { baseTabs } from 'components/Tabs/Tabs.constants'; export { default as useInterval } from 'hooks/useInterval'; export { roundToDecimalPlaces } from 'utils/number'; export { SWARM_STATE } from 'constants/swarm'; +export { default as Select } from 'components/Form/Select'; export type { IRootState } from 'redux/store'; export type { ITab } from 'types/tab.types'; diff --git a/locust/webui/src/test/mocks/statsRequest.mock.ts b/locust/webui/src/test/mocks/statsRequest.mock.ts index 859ec9030f..cdb1bccd7b 100644 --- a/locust/webui/src/test/mocks/statsRequest.mock.ts +++ b/locust/webui/src/test/mocks/statsRequest.mock.ts @@ -147,13 +147,12 @@ export const statsResponseTransformed = { exceptions: exceptionsResponseMock.exceptions, extendedStats: undefined, charts: { - 'responseTimePercentile0.5': [2], - 'responseTimePercentile0.95': [2], - currentRps: [1932.5], - currentFailPerSec: [1932.5], - userCount: [1], - totalAvgResponseTime: [0.41], - time: [mockDate.toUTCString()], + 'responseTimePercentile0.5': [[mockDate.toISOString(), 2]], + 'responseTimePercentile0.95': [[mockDate.toISOString(), 2]], + currentRps: [[mockDate.toISOString(), 1932.5]], + currentFailPerSec: [[mockDate.toISOString(), 1932.5]], + userCount: [[mockDate.toISOString(), 1]], + totalAvgResponseTime: [[mockDate.toISOString(), 0.41]], }, ratios: { perClass: { diff --git a/locust/webui/src/utils/date.ts b/locust/webui/src/utils/date.ts index 97663a0868..e68de1710a 100644 --- a/locust/webui/src/utils/date.ts +++ b/locust/webui/src/utils/date.ts @@ -1,7 +1,4 @@ -const isDate = (timestamp: string) => !isNaN(Date.parse(timestamp)); +const isDate = (timestamp: string) => !isNaN(new Date(timestamp).getTime()); export const formatLocaleString = (utcTimestamp: string) => utcTimestamp && isDate(utcTimestamp) ? new Date(utcTimestamp).toLocaleString() : ''; - -export const formatLocaleTime = (utcTimestamp: string) => - utcTimestamp && isDate(utcTimestamp) ? new Date(utcTimestamp).toLocaleTimeString() : ''; diff --git a/locust/webui/src/utils/number.ts b/locust/webui/src/utils/number.ts index 37d3a0361a..75a6e2b9f5 100644 --- a/locust/webui/src/utils/number.ts +++ b/locust/webui/src/utils/number.ts @@ -2,3 +2,6 @@ export const roundToDecimalPlaces = (n: number, decimalPlaces = 0) => { const factor = Math.pow(10, decimalPlaces); return Math.round(n * factor) / factor; }; + +export const padStart = (n: number, length = 0, padding = '0') => + String(n).padStart(length, padding); From 913be6d0af39766988ddc3dbe16d92415e83ff5c Mon Sep 17 00:00:00 2001 From: Andrew Baldwin Date: Tue, 13 Aug 2024 17:27:07 -0400 Subject: [PATCH 2/4] Update types and tests --- .../src/components/LineChart/LineChart.tsx | 8 +--- .../components/LineChart/LineChart.types.ts | 6 +-- .../components/LineChart/LineChart.utils.ts | 6 +-- .../LineChart/tests/LineChart.mocks.ts | 37 +++++++++++++------ .../LineChart/tests/LineChartUtils.test.tsx | 32 +++++++++++----- .../src/redux/slice/tests/ui.slice.test.ts | 10 +---- locust/webui/src/redux/slice/ui.slice.ts | 3 +- .../webui/src/test/mocks/statsRequest.mock.ts | 4 +- .../webui/src/test/mocks/swarmState.mock.ts | 11 +++--- locust/webui/src/types/swarm.types.ts | 11 +++--- locust/webui/src/types/ui.types.ts | 14 +++---- 11 files changed, 75 insertions(+), 67 deletions(-) diff --git a/locust/webui/src/components/LineChart/LineChart.tsx b/locust/webui/src/components/LineChart/LineChart.tsx index 8165f90723..04d6fafe51 100644 --- a/locust/webui/src/components/LineChart/LineChart.tsx +++ b/locust/webui/src/components/LineChart/LineChart.tsx @@ -2,11 +2,7 @@ import { useEffect, useState, useRef } from 'react'; import { init, dispose, ECharts, connect } from 'echarts'; import { CHART_THEME } from 'components/LineChart/LineChart.constants'; -import { - ILineChartTimeAxis, - ILineChart, - ILineChartMarkers, -} from 'components/LineChart/LineChart.types'; +import { ILineChart, ILineChartMarkers } from 'components/LineChart/LineChart.types'; import { createMarkLine, createOptions, @@ -15,7 +11,7 @@ import { } from 'components/LineChart/LineChart.utils'; import { useSelector } from 'redux/hooks'; -interface IBaseChartType extends ILineChartTimeAxis, ILineChartMarkers {} +interface IBaseChartType extends ILineChartMarkers {} export default function LineChart({ charts, diff --git a/locust/webui/src/components/LineChart/LineChart.types.ts b/locust/webui/src/components/LineChart/LineChart.types.ts index 6b188822d2..c04567181a 100644 --- a/locust/webui/src/components/LineChart/LineChart.types.ts +++ b/locust/webui/src/components/LineChart/LineChart.types.ts @@ -21,10 +21,6 @@ export interface ILineChartZoomEvent { batch?: { start: number; end: number }[]; } -export interface ILineChartTimeAxis { - time: string[]; -} - export interface ILineChartMarkers { markers?: string[]; } @@ -33,5 +29,5 @@ export interface ILineChartTooltipFormatterParams { axisValue: string; color: string; seriesName: string; - value: string | number | string[]; + value: string | number | [string, number]; } diff --git a/locust/webui/src/components/LineChart/LineChart.utils.ts b/locust/webui/src/components/LineChart/LineChart.utils.ts index ea3a03e498..2c92f9ac89 100644 --- a/locust/webui/src/components/LineChart/LineChart.utils.ts +++ b/locust/webui/src/components/LineChart/LineChart.utils.ts @@ -8,7 +8,6 @@ import { import { CHART_THEME } from 'components/LineChart/LineChart.constants'; import { - ILineChartTimeAxis, ILineChart, ILineChartZoomEvent, ILineChartTooltipFormatterParams, @@ -73,7 +72,7 @@ const renderChartTooltipValue = ({ value, }: { chartValueFormatter: ILineChart['chartValueFormatter']; - value: string | number | string[]; + value: ILineChartTooltipFormatterParams['value']; }) => { if (chartValueFormatter) { return chartValueFormatter(value); @@ -82,7 +81,7 @@ const renderChartTooltipValue = ({ return Array.isArray(value) ? value[1] : value; }; -export const createOptions = ({ +export const createOptions = ({ charts, title, lines, @@ -131,7 +130,6 @@ export const createOptions = ({ axisLabel: { formatter: formatTimeAxis, }, - data: charts.time, }, grid: { left: 50, right: 10 }, yAxis: createYAxis({ splitAxis, yAxisLabels }), diff --git a/locust/webui/src/components/LineChart/tests/LineChart.mocks.ts b/locust/webui/src/components/LineChart/tests/LineChart.mocks.ts index 9317d722eb..2f41b0152e 100644 --- a/locust/webui/src/components/LineChart/tests/LineChart.mocks.ts +++ b/locust/webui/src/components/LineChart/tests/LineChart.mocks.ts @@ -1,24 +1,37 @@ import { ICharts } from 'types/ui.types'; -export type MockChartType = Pick; +export type MockChartType = Pick; export const mockChartLines = [ { name: 'RPS', key: 'currentRps' as keyof MockChartType }, { name: 'Failures/s', key: 'currentFailPerSec' as keyof MockChartType }, ]; -export const mockCharts = { - currentRps: [3, 3.1, 3.27, 3.62, 4.19], - currentFailPerSec: [0, 0, 0, 0, 0], - time: [ - 'Tue, 06 Aug 2024 11:33:02 GMT', - 'Tue, 06 Aug 2024 11:33:08 GMT', - 'Tue, 06 Aug 2024 11:33:10 GMT', - 'Tue, 06 Aug 2024 11:33:12 GMT', - 'Tue, 06 Aug 2024 11:33:14 GMT', +export const mockCharts: MockChartType = { + currentRps: [ + ['Tue, 06 Aug 2024 11:33:02 GMT', 3], + ['Tue, 06 Aug 2024 11:33:08 GMT', 3.1], + ['Tue, 06 Aug 2024 11:33:10 GMT', 3.27], + ['Tue, 06 Aug 2024 11:33:12 GMT', 3.62], + ['Tue, 06 Aug 2024 11:33:14 GMT', 4.19], + ], + currentFailPerSec: [ + ['Tue, 06 Aug 2024 11:33:02 GMT', 0], + ['Tue, 06 Aug 2024 11:33:08 GMT', 0], + ['Tue, 06 Aug 2024 11:33:10 GMT', 0], + ['Tue, 06 Aug 2024 11:33:12 GMT', 0], + ['Tue, 06 Aug 2024 11:33:14 GMT', 0], ], }; +export const mockTimestamps = [ + 'Tue, 06 Aug 2024 11:33:02 GMT', + 'Tue, 06 Aug 2024 11:33:08 GMT', + 'Tue, 06 Aug 2024 11:33:10 GMT', + 'Tue, 06 Aug 2024 11:33:12 GMT', + 'Tue, 06 Aug 2024 11:33:14 GMT', +]; + export const mockSeriesData = [ { type: 'line', name: 'RPS', data: mockCharts.currentRps, symbolSize: 4 }, { type: 'line', name: 'Failures/s', data: mockCharts.currentFailPerSec, symbolSize: 4 }, @@ -34,12 +47,12 @@ export const mockTooltipParams = [ axisValue: 'Tue, 06 Aug 2024 11:33:02 GMT', color: '#ff0', seriesName: 'RPS', - value: 1, + value: ['Tue, 06 Aug 2024 11:33:08 GMT', 1] as [string, number], }, { axisValue: 'Tue, 06 Aug 2024 11:33:08 GMT', color: '#0ff', seriesName: 'User', - value: 10, + value: ['Tue, 06 Aug 2024 11:33:12 GMT', 10] as [string, number], }, ]; diff --git a/locust/webui/src/components/LineChart/tests/LineChartUtils.test.tsx b/locust/webui/src/components/LineChart/tests/LineChartUtils.test.tsx index 3615700978..a60291de55 100644 --- a/locust/webui/src/components/LineChart/tests/LineChartUtils.test.tsx +++ b/locust/webui/src/components/LineChart/tests/LineChartUtils.test.tsx @@ -15,8 +15,10 @@ import { MockChartType, mockScatterplotSeriesData, mockSeriesData, + mockTimestamps, mockTooltipParams, } from 'components/LineChart/tests/LineChart.mocks'; +import { swarmStateMock } from 'test/mocks/swarmState.mock'; import { formatLocaleString } from 'utils/date'; const removeWhitespace = (string: string) => string.replace(/\s+/g, ''); @@ -57,7 +59,8 @@ describe('createOptions', () => { const options = createOptions(createOptionsDefaultProps); expect(options.title.text).toBe('Test Chart'); - expect(options.xAxis.data).toEqual(mockCharts.time); + expect(options.xAxis.type).toBe('time'); + expect(options.xAxis.startValue).toBe(swarmStateMock.startTime); expect(options.yAxis).toEqual(defaultYAxis); expect(options.series).toEqual(mockSeriesData); expect(options.color).toEqual(['#fff']); @@ -78,7 +81,15 @@ describe('createOptions', () => { test('xAxis should be formatted as expected', () => { const options = createOptions(createOptionsDefaultProps); - expect(options.xAxis.axisLabel.formatter(mockCharts.time[0])).toBe('07:33:02'); + const formattedValue = options.xAxis.axisLabel.formatter(mockTimestamps[0]); + + expect(formattedValue.split(':').length).toBe(3); + + const [hours, minutes, seconds] = formattedValue.split(':'); + + expect(hours.length).toBe(2); + expect(minutes.length).toBe(2); + expect(seconds.length).toBe(2); }); test('should format the tooltip as expected', () => { @@ -90,7 +101,7 @@ describe('createOptions', () => {
- ${mockTooltipParams[0].seriesName}: ${mockTooltipParams[0].value} + ${mockTooltipParams[0].seriesName}: ${mockTooltipParams[0].value[1]} `), ); @@ -106,11 +117,11 @@ describe('createOptions', () => { ${formatLocaleString(mockTooltipParams[0].axisValue)}
- ${mockTooltipParams[0].seriesName}: ${mockTooltipParams[0].value} + ${mockTooltipParams[0].seriesName}: ${mockTooltipParams[0].value[1]}
- ${mockTooltipParams[1].seriesName}: ${mockTooltipParams[1].value} + ${mockTooltipParams[1].seriesName}: ${mockTooltipParams[1].value[1]} `), ); @@ -175,8 +186,9 @@ describe('createOptions', () => { }); expect(options.title.text).toBe('Test Chart'); - expect(options.xAxis.data).toEqual(mockCharts.time); expect(options.yAxis).toEqual(defaultYAxis); + expect(options.xAxis.type).toBe('time'); + expect(options.xAxis.startValue).toBe(swarmStateMock.startTime); expect(options.series).toEqual(mockScatterplotSeriesData); expect(options.color).toEqual(['#fff']); }); @@ -186,7 +198,7 @@ describe('createMarkLine', () => { test('should create a mark line', () => { const markChartsWithMarkers = { ...mockCharts, - markers: [mockCharts.time[1]], + markers: [mockTimestamps[1]], }; const options = createMarkLine(markChartsWithMarkers, false); @@ -199,7 +211,7 @@ describe('createMarkLine', () => { test('should create multiple mark lines', () => { const markChartsWithMarkers = { ...mockCharts, - markers: [mockCharts.time[1], mockCharts.time[3]], + markers: [mockTimestamps[1], mockTimestamps[3]], }; const options = createMarkLine(markChartsWithMarkers, false); @@ -213,7 +225,7 @@ describe('createMarkLine', () => { test('should format mark line label', () => { const markChartsWithMarkers = { ...mockCharts, - markers: [mockCharts.time[1]], + markers: [mockTimestamps[1]], }; const options = createMarkLine(markChartsWithMarkers, false); @@ -225,7 +237,7 @@ describe('createMarkLine', () => { test('should use dark mode when isDarkMode', () => { const markChartsWithMarkers = { ...mockCharts, - markers: [mockCharts.time[1]], + markers: [mockTimestamps[1]], }; const options = createMarkLine(markChartsWithMarkers, true); diff --git a/locust/webui/src/redux/slice/tests/ui.slice.test.ts b/locust/webui/src/redux/slice/tests/ui.slice.test.ts index 1fbed55c1f..dcc9b86f57 100644 --- a/locust/webui/src/redux/slice/tests/ui.slice.test.ts +++ b/locust/webui/src/redux/slice/tests/ui.slice.test.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from 'vitest'; import uiSlice, { IUiState, UiAction, uiActions } from 'redux/slice/ui.slice'; -import { percentilesToChart } from 'test/mocks/swarmState.mock'; +import { percentilesToChart, swarmStateMock } from 'test/mocks/swarmState.mock'; import { ICharts, ISwarmRatios } from 'types/ui.types'; const responseTimePercentileKey1 = @@ -22,7 +22,6 @@ const initialState = { currentFailPerSec: [], totalAvgResponseTime: [], userCount: [], - time: [], }, ratios: {} as ISwarmRatios, userCount: 0, @@ -49,7 +48,6 @@ describe('uiSlice', () => { [responseTimePercentileKey1]: 0.4, [responseTimePercentileKey2]: 0.2, userCount: 2, - time: '10:10:10', }); const nextState = uiSlice(initialState, action); @@ -60,7 +58,6 @@ describe('uiSlice', () => { expect(charts[responseTimePercentileKey1][0]).toBe(0.4); expect(charts[responseTimePercentileKey2][0]).toBe(0.2); expect(charts.userCount[0]).toBe(2); - expect(charts.time[0]).toBe('10:10:10'); }); test('should continue to extend the corresponding array of charts in UI state', () => { @@ -70,7 +67,6 @@ describe('uiSlice', () => { [responseTimePercentileKey1]: 0.4, [responseTimePercentileKey2]: 0.2, userCount: 2, - time: '10:10:10', }); const updatedState = uiSlice(initialState, action); @@ -83,7 +79,6 @@ describe('uiSlice', () => { expect(charts[responseTimePercentileKey1]).toEqual([0.4, 0.4]); expect(charts[responseTimePercentileKey2]).toEqual([0.2, 0.2]); expect(charts.userCount).toEqual([2, 2]); - expect(charts.time).toEqual(['10:10:10', '10:10:10']); }); test('should update chart markers in UI state', () => { @@ -93,7 +88,6 @@ describe('uiSlice', () => { ...initialState, charts: { ...initialState.charts, - time: ['10:10:10'], }, }, action, @@ -101,7 +95,7 @@ describe('uiSlice', () => { const charts = nextState.charts as ICharts; - expect(charts.markers).toEqual(['10:10:10', '20:20:20']); + expect(charts.markers).toEqual([swarmStateMock.startTime, '20:20:20']); // Add space between runs expect(charts.currentRps[0]).toEqual({ value: null }); diff --git a/locust/webui/src/redux/slice/ui.slice.ts b/locust/webui/src/redux/slice/ui.slice.ts index 96bcaa2966..f8e1ffaeee 100644 --- a/locust/webui/src/redux/slice/ui.slice.ts +++ b/locust/webui/src/redux/slice/ui.slice.ts @@ -54,7 +54,6 @@ const addSpaceToChartsBetweenTests = (charts: ICharts) => { currentFailPerSec: { value: null }, totalAvgResponseTime: { value: null }, userCount: { value: null }, - time: '', }); }; @@ -74,7 +73,7 @@ const uiSlice = createSlice({ ...addSpaceToChartsBetweenTests(state.charts as ICharts), markers: (state.charts as ICharts).markers ? [...((state.charts as ICharts).markers as string[]), payload] - : [(state.charts as ICharts).time[0], payload], + : [swarmTemplateArgs.startTime, payload], }, }; }, diff --git a/locust/webui/src/test/mocks/statsRequest.mock.ts b/locust/webui/src/test/mocks/statsRequest.mock.ts index cdb1bccd7b..0ad4dfb08e 100644 --- a/locust/webui/src/test/mocks/statsRequest.mock.ts +++ b/locust/webui/src/test/mocks/statsRequest.mock.ts @@ -1,3 +1,5 @@ +import { ICharts } from 'types/ui.types'; + export const statsResponseMock = { current_response_time_percentiles: { 'response_time_percentile_0.5': 2, @@ -153,7 +155,7 @@ export const statsResponseTransformed = { currentFailPerSec: [[mockDate.toISOString(), 1932.5]], userCount: [[mockDate.toISOString(), 1]], totalAvgResponseTime: [[mockDate.toISOString(), 0.41]], - }, + } as ICharts, ratios: { perClass: { Example: { diff --git a/locust/webui/src/test/mocks/swarmState.mock.ts b/locust/webui/src/test/mocks/swarmState.mock.ts index 884598d17e..9a409d06b0 100644 --- a/locust/webui/src/test/mocks/swarmState.mock.ts +++ b/locust/webui/src/test/mocks/swarmState.mock.ts @@ -22,7 +22,7 @@ export const swarmStateMock = { showUserclassPicker: false, spawnRate: null, state: 'ready', - startTime: '', + startTime: '10:10:10', percentilesToChart: percentilesToChart, statsHistoryEnabled: false, tasks: '{}', @@ -44,10 +44,9 @@ export const swarmReportMock: IReport = { responseTimeStatistics: [], tasks: {} as ISwarmRatios, charts: { - currentRps: [0], - currentFailPerSec: [0], - totalAvgResponseTime: [0], - userCount: [0], - time: [''], + currentRps: [['', 0]], + currentFailPerSec: [['', 0]], + totalAvgResponseTime: [['', 0]], + userCount: [['', 0]], }, }; diff --git a/locust/webui/src/types/swarm.types.ts b/locust/webui/src/types/swarm.types.ts index 8f39a15313..94eb963936 100644 --- a/locust/webui/src/types/swarm.types.ts +++ b/locust/webui/src/types/swarm.types.ts @@ -19,14 +19,13 @@ export interface IExtraOptions { } export interface IHistory { - currentRps: number; - currentFailPerSec: number; - userCount: number; - time: string; + currentRps: [string, number]; + currentFailPerSec: [string, number]; + userCount: [string, number]; currentResponseTimePercentiles: { - [key: `responseTimePercentile${number}`]: number | null; + [key: `responseTimePercentile${number}`]: [string, number | null]; }; - totalAvgResponseTime: number; + totalAvgResponseTime: [string, number]; } export interface IReport { diff --git a/locust/webui/src/types/ui.types.ts b/locust/webui/src/types/ui.types.ts index 467a95753d..a7a2f4a720 100644 --- a/locust/webui/src/types/ui.types.ts +++ b/locust/webui/src/types/ui.types.ts @@ -1,4 +1,4 @@ -import { ILineChartTimeAxis, ILineChartMarkers } from 'components/LineChart/LineChart.types'; +import { ILineChartMarkers } from 'components/LineChart/LineChart.types'; import { SWARM_STATE } from 'constants/swarm'; export type SwarmState = (typeof SWARM_STATE)[keyof typeof SWARM_STATE]; @@ -54,12 +54,12 @@ export interface NullChartValue { value: null; } -export interface ICharts extends ILineChartTimeAxis, ILineChartMarkers { - currentRps: (number | NullChartValue)[]; - currentFailPerSec: (number | NullChartValue)[]; - [key: `responseTimePercentile${number}`]: (number | null | NullChartValue)[]; - totalAvgResponseTime: (number | NullChartValue)[]; - userCount: (number | NullChartValue)[]; +export interface ICharts extends ILineChartMarkers { + currentRps: ((string | number)[] | NullChartValue)[]; + currentFailPerSec: ([string, number] | NullChartValue)[]; + [key: `responseTimePercentile${number}`]: ([string, number | null] | NullChartValue)[]; + totalAvgResponseTime: ([string, number] | NullChartValue)[]; + userCount: ([string, number] | NullChartValue)[]; } export interface IClassRatio { From 2d595a06a6c27669cc1b0817a2a9c51293a2ff0d Mon Sep 17 00:00:00 2001 From: Andrew Baldwin Date: Tue, 13 Aug 2024 18:09:40 -0400 Subject: [PATCH 3/4] Favicons for auth and report --- locust/webui/auth.html | 2 +- locust/webui/dev.html | 2 +- locust/webui/package.json | 4 +++- locust/webui/report.html | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/locust/webui/auth.html b/locust/webui/auth.html index d5cd7825c1..58fc1cadcb 100644 --- a/locust/webui/auth.html +++ b/locust/webui/auth.html @@ -2,7 +2,7 @@ - + diff --git a/locust/webui/dev.html b/locust/webui/dev.html index 42fa036472..6b201e664c 100644 --- a/locust/webui/dev.html +++ b/locust/webui/dev.html @@ -3,7 +3,7 @@ - + diff --git a/locust/webui/package.json b/locust/webui/package.json index 3759d29c25..953b2b9ec9 100644 --- a/locust/webui/package.json +++ b/locust/webui/package.json @@ -17,10 +17,12 @@ "prepack": "json -f package.json -I -e \"delete this.devDependencies; delete this.dependencies; delete this.scripts\"", "scripts": { "dev": "vite", - "dev:watch": "vite build --watch", + "watch": "npm-run-all --parallel watch:ui watch:report", "build:ui": "vite build", "build:lib": "vite build --config vite.lib.config.ts", "build:report": "vite build --config vite.report.config.ts", + "watch:ui": "vite build --watch", + "watch:report": "vite build --config vite.lib.config.ts --watch", "build": "yarn clean && npm-run-all --parallel build:ui build:report build:lib", "clean": "rimraf dist", "lint": "eslint './src/**/*.{ts,tsx}'", diff --git a/locust/webui/report.html b/locust/webui/report.html index 1da7b296ca..cdaaf56a56 100644 --- a/locust/webui/report.html +++ b/locust/webui/report.html @@ -3,7 +3,7 @@ {% raw %} - + From 001e876d77b9c131d3bb806bff3b61eefc36b0ac Mon Sep 17 00:00:00 2001 From: Andrew Baldwin Date: Wed, 14 Aug 2024 08:07:37 -0400 Subject: [PATCH 4/4] Add padding to markline --- locust/webui/src/components/LineChart/LineChart.utils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locust/webui/src/components/LineChart/LineChart.utils.ts b/locust/webui/src/components/LineChart/LineChart.utils.ts index 2c92f9ac89..2e134e398a 100644 --- a/locust/webui/src/components/LineChart/LineChart.utils.ts +++ b/locust/webui/src/components/LineChart/LineChart.utils.ts @@ -131,7 +131,7 @@ export const createOptions = ({ formatter: formatTimeAxis, }, }, - grid: { left: 50, right: 10 }, + grid: { left: 60, right: 40 }, yAxis: createYAxis({ splitAxis, yAxisLabels }), series: getSeriesData({ charts, lines, scatterplot }), color: colors, @@ -165,6 +165,7 @@ export const createMarkLine = >( symbol: 'none', label: { formatter: (params: DefaultLabelFormatterCallbackParams) => `Run #${params.dataIndex + 1}`, + padding: [0, 0, 8, 0], }, lineStyle: { color: isDarkMode ? CHART_THEME.DARK.axisColor : CHART_THEME.LIGHT.axisColor }, data: (charts.markers || []).map((timeMarker: string) => ({ xAxis: timeMarker })),