From f80c5c2201b20d06c2b38b084441cf842f2267ed Mon Sep 17 00:00:00 2001 From: Piumal Rathnayake Date: Fri, 28 Feb 2025 10:26:12 +0530 Subject: [PATCH 1/4] Add policy adherence chart to compliance page and change order --- .../src/app/components/Shared/DonutChart.jsx | 2 +- .../Apis/Details/APICompliance/Compliance.jsx | 240 ++++++++++++------ .../PolicyAdherenceSummaryTable.jsx | 1 + .../RulesetAdherenceSummaryTable.jsx | 1 + .../src/app/components/Shared/DonutChart.jsx | 4 +- 5 files changed, 168 insertions(+), 80 deletions(-) diff --git a/portals/admin/src/main/webapp/source/src/app/components/Shared/DonutChart.jsx b/portals/admin/src/main/webapp/source/src/app/components/Shared/DonutChart.jsx index 680d96234ee..fde19d766fe 100644 --- a/portals/admin/src/main/webapp/source/src/app/components/Shared/DonutChart.jsx +++ b/portals/admin/src/main/webapp/source/src/app/components/Shared/DonutChart.jsx @@ -59,7 +59,7 @@ const DonutChart = ({ cornerRadius: 5, cx: 100, startAngle: 90, - endAngle: 470, + endAngle: -270, }]} width={width} height={height} diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/APICompliance/Compliance.jsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/APICompliance/Compliance.jsx index fd212062cee..b0d05794904 100644 --- a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/APICompliance/Compliance.jsx +++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/APICompliance/Compliance.jsx @@ -47,7 +47,14 @@ export default function Compliance() { const [api] = useAPI(); const artifactId = api.id; const [statusCounts, setStatusCounts] = useState({ passed: 0, failed: 0, unapplied: 0 }); + const [policyAdherence, setPolicyAdherence] = useState({ + followedPolicies: 0, + violatedPolicies: 0, + pendingPolicies: 0, + unAppliedPolicies: 0, + }); const [complianceStatus, setComplianceStatus] = useState(''); + const [allPoliciesPending, setAllPoliciesPending] = useState(true); useEffect(() => { // Skip the API call if this is a revision @@ -64,7 +71,23 @@ export default function Compliance() { setComplianceStatus(response.body.status); return; } - + + // Check if all policies are pending + const isPending = response.body.governedPolicies.every( + policy => policy.status === 'PENDING' + ); + setAllPoliciesPending(isPending); + + // Calculate policy adherence counts + const policyCounts = response.body.governedPolicies.reduce((acc, policy) => { + if (policy.status === 'FOLLOWED') acc.followedPolicies += 1; + if (policy.status === 'VIOLATED') acc.violatedPolicies += 1; + if (policy.status === 'PENDING') acc.pendingPolicies += 1; + if (policy.status === 'UNAPPLIED') acc.unAppliedPolicies += 1; + return acc; + }, { followedPolicies: 0, violatedPolicies: 0, pendingPolicies: 0, unAppliedPolicies: 0 }); + setPolicyAdherence(policyCounts); + const rulesetMap = new Map(); response.body.governedPolicies.forEach((policy) => { @@ -89,6 +112,12 @@ export default function Compliance() { if (!abortController.signal.aborted) { console.error('Error fetching ruleset adherence data:', error); setStatusCounts({ passed: 0, failed: 0, unapplied: 0 }); + setPolicyAdherence({ + followedPolicies: 0, + violatedPolicies: 0, + pendingPolicies: 0, + unAppliedPolicies: 0 + }); } }); @@ -196,16 +225,140 @@ export default function Compliance() { /> - {/* Rule Violation Summary section */} - - - - - - - + {!allPoliciesPending && ( + <> + {/* Charts section */} + + + + + + + + + + + + + + + + + + + + + + {/* Rule Violation Summary section */} + + + + + + + + + {/* Ruleset Adherence Summary section */} + + + + + + + + + + + + )} - {/* Policy Adherence Summary section */} + {/* Policy Adherence Summary section - always shown */} - - {/* Ruleset Adherence Summary section */} - - - - - - - - - - - - - - - - - - - - - ); diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/APICompliance/PolicyAdherenceSummaryTable.jsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/APICompliance/PolicyAdherenceSummaryTable.jsx index 492026df35b..b0677cebeb0 100644 --- a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/APICompliance/PolicyAdherenceSummaryTable.jsx +++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/APICompliance/PolicyAdherenceSummaryTable.jsx @@ -297,6 +297,7 @@ export default function PolicyAdherenceSummaryTable({ artifactId }) { useContentBase={false} options={{ elevation: 0, + rowsPerPage: 5, }} enableCollapsable renderExpandableRow={renderExpandableRow} diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/APICompliance/RulesetAdherenceSummaryTable.jsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/APICompliance/RulesetAdherenceSummaryTable.jsx index fae3712646a..5086e8bd151 100644 --- a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/APICompliance/RulesetAdherenceSummaryTable.jsx +++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/APICompliance/RulesetAdherenceSummaryTable.jsx @@ -256,6 +256,7 @@ export default function RulesetAdherenceSummaryTable({ artifactId }) { useContentBase={false} options={{ elevation: 0, + rowsPerPage: 5, }} /> ); diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Shared/DonutChart.jsx b/portals/publisher/src/main/webapp/source/src/app/components/Shared/DonutChart.jsx index 884a8dfb7b7..fde19d766fe 100644 --- a/portals/publisher/src/main/webapp/source/src/app/components/Shared/DonutChart.jsx +++ b/portals/publisher/src/main/webapp/source/src/app/components/Shared/DonutChart.jsx @@ -59,7 +59,7 @@ const DonutChart = ({ cornerRadius: 5, cx: 100, startAngle: 90, - endAngle: 470, + endAngle: -270, }]} width={width} height={height} @@ -82,7 +82,7 @@ DonutChart.propTypes = { DonutChart.defaultProps = { height: 200, width: 400, - colors: ['#2E96FF', '#FF5252', 'grey'], + colors: ['#00B81D', '#FF5252', 'grey'], }; export default DonutChart; From ffacdcb9c4ef67605aff4db433645148261e9126 Mon Sep 17 00:00:00 2001 From: Piumal Rathnayake Date: Fri, 28 Feb 2025 12:37:09 +0530 Subject: [PATCH 2/4] Optimize the compliance page and create rule adherence chart --- .../Apis/Details/APICompliance/Compliance.jsx | 113 +++++++- .../PolicyAdherenceSummaryTable.jsx | 24 +- .../APICompliance/RuleViolationSummary.jsx | 241 ++++++++---------- .../RulesetAdherenceSummaryTable.jsx | 30 +-- 4 files changed, 212 insertions(+), 196 deletions(-) diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/APICompliance/Compliance.jsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/APICompliance/Compliance.jsx index b0d05794904..a461a6d246a 100644 --- a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/APICompliance/Compliance.jsx +++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/APICompliance/Compliance.jsx @@ -55,6 +55,13 @@ export default function Compliance() { }); const [complianceStatus, setComplianceStatus] = useState(''); const [allPoliciesPending, setAllPoliciesPending] = useState(true); + const [complianceData, setComplianceData] = useState(null); + const [ruleAdherence, setRuleAdherence] = useState({ + errors: 0, + warnings: 0, + info: 0, + passed: 0, + }); useEffect(() => { // Skip the API call if this is a revision @@ -66,7 +73,7 @@ export default function Compliance() { const restApi = new GovernanceAPI(); restApi.getComplianceByAPIId(artifactId, { signal: abortController.signal }) - .then((response) => { + .then(async (response) => { if (response.body.governedPolicies.length === 0) { setComplianceStatus(response.body.status); return; @@ -98,6 +105,21 @@ export default function Compliance() { }); }); + // Get validation results and ruleset details for each ruleset + const rulesetPromises = Array.from(rulesetMap.keys()).map(async rulesetId => { + const [validationResult, rulesetResult] = await Promise.all([ + restApi.getRulesetValidationResultsByAPIId(artifactId, rulesetId), + restApi.getRulesetById(rulesetId) + ]); + return { + ...validationResult.body, + ruleType: rulesetMap.get(rulesetId).ruleType, + documentationLink: rulesetResult.body.documentationLink, + }; + }); + + const rulesets = await Promise.all(rulesetPromises); + // Count statuses from unique rulesets const counts = Array.from(rulesetMap.values()).reduce((acc, result) => { if (result.status === 'PASSED') acc.passed += 1; @@ -106,11 +128,29 @@ export default function Compliance() { return acc; }, { passed: 0, failed: 0, unapplied: 0 }); + // Calculate rule adherence counts + const ruleCounts = rulesets.reduce((acc, ruleset) => { + // Count violated rules by severity + ruleset.violatedRules.forEach(rule => { + if (rule.severity === 'ERROR') acc.errors += 1; + if (rule.severity === 'WARN') acc.warnings += 1; + if (rule.severity === 'INFO') acc.info += 1; + }); + // Count passed rules + acc.passed += ruleset.followedRules.length; + return acc; + }, { errors: 0, warnings: 0, info: 0, passed: 0 }); + + setRuleAdherence(ruleCounts); setStatusCounts(counts); + setComplianceData({ + governedPolicies: response.body.governedPolicies, + rulesets + }); }) .catch((error) => { if (!abortController.signal.aborted) { - console.error('Error fetching ruleset adherence data:', error); + console.error('Error fetching compliance data:', error); setStatusCounts({ passed: 0, failed: 0, unapplied: 0 }); setPolicyAdherence({ followedPolicies: 0, @@ -118,6 +158,8 @@ export default function Compliance() { pendingPolicies: 0, unAppliedPolicies: 0 }); + setRuleAdherence({ errors: 0, warnings: 0, info: 0, passed: 0 }); + setComplianceData(null); } }); @@ -324,12 +366,67 @@ export default function Compliance() { + + + + + + + + + + {/* Rule Violation Summary section */} - + @@ -351,7 +448,10 @@ export default function Compliance() { defaultMessage='Ruleset Adherence Summary' /> - + @@ -376,7 +476,10 @@ export default function Compliance() { defaultMessage='Policy Adherence Summary' /> - + diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/APICompliance/PolicyAdherenceSummaryTable.jsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/APICompliance/PolicyAdherenceSummaryTable.jsx index b0677cebeb0..76550edf472 100644 --- a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/APICompliance/PolicyAdherenceSummaryTable.jsx +++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/APICompliance/PolicyAdherenceSummaryTable.jsx @@ -26,31 +26,11 @@ import RemoveCircleIcon from '@mui/icons-material/RemoveCircle'; import { useIntl } from 'react-intl'; import PolicyIcon from '@mui/icons-material/Policy'; -import GovernanceAPI from 'AppData/GovernanceAPI'; import Utils from 'AppData/Utils'; -export default function PolicyAdherenceSummaryTable({ artifactId }) { +export default function PolicyAdherenceSummaryTable({ complianceData }) { const intl = useIntl(); - /** - * API call to get Policies - * @returns {Promise}. - */ - function apiCall() { - const restApi = new GovernanceAPI(); - return restApi - .getComplianceByAPIId(artifactId) - .then((result) => { - return result.body.governedPolicies; - }) - .catch((error) => { - if (error.status === 404) { - return []; - } - throw error; - }); - } - const renderProgress = (followed, total, status) => { if (status === 'PENDING') { return ( @@ -287,7 +267,7 @@ export default function PolicyAdherenceSummaryTable({ artifactId }) { return ( { - const restApi = new GovernanceAPI(); - return restApi.getComplianceByAPIId(artifactId) - .then((response) => { - // Create a map of ruleset IDs to their results - const rulesetMap = new Map(); - response.body.governedPolicies.forEach((policy) => { - policy.rulesetValidationResults.forEach((result) => { - if (!rulesetMap.has(result.id)) { - rulesetMap.set(result.id, result); - } - }); - }); - - // Get the unique ruleset IDs - const rulesetIds = [...rulesetMap.keys()]; - - // Get validation results and ruleset details for each ruleset - return Promise.all( - rulesetIds.map(rulesetId => - Promise.all([ - restApi.getRulesetValidationResultsByAPIId(artifactId, rulesetId), - restApi.getRulesetById(rulesetId) - ]) - .then(([validationResult, rulesetResult]) => ({ - ...validationResult.body, - ruleType: rulesetMap.get(rulesetId).ruleType, - documentationLink: rulesetResult.body.documentationLink, - }))), - ).then((rulesets) => { - // Create rulesets array with severities catagorized - const rulesetCategories = rulesets.map(ruleset => ({ - id: ruleset.id, - rulesetName: ruleset.name, - ruleType: ruleset.ruleType, - documentationLink: ruleset.documentationLink, - error: ruleset.violatedRules.filter(rule => rule.severity === 'ERROR'), - warn: ruleset.violatedRules.filter(rule => rule.severity === 'WARN'), - info: ruleset.violatedRules.filter(rule => rule.severity === 'INFO'), - passed: ruleset.followedRules - })); - - // Group by severity level - const severityGroups = { - errors: [], - warnings: [], - info: [], - passed: [] - }; - - rulesetCategories.forEach(ruleset => { - if (ruleset.error.length > 0) { - severityGroups.errors.push({ - id: ruleset.id, - rulesetName: ruleset.rulesetName, - documentationLink: ruleset.documentationLink, - ruleType: ruleset.ruleType, - rules: ruleset.error - }); - } - if (ruleset.warn.length > 0) { - severityGroups.warnings.push({ - id: ruleset.id, - rulesetName: ruleset.rulesetName, - documentationLink: ruleset.documentationLink, - ruleType: ruleset.ruleType, - rules: ruleset.warn - }); - } - if (ruleset.info.length > 0) { - severityGroups.info.push({ - id: ruleset.id, - rulesetName: ruleset.rulesetName, - documentationLink: ruleset.documentationLink, - ruleType: ruleset.ruleType, - rules: ruleset.info - }); - } - if (ruleset.passed.length > 0) { - severityGroups.passed.push({ - id: ruleset.id, - rulesetName: ruleset.rulesetName, - documentationLink: ruleset.documentationLink, - ruleType: ruleset.ruleType, - rules: ruleset.passed - }); - } - }); - - return severityGroups; - }); - }) - .catch((error) => { - console.error('Error fetching ruleset adherence data:', error); - return { - errors: [], - warnings: [], - info: [], - passed: [] - }; - }); - }; + const [selectedTab, setSelectedTab] = useState(0); + const [expandedItems, setExpandedItems] = useState([]); + const [page, setPage] = useState(0); + const [rowsPerPage, setRowsPerPage] = useState(5); // Remove the mock complianceData and use state instead - const [complianceData, setComplianceData] = React.useState({ + const [complianceDataState, setComplianceData] = useState({ errors: [], warnings: [], info: [], passed: [] }); - React.useEffect(() => { - apiCall().then(setComplianceData); - }, [artifactId]); + useEffect(() => { + if (!complianceData) return; + + // Create rulesets array with severities categorized + const rulesetCategories = complianceData.rulesets.map(ruleset => ({ + id: ruleset.id, + rulesetName: ruleset.name, + ruleType: ruleset.ruleType, + documentationLink: ruleset.documentationLink, + error: ruleset.violatedRules.filter(rule => rule.severity === 'ERROR'), + warn: ruleset.violatedRules.filter(rule => rule.severity === 'WARN'), + info: ruleset.violatedRules.filter(rule => rule.severity === 'INFO'), + passed: ruleset.followedRules + })); + + // Group by severity level + const severityGroups = { + errors: [], + warnings: [], + info: [], + passed: [] + }; + + rulesetCategories.forEach(ruleset => { + if (ruleset.error.length > 0) { + severityGroups.errors.push({ + id: ruleset.id, + rulesetName: ruleset.rulesetName, + documentationLink: ruleset.documentationLink, + ruleType: ruleset.ruleType, + rules: ruleset.error + }); + } + if (ruleset.warn.length > 0) { + severityGroups.warnings.push({ + id: ruleset.id, + rulesetName: ruleset.rulesetName, + documentationLink: ruleset.documentationLink, + ruleType: ruleset.ruleType, + rules: ruleset.warn + }); + } + if (ruleset.info.length > 0) { + severityGroups.info.push({ + id: ruleset.id, + rulesetName: ruleset.rulesetName, + documentationLink: ruleset.documentationLink, + ruleType: ruleset.ruleType, + rules: ruleset.info + }); + } + if (ruleset.passed.length > 0) { + severityGroups.passed.push({ + id: ruleset.id, + rulesetName: ruleset.rulesetName, + documentationLink: ruleset.documentationLink, + ruleType: ruleset.ruleType, + rules: ruleset.passed + }); + } + }); + + setComplianceData(severityGroups); + }, [complianceData]); const handleTabChange = (e, newValue) => { setSelectedTab(newValue); @@ -332,7 +288,7 @@ export default function RuleViolationSummary({ artifactId }) { }} > - @@ -464,6 +420,25 @@ export default function RuleViolationSummary({ artifactId }) { } }; + const renderTabContent = (data, emptyMessage, isPassed = false) => { + if (!complianceData) { + return ( + + + + ); + } + + return data.length > 0 + ? renderComplianceCards(data, isPassed) + : renderEmptyContent(emptyMessage); + }; + return ( <> } @@ -529,7 +504,7 @@ export default function RuleViolationSummary({ artifactId }) { label={intl.formatMessage({ id: 'Apis.Details.Compliance.RuleViolation.tab.warnings', defaultMessage: 'Warnings ({count})', - }, { count: getTotalRuleCount(complianceData.warnings) })} + }, { count: getTotalRuleCount(complianceDataState.warnings) })} /> } @@ -537,7 +512,7 @@ export default function RuleViolationSummary({ artifactId }) { label={intl.formatMessage({ id: 'Apis.Details.Compliance.RuleViolation.tab.info', defaultMessage: 'Info ({count})', - }, { count: getTotalRuleCount(complianceData.info) })} + }, { count: getTotalRuleCount(complianceDataState.info) })} /> } @@ -545,29 +520,13 @@ export default function RuleViolationSummary({ artifactId }) { label={intl.formatMessage({ id: 'Apis.Details.Compliance.RuleViolation.tab.passed', defaultMessage: 'Passed ({count})', - }, { count: getTotalRuleCount(complianceData.passed) })} + }, { count: getTotalRuleCount(complianceDataState.passed) })} /> - {selectedTab === 0 && ( - complianceData.errors.length > 0 - ? renderComplianceCards(complianceData.errors) - : renderEmptyContent(getEmptyMessage(0)) - )} - {selectedTab === 1 && ( - complianceData.warnings.length > 0 - ? renderComplianceCards(complianceData.warnings) - : renderEmptyContent(getEmptyMessage(1)) - )} - {selectedTab === 2 && ( - complianceData.info.length > 0 - ? renderComplianceCards(complianceData.info) - : renderEmptyContent(getEmptyMessage(2)) - )} - {selectedTab === 3 && ( - complianceData.passed.length > 0 - ? renderComplianceCards(complianceData.passed, true) - : renderEmptyContent(getEmptyMessage(3)) - )} + {selectedTab === 0 && renderTabContent(complianceDataState.errors, getEmptyMessage(0))} + {selectedTab === 1 && renderTabContent(complianceDataState.warnings, getEmptyMessage(1))} + {selectedTab === 2 && renderTabContent(complianceDataState.info, getEmptyMessage(2))} + {selectedTab === 3 && renderTabContent(complianceDataState.passed, getEmptyMessage(3), true)} ); } diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/APICompliance/RulesetAdherenceSummaryTable.jsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/APICompliance/RulesetAdherenceSummaryTable.jsx index 5086e8bd151..448e8bde718 100644 --- a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/APICompliance/RulesetAdherenceSummaryTable.jsx +++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/APICompliance/RulesetAdherenceSummaryTable.jsx @@ -22,39 +22,13 @@ import ListBase from 'AppComponents/Addons/Addons/ListBase'; import ErrorIcon from '@mui/icons-material/Error'; import WarningIcon from '@mui/icons-material/Warning'; import InfoIcon from '@mui/icons-material/Info'; -import GovernanceAPI from 'AppData/GovernanceAPI'; import { useIntl } from 'react-intl'; import AssignmentIcon from '@mui/icons-material/Assignment'; import Utils from 'AppData/Utils'; -export default function RulesetAdherenceSummaryTable({ artifactId }) { +export default function RulesetAdherenceSummaryTable({ complianceData }) { const intl = useIntl(); - const apiCall = () => { - const restApi = new GovernanceAPI(); - return restApi.getComplianceByAPIId(artifactId) - .then((response) => { - // Get unique ruleset IDs from all policies - const rulesetIds = [...new Set( - response.body.governedPolicies.flatMap(policy => - policy.rulesetValidationResults.map(result => result.id) - ) - )]; - - // Get validation results for each ruleset - return Promise.all( - rulesetIds.map(rulesetId => - restApi.getRulesetValidationResultsByAPIId(artifactId, rulesetId) - .then((result) => result.body) - ) - ); - }) - .catch((error) => { - console.error('Error fetching ruleset adherence data:', error); - return []; - }); - }; - const renderComplianceIcons = (violations) => { const { error, warn, info } = violations; return ( @@ -246,7 +220,7 @@ export default function RulesetAdherenceSummaryTable({ artifactId }) { return ( Date: Fri, 28 Feb 2025 15:30:44 +0530 Subject: [PATCH 3/4] Optimize the API compliance page in admin portal --- .../APICompliance/Compliance.jsx | 356 ++++++++++++++---- .../PolicyAdherenceSummaryTable.jsx | 22 +- .../APICompliance/RuleViolationSummary.jsx | 231 +++++------- .../RulesetAdherenceSummaryTable.jsx | 29 +- .../APICompliance/RuleViolationSummary.jsx | 1 - 5 files changed, 381 insertions(+), 258 deletions(-) diff --git a/portals/admin/src/main/webapp/source/src/app/components/Governance/ComplianceDashboard/APICompliance/Compliance.jsx b/portals/admin/src/main/webapp/source/src/app/components/Governance/ComplianceDashboard/APICompliance/Compliance.jsx index 094be9ec7d5..3ea8af9fe1a 100644 --- a/portals/admin/src/main/webapp/source/src/app/components/Governance/ComplianceDashboard/APICompliance/Compliance.jsx +++ b/portals/admin/src/main/webapp/source/src/app/components/Governance/ComplianceDashboard/APICompliance/Compliance.jsx @@ -38,14 +38,28 @@ export default function Compliance(props) { const [statusCounts, setStatusCounts] = useState({ passed: 0, failed: 0, unapplied: 0 }); const [artifactName, setArtifactName] = useState(''); const [artifactOwner, setArtifactOwner] = useState(''); + const [policyAdherence, setPolicyAdherence] = useState({ + followedPolicies: 0, + violatedPolicies: 0, + pendingPolicies: 0, + unAppliedPolicies: 0, + }); const [complianceStatus, setComplianceStatus] = useState(''); + const [allPoliciesPending, setAllPoliciesPending] = useState(true); + const [complianceData, setComplianceData] = useState(null); + const [ruleAdherence, setRuleAdherence] = useState({ + errors: 0, + warnings: 0, + info: 0, + passed: 0, + }); useEffect(() => { const abortController = new AbortController(); const restApi = new GovernanceAPI(); restApi.getComplianceByAPIId(artifactId, { signal: abortController.signal }) - .then((response) => { + .then(async (response) => { setArtifactName( response.body.info.name + ' :' + response.body.info.version, @@ -56,6 +70,24 @@ export default function Compliance(props) { return; } + // Check if all policies are pending + const isPending = response.body.governedPolicies.every( + (policy) => policy.status === 'PENDING', + ); + setAllPoliciesPending(isPending); + + // Calculate policy adherence counts + const policyCounts = response.body.governedPolicies.reduce((acc, policy) => { + if (policy.status === 'FOLLOWED') acc.followedPolicies += 1; + if (policy.status === 'VIOLATED') acc.violatedPolicies += 1; + if (policy.status === 'PENDING') acc.pendingPolicies += 1; + if (policy.status === 'UNAPPLIED') acc.unAppliedPolicies += 1; + return acc; + }, { + followedPolicies: 0, violatedPolicies: 0, pendingPolicies: 0, unAppliedPolicies: 0, + }); + setPolicyAdherence(policyCounts); + const rulesetMap = new Map(); response.body.governedPolicies.forEach((policy) => { @@ -66,6 +98,21 @@ export default function Compliance(props) { }); }); + // Get validation results and ruleset details for each ruleset + const rulesetPromises = Array.from(rulesetMap.keys()).map(async (rulesetId) => { + const [validationResult, rulesetResult] = await Promise.all([ + restApi.getRulesetValidationResultsByAPIId(artifactId, rulesetId), + restApi.getRulesetById(rulesetId), + ]); + return { + ...validationResult.body, + ruleType: rulesetMap.get(rulesetId).ruleType, + documentationLink: rulesetResult.body.documentationLink, + }; + }); + + const rulesets = await Promise.all(rulesetPromises); + // Count statuses from unique rulesets const counts = Array.from(rulesetMap.values()).reduce((acc, result) => { if (result.status === 'PASSED') acc.passed += 1; @@ -74,13 +121,42 @@ export default function Compliance(props) { return acc; }, { passed: 0, failed: 0, unapplied: 0 }); + // Calculate rule adherence counts + const ruleCounts = rulesets.reduce((acc, ruleset) => { + // Count violated rules by severity + ruleset.violatedRules.forEach((rule) => { + if (rule.severity === 'ERROR') acc.errors += 1; + if (rule.severity === 'WARN') acc.warnings += 1; + if (rule.severity === 'INFO') acc.info += 1; + }); + // Count passed rules + acc.passed += ruleset.followedRules.length; + return acc; + }, { + errors: 0, warnings: 0, info: 0, passed: 0, + }); + setRuleAdherence(ruleCounts); setStatusCounts(counts); + setComplianceData({ + governedPolicies: response.body.governedPolicies, + rulesets, + }); }) .catch((error) => { if (!abortController.signal.aborted) { - console.error('Error fetching ruleset adherence data:', error); + console.error('Error fetching compliance data:', error); setStatusCounts({ passed: 0, failed: 0, unapplied: 0 }); setArtifactName(''); + setPolicyAdherence({ + followedPolicies: 0, + violatedPolicies: 0, + pendingPolicies: 0, + unAppliedPolicies: 0, + }); + setRuleAdherence({ + errors: 0, warnings: 0, info: 0, passed: 0, + }); + setComplianceData(null); } }); @@ -200,16 +276,201 @@ export default function Compliance(props) { - {/* Rule Violation Summary section */} - - - - - - - + {!allPoliciesPending && ( + <> + {/* Charts section */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - {/* Policy Adherence Summary section */} + {/* Rule Violation Summary section */} + + + + + + + + + {/* Ruleset Adherence Summary section */} + + + + + + + + + + + + )} + + {/* Policy Adherence Summary section - always shown */} - - - - - - {/* Ruleset Adherence Summary section */} - - - - - - - - - - - - - - - - - - ); diff --git a/portals/admin/src/main/webapp/source/src/app/components/Governance/ComplianceDashboard/APICompliance/PolicyAdherenceSummaryTable.jsx b/portals/admin/src/main/webapp/source/src/app/components/Governance/ComplianceDashboard/APICompliance/PolicyAdherenceSummaryTable.jsx index 772264c5957..4b4d1b5afb7 100644 --- a/portals/admin/src/main/webapp/source/src/app/components/Governance/ComplianceDashboard/APICompliance/PolicyAdherenceSummaryTable.jsx +++ b/portals/admin/src/main/webapp/source/src/app/components/Governance/ComplianceDashboard/APICompliance/PolicyAdherenceSummaryTable.jsx @@ -28,28 +28,11 @@ import RemoveCircleIcon from '@mui/icons-material/RemoveCircle'; import { useIntl } from 'react-intl'; import PolicyIcon from '@mui/icons-material/Policy'; -import GovernanceAPI from 'AppData/GovernanceAPI'; import Utils from 'AppData/Utils'; -export default function PolicyAdherenceSummaryTable({ artifactId }) { +export default function PolicyAdherenceSummaryTable({ complianceData }) { const intl = useIntl(); - /** - * API call to get Policies - * @returns {Promise}. - */ - function apiCall() { - const restApi = new GovernanceAPI(); - return restApi - .getComplianceByAPIId(artifactId) - .then((result) => { - return result.body.governedPolicies; - }) - .catch((error) => { - throw error; - }); - } - const renderProgress = (followed, total, status) => { if (status === 'PENDING') { return ( @@ -286,7 +269,7 @@ export default function PolicyAdherenceSummaryTable({ artifactId }) { return ( { - const restApi = new GovernanceAPI(); - return restApi.getComplianceByAPIId(artifactId) - .then((response) => { - // Create a map of ruleset IDs to their results - const rulesetMap = new Map(); - response.body.governedPolicies.forEach((policy) => { - policy.rulesetValidationResults.forEach((result) => { - if (!rulesetMap.has(result.id)) { - rulesetMap.set(result.id, result); - } - }); - }); - - // Get unique ruleset IDs from all policies - const rulesetIds = [...rulesetMap.keys()]; - - // Get validation results for each ruleset - return Promise.all( - rulesetIds.map((rulesetId) => restApi.getRulesetValidationResultsByAPIId(artifactId, rulesetId) - .then((result) => ({ - ...result.body, - ruleType: rulesetMap.get(rulesetId).ruleType, - }))), - ).then((rulesets) => { - // Create rulesets array with severities catagorized - const rulesetCategories = rulesets.map((ruleset) => ({ - id: ruleset.id, - rulesetName: ruleset.name, - ruleType: ruleset.ruleType, - error: ruleset.violatedRules.filter((rule) => rule.severity === 'ERROR'), - warn: ruleset.violatedRules.filter((rule) => rule.severity === 'WARN'), - info: ruleset.violatedRules.filter((rule) => rule.severity === 'INFO'), - passed: ruleset.followedRules, - })); - - // Group by severity level - const severityGroups = { - errors: [], - warnings: [], - info: [], - passed: [], - }; - - rulesetCategories.forEach((ruleset) => { - if (ruleset.error.length > 0) { - severityGroups.errors.push({ - id: ruleset.id, - rulesetName: ruleset.rulesetName, - ruleType: ruleset.ruleType, - rules: ruleset.error, - }); - } - if (ruleset.warn.length > 0) { - severityGroups.warnings.push({ - id: ruleset.id, - rulesetName: ruleset.rulesetName, - ruleType: ruleset.ruleType, - rules: ruleset.warn, - }); - } - if (ruleset.info.length > 0) { - severityGroups.info.push({ - id: ruleset.id, - rulesetName: ruleset.rulesetName, - ruleType: ruleset.ruleType, - rules: ruleset.info, - }); - } - if (ruleset.passed.length > 0) { - severityGroups.passed.push({ - id: ruleset.id, - rulesetName: ruleset.rulesetName, - ruleType: ruleset.ruleType, - rules: ruleset.passed, - }); - } - }); - - return severityGroups; - }); - }) - .catch((error) => { - console.error('Error fetching ruleset adherence data:', error); - return { - errors: [], - warnings: [], - info: [], - passed: [], - }; - }); - }; - - // Remove the mock complianceData and use state instead - const [complianceData, setComplianceData] = React.useState({ + const [complianceDataState, setComplianceData] = useState({ errors: [], warnings: [], info: [], passed: [], }); - React.useEffect(() => { - apiCall().then(setComplianceData); - }, [artifactId]); + useEffect(() => { + if (!complianceData) return; + + // Create rulesets array with severities categorized + const rulesetCategories = complianceData.rulesets.map((ruleset) => ({ + id: ruleset.id, + rulesetName: ruleset.name, + ruleType: ruleset.ruleType, + documentationLink: ruleset.documentationLink, + error: ruleset.violatedRules.filter((rule) => rule.severity === 'ERROR'), + warn: ruleset.violatedRules.filter((rule) => rule.severity === 'WARN'), + info: ruleset.violatedRules.filter((rule) => rule.severity === 'INFO'), + passed: ruleset.followedRules, + })); + + // Group by severity level + const severityGroups = { + errors: [], + warnings: [], + info: [], + passed: [], + }; + + rulesetCategories.forEach((ruleset) => { + if (ruleset.error.length > 0) { + severityGroups.errors.push({ + id: ruleset.id, + rulesetName: ruleset.rulesetName, + documentationLink: ruleset.documentationLink, + ruleType: ruleset.ruleType, + rules: ruleset.error, + }); + } + if (ruleset.warn.length > 0) { + severityGroups.warnings.push({ + id: ruleset.id, + rulesetName: ruleset.rulesetName, + documentationLink: ruleset.documentationLink, + ruleType: ruleset.ruleType, + rules: ruleset.warn, + }); + } + if (ruleset.info.length > 0) { + severityGroups.info.push({ + id: ruleset.id, + rulesetName: ruleset.rulesetName, + documentationLink: ruleset.documentationLink, + ruleType: ruleset.ruleType, + rules: ruleset.info, + }); + } + if (ruleset.passed.length > 0) { + severityGroups.passed.push({ + id: ruleset.id, + rulesetName: ruleset.rulesetName, + documentationLink: ruleset.documentationLink, + ruleType: ruleset.ruleType, + rules: ruleset.passed, + }); + } + }); + + setComplianceData(severityGroups); + }, [complianceData]); const handleTabChange = (e, newValue) => { setSelectedTab(newValue); @@ -430,6 +395,26 @@ export default function RuleViolationSummary({ artifactId }) { } }; + const renderTabContent = (data, emptyMessage, isPassed = false) => { + if (!complianceData) { + return ( + + + + ); + } + + return data.length > 0 + ? renderComplianceCards(data, isPassed) + : renderEmptyContent(emptyMessage); + }; + return ( <> } @@ -495,7 +480,7 @@ export default function RuleViolationSummary({ artifactId }) { label={intl.formatMessage({ id: 'Governance.ComplianceDashboard.APICompliance.RuleViolation.tab.warnings', defaultMessage: 'Warnings ({count})', - }, { count: getTotalRuleCount(complianceData.warnings) })} + }, { count: getTotalRuleCount(complianceDataState.warnings) })} /> } @@ -503,7 +488,7 @@ export default function RuleViolationSummary({ artifactId }) { label={intl.formatMessage({ id: 'Governance.ComplianceDashboard.APICompliance.RuleViolation.tab.info', defaultMessage: 'Info ({count})', - }, { count: getTotalRuleCount(complianceData.info) })} + }, { count: getTotalRuleCount(complianceDataState.info) })} /> } @@ -511,29 +496,13 @@ export default function RuleViolationSummary({ artifactId }) { label={intl.formatMessage({ id: 'Governance.ComplianceDashboard.APICompliance.RuleViolation.tab.passed', defaultMessage: 'Passed ({count})', - }, { count: getTotalRuleCount(complianceData.passed) })} + }, { count: getTotalRuleCount(complianceDataState.passed) })} /> - {selectedTab === 0 && ( - complianceData.errors.length > 0 - ? renderComplianceCards(complianceData.errors) - : renderEmptyContent(getEmptyMessage(0)) - )} - {selectedTab === 1 && ( - complianceData.warnings.length > 0 - ? renderComplianceCards(complianceData.warnings) - : renderEmptyContent(getEmptyMessage(1)) - )} - {selectedTab === 2 && ( - complianceData.info.length > 0 - ? renderComplianceCards(complianceData.info) - : renderEmptyContent(getEmptyMessage(2)) - )} - {selectedTab === 3 && ( - complianceData.passed.length > 0 - ? renderComplianceCards(complianceData.passed, true) - : renderEmptyContent(getEmptyMessage(3)) - )} + {selectedTab === 0 && renderTabContent(complianceDataState.errors, getEmptyMessage(0))} + {selectedTab === 1 && renderTabContent(complianceDataState.warnings, getEmptyMessage(1))} + {selectedTab === 2 && renderTabContent(complianceDataState.info, getEmptyMessage(2))} + {selectedTab === 3 && renderTabContent(complianceDataState.passed, getEmptyMessage(3), true)} ); } diff --git a/portals/admin/src/main/webapp/source/src/app/components/Governance/ComplianceDashboard/APICompliance/RulesetAdherenceSummaryTable.jsx b/portals/admin/src/main/webapp/source/src/app/components/Governance/ComplianceDashboard/APICompliance/RulesetAdherenceSummaryTable.jsx index ce9e2c8ebf2..369be634c2b 100644 --- a/portals/admin/src/main/webapp/source/src/app/components/Governance/ComplianceDashboard/APICompliance/RulesetAdherenceSummaryTable.jsx +++ b/portals/admin/src/main/webapp/source/src/app/components/Governance/ComplianceDashboard/APICompliance/RulesetAdherenceSummaryTable.jsx @@ -24,37 +24,13 @@ import ListBase from 'AppComponents/AdminPages/Addons/ListBase'; import ErrorIcon from '@mui/icons-material/Error'; import WarningIcon from '@mui/icons-material/Warning'; import InfoIcon from '@mui/icons-material/Info'; -import GovernanceAPI from 'AppData/GovernanceAPI'; import { useIntl } from 'react-intl'; import AssignmentIcon from '@mui/icons-material/Assignment'; import Utils from 'AppData/Utils'; -export default function RulesetAdherenceSummaryTable({ artifactId }) { +export default function RulesetAdherenceSummaryTable({ complianceData }) { const intl = useIntl(); - const apiCall = () => { - const restApi = new GovernanceAPI(); - return restApi.getComplianceByAPIId(artifactId) - .then((response) => { - // Get unique ruleset IDs from all policies - const rulesetIds = [...new Set( - response.body.governedPolicies.flatMap( - (policy) => policy.rulesetValidationResults.map((result) => result.id), - ), - )]; - - // Get validation results for each ruleset - return Promise.all( - rulesetIds.map((rulesetId) => restApi.getRulesetValidationResultsByAPIId(artifactId, rulesetId) - .then((result) => result.body)), - ); - }) - .catch((error) => { - console.error('Error fetching ruleset adherence data:', error); - return []; - }); - }; - const renderComplianceIcons = (violations) => { const { error, warn, info } = violations; return ( @@ -247,7 +223,7 @@ export default function RulesetAdherenceSummaryTable({ artifactId }) { return ( ); diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/APICompliance/RuleViolationSummary.jsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/APICompliance/RuleViolationSummary.jsx index eaff91a6d87..79b910e1e16 100644 --- a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/APICompliance/RuleViolationSummary.jsx +++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/APICompliance/RuleViolationSummary.jsx @@ -42,7 +42,6 @@ export default function RuleViolationSummary({ complianceData }) { const [page, setPage] = useState(0); const [rowsPerPage, setRowsPerPage] = useState(5); - // Remove the mock complianceData and use state instead const [complianceDataState, setComplianceData] = useState({ errors: [], warnings: [], From 47d2686fc553bee69571f5393f8bffb5b0a0735d Mon Sep 17 00:00:00 2001 From: Piumal Rathnayake Date: Fri, 28 Feb 2025 16:01:04 +0530 Subject: [PATCH 4/4] Change compliance progress bar colors --- .../APICompliance/PolicyAdherenceSummaryTable.jsx | 3 +-- .../Governance/ComplianceDashboard/ApiComplianceTable.jsx | 3 +-- .../Governance/ComplianceDashboard/PolicyAdherenceTable.jsx | 3 +-- .../Apis/Details/APICompliance/PolicyAdherenceSummaryTable.jsx | 3 +-- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/portals/admin/src/main/webapp/source/src/app/components/Governance/ComplianceDashboard/APICompliance/PolicyAdherenceSummaryTable.jsx b/portals/admin/src/main/webapp/source/src/app/components/Governance/ComplianceDashboard/APICompliance/PolicyAdherenceSummaryTable.jsx index 4b4d1b5afb7..3666ad95504 100644 --- a/portals/admin/src/main/webapp/source/src/app/components/Governance/ComplianceDashboard/APICompliance/PolicyAdherenceSummaryTable.jsx +++ b/portals/admin/src/main/webapp/source/src/app/components/Governance/ComplianceDashboard/APICompliance/PolicyAdherenceSummaryTable.jsx @@ -57,7 +57,6 @@ export default function PolicyAdherenceSummaryTable({ complianceData }) { } const percentage = (followed / total) * 100; - const isComplete = followed === total; return ( @@ -77,7 +76,7 @@ export default function PolicyAdherenceSummaryTable({ complianceData }) { borderRadius: 1, backgroundColor: '#e0e0e0', '& .MuiLinearProgress-bar': { - backgroundColor: isComplete ? '#00B81D' : '#FF5252', + backgroundColor: '#00B81D', borderRadius: 1, }, }} diff --git a/portals/admin/src/main/webapp/source/src/app/components/Governance/ComplianceDashboard/ApiComplianceTable.jsx b/portals/admin/src/main/webapp/source/src/app/components/Governance/ComplianceDashboard/ApiComplianceTable.jsx index 5f4ab10b791..b7c4abc3262 100644 --- a/portals/admin/src/main/webapp/source/src/app/components/Governance/ComplianceDashboard/ApiComplianceTable.jsx +++ b/portals/admin/src/main/webapp/source/src/app/components/Governance/ComplianceDashboard/ApiComplianceTable.jsx @@ -76,7 +76,6 @@ export default function ApiComplianceTable() { } const percentage = (followed / total) * 100; - const isComplete = followed === total; return ( @@ -96,7 +95,7 @@ export default function ApiComplianceTable() { borderRadius: 1, backgroundColor: '#e0e0e0', '& .MuiLinearProgress-bar': { - backgroundColor: isComplete ? '#00B81D' : '#FF5252', + backgroundColor: '#00B81D', borderRadius: 1, }, }} diff --git a/portals/admin/src/main/webapp/source/src/app/components/Governance/ComplianceDashboard/PolicyAdherenceTable.jsx b/portals/admin/src/main/webapp/source/src/app/components/Governance/ComplianceDashboard/PolicyAdherenceTable.jsx index b457b21fe2e..9560f5144e2 100644 --- a/portals/admin/src/main/webapp/source/src/app/components/Governance/ComplianceDashboard/PolicyAdherenceTable.jsx +++ b/portals/admin/src/main/webapp/source/src/app/components/Governance/ComplianceDashboard/PolicyAdherenceTable.jsx @@ -89,7 +89,6 @@ export default function PolicyAdherenceTable() { } const percentage = (followed / total) * 100; - const isComplete = followed === total; return ( @@ -109,7 +108,7 @@ export default function PolicyAdherenceTable() { borderRadius: 1, backgroundColor: '#e0e0e0', '& .MuiLinearProgress-bar': { - backgroundColor: isComplete ? '#00B81D' : '#FF5252', + backgroundColor: '#00B81D', borderRadius: 1, }, }} diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/APICompliance/PolicyAdherenceSummaryTable.jsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/APICompliance/PolicyAdherenceSummaryTable.jsx index 76550edf472..ea3bee3646b 100644 --- a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/APICompliance/PolicyAdherenceSummaryTable.jsx +++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/APICompliance/PolicyAdherenceSummaryTable.jsx @@ -55,7 +55,6 @@ export default function PolicyAdherenceSummaryTable({ complianceData }) { } const percentage = (followed / total) * 100; - const isComplete = followed === total; return ( @@ -75,7 +74,7 @@ export default function PolicyAdherenceSummaryTable({ complianceData }) { borderRadius: 1, backgroundColor: '#e0e0e0', '& .MuiLinearProgress-bar': { - backgroundColor: isComplete ? '#00B81D' : '#FF5252', + backgroundColor: '#00B81D', borderRadius: 1, }, }}