mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-11-02 01:15:31 +00:00
feat(compliance): add resource metadata tab inside req find (#11187)
This commit is contained in:
1 parent
e55d1d470e
commit
dbbefd0558
14 files changed
+569
-63
No files matched your search
+160
@@ -215,6 +215,7 @@ vi.mock("@/components/shared/query-code-editor", () => ({
|
||||
HCL: "hcl",
|
||||
BICEP: "bicep",
|
||||
YAML: "yaml",
|
||||
JSON: "json",
|
||||
},
|
||||
QueryCodeEditor: ({
|
||||
ariaLabel,
|
||||
@@ -426,6 +427,8 @@ const mockFinding: ResourceDrawerFinding = {
|
||||
resourceRegion: "us-east-1",
|
||||
resourceType: "Bucket",
|
||||
resourceGroup: "default",
|
||||
resourceDetails: null,
|
||||
resourceMetadata: null,
|
||||
providerType: "aws",
|
||||
providerAlias: "prod",
|
||||
providerUid: "123456789",
|
||||
@@ -1764,3 +1767,160 @@ describe("ResourceDetailDrawerContent — other findings delta/muted indicator",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("ResourceDetailDrawerContent — Metadata tab", () => {
|
||||
const getMetadataEditor = () =>
|
||||
screen
|
||||
.queryAllByTestId("query-code-editor")
|
||||
.find(
|
||||
(editor) =>
|
||||
editor.getAttribute("data-aria-label") === "Resource metadata",
|
||||
);
|
||||
|
||||
it("should render a Metadata tab trigger", () => {
|
||||
// Given/When
|
||||
render(
|
||||
<ResourceDetailDrawerContent
|
||||
isLoading={false}
|
||||
isNavigating={false}
|
||||
checkMeta={mockCheckMeta}
|
||||
currentIndex={0}
|
||||
totalResources={1}
|
||||
currentFinding={mockFinding}
|
||||
otherFindings={[]}
|
||||
onNavigatePrev={vi.fn()}
|
||||
onNavigateNext={vi.fn()}
|
||||
onMuteComplete={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(
|
||||
screen.getByRole("button", { name: "Resource Metadata / Evidence" }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render the resource metadata as formatted JSON and copy it to the clipboard", async () => {
|
||||
// Given
|
||||
const user = userEvent.setup();
|
||||
const findingWithMetadata: ResourceDrawerFinding = {
|
||||
...mockFinding,
|
||||
resourceDetails: "Python",
|
||||
resourceMetadata: {
|
||||
VulnerabilityID: "CVE-2026-0001",
|
||||
PkgName: "requests",
|
||||
},
|
||||
};
|
||||
|
||||
render(
|
||||
<ResourceDetailDrawerContent
|
||||
isLoading={false}
|
||||
isNavigating={false}
|
||||
checkMeta={mockCheckMeta}
|
||||
currentIndex={0}
|
||||
totalResources={1}
|
||||
currentFinding={findingWithMetadata}
|
||||
otherFindings={[]}
|
||||
onNavigatePrev={vi.fn()}
|
||||
onNavigateNext={vi.fn()}
|
||||
onMuteComplete={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Then — Details section + JSON editor are rendered
|
||||
expect(screen.getByText("Details:")).toBeInTheDocument();
|
||||
expect(screen.getByText("Python")).toBeInTheDocument();
|
||||
|
||||
const metadataEditor = getMetadataEditor();
|
||||
expect(metadataEditor).toBeDefined();
|
||||
expect(metadataEditor).toHaveAttribute("data-language", "json");
|
||||
expect(metadataEditor?.textContent).toContain("CVE-2026-0001");
|
||||
|
||||
// When — copy the metadata JSON
|
||||
await user.click(
|
||||
screen.getByRole("button", { name: "Copy Resource metadata" }),
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(mockClipboardWriteText).toHaveBeenCalledWith(
|
||||
JSON.stringify(findingWithMetadata.resourceMetadata, null, 2),
|
||||
);
|
||||
});
|
||||
|
||||
it("should parse stringified resource metadata", () => {
|
||||
// Given
|
||||
const findingWithStringMetadata: ResourceDrawerFinding = {
|
||||
...mockFinding,
|
||||
resourceMetadata: '{"PkgName":"requests"}',
|
||||
};
|
||||
|
||||
render(
|
||||
<ResourceDetailDrawerContent
|
||||
isLoading={false}
|
||||
isNavigating={false}
|
||||
checkMeta={mockCheckMeta}
|
||||
currentIndex={0}
|
||||
totalResources={1}
|
||||
currentFinding={findingWithStringMetadata}
|
||||
otherFindings={[]}
|
||||
onNavigatePrev={vi.fn()}
|
||||
onNavigateNext={vi.fn()}
|
||||
onMuteComplete={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(getMetadataEditor()?.textContent).toContain("requests");
|
||||
});
|
||||
|
||||
it("should show an empty state when no metadata or details are available", () => {
|
||||
// Given/When
|
||||
render(
|
||||
<ResourceDetailDrawerContent
|
||||
isLoading={false}
|
||||
isNavigating={false}
|
||||
checkMeta={mockCheckMeta}
|
||||
currentIndex={0}
|
||||
totalResources={1}
|
||||
currentFinding={mockFinding}
|
||||
otherFindings={[]}
|
||||
onNavigatePrev={vi.fn()}
|
||||
onNavigateNext={vi.fn()}
|
||||
onMuteComplete={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(
|
||||
screen.getByText("No metadata available for this resource."),
|
||||
).toBeInTheDocument();
|
||||
expect(getMetadataEditor()).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should show a metadata skeleton while navigating", () => {
|
||||
// Given/When
|
||||
render(
|
||||
<ResourceDetailDrawerContent
|
||||
isLoading={false}
|
||||
isNavigating
|
||||
checkMeta={mockCheckMeta}
|
||||
currentIndex={0}
|
||||
totalResources={2}
|
||||
currentResource={mockResourceRow}
|
||||
currentFinding={mockFinding}
|
||||
otherFindings={[]}
|
||||
onNavigatePrev={vi.fn()}
|
||||
onNavigateNext={vi.fn()}
|
||||
onMuteComplete={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(
|
||||
screen.getByTestId("metadata-navigation-skeleton"),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByText("No metadata available for this resource."),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
+34
@@ -53,6 +53,7 @@ import {
|
||||
QueryCodeEditor,
|
||||
type QueryEditorLanguage,
|
||||
} from "@/components/shared/query-code-editor";
|
||||
import { ResourceMetadataPanel } from "@/components/shared/resource-metadata-panel";
|
||||
import { CodeSnippet } from "@/components/ui/code-snippet/code-snippet";
|
||||
import { CustomLink } from "@/components/ui/custom/custom-link";
|
||||
import { DateWithTime } from "@/components/ui/entities/date-with-time";
|
||||
@@ -845,6 +846,9 @@ export function ResourceDetailDrawerContent({
|
||||
<TabsList>
|
||||
<TabsTrigger value="overview">Finding Overview</TabsTrigger>
|
||||
<TabsTrigger value="remediation">Remediation</TabsTrigger>
|
||||
<TabsTrigger value="metadata">
|
||||
Resource Metadata / Evidence
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="other-findings">
|
||||
Findings for this resource
|
||||
</TabsTrigger>
|
||||
@@ -1072,6 +1076,21 @@ export function ResourceDetailDrawerContent({
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
{/* Metadata */}
|
||||
<TabsContent
|
||||
value="metadata"
|
||||
className="minimal-scrollbar flex flex-col gap-4 overflow-y-auto"
|
||||
>
|
||||
{isNavigating ? (
|
||||
<MetadataNavigationSkeleton />
|
||||
) : (
|
||||
<ResourceMetadataPanel
|
||||
metadata={f?.resourceMetadata}
|
||||
details={f?.resourceDetails}
|
||||
/>
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
{/* Findings for this resource */}
|
||||
<TabsContent
|
||||
value="other-findings"
|
||||
@@ -1400,6 +1419,21 @@ function EventsNavigationSkeleton() {
|
||||
);
|
||||
}
|
||||
|
||||
function MetadataNavigationSkeleton() {
|
||||
return (
|
||||
<div
|
||||
className="flex flex-col gap-4"
|
||||
data-testid="metadata-navigation-skeleton"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<Card variant="inner">
|
||||
<OverviewCardSkeleton lineWidths={["w-20", "w-full", "w-3/4"]} />
|
||||
</Card>
|
||||
<Skeleton className="h-56 w-full rounded" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function OtherFindingRow({
|
||||
finding,
|
||||
isOptimisticallyMuted,
|
||||
|
||||
@@ -86,6 +86,8 @@ function makeDrawerFinding(
|
||||
resourceRegion: "us-east-1",
|
||||
resourceType: "bucket",
|
||||
resourceGroup: "default",
|
||||
resourceDetails: null,
|
||||
resourceMetadata: null,
|
||||
providerType: "aws",
|
||||
providerAlias: "prod",
|
||||
providerUid: "123",
|
||||
|
||||
Reference in new issue
Block a user