Skip to content

Commit

Permalink
[POR-1917] use the correct key path when checking if a service is pri…
Browse files Browse the repository at this point in the history
…vate for displaying the throughput graph (#3934)

Co-authored-by: Feroze Mohideen <[email protected]>
  • Loading branch information
jose-fully-ported and Feroze Mohideen authored Nov 6, 2023
1 parent 97cb1c5 commit b9a3cf0
Show file tree
Hide file tree
Showing 2 changed files with 149 additions and 91 deletions.
Original file line number Diff line number Diff line change
@@ -1,21 +1,38 @@
import React, { useEffect, useMemo, useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { useLocation } from "react-router";
import styled from "styled-components";
import { match } from "ts-pattern";

import CheckboxRow from "components/CheckboxRow";
import Loading from "components/Loading";
import Filter from "components/porter/Filter";
import TabSelector from "components/TabSelector";
import {
type AvailableMetrics,
type GenericMetricResponseResults,
type NormalizedMetricsData,
} from "main/home/cluster-dashboard/expanded-chart/metrics/types";
import { type ClientService } from "lib/porter-apps/services";

import api from "shared/api";

import TabSelector from "components/TabSelector";
import { MetricNormalizer, resolutions, secondsBeforeNow } from "../../expanded-app/metrics/utils";
import { Metric, MetricType, NginxStatusMetric } from "../../expanded-app/metrics/types";
import { match } from "ts-pattern";
import { AvailableMetrics, NormalizedMetricsData } from "main/home/cluster-dashboard/expanded-chart/metrics/types";
import {
GenericFilterOption,
type FilterName,
type GenericFilter,
} from "../../expanded-app/logs/types";
import MetricsChart from "../../expanded-app/metrics/MetricsChart";
import { useQuery } from "@tanstack/react-query";
import Loading from "components/Loading";
import CheckboxRow from "components/CheckboxRow";
import { useLocation } from "react-router";
import Filter from "components/porter/Filter";
import { GenericFilterOption, GenericFilter, FilterName } from "../../expanded-app/logs/types";
import { ClientService } from "lib/porter-apps/services";
import {
type Metric,
type MetricType,
type NginxStatusMetric,
} from "../../expanded-app/metrics/types";
import {
MetricNormalizer,
resolutions,
secondsBeforeNow,
} from "../../expanded-app/metrics/utils";

type PropsType = {
projectId: number;
Expand All @@ -36,12 +53,17 @@ const MetricsSection: React.FunctionComponent<PropsType> = ({
const queryParams = new URLSearchParams(search);
const serviceFromQueryParams = queryParams.get("service");
const [selectedRange, setSelectedRange] = useState("1H");
const [showAutoscalingThresholds, setShowAutoscalingThresholds] = useState(true);
const [showAutoscalingThresholds, setShowAutoscalingThresholds] =
useState(true);

// filter out jobs until we can display metrics on them
const serviceOptions: GenericFilterOption[] = useMemo(() => {
const nonJobServiceNames = services.filter((s) => s.config.type !== "job").map((s) => s.name);
return nonJobServiceNames.map(({ value }) => GenericFilterOption.of(value, value));
const nonJobServiceNames = services
.filter((s) => s.config.type !== "job")
.map((s) => s.name);
return nonJobServiceNames.map(({ value }) =>
GenericFilterOption.of(value, value)
);
}, [services]);

const filters: GenericFilter[] = useMemo(() => {
Expand All @@ -54,17 +76,26 @@ const MetricsSection: React.FunctionComponent<PropsType> = ({
setValue: (value: string) => {
setSelectedFilterValues((prev) => ({ ...prev, service_name: value }));
},
} as GenericFilter,
} satisfies GenericFilter,
];
},[serviceOptions]);

const [selectedFilterValues, setSelectedFilterValues] = useState<Partial<Record<FilterName, string>>>({
service_name: serviceFromQueryParams && Object.keys(services).includes(serviceFromQueryParams) ? serviceFromQueryParams : "",
});
}, [serviceOptions]);

const [selectedFilterValues, setSelectedFilterValues] = useState<
Partial<Record<FilterName, string>>
>({
service_name:
serviceFromQueryParams &&
services.map((s) => s.name.value).includes(serviceFromQueryParams)
? serviceFromQueryParams
: "",
});

useEffect(() => {
if (serviceOptions.length > 0 && selectedFilterValues.service_name === "") {
setSelectedFilterValues((prev) => ({ ...prev, service_name: serviceOptions[0].value }));
setSelectedFilterValues((prev) => ({
...prev,
service_name: serviceOptions[0].value,
}));
}
}, [serviceOptions, selectedFilterValues.service_name]);

Expand All @@ -73,7 +104,9 @@ const MetricsSection: React.FunctionComponent<PropsType> = ({
return ["", "", [], false];
}

const service = services.find(s => s.name.value === selectedFilterValues.service_name);
const service = services.find(
(s) => s.name.value === selectedFilterValues.service_name
);
if (!service) {
return ["", "", [], false];
}
Expand All @@ -91,21 +124,24 @@ const MetricsSection: React.FunctionComponent<PropsType> = ({
}
if (service.config.type === "web") {
metricTypes.push("network");
if (!service.config.private) {
if (!service.config.private?.value) {
metricTypes.push("nginx:status");
}
}
}
}
}

if (isHpaEnabled) {
metricTypes.push("hpa_replicas");
}

return [serviceName, serviceKind, metricTypes, isHpaEnabled]
}, [selectedFilterValues.service_name])

