mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-04 19:21:51 +00:00
157 lines
5.7 KiB
TypeScript
157 lines
5.7 KiB
TypeScript
import { ClientAccordionContent } from "@/components/compliance/compliance-accordion/client-accordion-content";
|
|
import { ComplianceAccordionRequirementTitle } from "@/components/compliance/compliance-accordion/compliance-accordion-requeriment-title";
|
|
import { ComplianceAccordionTitle } from "@/components/compliance/compliance-accordion/compliance-accordion-title";
|
|
import { AccordionItemProps } from "@/components/ui/accordion/Accordion";
|
|
import { FindingStatus } from "@/components/ui/table/status-finding-badge";
|
|
import {
|
|
AttributesData,
|
|
DORAAttributesMetadata,
|
|
Framework,
|
|
Requirement,
|
|
REQUIREMENT_STATUS,
|
|
RequirementsData,
|
|
RequirementStatus,
|
|
} from "@/types/compliance";
|
|
|
|
import {
|
|
calculateFrameworkCounters,
|
|
createRequirementsMap,
|
|
findOrCreateCategory,
|
|
findOrCreateControl,
|
|
findOrCreateFramework,
|
|
} from "./commons";
|
|
|
|
// Display order for DORA pillars in the accordion and any grouped chart. The
|
|
// regulation arranges them in this exact order (Articles 5-14, 17-19, 24-25,
|
|
// 28+30, 45) — preserving it here means the UI always renders pillars in the
|
|
// "logical" reading order regardless of how the API returns them.
|
|
export const DORA_PILLAR_ORDER: readonly string[] = [
|
|
"ICT Risk Management",
|
|
"ICT-Related Incident Reporting",
|
|
"Digital Operational Resilience Testing",
|
|
"ICT Third-Party Risk Management",
|
|
"Information Sharing",
|
|
];
|
|
|
|
const getStatusCounters = (status: RequirementStatus) => ({
|
|
pass: status === REQUIREMENT_STATUS.PASS ? 1 : 0,
|
|
fail: status === REQUIREMENT_STATUS.FAIL ? 1 : 0,
|
|
manual: status === REQUIREMENT_STATUS.MANUAL ? 1 : 0,
|
|
});
|
|
|
|
export const mapComplianceData = (
|
|
attributesData: AttributesData,
|
|
requirementsData: RequirementsData,
|
|
): Framework[] => {
|
|
const attributes = attributesData?.data || [];
|
|
const requirementsMap = createRequirementsMap(requirementsData);
|
|
const frameworks: Framework[] = [];
|
|
|
|
for (const attributeItem of attributes) {
|
|
const id = attributeItem.id;
|
|
const metadataArray = attributeItem.attributes?.attributes
|
|
?.metadata as unknown as DORAAttributesMetadata[];
|
|
const attrs = metadataArray?.[0];
|
|
if (!attrs) continue;
|
|
|
|
const requirementData = requirementsMap.get(id);
|
|
if (!requirementData) continue;
|
|
|
|
const frameworkName = attributeItem.attributes.framework;
|
|
// Group by Pillar (top-level accordion section). Article + ArticleTitle
|
|
// live inside the requirement so they show up on the detail drawer.
|
|
const categoryName = attrs.Pillar;
|
|
const requirementName = attributeItem.attributes.name || "";
|
|
const description = attributeItem.attributes.description;
|
|
const status = requirementData.attributes.status || "";
|
|
const checks = attributeItem.attributes.attributes.check_ids || [];
|
|
|
|
const framework = findOrCreateFramework(frameworks, frameworkName);
|
|
const category = findOrCreateCategory(framework.categories, categoryName);
|
|
// Flat 2-level structure: pillar → requirements (no intermediate control).
|
|
const control = findOrCreateControl(category.controls, categoryName);
|
|
|
|
const finalStatus: RequirementStatus = status as RequirementStatus;
|
|
const requirement: Requirement = {
|
|
name: requirementName ? `${id} - ${requirementName}` : id,
|
|
description,
|
|
status: finalStatus,
|
|
check_ids: checks,
|
|
invalid_config: requirementData.attributes.invalid_config || false,
|
|
...getStatusCounters(finalStatus),
|
|
pillar: attrs.Pillar,
|
|
article: attrs.Article,
|
|
article_title: attrs.ArticleTitle,
|
|
};
|
|
|
|
control.requirements.push(requirement);
|
|
}
|
|
|
|
// Sort categories by canonical pillar order so DORA always reads from "ICT
|
|
// Risk Management" down to "Information Sharing", regardless of map insertion
|
|
// order driven by the API response.
|
|
for (const framework of frameworks) {
|
|
framework.categories.sort((a, b) => {
|
|
const ia = DORA_PILLAR_ORDER.indexOf(a.name);
|
|
const ib = DORA_PILLAR_ORDER.indexOf(b.name);
|
|
// Unknown pillars (defensive — shouldn't happen) sink to the bottom.
|
|
const orderA = ia === -1 ? DORA_PILLAR_ORDER.length : ia;
|
|
const orderB = ib === -1 ? DORA_PILLAR_ORDER.length : ib;
|
|
return orderA - orderB;
|
|
});
|
|
}
|
|
|
|
calculateFrameworkCounters(frameworks);
|
|
|
|
return frameworks;
|
|
};
|
|
|
|
export const toAccordionItems = (
|
|
data: Framework[],
|
|
scanId: string | undefined,
|
|
): AccordionItemProps[] => {
|
|
const safeId = scanId || "";
|
|
|
|
return data.flatMap((framework) =>
|
|
framework.categories.map((category) => ({
|
|
key: `${framework.name}-${category.name}`,
|
|
title: (
|
|
<ComplianceAccordionTitle
|
|
label={category.name}
|
|
pass={category.pass}
|
|
fail={category.fail}
|
|
manual={category.manual}
|
|
isParentLevel={true}
|
|
/>
|
|
),
|
|
content: "",
|
|
// Pillar → requirements (flat, no intermediate "control" level).
|
|
items: category.controls.flatMap((control) =>
|
|
control.requirements.map((requirement, reqIndex) => ({
|
|
key: `${framework.name}-${category.name}-req-${reqIndex}`,
|
|
title: (
|
|
<ComplianceAccordionRequirementTitle
|
|
type=""
|
|
name={requirement.name}
|
|
status={requirement.status as FindingStatus}
|
|
invalidConfig={requirement.invalid_config}
|
|
/>
|
|
),
|
|
content: (
|
|
<ClientAccordionContent
|
|
key={`content-${framework.name}-${category.name}-req-${reqIndex}`}
|
|
requirement={requirement}
|
|
scanId={safeId}
|
|
framework={framework.name}
|
|
disableFindings={
|
|
requirement.check_ids.length === 0 && requirement.manual === 0
|
|
}
|
|
/>
|
|
),
|
|
items: [],
|
|
})),
|
|
),
|
|
})),
|
|
);
|
|
};
|