فهرست منبع

Use average data correctly on app metric graphs (#3387)

jose-fully-ported 2 سال پیش
والد
کامیت
a330b9ba69

+ 59 - 50
dashboard/src/main/home/app-dashboard/expanded-app/MetricsSection.tsx

@@ -10,7 +10,7 @@ import { ChartTypeWithExtendedConfig, StorageType } from "shared/types";
 import TabSelector from "components/TabSelector";
 import Loading from "components/Loading";
 import SelectRow from "components/form-components/SelectRow";
-import AreaChart from "../../cluster-dashboard/expanded-chart/metrics/AreaChart";
+import AreaChart from "./metrics/AreaChart";
 import { MetricNormalizer } from "../../cluster-dashboard/expanded-chart/metrics/MetricNormalizer";
 import {
   AvailableMetrics,
@@ -54,6 +54,7 @@ const MetricsSection: React.FunctionComponent<PropsType> = ({
   );
   const [dropdownExpanded, setDropdownExpanded] = useState(false);
   const [data, setData] = useState<NormalizedMetricsData[]>([]);
+  const [isAggregated, setIsAggregated] = useState<boolean>(false);
   const [aggregatedData, setAggregatedData] = useState<
     Record<string, NormalizedMetricsData[]>
   >({});
@@ -300,6 +301,7 @@ const MetricsSection: React.FunctionComponent<PropsType> = ({
       setIsLoading((prev) => prev + 1);
       setData([]);
       setAggregatedData({});
+      setIsAggregated(shouldsum)
 
       // Get aggregated metrics
       const allPodsRes = await api.getMetrics(
@@ -325,59 +327,65 @@ const MetricsSection: React.FunctionComponent<PropsType> = ({
       const allPodsMetrics = allPodsData.flatMap((d) => d.results);
       const allPodsMetricsNormalized = new MetricNormalizer(
         [{ results: allPodsMetrics }],
-        selectedMetric as AvailableMetrics
+        selectedMetric as AvailableMetrics,
       );
-      setAggregatedData(allPodsMetricsNormalized.getAggregatedData());
-      //
+      const allPodsAggregatedData = allPodsMetricsNormalized.getAggregatedData()
+      let data: NormalizedMetricsData[] = []
+      if (shouldsum) {
+        setData(allPodsAggregatedData["avg"])
+        delete allPodsAggregatedData["avg"]
+      }
+      setAggregatedData(allPodsAggregatedData);
 
-      const res = await api.getMetrics(
-        "<token>",
-        {
-          metric: selectedMetric,
-          shouldsum: shouldsum,
-          kind: selectedController?.kind,
-          name: selectedController?.metadata.name,
-          namespace: namespace,
-          startrange: start,
-          endrange: end,
-          resolution: resolutions[selectedRange],
-          pods: podNames,
-        },
-        {
-          id: currentProject.id,
-          cluster_id: currentCluster.id,
-        }
-      );
+      if (!shouldsum) {
+        const res = await api.getMetrics(
+          "<token>",
+          {
+            metric: selectedMetric,
+            shouldsum: shouldsum,
+            kind: selectedController?.kind,
+            name: selectedController?.metadata.name,
+            namespace: namespace,
+            startrange: start,
+            endrange: end,
+            resolution: resolutions[selectedRange],
+            pods: podNames,
+          },
+          {
+            id: currentProject.id,
+            cluster_id: currentCluster.id,
+          }
+        );
 
-      setHpaData([]);
-      const isHpaEnabled = currentChart?.config?.autoscaling?.enabled;
-      if (shouldsum && isHpaEnabled) {
-        if (selectedMetric === "cpu") {
-          await getAutoscalingThreshold(
-            "cpu_hpa_threshold",
-            shouldsum,
-            namespace,
-            start,
-            end
-          );
-        } else if (selectedMetric === "memory") {
-          await getAutoscalingThreshold(
-            "memory_hpa_threshold",
-            shouldsum,
-            namespace,
-            start,
-            end
-          );
+        setHpaData([]);
+        const isHpaEnabled = currentChart?.config?.autoscaling?.enabled;
+        if (shouldsum && isHpaEnabled) {
+          if (selectedMetric === "cpu") {
+            await getAutoscalingThreshold(
+              "cpu_hpa_threshold",
+              shouldsum,
+              namespace,
+              start,
+              end
+            );
+          } else if (selectedMetric === "memory") {
+            await getAutoscalingThreshold(
+              "memory_hpa_threshold",
+              shouldsum,
+              namespace,
+              start,
+              end
+            );
+          }
         }
-      }
 
-      const metrics = new MetricNormalizer(
-        res.data,
-        selectedMetric as AvailableMetrics
-      );
-
-      // transform the metrics to expected form
-      setData(metrics.getParsedData());
+        const metrics = new MetricNormalizer(
+          res.data,
+          selectedMetric as AvailableMetrics
+        );
+        // transform the metrics to expected form
+        setData(metrics.getParsedData());
+      }
     } catch (error) {
       setCurrentError(JSON.stringify(error));
     } finally {
@@ -540,6 +548,7 @@ const MetricsSection: React.FunctionComponent<PropsType> = ({
               <AreaChart
                 dataKey={selectedMetricLabel}
                 aggregatedData={aggregatedData}
+                isAggregated={isAggregated}
                 data={data}
                 hpaData={hpaData}
                 hpaEnabled={
@@ -553,7 +562,7 @@ const MetricsSection: React.FunctionComponent<PropsType> = ({
             )}
           </ParentSize>
           <RowWrapper>
-            <AggregatedDataLegend data={data} />
+            <AggregatedDataLegend data={data} hideAvg={isAggregated} />
           </RowWrapper>
         </>
       )}

+ 506 - 0
dashboard/src/main/home/app-dashboard/expanded-app/metrics/AreaChart.tsx

@@ -0,0 +1,506 @@
+import _ from "lodash";
+import React, { useCallback, useMemo, useRef } from "react";
+import chroma from "chroma-js";
+import * as stats from "simple-statistics";
+import styled from "styled-components";
+import { AreaClosed, Bar, Line, LinePath } from "@visx/shape";
+import { curveMonotoneX } from "@visx/curve";
+import { scaleLinear, scaleTime } from "@visx/scale";
+import { AxisBottom, AxisLeft } from "@visx/axis";
+
+import { defaultStyles, TooltipWithBounds, useTooltip } from "@visx/tooltip";
+
+import { GridColumns, GridRows } from "@visx/grid";
+
+import { localPoint } from "@visx/event";
+import { LinearGradient } from "@visx/gradient";
+import { bisector, extent, max } from "d3-array";
+import { timeFormat } from "d3-time-format";
+import { NormalizedMetricsData } from "../../../cluster-dashboard/expanded-chart/metrics/types";
+import { AggregatedDataColors } from "../../../cluster-dashboard/expanded-chart/metrics/utils";
+import { pickColor } from "./utils";
+
+var globalData: NormalizedMetricsData[];
+
+export const background = "#3b697800";
+export const background2 = "#20405100";
+export const accentColor = "#949eff";
+export const accentColorDark = "#949eff";
+
+// util
+const formatDate = timeFormat("%H:%M:%S %b %d, '%y");
+
+const hourFormat = timeFormat("%H:%M");
+const dayFormat = timeFormat("%b %d");
+
+// map resolutions to formats
+const formats: { [range: string]: (date: Date) => string } = {
+    "1H": hourFormat,
+    "6H": hourFormat,
+    "1D": hourFormat,
+    "1M": dayFormat,
+};
+
+// accessors
+const getDate = (d: NormalizedMetricsData) => new Date(d.date * 1000);
+const getValue = (d: NormalizedMetricsData) =>
+    d?.value && Number(d.value?.toFixed(4));
+
+const bisectDate = bisector<NormalizedMetricsData, Date>(
+    (d) => new Date(d.date * 1000)
+).left;
+
+export type AreaProps = {
+    data: NormalizedMetricsData[];
+    aggregatedData?: Record<string, NormalizedMetricsData[]>;
+    dataKey: string;
+    isAggregated?: boolean;
+    hpaEnabled?: boolean;
+    hpaData?: NormalizedMetricsData[];
+    resolution: string;
+    width: number;
+    height: number;
+    margin?: { top: number; right: number; bottom: number; left: number };
+};
+
+const AreaChart: React.FunctionComponent<AreaProps> = ({
+    data,
+    aggregatedData = {},
+    dataKey,
+    isAggregated = false,
+    hpaEnabled = false,
+    hpaData = [],
+    resolution,
+    width,
+    height,
+    margin = { top: 0, right: 0, bottom: 0, left: 0 },
+}) => {
+    globalData = data;
+
+    const {
+        showTooltip,
+        hideTooltip,
+        tooltipData,
+        tooltipTop,
+        tooltipLeft,
+    } = useTooltip<{
+        data: NormalizedMetricsData;
+        tooltipHpaData: NormalizedMetricsData;
+        aggregatedData?: Record<string, NormalizedMetricsData>;
+    }>();
+
+    const svgContainer = useRef();
+    // bounds
+    const innerWidth = width - margin.left - margin.right - 40;
+    const innerHeight = height - margin.top - margin.bottom - 20;
+    const isHpaEnabled = hpaEnabled && !!hpaData.length;
+
+    // scales
+    const dateScale = useMemo(
+        () =>
+            scaleTime({
+                range: [margin.left, innerWidth + margin.left],
+                domain: extent(
+                    [...globalData, ...(isHpaEnabled ? hpaData : [])],
+                    getDate
+                ) as [Date, Date],
+            }),
+        [margin.left, width, height, data, hpaData, isHpaEnabled]
+    );
+    const valueScale = useMemo(
+        () =>
+            scaleLinear({
+                range: [innerHeight + margin.top, margin.top],
+                domain: [
+                    0,
+                    1.25 *
+                    max(
+                        [
+                            ...globalData,
+                            ...Object.values(aggregatedData).flat(),
+                            ...(isHpaEnabled ? hpaData : []),
+                        ],
+                        getValue
+                    ),
+                ],
+                nice: true,
+            }),
+        [margin.top, width, height, data, hpaData, isHpaEnabled]
+    );
+
+    const getAggregatedDataTooltip = (x0: Date) => {
+        let aggregatedTooltipData: Record<string, NormalizedMetricsData> = {};
+        for (let [key, values] of Object.entries(aggregatedData)) {
+            const index = bisectDate(values, x0, 1);
+            const d0 = values[index - 1];
+            const d1 = values[index];
+            let d = d0;
+
+            if (d1 && getDate(d1)) {
+                d =
+                    x0.valueOf() - getDate(d0).valueOf() >
+                        getDate(d1).valueOf() - x0.valueOf()
+                        ? d1
+                        : d0;
+            }
+
+            aggregatedTooltipData[key] = d;
+        }
+
+        return aggregatedTooltipData;
+    };
+
+    // tooltip handler
+    const handleTooltip = useCallback(
+        (
+            event: React.TouchEvent<SVGRectElement> | React.MouseEvent<SVGRectElement>
+        ) => {
+            const isHpaEnabled = hpaEnabled && !!hpaData.length;
+
+            const { x } = localPoint(event) || { x: 0 };
+            const x0 = dateScale.invert(x);
+
+            const index = bisectDate(globalData, x0, 1);
+            const d0 = globalData[index - 1];
+            const d1 = globalData[index];
+            let d = d0;
+
+            if (d1 && getDate(d1)) {
+                d =
+                    x0.valueOf() - getDate(d0).valueOf() >
+                        getDate(d1).valueOf() - x0.valueOf()
+                        ? d1
+                        : d0;
+            }
+
+            const hpaIndex = bisectDate(hpaData, x0, 1);
+            // Get new index without min value to be sure that data exists for HPA
+            const hpaIndex2 = bisectDate(hpaData, x0);
+
+            if (!isHpaEnabled || hpaIndex !== hpaIndex2) {
+                showTooltip({
+                    tooltipData: {
+                        data: d,
+                        tooltipHpaData: undefined,
+                        aggregatedData: getAggregatedDataTooltip(x0),
+                    },
+                    tooltipLeft: x || 0,
+                    tooltipTop: valueScale(getValue(d)) || 0,
+                });
+                return;
+            }
+
+            const tooltipHpaData0 = hpaData[hpaIndex - 1];
+            const tooltipHpaData1 = hpaData[hpaIndex];
+            let tooltipHpaData = tooltipHpaData0;
+
+            if (tooltipHpaData1 && getDate(tooltipHpaData1)) {
+                tooltipHpaData =
+                    x0.valueOf() - getDate(tooltipHpaData0).valueOf() >
+                        getDate(tooltipHpaData1).valueOf() - x0.valueOf()
+                        ? tooltipHpaData1
+                        : tooltipHpaData0;
+            }
+
+            const container: SVGSVGElement = svgContainer.current;
+
+            let point = container.createSVGPoint();
+            // @ts-ignore
+            point.x = (event as any)?.clientX || 0;
+            // @ts-ignore
+            point.y = (event as any)?.clientY || 0;
+            point = point?.matrixTransform(container.getScreenCTM().inverse());
+
+            showTooltip({
+                tooltipData: {
+                    data: d,
+                    tooltipHpaData,
+                    aggregatedData: getAggregatedDataTooltip(x0),
+                },
+                tooltipLeft: x || 0,
+                tooltipTop: point.y || 0,
+            });
+        },
+        [
+            showTooltip,
+            valueScale,
+            dateScale,
+            width,
+            height,
+            data,
+            hpaData,
+            svgContainer,
+            hpaEnabled,
+        ]
+    );
+
+    if (width == 0 || height == 0 || width < 10) {
+        return null;
+    }
+    const hpaGraphTooltipGlyphPosition =
+        (hpaEnabled &&
+            tooltipData?.tooltipHpaData &&
+            valueScale(getValue(tooltipData?.tooltipHpaData))) ||
+        null;
+
+    const dataGraphTooltipGlyphPosition =
+        (tooltipData?.data && valueScale(getValue(tooltipData.data))) || 0;
+
+    return (
+        <div>
+            <svg width={width} height={height} ref={svgContainer}>
+                <rect
+                    x={0}
+                    y={0}
+                    width={width}
+                    height={height}
+                    fill="url(#area-background-gradient)"
+                    rx={14}
+                />
+
+                <LinearGradient
+                    id="area-background-gradient"
+                    from={background}
+                    to={background2}
+                />
+                <LinearGradient
+                    id="area-gradient"
+                    from={accentColor}
+                    to={accentColor}
+                    toOpacity={0}
+                />
+                {Object.entries(AggregatedDataColors).map(([dataKey, color]) => (
+                    <LinearGradient
+                        key={dataKey}
+                        id={`area-gradient-${dataKey}`}
+                        from={color}
+                        to={color}
+                        toOpacity={0}
+                    />
+                ))}
+                <GridRows
+                    left={margin.left}
+                    scale={valueScale}
+                    width={innerWidth}
+                    strokeDasharray="1,3"
+                    stroke="white"
+                    strokeOpacity={0.2}
+                    pointerEvents="none"
+                />
+                <GridColumns
+                    top={margin.top}
+                    scale={dateScale}
+                    height={innerHeight}
+                    strokeDasharray="1,3"
+                    stroke="white"
+                    strokeOpacity={0.2}
+                    pointerEvents="none"
+                />
+                <AreaClosed<NormalizedMetricsData>
+                    data={data}
+                    x={(d) => dateScale(getDate(d)) ?? 0}
+                    y={(d) => valueScale(getValue(d)) ?? 0}
+                    height={innerHeight}
+                    yScale={valueScale}
+                    strokeWidth={1}
+                    stroke="url(#area-gradient)"
+                    fill="url(#area-gradient)"
+                    curve={curveMonotoneX}
+                />
+                {Object.entries(aggregatedData).map(([key, data]) => (
+                    <LinePath<NormalizedMetricsData>
+                        key={key}
+                        data={data}
+                        x={(d) => dateScale(getDate(d)) ?? 0}
+                        y={(d) => valueScale(getValue(d)) ?? 0}
+                        height={innerHeight}
+                        strokeWidth={1}
+                        stroke={AggregatedDataColors[key]}
+                        // fill={`url(#area-gradient-${key})`}
+                        curve={curveMonotoneX}
+                    />
+                ))}
+                {isHpaEnabled && (
+                    <LinePath<NormalizedMetricsData>
+                        stroke="#ffffff"
+                        strokeWidth={2}
+                        data={hpaData}
+                        x={(d) => dateScale(getDate(d)) ?? 0}
+                        y={(d) => valueScale(getValue(d)) ?? 0}
+                        strokeDasharray="6,4"
+                        strokeOpacity={1}
+                        pointerEvents="none"
+                    />
+                )}
+
+                <AxisLeft
+                    left={10}
+                    scale={valueScale}
+                    hideAxisLine={true}
+                    hideTicks={true}
+                    tickLabelProps={() => ({
+                        fill: "white",
+                        fontSize: 11,
+                        textAnchor: "start",
+                        fillOpacity: 0.4,
+                        dy: 0,
+                    })}
+                />
+                <AxisBottom
+                    top={height - 20}
+                    scale={dateScale}
+                    tickFormat={formats[resolution]}
+                    hideAxisLine={true}
+                    hideTicks={true}
+                    tickLabelProps={() => ({
+                        fill: "white",
+                        fontSize: 11,
+                        textAnchor: "middle",
+                        fillOpacity: 0.4,
+                    })}
+                />
+                <Bar
+                    x={margin.left}
+                    y={margin.top}
+                    width={innerWidth}
+                    height={innerHeight}
+                    fill="transparent"
+                    rx={14}
+                    onTouchStart={handleTooltip}
+                    onTouchMove={handleTooltip}
+                    onMouseMove={handleTooltip}
+                    onMouseLeave={() => hideTooltip()}
+                />
+                {tooltipData && (
+                    <g>
+                        <Line
+                            from={{ x: tooltipLeft, y: margin.top }}
+                            to={{ x: tooltipLeft, y: innerHeight + margin.top }}
+                            stroke={accentColorDark}
+                            strokeWidth={2}
+                            pointerEvents="none"
+                            strokeDasharray="5,2"
+                        />
+                        <circle
+                            cx={tooltipLeft}
+                            cy={dataGraphTooltipGlyphPosition + 1}
+                            r={4}
+                            fill="black"
+                            fillOpacity={0.1}
+                            stroke="black"
+                            strokeOpacity={0.1}
+                            strokeWidth={2}
+                            pointerEvents="none"
+                        />
+                        {Object.entries(tooltipData.aggregatedData).map(([key, d]) => (
+                            <circle
+                                key={key}
+                                cx={tooltipLeft}
+                                cy={valueScale(getValue(d)) + 1}
+                                r={4}
+                                fill="black"
+                                fillOpacity={0.1}
+                                stroke="black"
+                                strokeOpacity={0.1}
+                                strokeWidth={2}
+                                pointerEvents="none"
+                            />
+                        ))}
+                        <circle
+                            cx={tooltipLeft}
+                            cy={dataGraphTooltipGlyphPosition}
+                            r={4}
+                            fill={accentColorDark}
+                            stroke="white"
+                            strokeWidth={2}
+                            pointerEvents="none"
+                        />
+                        {Object.entries(tooltipData.aggregatedData).map(([key, d]) => (
+                            <circle
+                                key={key}
+                                cx={tooltipLeft}
+                                cy={valueScale(getValue(d))}
+                                r={4}
+                                fill={accentColorDark}
+                                stroke="white"
+                                strokeWidth={2}
+                                pointerEvents="none"
+                            />
+                        ))}
+                        {isHpaEnabled && hpaGraphTooltipGlyphPosition !== null && (
+                            <>
+                                <circle
+                                    cx={tooltipLeft}
+                                    cy={hpaGraphTooltipGlyphPosition + 1}
+                                    r={4}
+                                    fill="black"
+                                    fillOpacity={0.1}
+                                    stroke="black"
+                                    strokeOpacity={0.1}
+                                    strokeWidth={2}
+                                    pointerEvents="none"
+                                />
+                                <circle
+                                    cx={tooltipLeft}
+                                    cy={hpaGraphTooltipGlyphPosition}
+                                    r={4}
+                                    fill={accentColorDark}
+                                    stroke="white"
+                                    strokeWidth={2}
+                                    pointerEvents="none"
+                                />
+                            </>
+                        )}
+                    </g>
+                )}
+            </svg>
+            {tooltipData && (
+                <div>
+                    <TooltipWithBounds
+                        key={Math.random()}
+                        top={tooltipTop - 12}
+                        left={tooltipLeft + 12}
+                        style={{
+                            ...defaultStyles,
+                            background: "#26272f",
+                            color: "#aaaabb",
+                        }}
+                    >
+                        <TooltipDate>{formatDate(getDate(tooltipData.data))}</TooltipDate>
+                        {isAggregated && (
+                            <TooltipDataRow>
+                                AVG. {dataKey}: {getValue(tooltipData.data)}
+                            </TooltipDataRow>
+                        ) || (
+                            <TooltipDataRow>
+                                Pod {dataKey}: {getValue(tooltipData.data)}
+                            </TooltipDataRow>
+                        )}
+                        {Object.entries(tooltipData.aggregatedData).map(([key, value]) => (
+                            <TooltipDataRow color={AggregatedDataColors[key]} key={key}>
+                                {`${key.toUpperCase()}. ${dataKey}`}: {getValue(value)}
+                            </TooltipDataRow>
+                        ))}
+                        {isHpaEnabled && hpaGraphTooltipGlyphPosition !== null && (
+                            <div style={{ color: "#FFF" }}>
+                                Autoscaling Threshold: {getValue(tooltipData.tooltipHpaData)}
+                            </div>
+                        )}
+                    </TooltipWithBounds>
+                </div>
+            )}
+        </div>
+    );
+};
+
+export default AreaChart;
+
+const TooltipDate = styled.div`
+  text-align: center;
+  margin-bottom: 8px;
+`;
+
+const TooltipDataRow = styled.div<{ color?: string }>`
+  color: ${(props) => props.color ?? accentColor};
+  margin-bottom: 4px;
+`;

+ 57 - 0
dashboard/src/main/home/app-dashboard/expanded-app/metrics/utils.tsx

@@ -0,0 +1,57 @@
+type RGB = {
+    r: number;
+    g: number;
+    b: number;
+};
+
+function componentToHex(c: number) {
+    c = Math.round(c);
+    var hex = c.toString(16);
+    return hex.length == 1 ? "0" + hex : hex;
+}
+
+function hexToRgb(hex: string): RGB | null {
+    const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
+    return result
+        ? {
+            r: parseInt(result[1], 16),
+            g: parseInt(result[2], 16),
+            b: parseInt(result[3], 16),
+        }
+        : null;
+}
+
+function rgbToHex(rgb: RGB) {
+    return (
+        "#" + componentToHex(rgb.r) + componentToHex(rgb.g) + componentToHex(rgb.b)
+    );
+}
+
+export function pickColor(
+    color1: string,
+    color2: string,
+    index: number,
+    total: number
+) {
+    if (total == 1) {
+        return color1;
+    }
+
+    const w1 = index / (total - 1);
+    const w2 = 1 - w1;
+
+    const rgb1 = hexToRgb(color1);
+    const rgb2 = hexToRgb(color2);
+
+    if (rgb1 == null || rgb2 == null) {
+        return "#000000";
+    }
+
+    const rgb: RGB = {
+        r: Math.round(rgb1.r * w1 + rgb2.r * w2),
+        g: Math.round(rgb1.g * w1 + rgb2.g * w2),
+        b: Math.round(rgb1.b * w1 + rgb2.b * w2),
+    };
+
+    return rgbToHex(rgb);
+}

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/dashboard/Metrics.tsx

@@ -358,7 +358,7 @@ const Metrics: React.FC = () => {
       )}
       {data.length > 0 && isLoading === 0 && (
         <>
-          <AggregatedDataLegend data={data} />
+          <AggregatedDataLegend data={data} hideAvg={false} />
           <ParentSize>
             {({ width, height }) => (
               <AreaChart

+ 15 - 6
dashboard/src/main/home/cluster-dashboard/expanded-chart/metrics/AggregatedDataLegend.tsx

@@ -7,18 +7,27 @@ import { AggregatedDataColors } from "./utils";
 
 interface AggregatedDataLegendProps {
   data: NormalizedMetricsData[];
+  hideAvg: boolean;
 }
 
-const AggregatedDataLegend = ({ data }: AggregatedDataLegendProps) => {
+const AggregatedDataLegend = ({ data, hideAvg }: AggregatedDataLegendProps) => {
   const min = stats.min(data.map((d) => d.value));
   const avg = stats.mean(data.map((d) => d.value));
   const max = stats.max(data.map((d) => d.value));
 
-  const aggregatedData = {
-    min,
-    avg,
-    max,
-  };
+  let aggregatedData = {};
+  if (hideAvg) {
+    aggregatedData = {
+      min,
+      max,
+    }
+  } else {
+    aggregatedData = {
+      min,
+      avg,
+      max,
+    }
+  }
 
   return (
     <AggregatedDataContainer>

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/expanded-chart/metrics/MetricsSection.tsx

@@ -527,7 +527,7 @@ const MetricsSection: React.FunctionComponent<PropsType> = ({
       )}
       {data.length > 0 && isLoading === 0 && (
         <>
-          <AggregatedDataLegend data={data} />
+          <AggregatedDataLegend data={data} hideAvg={false} />
           {currentChart?.config?.autoscaling?.enabled &&
             ["cpu", "memory"].includes(selectedMetric) && (
               <CheckboxRow