mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-24 04:51:51 +00:00
feat(compliance): add resource metadata tab inside req find (#11187)
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import type { ReactNode } from "react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { ResourceMetadataPanel } from "./resource-metadata-panel";
|
||||
|
||||
vi.mock("@/components/shadcn/card/card", () => ({
|
||||
Card: ({ children, variant }: { children: ReactNode; variant?: string }) => (
|
||||
<div data-slot="card" data-variant={variant}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock("@/components/shared/query-code-editor", () => ({
|
||||
QUERY_EDITOR_LANGUAGE: {
|
||||
JSON: "json",
|
||||
},
|
||||
QueryCodeEditor: ({
|
||||
ariaLabel,
|
||||
value,
|
||||
copyValue,
|
||||
}: {
|
||||
ariaLabel: string;
|
||||
value: string;
|
||||
copyValue?: string;
|
||||
}) => (
|
||||
<div
|
||||
data-testid="query-code-editor"
|
||||
data-aria-label={ariaLabel}
|
||||
data-value={value}
|
||||
data-copy-value={copyValue}
|
||||
/>
|
||||
),
|
||||
}));
|
||||
|
||||
const EMPTY_STATE = "No metadata available for this resource.";
|
||||
|
||||
describe("ResourceMetadataPanel", () => {
|
||||
it("renders only the details card when just details are present", () => {
|
||||
render(
|
||||
<ResourceMetadataPanel metadata={null} details="Some resource details" />,
|
||||
);
|
||||
|
||||
expect(screen.getByText("Details:")).toBeInTheDocument();
|
||||
expect(screen.getByText("Some resource details")).toBeInTheDocument();
|
||||
expect(screen.queryByTestId("query-code-editor")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(EMPTY_STATE)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders only the metadata editor when just metadata is present", () => {
|
||||
render(
|
||||
<ResourceMetadataPanel
|
||||
metadata={{ VulnerabilityID: "CVE-2026-0001" }}
|
||||
details={null}
|
||||
/>,
|
||||
);
|
||||
|
||||
const editor = screen.getByTestId("query-code-editor");
|
||||
expect(editor).toBeInTheDocument();
|
||||
// value and copyValue must reference the same serialized string.
|
||||
const serialized = JSON.stringify(
|
||||
{ VulnerabilityID: "CVE-2026-0001" },
|
||||
null,
|
||||
2,
|
||||
);
|
||||
expect(editor).toHaveAttribute("data-value", serialized);
|
||||
expect(editor).toHaveAttribute("data-copy-value", serialized);
|
||||
expect(screen.queryByText("Details:")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(EMPTY_STATE)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders both the details card and the metadata editor", () => {
|
||||
render(
|
||||
<ResourceMetadataPanel
|
||||
metadata='{"PkgName":"requests"}'
|
||||
details="Detected on instance i-123"
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText("Detected on instance i-123")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("query-code-editor")).toBeInTheDocument();
|
||||
expect(screen.queryByText(EMPTY_STATE)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders the empty state when neither details nor metadata are present", () => {
|
||||
render(<ResourceMetadataPanel metadata={null} details={null} />);
|
||||
|
||||
expect(screen.getByText(EMPTY_STATE)).toBeInTheDocument();
|
||||
expect(screen.queryByText("Details:")).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId("query-code-editor")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("falls back to the empty state for an empty metadata object", () => {
|
||||
render(<ResourceMetadataPanel metadata={{}} details=" " />);
|
||||
|
||||
expect(screen.getByText(EMPTY_STATE)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
"use client";
|
||||
|
||||
import { Card } from "@/components/shadcn/card/card";
|
||||
import {
|
||||
QUERY_EDITOR_LANGUAGE,
|
||||
QueryCodeEditor,
|
||||
} from "@/components/shared/query-code-editor";
|
||||
import { parseMetadata } from "@/lib/resource-metadata";
|
||||
|
||||
interface ResourceMetadataPanelProps {
|
||||
metadata: Record<string, unknown> | string | null | undefined;
|
||||
details: string | null | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared "Metadata" panel for a resource.
|
||||
*
|
||||
* Renders the resource `details` text and its `metadata` as formatted JSON
|
||||
* with a copy-to-clipboard action, falling back to an empty state when
|
||||
* neither is available. Reused by the resource detail view and the finding
|
||||
* detail drawer (compliance requirement findings view) to keep the UX
|
||||
* consistent across surfaces.
|
||||
*/
|
||||
export function ResourceMetadataPanel({
|
||||
metadata,
|
||||
details,
|
||||
}: ResourceMetadataPanelProps) {
|
||||
const parsedMetadata = parseMetadata(metadata);
|
||||
const hasMetadata =
|
||||
parsedMetadata !== null && Object.keys(parsedMetadata).length > 0;
|
||||
const hasDetails = Boolean(details?.trim());
|
||||
const formattedMetadata =
|
||||
hasMetadata && parsedMetadata
|
||||
? JSON.stringify(parsedMetadata, null, 2)
|
||||
: null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{hasDetails && (
|
||||
<Card variant="inner">
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="text-text-neutral-secondary text-sm font-semibold">
|
||||
Details:
|
||||
</span>
|
||||
<p className="text-text-neutral-primary text-sm break-words whitespace-pre-wrap">
|
||||
{details}
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{formattedMetadata && (
|
||||
<QueryCodeEditor
|
||||
ariaLabel="Resource metadata"
|
||||
visibleLabel={null}
|
||||
language={QUERY_EDITOR_LANGUAGE.JSON}
|
||||
value={formattedMetadata}
|
||||
copyValue={formattedMetadata}
|
||||
editable={false}
|
||||
minHeight={220}
|
||||
showCopyButton
|
||||
onChange={() => {}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!hasDetails && !hasMetadata && (
|
||||
<p className="text-text-neutral-tertiary py-8 text-center text-sm">
|
||||
No metadata available for this resource.
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user