return [serviceName, serviceKind, metricTypes, isHpaEnabled];
}, [selectedFilterValues.service_name]);

const { data: metricsData, isLoading: isMetricsDataLoading, refetch } = useQuery(
const {
data: metricsData,
isLoading: isMetricsDataLoading,
refetch,
} = useQuery(
[
"getMetrics",
projectId,
Expand All @@ -115,7 +151,11 @@ const MetricsSection: React.FunctionComponent<PropsType> = ({
deploymentTargetId,
],
async () => {
if (serviceName === "" || serviceKind === "" || metricTypes.length === 0) {
if (
serviceName === "" ||
serviceKind === "" ||
metricTypes.length === 0
) {
return;
}

Expand All @@ -126,7 +166,7 @@ const MetricsSection: React.FunctionComponent<PropsType> = ({
const start = end - secondsBeforeNow[selectedRange];

for (const metricType of metricTypes) {
var kind = "";
let kind = "";
if (serviceKind === "web") {
kind = "deployment";
} else if (serviceKind === "worker") {
Expand All @@ -135,15 +175,15 @@ const MetricsSection: React.FunctionComponent<PropsType> = ({
kind = "job";
}
if (metricType === "nginx:status") {
kind = "Ingress"
kind = "Ingress";
}

const aggregatedMetricsResponse = await api.appMetrics(
"<token>",
{
metric: metricType,
shouldsum: false,
kind: kind,
kind,
name: serviceName,
deployment_target_id: deploymentTargetId,
startrange: start,
Expand All @@ -158,32 +198,39 @@ const MetricsSection: React.FunctionComponent<PropsType> = ({
);

const metricsNormalizer = new MetricNormalizer(
[{ results: (aggregatedMetricsResponse.data ?? []).flatMap((d: any) => d.results) }],
metricType,
[
{
results: (aggregatedMetricsResponse.data ?? []).flatMap(
(d: { results: GenericMetricResponseResults }) => d.results
),
},
],
metricType
);
if (metricType === "nginx:status") {
const nginxMetric: NginxStatusMetric = {
type: metricType,
label: "Throughput",
areaData: metricsNormalizer.getNginxStatusData(),
}
metrics.push(nginxMetric)
};
metrics.push(nginxMetric);
} else {
const [data, allPodsAggregatedData] = metricsNormalizer.getAggregatedData();
const [data, allPodsAggregatedData] =
metricsNormalizer.getAggregatedData();
const hpaData: NormalizedMetricsData[] = [];

if (isHpaEnabled && ["cpu", "memory"].includes(metricType)) {
let hpaMetricType = "cpu_hpa_threshold"
let hpaMetricType = "cpu_hpa_threshold";
if (metricType === "memory") {
hpaMetricType = "memory_hpa_threshold"
hpaMetricType = "memory_hpa_threshold";
}

const hpaRes = await api.appMetrics(
"<token>",
{
metric: hpaMetricType,
shouldsum: false,
kind: kind,
kind,
name: serviceName,
deployment_target_id: deploymentTargetId,
startrange: start,
Expand All @@ -197,50 +244,53 @@ const MetricsSection: React.FunctionComponent<PropsType> = ({
}
);

const autoscalingMetrics = new MetricNormalizer(hpaRes.data, hpaMetricType as AvailableMetrics);
const autoscalingMetrics = new MetricNormalizer(
hpaRes.data,
hpaMetricType as AvailableMetrics
);
hpaData.push(...autoscalingMetrics.getParsedData());
}

const metric: Metric = match(metricType)
.with("cpu", () => ({
type: metricType,
label: "CPU Utilization (vCPUs)",
data: data,
data,
aggregatedData: allPodsAggregatedData,
hpaData,
}))
.with("memory", () => ({
type: metricType,
label: "RAM Utilization (Mi)",
data: data,
data,
aggregatedData: allPodsAggregatedData,
hpaData,
}))
.with("network", () => ({
type: metricType,
label: "Network Received Bytes (Ki)",
data: data,
data,
aggregatedData: allPodsAggregatedData,
hpaData,
}))
.with("hpa_replicas", () => ({
type: metricType,
label: "Number of replicas",
data: data,
data,
aggregatedData: allPodsAggregatedData,
hpaData,
}))
.with("nginx:errors", () => ({
type: metricType,
label: "5XX Error Percentage",
data: data,
data,
aggregatedData: allPodsAggregatedData,
hpaData,
}))
.exhaustive();
metrics.push(metric);
}
};
}
return metrics;
},
{
Expand All @@ -250,11 +300,11 @@ const MetricsSection: React.FunctionComponent<PropsType> = ({
}
);

const renderMetrics = () => {
const renderMetrics = (): JSX.Element | JSX.Element[] => {
if (metricsData == null || isMetricsDataLoading) {
return <Loading />;
}
return metricsData.map((metric: Metric, i: number) => {
return metricsData.map((metric: Metric, _: number) => {
return (
<MetricsChart
key={metric.type}
Expand All @@ -264,10 +314,13 @@ const MetricsSection: React.FunctionComponent<PropsType> = ({
showAutoscalingLine={showAutoscalingThresholds}
/>
);
})
}
});
};

const renderShowAutoscalingThresholdsCheckbox = (serviceName: string, isHpaEnabled: boolean) => {
const renderShowAutoscalingThresholdsCheckbox = (
serviceName: string,
isHpaEnabled: boolean
): JSX.Element | null => {
if (serviceName === "") {
return null;
}
Expand All @@ -277,12 +330,14 @@ const MetricsSection: React.FunctionComponent<PropsType> = ({
}
return (
<CheckboxRow
toggle={() => setShowAutoscalingThresholds(!showAutoscalingThresholds)}
toggle={() => {
setShowAutoscalingThresholds(!showAutoscalingThresholds);
}}
checked={showAutoscalingThresholds}
label="Show Autoscaling Thresholds"
/>
)
}
);
};

return (
<StyledMetricsSection>
Expand All @@ -292,14 +347,13 @@ const MetricsSection: React.FunctionComponent<PropsType> = ({
filters={filters}
selectedFilterValues={selectedFilterValues}
/>
<Highlight color={"#7d7d81"} onClick={() => refetch()}>
<Highlight color={"#7d7d81"} onClick={async () => await refetch()}>
<i className="material-icons">autorenew</i>
</Highlight>
{renderShowAutoscalingThresholdsCheckbox(serviceName, isHpaEnabled)}
</Flex>
<RangeWrapper>
<Relative>
</Relative>
<Relative></Relative>
<TabSelector
noBuffer={true}
options={[
Expand All @@ -309,7 +363,9 @@ const MetricsSection: React.FunctionComponent<PropsType> = ({
{ value: "1M", label: "1M" },
]}
currentTab={selectedRange}
setCurrentTab={(x: string) => setSelectedRange(x)}
setCurrentTab={(x: string) => {
setSelectedRange(x);
}}
/>
</RangeWrapper>
</MetricsHeader>
Expand Down Expand Up @@ -366,4 +422,4 @@ const Highlight = styled.div`
font-size: 20px;
margin-right: 3px;
}
`;
`;
Loading

0 comments on commit b9a3cf0

Please sign in to comment.