feat: Include/exclude muted findings (#8228)

This commit is contained in:
Alejandro Bailo
2025-07-09 16:06:05 +02:00
committed by GitHub
parent a319f80701
commit 8d2f6aa30c
9 changed files with 151 additions and 46 deletions
@@ -1,18 +1,38 @@
"use client";
import { Checkbox } from "@nextui-org/react";
import React from "react";
import { useSearchParams } from "next/navigation";
import { useState } from "react";
import { useUrlFilters } from "@/hooks/use-url-filters";
export const CustomCheckboxMutedFindings = () => {
const { updateFilter } = useUrlFilters();
const searchParams = useSearchParams();
const [excludeMuted, setExcludeMuted] = useState(
searchParams.get("filter[muted]") === "false",
);
const handleMutedChange = (value: boolean) => {
setExcludeMuted(value);
updateFilter("muted", value ? "false" : "true");
};
return (
<Checkbox
classNames={{
label: "text-small",
wrapper: "checkbox-update xl:-mt-8",
}}
size="md"
color="danger"
aria-label="Include Mutelist"
>
Include Mutelist
</Checkbox>
<div className="flex h-full">
<Checkbox
classNames={{
label: "text-small",
wrapper: "checkbox-update",
}}
size="md"
color="primary"
aria-label="Include Mutelist"
isSelected={excludeMuted}
onValueChange={handleMutedChange}
>
Exclude muted findings
</Checkbox>
</div>
);
};
+18 -4
View File
@@ -4,7 +4,7 @@ import { Spacer } from "@nextui-org/react";
import { useSearchParams } from "next/navigation";
import React, { useEffect, useState } from "react";
import { FilterControlsProps } from "@/types";
import { FilterOption } from "@/types";
import { DataTableFilterCustom } from "../ui/table";
import { ClearFiltersButton } from "./clear-filters-button";
@@ -15,6 +15,17 @@ import { CustomRegionSelection } from "./custom-region-selection";
import { CustomSearchInput } from "./custom-search-input";
import { CustomSelectProvider } from "./custom-select-provider";
export interface FilterControlsProps {
search?: boolean;
providers?: boolean;
date?: boolean;
regions?: boolean;
accounts?: boolean;
mutedFindings?: boolean;
customFilters?: FilterOption[];
showClearButton?: boolean;
}
export const FilterControls: React.FC<FilterControlsProps> = ({
search = false,
providers = false,
@@ -22,16 +33,17 @@ export const FilterControls: React.FC<FilterControlsProps> = ({
regions = false,
accounts = false,
mutedFindings = false,
showClearButton = true,
customFilters,
}) => {
const searchParams = useSearchParams();
const [showClearButton, setShowClearButton] = useState(false);
const [hasFilters, setHasFilters] = useState(false);
useEffect(() => {
const hasFilters = Array.from(searchParams.keys()).some(
(key) => key.startsWith("filter[") || key === "sort",
);
setShowClearButton(hasFilters);
setHasFilters(hasFilters);
}, [searchParams]);
return (
@@ -43,7 +55,9 @@ export const FilterControls: React.FC<FilterControlsProps> = ({
{regions && <CustomRegionSelection />}
{accounts && <CustomAccountSelection />}
{mutedFindings && <CustomCheckboxMutedFindings />}
{!customFilters && showClearButton && <ClearFiltersButton />}
{!customFilters && hasFilters && showClearButton && (
<ClearFiltersButton />
)}
</div>
<Spacer y={8} />
{customFilters && (
@@ -38,6 +38,7 @@ export const FindingsFilters = ({
<FilterControls
search
date
mutedFindings
customFilters={[
...filterFindings,
{
@@ -4,10 +4,11 @@ import { Card, CardBody } from "@nextui-org/react";
import { Chip } from "@nextui-org/react";
import { TrendingUp } from "lucide-react";
import Link from "next/link";
import { useSearchParams } from "next/navigation";
import React, { useMemo } from "react";
import { Label, Pie, PieChart } from "recharts";
import { NotificationIcon, SuccessIcon } from "@/components/icons";
import { MutedIcon, NotificationIcon, SuccessIcon } from "@/components/icons";
import {
ChartConfig,
ChartContainer,
@@ -32,8 +33,10 @@ interface FindingsByStatusChartProps {
attributes: {
fail: number;
pass: number;
muted: number;
pass_new: number;
fail_new: number;
muted_new: number;
total: number;
};
};
@@ -52,19 +55,29 @@ const chartConfig = {
label: "Fail",
color: "hsl(var(--chart-fail))",
},
muted: {
label: "Muted",
color: "hsl(var(--chart-muted))",
},
} satisfies ChartConfig;
export const FindingsByStatusChart: React.FC<FindingsByStatusChartProps> = ({
findingsByStatus,
}) => {
const searchParams = useSearchParams();
const shouldShowMuted = searchParams.get("filter[muted]") !== "false";
const {
fail = 0,
pass = 0,
muted = 0,
pass_new = 0,
fail_new = 0,
muted_new = 0,
} = findingsByStatus?.data?.attributes || {};
const chartData = useMemo(
() => [
const chartData = useMemo(() => {
const data = [
{
findings: "Success",
number: pass,
@@ -75,9 +88,18 @@ export const FindingsByStatusChart: React.FC<FindingsByStatusChartProps> = ({
number: fail,
fill: "var(--color-fail)",
},
],
[pass, fail],
);
];
if (shouldShowMuted) {
data.push({
findings: "Muted",
number: muted,
fill: "var(--color-muted)",
});
}
return data;
}, [pass, fail, muted, shouldShowMuted]);
const updatedChartData = calculatePercent(chartData);
@@ -86,6 +108,8 @@ export const FindingsByStatusChart: React.FC<FindingsByStatusChartProps> = ({
[chartData],
);
const hasDataToShow = totalFindings > 0;
const emptyChartData = [
{
findings: "Empty",
@@ -105,7 +129,7 @@ export const FindingsByStatusChart: React.FC<FindingsByStatusChartProps> = ({
<PieChart>
<ChartTooltip cursor={false} content={<ChartTooltipContent />} />
<Pie
data={totalFindings > 0 ? chartData : emptyChartData}
data={hasDataToShow ? chartData : emptyChartData}
dataKey="number"
nameKey="findings"
innerRadius={65}
@@ -126,7 +150,7 @@ export const FindingsByStatusChart: React.FC<FindingsByStatusChartProps> = ({
y={viewBox.cy}
className="fill-foreground text-xl font-bold"
>
{totalFindings > 0
{hasDataToShow
? totalFindings.toLocaleString()
: "0"}
</tspan>
@@ -135,7 +159,7 @@ export const FindingsByStatusChart: React.FC<FindingsByStatusChartProps> = ({
y={(viewBox.cy || 0) + 20}
className="fill-foreground text-xs"
>
Findings
{"Findings"}
</tspan>
</text>
);
@@ -204,6 +228,46 @@ export const FindingsByStatusChart: React.FC<FindingsByStatusChartProps> = ({
<TrendingUp className="h-4 w-4" />
</div>
</div>
{shouldShowMuted && (
<div className="flex flex-col gap-2">
<div className="flex items-center space-x-2">
<Link
href="/findings?filter[muted]=true"
className="flex items-center space-x-2"
>
<Chip
className="h-5"
variant="flat"
startContent={<MutedIcon size={18} />}
color="warning"
radius="lg"
size="md"
>
{chartData.find((item) => item.findings === "Muted")
?.number || 0}
</Chip>
<span>
{updatedChartData.find(
(item) => item.findings === "Muted",
)?.percent || "0%"}
</span>
</Link>
</div>
<div className="text-muted-foreground flex items-center gap-1 text-xs font-medium leading-none">
{muted_new > 0 ? (
<>
+{muted_new} muted findings from last day{" "}
<TrendingUp className="h-4 w-4" />
</>
) : muted_new < 0 ? (
<>{muted_new} muted findings from last day</>
) : (
"No change from last day"
)}
</div>
</div>
)}
</div>
</div>
</CardBody>