mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-01-25 02:08:11 +00:00
fix: useActionState remplaced by controlled client state to render the DOM changes when muting inside the drawer from the action button
This commit is contained in:
@@ -4,9 +4,10 @@ import { Input, Textarea } from "@heroui/input";
|
||||
import {
|
||||
Dispatch,
|
||||
SetStateAction,
|
||||
useActionState,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
useTransition,
|
||||
} from "react";
|
||||
|
||||
import { createMuteRule } from "@/actions/mute-rules";
|
||||
@@ -29,6 +30,8 @@ export function MuteFindingsModal({
|
||||
onComplete,
|
||||
}: MuteFindingsModalProps) {
|
||||
const { toast } = useToast();
|
||||
const [state, setState] = useState<MuteRuleActionState | null>(null);
|
||||
const [isPending, startTransition] = useTransition();
|
||||
|
||||
// Use refs to avoid stale closures in useEffect
|
||||
const onCompleteRef = useRef(onComplete);
|
||||
@@ -37,18 +40,12 @@ export function MuteFindingsModal({
|
||||
const onOpenChangeRef = useRef(onOpenChange);
|
||||
onOpenChangeRef.current = onOpenChange;
|
||||
|
||||
const [state, formAction, isPending] = useActionState<
|
||||
MuteRuleActionState,
|
||||
FormData
|
||||
>(createMuteRule, null);
|
||||
|
||||
useEffect(() => {
|
||||
if (state?.success) {
|
||||
toast({
|
||||
title: "Success",
|
||||
description: state.success,
|
||||
});
|
||||
|
||||
onCompleteRef.current?.();
|
||||
onOpenChangeRef.current(false);
|
||||
} else if (state?.errors?.general) {
|
||||
@@ -71,7 +68,20 @@ export function MuteFindingsModal({
|
||||
title="Mute Findings"
|
||||
size="lg"
|
||||
>
|
||||
<form action={formAction} className="flex flex-col gap-4">
|
||||
<form
|
||||
className="flex flex-col gap-4"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.currentTarget);
|
||||
|
||||
startTransition(() => {
|
||||
void (async () => {
|
||||
const result = await createMuteRule(null, formData);
|
||||
setState(result);
|
||||
})();
|
||||
});
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="hidden"
|
||||
name="finding_ids"
|
||||
|
||||
@@ -19,9 +19,13 @@ import { FindingsSelectionContext } from "./findings-selection-context";
|
||||
|
||||
interface DataTableRowActionsProps {
|
||||
row: Row<FindingProps>;
|
||||
onMuteComplete?: (findingIds: string[]) => void;
|
||||
}
|
||||
|
||||
export function DataTableRowActions({ row }: DataTableRowActionsProps) {
|
||||
export function DataTableRowActions({
|
||||
row,
|
||||
onMuteComplete,
|
||||
}: DataTableRowActionsProps) {
|
||||
const router = useRouter();
|
||||
const finding = row.original;
|
||||
const [isJiraModalOpen, setIsJiraModalOpen] = useState(false);
|
||||
@@ -84,6 +88,11 @@ export function DataTableRowActions({ row }: DataTableRowActionsProps) {
|
||||
// 1. If the muted finding was selected, its index now points to a different finding
|
||||
// 2. rowSelection uses indices (0, 1, 2...) not IDs, so after refresh the wrong findings would appear selected
|
||||
clearSelection();
|
||||
if (onMuteComplete) {
|
||||
onMuteComplete(getMuteIds());
|
||||
return;
|
||||
}
|
||||
|
||||
router.refresh();
|
||||
};
|
||||
|
||||
@@ -130,7 +139,9 @@ export function DataTableRowActions({ row }: DataTableRowActionsProps) {
|
||||
label={getMuteLabel()}
|
||||
description={getMuteDescription()}
|
||||
disabled={isMuted}
|
||||
onSelect={() => setIsMuteModalOpen(true)}
|
||||
onSelect={() => {
|
||||
setIsMuteModalOpen(true);
|
||||
}}
|
||||
/>
|
||||
<ActionDropdownItem
|
||||
icon={<JiraIcon size={20} />}
|
||||
|
||||
@@ -126,6 +126,7 @@ const getResourceFindingsColumns = (
|
||||
rowSelection: RowSelectionState,
|
||||
selectableRowCount: number,
|
||||
onNavigate: (id: string) => void,
|
||||
onMuteComplete?: (findingIds: string[]) => void,
|
||||
): ColumnDef<ResourceFinding>[] => {
|
||||
const selectedCount = Object.values(rowSelection).filter(Boolean).length;
|
||||
const isAllSelected =
|
||||
@@ -227,7 +228,10 @@ const getResourceFindingsColumns = (
|
||||
id: "actions",
|
||||
header: () => <div className="w-10" />,
|
||||
cell: ({ row }) => (
|
||||
<DataTableRowActions row={row as unknown as Row<FindingProps>} />
|
||||
<DataTableRowActions
|
||||
row={row as unknown as Row<FindingProps>}
|
||||
onMuteComplete={onMuteComplete}
|
||||
/>
|
||||
),
|
||||
enableSorting: false,
|
||||
},
|
||||
@@ -255,6 +259,7 @@ export const ResourceDetail = ({
|
||||
const [resourceTags, setResourceTags] = useState<Record<string, string>>({});
|
||||
const [findingsLoading, setFindingsLoading] = useState(true);
|
||||
const [hasInitiallyLoaded, setHasInitiallyLoaded] = useState(false);
|
||||
const [findingsReloadNonce, setFindingsReloadNonce] = useState(0);
|
||||
const [selectedFindingId, setSelectedFindingId] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
@@ -377,7 +382,14 @@ export const ResourceDetail = ({
|
||||
if (attributes.uid && isDrawerOpen) {
|
||||
loadFindings();
|
||||
}
|
||||
}, [attributes.uid, currentPage, pageSize, searchQuery, isDrawerOpen]);
|
||||
}, [
|
||||
attributes.uid,
|
||||
currentPage,
|
||||
pageSize,
|
||||
searchQuery,
|
||||
isDrawerOpen,
|
||||
findingsReloadNonce,
|
||||
]);
|
||||
|
||||
const navigateToFinding = async (findingId: string) => {
|
||||
// Cancel any in-flight request
|
||||
@@ -438,8 +450,12 @@ export const ResourceDetail = ({
|
||||
setFindingDetailLoading(false);
|
||||
};
|
||||
|
||||
const handleMuteComplete = () => {
|
||||
const handleMuteComplete = (_findingIds?: string[]) => {
|
||||
const ids =
|
||||
_findingIds && _findingIds.length > 0 ? _findingIds : selectedFindingIds;
|
||||
|
||||
setRowSelection({});
|
||||
if (ids.length > 0) setFindingsReloadNonce((v) => v + 1);
|
||||
router.refresh();
|
||||
};
|
||||
|
||||
@@ -470,6 +486,7 @@ export const ResourceDetail = ({
|
||||
rowSelection,
|
||||
selectableRowCount,
|
||||
navigateToFinding,
|
||||
handleMuteComplete,
|
||||
);
|
||||
|
||||
// Build Git URL for IaC resources
|
||||
|
||||
Reference in New Issue
Block a user