fix(ui): handle missing relationships in FindingDetail to prevent crash (#10314)

This commit is contained in:
Alejandro Bailo
2026-03-12 11:38:03 +01:00
committed by GitHub
parent 4dc3765670
commit 5346222be2
3 changed files with 146 additions and 125 deletions

View File

@@ -11,6 +11,10 @@ All notable changes to the **Prowler UI** are documented in this file.
- Providers page redesigned with cloud organization hierarchy, HeroUI-to-shadcn migration, organization and account group filters, and row selection for bulk actions [(#10292)](https://github.com/prowler-cloud/prowler/pull/10292)
- AWS Organizations onboarding now uses a clearer 3-step flow: deploy the ProwlerScan role in the management account via CloudFormation Stack, deploy to member accounts via StackSet with a copyable template URL, and confirm with the Role ARN [(#10274)](https://github.com/prowler-cloud/prowler/pull/10274)
### 🐞 Fixed
- Finding detail drawer crashing when resource, scan, or provider relationships are missing from the API response [(#10314)](https://github.com/prowler-cloud/prowler/pull/10314)
### 🔐 Security
- npm transitive dependencies patched to resolve 11 Dependabot alerts (6 HIGH, 4 MEDIUM, 1 LOW): hono, @hono/node-server, fast-xml-parser, serialize-javascript, minimatch [(#10267)](https://github.com/prowler-cloud/prowler/pull/10267)

View File

@@ -33,20 +33,14 @@ const getResourceData = (
row: { original: FindingProps },
field: keyof FindingProps["relationships"]["resource"]["attributes"],
) => {
return (
row.original.relationships?.resource?.attributes?.[field] ||
`No ${field} found in resource`
);
return row.original.relationships?.resource?.attributes?.[field] || "-";
};
const getProviderData = (
row: { original: FindingProps },
field: keyof FindingProps["relationships"]["provider"]["attributes"],
) => {
return (
row.original.relationships?.provider?.attributes?.[field] ||
`No ${field} found in provider`
);
return row.original.relationships?.provider?.attributes?.[field] || "-";
};
// Component for finding title that opens the detail drawer
@@ -186,6 +180,10 @@ export function getColumnFindings(
cell: ({ row }) => {
const resourceName = getResourceData(row, "name");
if (resourceName === "-") {
return <p className="text-text-neutral-primary text-sm">-</p>;
}
return (
<CodeSnippet
value={resourceName as string}

View File

@@ -82,9 +82,9 @@ export const FindingDetail = ({
}: FindingDetailProps) => {
const finding = findingDetails;
const attributes = finding.attributes;
const resource = finding.relationships.resource.attributes;
const scan = finding.relationships.scan.attributes;
const providerDetails = finding.relationships.provider.attributes;
const resource = finding.relationships?.resource?.attributes;
const scan = finding.relationships?.scan?.attributes;
const providerDetails = finding.relationships?.provider?.attributes;
const pathname = usePathname();
const searchParams = useSearchParams();
@@ -97,7 +97,7 @@ export const FindingDetail = ({
// Build Git URL for IaC findings
const gitUrl =
providerDetails.provider === "iac"
providerDetails?.provider === "iac" && resource
? buildGitFileUrl(
providerDetails.uid,
resource.name,
@@ -160,23 +160,24 @@ export const FindingDetail = ({
<TabsTrigger value="scans">Scans</TabsTrigger>
</TabsList>
<p className="text-text-neutral-primary mb-4 text-sm">
Here is an overview of this finding:
</p>
{/* General Tab */}
<TabsContent value="general" className="flex flex-col gap-4">
<p className="text-text-neutral-primary text-sm">
Here is an overview of this finding:
</p>
<div className="flex flex-wrap gap-4">
{providerDetails && (
<EntityInfo
cloudProvider={providerDetails.provider as ProviderType}
entityAlias={providerDetails.alias}
entityId={providerDetails.uid}
showConnectionStatus={providerDetails.connection.connected}
/>
)}
<InfoField label="Service">
{attributes.check_metadata.servicename}
</InfoField>
<InfoField label="Region">{resource.region}</InfoField>
<InfoField label="Region">{resource?.region ?? "-"}</InfoField>
</div>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
@@ -305,7 +306,9 @@ export const FindingDetail = ({
{/* Resources Tab */}
<TabsContent value="resources" className="flex flex-col gap-4">
{providerDetails.provider === "iac" && gitUrl && (
{resource ? (
<>
{providerDetails?.provider === "iac" && gitUrl && (
<div className="flex justify-end">
<Tooltip>
<TooltipTrigger asChild>
@@ -340,7 +343,9 @@ export const FindingDetail = ({
<InfoField label="Service">
{renderValue(resource.service)}
</InfoField>
<InfoField label="Region">{renderValue(resource.region)}</InfoField>
<InfoField label="Region">
{renderValue(resource.region)}
</InfoField>
</div>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
@@ -379,10 +384,18 @@ export const FindingDetail = ({
<DateWithTime inline dateTime={resource.updated_at || "-"} />
</InfoField>
</div>
</>
) : (
<p className="text-text-neutral-tertiary text-sm">
Resource information is not available.
</p>
)}
</TabsContent>
{/* Scans Tab */}
<TabsContent value="scans" className="flex flex-col gap-4">
{scan ? (
<>
<div className="grid grid-cols-1 gap-4 md:grid-cols-3">
<InfoField label="Scan Name">{scan.name || "N/A"}</InfoField>
<InfoField label="Resources Scanned">
@@ -418,6 +431,12 @@ export const FindingDetail = ({
</InfoField>
)}
</div>
</>
) : (
<p className="text-text-neutral-tertiary text-sm">
Scan information is not available.
</p>
)}
</TabsContent>
</Tabs>
</div>