feat(ui): add View Resource action to findings drawer (#10847)

This commit is contained in:
Hugo Pereira Brito
2026-04-27 13:19:18 +01:00
committed by GitHub
parent 013809919c
commit 059b71d34b
3 changed files with 89 additions and 4 deletions
+1
View File
@@ -7,6 +7,7 @@ All notable changes to the **Prowler UI** are documented in this file.
### 🚀 Added
- Download PDF button for CIS Benchmark compliance cards, surfaced only on the latest CIS variant per provider to match the backend's latest-only PDF generation [(#10650)](https://github.com/prowler-cloud/prowler/pull/10650)
- View Resource button in the findings resource detail drawer to open the related resource page [(#10847)](https://github.com/prowler-cloud/prowler/pull/10847)
### 🔄 Changed
@@ -1,6 +1,13 @@
import { render, screen, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import type { ButtonHTMLAttributes, HTMLAttributes, ReactNode } from "react";
import {
type AnchorHTMLAttributes,
type ButtonHTMLAttributes,
cloneElement,
type HTMLAttributes,
isValidElement,
type ReactNode,
} from "react";
import { createPortal } from "react-dom";
import { afterEach, describe, expect, it, vi } from "vitest";
@@ -36,8 +43,17 @@ vi.mock("next/image", () => ({
}));
vi.mock("next/link", () => ({
default: ({ children, href }: { children: ReactNode; href: string }) => (
<a href={href}>{children}</a>
default: ({
children,
href,
...props
}: AnchorHTMLAttributes<HTMLAnchorElement> & {
children: ReactNode;
href: string;
}) => (
<a href={href} {...props}>
{children}
</a>
),
}));
@@ -64,7 +80,12 @@ vi.mock("@/components/shadcn", () => {
variant?: string;
size?: string;
asChild?: boolean;
}) => <button {...props}>{children}</button>,
}) =>
_asChild && isValidElement(children) ? (
cloneElement(children, props)
) : (
<button {...props}>{children}</button>
),
InfoField: ({
children,
label,
@@ -386,6 +407,45 @@ const mockFinding: ResourceDrawerFinding = {
scan: null,
};
describe("ResourceDetailDrawerContent — resource navigation", () => {
it("should render a View Resource link below the resource actions menu", () => {
// Given
render(
<ResourceDetailDrawerContent
isLoading={false}
isNavigating={false}
checkMeta={mockCheckMeta}
currentIndex={0}
totalResources={1}
currentFinding={mockFinding}
otherFindings={[]}
onNavigatePrev={vi.fn()}
onNavigateNext={vi.fn()}
onMuteComplete={vi.fn()}
/>,
);
// When
const viewResourceLink = screen.getByRole("link", {
name: "View Resource",
});
const resourceActionsMenu = screen.getByRole("menu", {
name: "Resource actions",
});
// Then
expect(viewResourceLink).toHaveAttribute(
"href",
"/resources?resourceId=res-1",
);
expect(viewResourceLink).toHaveAttribute("target", "_blank");
expect(viewResourceLink).toHaveAttribute("rel", "noopener noreferrer");
expect(
resourceActionsMenu.compareDocumentPosition(viewResourceLink) &
Node.DOCUMENT_POSITION_FOLLOWING,
).not.toBe(0);
});
});
const mockResourceRow: FindingResourceRow = {
id: "row-1",
rowType: "resource",
@@ -299,6 +299,12 @@ function buildComplianceDetailHref({
return `/compliance/${encodeURIComponent(framework)}?${params.toString()}`;
}
function buildResourceDetailHref(resourceId: string): string {
const params = new URLSearchParams();
params.set("resourceId", resourceId);
return `/resources?${params.toString()}`;
}
interface ResourceDetailDrawerContentProps {
isLoading: boolean;
isNavigating: boolean;
@@ -416,6 +422,9 @@ export function ResourceDetailDrawerContent({
const nativeIacConfig = resolveNativeIacConfig(providerType);
const showOverviewCheckMetaContent = showCheckMetaContent;
const showOverviewFindingContent = Boolean(f);
const resourceDetailHref = f?.resourceId
? buildResourceDetailHref(f.resourceId)
: null;
const overviewStatusExtended = f?.statusExtended;
const showOverviewStatusExtended = Boolean(overviewStatusExtended);
@@ -757,6 +766,21 @@ export function ResourceDetailDrawerContent({
)}
</div>
</div>
{resourceDetailHref && (
<div className="border-border-neutral-secondary flex justify-end border-t pt-3">
<Button variant="link" size="link-sm" asChild>
<Link
href={resourceDetailHref}
target="_blank"
rel="noopener noreferrer"
>
View Resource
<ExternalLink className="size-3" />
</Link>
</Button>
</div>
)}
</>
)}