From 95d36426878bdd832c4ef5366dbfa885b0d1faa0 Mon Sep 17 00:00:00 2001 From: crazyplayy <48414755+crazyplayy@users.noreply.github.com> Date: Fri, 6 Oct 2023 17:23:00 +0100 Subject: [PATCH] Feat: Reduce API calls while running a test (#835) --- .../MetricsHandler/MetricsHandler.js | 179 +++++++++++++ .../Run/RunningStatus/MetricsHandler/index.js | 3 + .../OverviewMetrics/OverviewMetrics.js | 232 ++++------------ .../OverviewMetrics/OverviewMetrics.test.js | 52 +++- .../OverviewMetrics.test.js.snap | 3 +- .../ResponseCodes/ResponseCodes.js | 55 +--- .../ResponseCodes/ResponseCodes.test.js | 7 +- .../__snapshots__/ResponseCodes.test.js.snap | 2 +- .../Run/RunningStatus/RunRunningStatus.js | 247 ++++++++++-------- .../SummaryTable/RunSummaryTable.js | 203 ++++---------- .../SummaryTable/RunSummaryTable.test.js | 48 ++++ .../RunSummaryTable.test.js.snap | 3 + web/frontend/src/lib/utils.js | 8 + 13 files changed, 541 insertions(+), 501 deletions(-) create mode 100644 web/frontend/src/components/Run/RunningStatus/MetricsHandler/MetricsHandler.js create mode 100644 web/frontend/src/components/Run/RunningStatus/MetricsHandler/index.js create mode 100644 web/frontend/src/components/Run/RunningStatus/SummaryTable/RunSummaryTable.test.js create mode 100644 web/frontend/src/components/Run/RunningStatus/SummaryTable/__snapshots__/RunSummaryTable.test.js.snap diff --git a/web/frontend/src/components/Run/RunningStatus/MetricsHandler/MetricsHandler.js b/web/frontend/src/components/Run/RunningStatus/MetricsHandler/MetricsHandler.js new file mode 100644 index 00000000..86000a08 --- /dev/null +++ b/web/frontend/src/components/Run/RunningStatus/MetricsHandler/MetricsHandler.js @@ -0,0 +1,179 @@ +/* eslint-disable max-statements */ +import moment from "moment"; +import { useEffect, useRef, useState } from "react"; + +import { fetchMetrics } from "../../../../lib/api/endpoints/runMetric"; +import { runStatus as runStatusModel } from "../../../../lib/api/models"; +import { calculateErrorRate } from "../../../../lib/utils"; + +const MetricsHandler = ({ run, children }) => { + const [runMetrics, setMetrics] = useState([]); + const [errorCodes, setErrorCodes] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [totals, setTotals] = useState({ + totalLatencyAvg: 0, + totalLatencyP50: 0, + totalLatencyP90: 0, + totalLatencyP99: 0, + totalSuccessCount: 0, + totalErrorsCount: 0, + totalErrorsRate: 0, + totalCount: 0, + totalRpm: 0 + }); + const refreshInterval = 5000; + const timerRef = useRef(null); + + const updateRunMetrics = async (runIdToFetch) => { + setIsLoading(true); + + const metricsRes = await fetchMetrics(runIdToFetch, 0, true); + + const getRpm = (minDatetime, maxDatetime, totalCount) => { + const dateDuration = moment.duration(maxDatetime.diff(minDatetime)); + const diffInSeconds = + dateDuration.asSeconds() > 0 ? dateDuration.asSeconds() : 1; + + return parseFloat((totalCount / diffInSeconds) * 60).toFixed(0); + }; + + const formattedUrlMetrics = metricsRes.map( + ({ + minDatetime, + maxDatetime, + totalCount, + successCount, + label, + latencyAvg, + latencyP50, + latencyP90, + latencyP99, + responses + }) => { + const errorsCount = totalCount - successCount; + const errorRate = calculateErrorRate(successCount, totalCount); + const rpm = getRpm(minDatetime, maxDatetime, totalCount); + + return { + key: label, + errorsCount, + successCount, + errorRate, + label, + rpm, + totalCount, + latencyAvg, + latencyP50, + latencyP90, + latencyP99, + responses + }; + } + ); + + setMetrics(formattedUrlMetrics); + setIsLoading(false); + }; + + const calculateTotals = () => { + const totalLatencyAvg = + runMetrics.length > 0 + ? runMetrics.reduce((total, metric) => total + metric.latencyAvg, 0) + : 0; + const totalLatencyP50 = + runMetrics.length > 0 + ? runMetrics.reduce((total, metric) => total + metric.latencyP50, 0) + : 0; + const totalLatencyP90 = + runMetrics.length > 0 + ? runMetrics.reduce((total, metric) => total + metric.latencyP90, 0) + : 0; + const totalLatencyP99 = + runMetrics.length > 0 + ? runMetrics.reduce((total, metric) => total + metric.latencyP99, 0) + : 0; + const totalSuccessCount = + runMetrics.length > 0 + ? runMetrics.reduce((total, metric) => total + metric.successCount, 0) + : 0; + const totalErrorsCount = + runMetrics.length > 0 + ? runMetrics.reduce((total, metric) => total + metric.errorsCount, 0) + : 0; + const totalCount = + runMetrics.length > 0 + ? runMetrics.reduce((total, metric) => total + metric.totalCount, 0) + : 0; + const totalRpm = + runMetrics.length > 0 + ? runMetrics.reduce( + (total, metric) => total + parseFloat(metric.rpm), + 0 + ) + : 0; + const totalErrorsRate = parseFloat( + calculateErrorRate(totalSuccessCount, totalCount) + ); + + setTotals({ + totalLatencyAvg, + totalLatencyP50, + totalLatencyP90, + totalLatencyP99, + totalSuccessCount, + totalErrorsCount, + totalErrorsRate, + totalCount, + totalRpm + }); + }; + + const groupByErrorCode = () => { + const responseCodeGroups = {}; + + runMetrics.forEach((metric) => { + metric.responses.forEach((response) => { + const { responseCode } = response; + + if (!responseCodeGroups[responseCode]) { + responseCodeGroups[responseCode] = []; + } + + responseCodeGroups[responseCode].push(metric); + }); + }); + + setErrorCodes(responseCodeGroups); + }; + + const startMetricsRefreshTimer = () => { + if (run.runStatus === runStatusModel.RUNNING) { + timerRef.current = setInterval(() => { + updateRunMetrics(run.id); + }, refreshInterval); + } + }; + + const stopMetricsRefreshTimer = () => { + clearInterval(timerRef.current); + }; + + useEffect(() => { + updateRunMetrics(run.id); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [run.id]); + + useEffect(() => { + calculateTotals(); + groupByErrorCode(); + startMetricsRefreshTimer(); + return () => { + stopMetricsRefreshTimer(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [runMetrics, run.runStatus]); + + return children(runMetrics, isLoading, totals, errorCodes); +}; + +export default MetricsHandler; diff --git a/web/frontend/src/components/Run/RunningStatus/MetricsHandler/index.js b/web/frontend/src/components/Run/RunningStatus/MetricsHandler/index.js new file mode 100644 index 00000000..24de5392 --- /dev/null +++ b/web/frontend/src/components/Run/RunningStatus/MetricsHandler/index.js @@ -0,0 +1,3 @@ +import MetricsHandler from "./MetricsHandler"; + +export default MetricsHandler; diff --git a/web/frontend/src/components/Run/RunningStatus/OverviewMetrics/OverviewMetrics.js b/web/frontend/src/components/Run/RunningStatus/OverviewMetrics/OverviewMetrics.js index 172a6802..3de88ee1 100644 --- a/web/frontend/src/components/Run/RunningStatus/OverviewMetrics/OverviewMetrics.js +++ b/web/frontend/src/components/Run/RunningStatus/OverviewMetrics/OverviewMetrics.js @@ -1,191 +1,57 @@ /* eslint-disable max-statements */ import { Descriptions } from "antd"; -import moment from "moment"; -import React, { useEffect, useRef, useState } from "react"; +import React from "react"; -import { fetchMetrics } from "../../../../lib/api/endpoints/runMetric"; -import { runStatus as runStatusModel } from "../../../../lib/api/models"; import { avg } from "../../../../lib/utils"; import MetricCard from "./MetricCard"; -const OverviewMetrics = ({ run }) => { - const [runMetrics, setrunMetrics] = useState([]); - const [totals, setTotals] = useState({ - totalLatencyAvg: 0, - totallatencyP90: 0, - totalSuccessCount: 0, - totalCount: 0, - totalRpm: 0 - }); - const refreshInterval = 3000; - const timerRef = useRef(null); - - const calculateErrorRate = (successCount, totalCount) => { - const errorRate = parseFloat((1 - successCount / totalCount) * 100).toFixed( - 2 - ); - - return errorRate === "NaN" ? 0 : errorRate; - }; - - const updaterunMetrics = async (runIdToFetch) => { - const metricsRes = await fetchMetrics(runIdToFetch, 0, true); - - const getRpm = (minDatetime, maxDatetime, totalCount) => { - const dateDuration = moment.duration(maxDatetime.diff(minDatetime)); - const diffInSeconds = - dateDuration.asSeconds() > 0 ? dateDuration.asSeconds() : 1; - - return parseFloat((totalCount / diffInSeconds) * 60).toFixed(0); - }; - - const formattedrunMetrics = metricsRes.map( - ({ - minDatetime, - maxDatetime, - totalCount, - successCount, - label, - latencyAvg, - latencyP90, - responses - }) => { - const errorsCount = totalCount - successCount; - const errorRate = calculateErrorRate(successCount, totalCount); - const rpm = getRpm(minDatetime, maxDatetime, totalCount); - - return { - key: label, - errorsCount, - successCount, - errorRate, - label, - rpm, - totalCount, - latencyAvg, - latencyP90, - responses - }; - } - ); - - setrunMetrics(formattedrunMetrics); - }; - - const calculateTotals = () => { - const totalLatencyAvg = - runMetrics.length > 0 - ? runMetrics.reduce((total, metric) => total + metric.latencyAvg, 0) - : 0; - const totallatencyP90 = - runMetrics.length > 0 - ? runMetrics.reduce((total, metric) => total + metric.latencyP90, 0) - : 0; - const totalSuccessCount = - runMetrics.length > 0 - ? runMetrics.reduce((total, metric) => total + metric.successCount, 0) - : 0; - const totalCount = - runMetrics.length > 0 - ? runMetrics.reduce((total, metric) => total + metric.totalCount, 0) - : 0; - const totalRpm = - runMetrics.length > 0 - ? runMetrics.reduce( - (total, metric) => total + parseFloat(metric.rpm), - 0 - ) - : 0; - - setTotals({ - totalLatencyAvg, - totallatencyP90, - totalSuccessCount, - totalCount, - totalRpm - }); - }; - - const startMetricsRefreshTimer = () => { - if (run.runStatus === runStatusModel.RUNNING) { - timerRef.current = setInterval(() => { - updaterunMetrics(run.id); - }, refreshInterval); - } - }; - - const stopMetricsRefreshTimer = () => { - clearInterval(timerRef.current); - }; - - useEffect(() => { - updaterunMetrics(run.id); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [run]); - - useEffect(() => { - calculateTotals(); - startMetricsRefreshTimer(); - return () => { - stopMetricsRefreshTimer(); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [runMetrics, run.runStatus]); - - return ( -
- -
- - - - - - - - - - - - -
-
-
- ); -}; +const OverviewMetrics = ({ metrics, totals }) => ( +
+ +
+ + + + + + + + + + + + +
+
+
+); export default OverviewMetrics; diff --git a/web/frontend/src/components/Run/RunningStatus/OverviewMetrics/OverviewMetrics.test.js b/web/frontend/src/components/Run/RunningStatus/OverviewMetrics/OverviewMetrics.test.js index 51a080ff..a04f55c3 100644 --- a/web/frontend/src/components/Run/RunningStatus/OverviewMetrics/OverviewMetrics.test.js +++ b/web/frontend/src/components/Run/RunningStatus/OverviewMetrics/OverviewMetrics.test.js @@ -4,13 +4,57 @@ import React from "react"; import OverviewMetrics from "./OverviewMetrics"; describe("OverviewMetrics", () => { - const mockRun = { - id: "123", - runStatus: "RUNNING" + const mockMetrics = [ + { + key: "metric1", + errorsCount: 5, + successCount: 20, + errorRate: 25, + label: "Label 1", + rpm: 50, + totalCount: 25, + latencyAvg: 50, + latencyP50: 45, + latencyP90: 60, + latencyP99: 75, + responses: [{ responseCode: "200" }, { responseCode: "404" }] + }, + { + key: "metric2", + errorsCount: 3, + successCount: 15, + errorRate: 20, + label: "Label 2", + rpm: 60, + totalCount: 18, + latencyAvg: 55, + latencyP50: 50, + latencyP90: 65, + latencyP99: 80, + responses: [{ responseCode: "200" }, { responseCode: "500" }] + } + ]; + + const mockTotals = { + totalLatencyAvg: 105, + totalLatencyP50: 95, + totalLatencyP90: 125, + totalLatencyP99: 155, + totalSuccessCount: 35, + totalErrorsCount: 8, + totalErrorsRate: 22.86, + totalCount: 43, + totalRpm: 110 }; test("should render OverviewMetrics component", () => { - const rendered = render(); + const rendered = render( + + ); const component = rendered.container; expect(component.outerHTML).toMatchSnapshot(); }); diff --git a/web/frontend/src/components/Run/RunningStatus/OverviewMetrics/__snapshots__/OverviewMetrics.test.js.snap b/web/frontend/src/components/Run/RunningStatus/OverviewMetrics/__snapshots__/OverviewMetrics.test.js.snap index e18b1f30..479ed115 100644 --- a/web/frontend/src/components/Run/RunningStatus/OverviewMetrics/__snapshots__/OverviewMetrics.test.js.snap +++ b/web/frontend/src/components/Run/RunningStatus/OverviewMetrics/__snapshots__/OverviewMetrics.test.js.snap @@ -1,3 +1,4 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`OverviewMetrics should render OverviewMetrics component 1`] = `"
Average Latency
0.00ms
90% Latency
0.00ms
Errors
0%
Average RPM
0req/min
"`; +exports[`OverviewMetrics should render OverviewMetrics component 1`] = `"
Average Latency
52.50ms
90% Latency
62.50ms
Errors
22.86%
Average RPM
55req/min
"`; + diff --git a/web/frontend/src/components/Run/RunningStatus/ResponseCodes/ResponseCodes.js b/web/frontend/src/components/Run/RunningStatus/ResponseCodes/ResponseCodes.js index 141876c1..b7871588 100644 --- a/web/frontend/src/components/Run/RunningStatus/ResponseCodes/ResponseCodes.js +++ b/web/frontend/src/components/Run/RunningStatus/ResponseCodes/ResponseCodes.js @@ -1,63 +1,12 @@ import { Badge, Collapse, Table, Typography } from "antd"; -import React, { useEffect, useState } from "react"; +import React from "react"; -import { fetchMetrics } from "../../../../lib/api/endpoints/runMetric"; import PageSpinner from "../../../layout/PageSpinner"; const { Panel } = Collapse; const { Text } = Typography; -const ResponseCodes = ({ runId }) => { - const [errorCodes, setErrorCodes] = useState([]); - const [isLoading, setIsLoading] = useState(false); - - const updateUrlMetrics = async (runIdToFetch) => { - setIsLoading(true); - - const metricsRes = await fetchMetrics(runIdToFetch, 0, true); - - const filterMetrics = (metricsArray) => { - const responseCodeGroups = {}; - - metricsArray.forEach((metric) => { - metric.responses.forEach((response) => { - const { responseCode } = response; - - if (!responseCodeGroups[responseCode]) { - responseCodeGroups[responseCode] = []; - } - - responseCodeGroups[responseCode].push(metric); - }); - }); - - return responseCodeGroups; - }; - - const formattedUrlMetrics = metricsRes.map( - ({ totalCount, successCount, label, responses }) => { - const errorsCount = totalCount - successCount; - - return { - key: label, - errorsCount, - responses - }; - } - ); - - const errorMetrics = filterMetrics(formattedUrlMetrics); - - setErrorCodes(errorMetrics); - - setIsLoading(false); - }; - - useEffect(() => { - updateUrlMetrics(runId); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [runId]); - +const ResponseCodes = ({ errorCodes, isLoading }) => { const columns = [ { title: "Label", diff --git a/web/frontend/src/components/Run/RunningStatus/ResponseCodes/ResponseCodes.test.js b/web/frontend/src/components/Run/RunningStatus/ResponseCodes/ResponseCodes.test.js index 5353325e..c0a9edfa 100644 --- a/web/frontend/src/components/Run/RunningStatus/ResponseCodes/ResponseCodes.test.js +++ b/web/frontend/src/components/Run/RunningStatus/ResponseCodes/ResponseCodes.test.js @@ -4,10 +4,13 @@ import React from "react"; import ResponseCodes from "./ResponseCodes"; describe("ResponseCodes", () => { - const mockRunId = "123"; + const mockErrorCodes = { + 500: [{ key: "error1", errorsCount: 5, responses: [] }], + 501: [{ key: "error2", errorsCount: 3, responses: [] }] + }; test("should render ResponseCodes component", () => { - const rendered = render(); + const rendered = render(); const component = rendered.container; expect(component.outerHTML).toMatchSnapshot(); }); diff --git a/web/frontend/src/components/Run/RunningStatus/ResponseCodes/__snapshots__/ResponseCodes.test.js.snap b/web/frontend/src/components/Run/RunningStatus/ResponseCodes/__snapshots__/ResponseCodes.test.js.snap index 2cb33b35..c6de8a68 100644 --- a/web/frontend/src/components/Run/RunningStatus/ResponseCodes/__snapshots__/ResponseCodes.test.js.snap +++ b/web/frontend/src/components/Run/RunningStatus/ResponseCodes/__snapshots__/ResponseCodes.test.js.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ResponseCodes should render ResponseCodes component 1`] = `"

Error Codes ( Filter applied: < 100 and > 400 )

"`; +exports[`ResponseCodes should render ResponseCodes component 1`] = `"

Error Codes ( Filter applied: < 100 and > 400 )

500 5
501 3
"`; diff --git a/web/frontend/src/components/Run/RunningStatus/RunRunningStatus.js b/web/frontend/src/components/Run/RunningStatus/RunRunningStatus.js index 2b71ae33..b3662857 100644 --- a/web/frontend/src/components/Run/RunningStatus/RunRunningStatus.js +++ b/web/frontend/src/components/Run/RunningStatus/RunRunningStatus.js @@ -22,6 +22,7 @@ import RunEditableLabelsGroup from "./EditableLabelsGroup"; import EditableTitle from "./EditableTitle"; import RunEndpointCharts from "./EndpointCharts"; import InitialConfiguration from "./InitialConfiguration"; +import MetricsHandler from "./MetricsHandler"; import MoreButtonsMenu from "./MoreButtonsMenu"; import RunNotesInput from "./NotesInput"; import OverviewMetrics from "./OverviewMetrics"; @@ -98,122 +99,144 @@ const RunRunningStatus = ({ run }) => { }; return ( - navigate(-1)} - title={} - subTitle={ - - } - extra={getButtonsByStatus(run.runStatus)} - breadcrumb={{ - routes, - itemRender: (route, params, routesToRender) => ( - - ) - }} - tags={} - > - - - - + {(metrics, isLoading, totals, errorCodes) => ( + navigate(-1)} + title={} + subTitle={ + + } + extra={getButtonsByStatus(run.runStatus)} + breadcrumb={{ + routes, + itemRender: (route, params, routesToRender) => ( + + ) + }} + tags={ + + } + > + + + + - -
- Started at: - {run.startedAt ? run.startedAt.format("L HH:mm:ss") : null} -
-
- Agents: - {run.agentIds.length} -
-
-
- - - {isTimerAvailable ? ( - <> - +
+ + Started at:{" "} + + {run.startedAt ? run.startedAt.format("L HH:mm:ss") : null} +
+
+ Agents: + {run.agentIds.length} +
+
+ + + + {isTimerAvailable ? ( + <> + + + ) : null} + + + - - ) : null} - - - - - - - - - - - - - - - - {renderLabel ? ( - - - - ) : ( - + + - - - )} - - - - - -
-
+ + + + + + + + + + {renderLabel ? ( + + + + ) : ( + + + + )} + + + + + + + + )} + ); }; export default RunRunningStatus; diff --git a/web/frontend/src/components/Run/RunningStatus/SummaryTable/RunSummaryTable.js b/web/frontend/src/components/Run/RunningStatus/SummaryTable/RunSummaryTable.js index cdbe72b6..c9b51379 100644 --- a/web/frontend/src/components/Run/RunningStatus/SummaryTable/RunSummaryTable.js +++ b/web/frontend/src/components/Run/RunningStatus/SummaryTable/RunSummaryTable.js @@ -1,23 +1,16 @@ import { LineChartOutlined } from "@ant-design/icons"; import { Button, Table, Typography } from "antd"; -import moment from "moment"; -import React, { useEffect, useState } from "react"; +import React from "react"; -import { fetchMetrics } from "../../../../lib/api/endpoints/runMetric"; -import { avg } from "../../../../lib/utils"; - -const RunSummaryTable = ({ runId, setLabelToShowGraph, setActiveTabKey }) => { - const [urlMetrics, setUrlMetrics] = useState([]); - const [isLoading, setIsLoading] = useState(false); - - const calculateErrorRate = (successCount, totalCount) => { - const errorRate = parseFloat((1 - successCount / totalCount) * 100).toFixed( - 2 - ); - - return errorRate; - }; +import { avg, calculateErrorRate } from "../../../../lib/utils"; +const RunSummaryTable = ({ + metrics, + isLoading, + totals, + setLabelToShowGraph, + setActiveTabKey +}) => { const columns = [ { title: "Label", @@ -208,148 +201,68 @@ const RunSummaryTable = ({ runId, setLabelToShowGraph, setActiveTabKey }) => { ); }; - const updateUrlMetrics = async (runIdToFetch) => { - setIsLoading(true); - - const metricsRes = await fetchMetrics(runIdToFetch, 0, true); - - const getRpm = (minDatetime, maxDatetime, totalCount) => { - const dateDuration = moment.duration(maxDatetime.diff(minDatetime)); - const diffInSeconds = - dateDuration.asSeconds() > 0 ? dateDuration.asSeconds() : 1; - - return parseFloat((totalCount / diffInSeconds) * 60).toFixed(0); - }; - - const formattedUrlMetrics = metricsRes.map( - ({ - minDatetime, - maxDatetime, - totalCount, - successCount, - label, - latencyAvg, - latencyP50, - latencyP99, - responses - }) => { - const errorsCount = totalCount - successCount; - const errorRate = calculateErrorRate(successCount, totalCount); - const rpm = getRpm(minDatetime, maxDatetime, totalCount); - - return { - key: label, - errorsCount, - successCount, - errorRate, - label, - rpm, - totalCount, - latencyAvg, - latencyP50, - latencyP99, - responses - }; - } - ); - - setUrlMetrics(formattedUrlMetrics); - - setIsLoading(false); - }; - - useEffect(() => { - updateUrlMetrics(runId); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [runId]); - return ( <> { - let total = 0; - let totalLatencyP50 = 0; - let totalLatencyP99 = 0; - let totalSuccessCount = 0; - let totalRpm = 0; - let totalErrorsCount = 0; - let totalErrorRate = 0; - const itemsCount = pageData.length; - - pageData.forEach( - ({ - totalCount, - latencyP50, - latencyP99, - successCount, - rpm, - // errorRate, - errorsCount - }) => { - total += totalCount; - totalLatencyP50 += latencyP50; - totalLatencyP99 += latencyP99; - totalSuccessCount += successCount; - totalRpm += parseFloat(rpm); - totalErrorRate = calculateErrorRate(totalSuccessCount, total); - totalErrorsCount += errorsCount; - } - ); - - return ( - <> - - - - Total - - - - - {avg(totalLatencyP50, itemsCount).toFixed(2)} - - ms - - - - - - {avg(totalLatencyP99, itemsCount).toFixed(2)} - - ms + summary={() => ( + <> + + + + Total + + + + + {avg(totals.totalLatencyP50, metrics.length).toFixed(2)} - - - {total} - - - {totalSuccessCount} - - - {totalErrorsCount} - - - {totalErrorRate} - % - - + ms + + + + - {avg(totalRpm, itemsCount).toFixed(0)} + {avg(totals.totalLatencyP99, metrics.length).toFixed(2)} - req/min - - - - ); - }} + ms + + + + {totals.totalCount} + + + + {totals.totalSuccessCount} + + + + + {totals.totalErrorsCount} + + + + + {parseFloat(totals.totalErrorsRate)} + + % + + + + {avg(totals.totalRpm, metrics.length).toFixed(0)} + + req/min + + + + )} /> ); diff --git a/web/frontend/src/components/Run/RunningStatus/SummaryTable/RunSummaryTable.test.js b/web/frontend/src/components/Run/RunningStatus/SummaryTable/RunSummaryTable.test.js new file mode 100644 index 00000000..261f76cd --- /dev/null +++ b/web/frontend/src/components/Run/RunningStatus/SummaryTable/RunSummaryTable.test.js @@ -0,0 +1,48 @@ +import { render } from "@testing-library/react"; +import React from "react"; + +import RunSummaryTable from "./RunSummaryTable"; + +describe("RunSummaryTable", () => { + const mockMetrics = [ + { + label: "Label 1", + latencyP50: 50, + latencyP99: 60, + totalCount: 100, + successCount: 90, + errorsCount: 10, + errorRate: 10, + rpm: 200, + responses: [ + { responseCode: "200", messages: ["Success message 1"] }, + { responseCode: "400", messages: ["Error message 1"] } + ] + } + ]; + + const mockTotals = { + totalLatencyP50: 55, + totalLatencyP99: 65, + totalCount: 200, + totalSuccessCount: 180, + totalErrorsCount: 20, + totalErrorsRate: 10, + totalRpm: 400 + }; + + test("should render RunSummaryTable component", () => { + const rendered = render( + {}} + setActiveTabKey={() => {}} + /> + ); + + const component = rendered.container; + expect(component.outerHTML).toMatchSnapshot(); + }); +}); diff --git a/web/frontend/src/components/Run/RunningStatus/SummaryTable/__snapshots__/RunSummaryTable.test.js.snap b/web/frontend/src/components/Run/RunningStatus/SummaryTable/__snapshots__/RunSummaryTable.test.js.snap new file mode 100644 index 00000000..1492175f --- /dev/null +++ b/web/frontend/src/components/Run/RunningStatus/SummaryTable/__snapshots__/RunSummaryTable.test.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`RunSummaryTable should render RunSummaryTable component 1`] = `"
Label
Latency (p50)
Latency (p99)
Total
Success
Error count
Error rate
RPM
Label 150 ms60 ms100901010%200 req/min
Total55.00 ms65.00 ms2001802010 %400 req/min
"`; diff --git a/web/frontend/src/lib/utils.js b/web/frontend/src/lib/utils.js index 174f7894..bf09af15 100644 --- a/web/frontend/src/lib/utils.js +++ b/web/frontend/src/lib/utils.js @@ -1,2 +1,10 @@ export const avg = (value, itemsCount) => itemsCount === 0 ? 0 : value / itemsCount; + +export const calculateErrorRate = (successCount, totalCount) => { + const errorRate = parseFloat((1 - successCount / totalCount) * 100).toFixed( + 2 + ); + + return errorRate === "NaN" ? 0 : errorRate; +};