Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## v1.4.1 [TBD]
_Bug Fixes_
- Improve benchmark/detection summary chart width calculations. ([#945](https://github.com/turbot/powerpipe/issues/945))
- Fix filtering by false boolean values in tables. ([#946](https://github.com/turbot/powerpipe/issues/946))

## v1.4.0 [2025-09-22]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import IntegerDisplay from "../../../IntegerDisplay";
import usePrevious from "@powerpipe/hooks/usePrevious";
import { CheckNodeStatus, CheckSummary } from "../common";
import { classNames } from "@powerpipe/utils/styles";
import { ReactNode } from "react";
import { ReactNode, useEffect, useRef, useState } from "react";

type ProgressBarGroupProps = {
children: ReactNode;
Expand All @@ -10,7 +11,7 @@ type ProgressBarGroupProps = {

type ProgressBarProps = {
className?: string;
percent: number;
width: number;
};

type CheckSummaryChartProps = {
Expand All @@ -34,7 +35,7 @@ type ProgressBarGroupTotalProps = {
total: number;
};

const getWidth = (x, y) => {
const getWidth = (x: number, y: number) => {
const percent = (x / (x + y)) * 100;
return percent >= 0.5 ? Math.round(percent) : 1;
};
Expand Down Expand Up @@ -89,7 +90,7 @@ const NonAlertProgressBarGroupTotal = ({
summary,
}: NonAlertProgressBarGroupTotalProps) => {
const nonAlertTotal = summary.ok + summary.info + summary.skip;
let textClassName;
let textClassName: string;
if (nonAlertTotal === 0) {
textClassName = "text-foreground-lightest";
} else if (summary.skip > summary.info && summary.skip > summary.ok) {
Expand All @@ -115,36 +116,62 @@ export const ProgressBarGroup = ({
</div>
);

export const ProgressBar = ({ className, percent }: ProgressBarProps) => {
if (!percent) {
export const ProgressBar = ({ className, width }: ProgressBarProps) => {
if (!width) {
return null;
}

return (
<div
className={classNames("h-3", className)}
aria-valuenow={percent}
aria-valuemin={0}
aria-valuemax={100}
style={{ display: "inline-block", width: `${percent}%` }}
className={classNames("flex-shrink-0 h-3", className)}
style={{ display: "inline-block", width: `${width}px` }}
/>
);
};

export const getCheckSummaryChartPercent = (value, total) => {
if (!value) {
const getSegmentPixelWidth = (
value: number,
divisor: number,
containerWidth: number,
) => {
if (!value || !divisor || !containerWidth) {
return 0;
}
const percentOfTotal = value / total;
const rounded = Math.floor(percentOfTotal * 100);
return Math.max(rounded, 3);

const percent = value / divisor;
return Math.max(Math.round(percent * containerWidth), 1);
};

const CheckSummaryChart = ({
status,
summary,
firstChildSummaries,
}: CheckSummaryChartProps) => {
const [, setVersion] = useState(0);
const alertsContainerRef = useRef<HTMLDivElement>(null);
const nonAlertsContainerRef = useRef<HTMLDivElement>(null);
const previousContainers = usePrevious<{
alerts: HTMLDivElement | null;
nonAlerts: HTMLDivElement | null;
}>({
alerts: alertsContainerRef.current,
nonAlerts: nonAlertsContainerRef.current,
});

useEffect(() => {
if (
(!previousContainers &&
(alertsContainerRef.current || nonAlertsContainerRef.current)) ||
(previousContainers &&
(previousContainers.alerts !== alertsContainerRef.current ||
previousContainers.nonAlerts !== nonAlertsContainerRef.current))
) {
// Trigger a re-render when the container refs change
// This is necessary to recalculate widths based on new container sizes
setVersion((v) => v + 1);
}
}, [previousContainers]);

let maxAlerts = 0;
let maxNonAlerts = 0;
for (const firstChildSummary of firstChildSummaries) {
Expand All @@ -158,70 +185,124 @@ const CheckSummaryChart = ({
maxNonAlerts = currentMaxNonAlerts;
}
}
// const [alarm, error, ok, info, skip] = ensureMinPercentages(name, [
// summary.alarm,
// summary.error,
// summary.ok,
// summary.info,
// summary.skip,
// ]);
let alertsWidth = getWidth(maxAlerts, maxNonAlerts);
let nonAlertsWidth = getWidth(maxNonAlerts, maxAlerts);
if (alertsWidth > nonAlertsWidth) {
alertsWidth -= 2;
} else {
nonAlertsWidth -= 2;
}
const alertsWidth = getWidth(maxAlerts, maxNonAlerts) * 0.9;
const nonAlertsWidth = getWidth(maxNonAlerts, maxAlerts) * 0.9;

const calculateWidths = () => {
if (!alertsContainerRef.current || !nonAlertsContainerRef.current) {
return {
renderDivider: false,
alarm: 0,
error: 0,
ok: 0,
info: 0,
skip: 0,
};
}

const alertsContainerWidth = alertsContainerRef.current?.clientWidth ?? 0;
const nonAlertsContainerWidth =
nonAlertsContainerRef.current?.clientWidth ?? 0;

const rawAlarm = getSegmentPixelWidth(
summary.alarm,
maxAlerts,
alertsContainerWidth,
);
const rawError = getSegmentPixelWidth(
summary.error,
maxAlerts,
alertsContainerWidth,
);
const rawOk = getSegmentPixelWidth(
summary.ok,
maxNonAlerts,
nonAlertsContainerWidth,
);
const rawInfo = getSegmentPixelWidth(
summary.info,
maxNonAlerts,
nonAlertsContainerWidth,
);
const rawSkip = getSegmentPixelWidth(
summary.skip,
maxNonAlerts,
nonAlertsContainerWidth,
);

return {
alertsContainerWidth,
nonAlertsContainerWidth,
renderDivider: true,
alarm: rawAlarm,
error: rawError,
ok: rawOk,
info: rawInfo,
skip: rawSkip,
};
};

const widths = calculateWidths();

return (
<div className="flex items-center" title={getSummaryTitle(summary)}>
<div className="my-auto px-0" style={{ width: `${alertsWidth}%` }}>
<div
ref={alertsContainerRef}
className="my-auto px-0"
style={{ width: `${alertsWidth}%` }}
>
<ProgressBarGroup className="flex-row-reverse">
<ProgressBar
className={classNames(
"border border-alert",
status === "running" ? "summary-chart-alarm-animate" : "bg-alert",
)}
percent={getCheckSummaryChartPercent(summary.alarm, maxAlerts)}
width={widths.alarm}
/>
<ProgressBar
className={classNames(
"border border-alert",
status === "running" ? "summary-chart-error-animate" : null,
"border-alert",
status === "running"
? "border summary-chart-error-animate"
: widths.error < 2
? "border-l"
: "border",
)}
percent={getCheckSummaryChartPercent(summary.error, maxAlerts)}
width={widths.error}
/>
<AlertProgressBarGroupTotal className="mr-2" summary={summary} />
</ProgressBarGroup>
</div>
{widths.renderDivider && (
<div
className={classNames(
"h-6 w-px bg-black-scale-4",
status === "running" ? "subtle-ping" : null,
)}
/>
)}
<div
className={classNames(
"h-6 w-0 border-l border-black-scale-4",
status === "running" ? "subtle-ping" : null,
)}
/>
<div className="my-auto px-0" style={{ width: `${nonAlertsWidth}%` }}>
ref={nonAlertsContainerRef}
className="my-auto px-0"
style={{ width: `${nonAlertsWidth}%` }}
>
<ProgressBarGroup>
<ProgressBar
className={classNames(
"border border-ok",
status === "running" ? "summary-chart-ok-animate" : "bg-ok",
)}
percent={getCheckSummaryChartPercent(summary.ok, maxNonAlerts)}
width={widths.ok}
/>
<ProgressBar
className={classNames(
"border border-info",
status === "running" ? "summary-chart-info-animate" : "bg-info",
)}
percent={getCheckSummaryChartPercent(summary.info, maxNonAlerts)}
width={widths.info}
/>
<ProgressBar
className={classNames(
"border border-skip",
status === "running" ? "summary-chart-skip-animate" : "bg-skip",
)}
percent={getCheckSummaryChartPercent(summary.skip, maxNonAlerts)}
width={widths.skip}
/>
<NonAlertProgressBarGroupTotal className="ml-2" summary={summary} />
</ProgressBarGroup>
Expand Down