diff --git a/frontend/src/component/project/Project/ProjectInsights/ProjectHealthChart.test.tsx b/frontend/src/component/project/Project/ProjectInsights/ProjectHealthChart.test.tsx new file mode 100644 index 0000000000..8bb05b261e --- /dev/null +++ b/frontend/src/component/project/Project/ProjectInsights/ProjectHealthChart.test.tsx @@ -0,0 +1,113 @@ +import React from 'react'; +import '@testing-library/jest-dom'; +import { ProjectHealthChart } from './ProjectHealthChart'; +import { render } from 'utils/testRenderer'; +import { screen } from '@testing-library/react'; + +describe('ProjectHealthChart', () => { + test('renders correctly with no flags', () => { + const { container } = render( + , + ); + + const activeCircle = container.querySelector( + 'circle[data-testid="active-circle"]', + ); + const staleCircle = container.querySelector( + 'circle[data-testid="stale-circle"]', + ); + const potentiallyStaleCircle = container.querySelector( + 'circle[data-testid="potentially-stale-circle"]', + ); + + expect(activeCircle).toBeInTheDocument(); + expect(staleCircle).not.toBeInTheDocument(); + expect(potentiallyStaleCircle).not.toBeInTheDocument(); + }); + + test('renders correctly with 1 active and 0 stale', () => { + const { container } = render( + , + ); + + const activeCircle = container.querySelector( + 'circle[data-testid="active-circle"]', + ); + const staleCircle = container.querySelector( + 'circle[data-testid="stale-circle"]', + ); + const potentiallyStaleCircle = container.querySelector( + 'circle[data-testid="potentially-stale-circle"]', + ); + + expect(activeCircle).toBeInTheDocument(); + expect(staleCircle).not.toBeInTheDocument(); + expect(potentiallyStaleCircle).not.toBeInTheDocument(); + }); + + test('renders correctly with 0 active and 1 stale', () => { + const { container } = render( + , + ); + + const staleCircle = container.querySelector( + 'circle[data-testid="stale-circle"]', + ); + + expect(staleCircle).toBeInTheDocument(); + }); + + test('renders correctly with active, stale and potentially stale', () => { + const { container } = render( + , + ); + + const activeCircle = container.querySelector( + 'circle[data-testid="active-circle"]', + ); + const staleCircle = container.querySelector( + 'circle[data-testid="stale-circle"]', + ); + const potentiallyStaleCircle = container.querySelector( + 'circle[data-testid="potentially-stale-circle"]', + ); + + expect(activeCircle).toBeInTheDocument(); + expect(staleCircle).toBeInTheDocument(); + expect(potentiallyStaleCircle).toBeInTheDocument(); + }); + + test('renders flags count and health', () => { + const { container } = render( + , + ); + + expect(screen.queryByText('3 flags')).toBeInTheDocument(); + expect(screen.queryByText('50%')).toBeInTheDocument(); + }); +}); diff --git a/frontend/src/component/project/Project/ProjectInsights/ProjectHealthChart.tsx b/frontend/src/component/project/Project/ProjectInsights/ProjectHealthChart.tsx new file mode 100644 index 0000000000..70d407f1ce --- /dev/null +++ b/frontend/src/component/project/Project/ProjectInsights/ProjectHealthChart.tsx @@ -0,0 +1,128 @@ +import type React from 'react'; +import { useTheme } from '@mui/material'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; + +interface ProgressComponentProps { + active: number; + stale: number; + potentiallyStale: number; + health: number; +} + +export const ProjectHealthChart: React.FC = ({ + active, + stale, + potentiallyStale, + health, +}) => { + const theme = useTheme(); + const gap = active === 0 || stale === 0 ? 0 : 10; + const strokeWidth = 6; + const radius = 50 - strokeWidth / 2; + const circumference = 2 * Math.PI * radius; + const gapAngle = (gap / circumference) * 360; + + const totalCount = active + stale; + const activePercentage = + totalCount === 0 ? 100 : (active / totalCount) * 100; + const stalePercentage = totalCount === 0 ? 0 : (stale / totalCount) * 100; + const potentiallyStalePercentage = + active === 0 ? 0 : (potentiallyStale / active) * 100; + + const activeLength = (activePercentage / 100) * circumference - gap; + const staleLength = (stalePercentage / 100) * circumference - gap; + const potentiallyStaleLength = + (potentiallyStalePercentage / 100) * activeLength; + + const activeRotation = -90 + gapAngle / 2; + const potentiallyStaleRotation = + activeRotation + + ((activeLength - potentiallyStaleLength) / circumference) * 360; + const staleRotation = + activeRotation + (activeLength / circumference) * 360 + gapAngle; + + const innerRadius = radius / 1.2; + + return ( + + Project Health Chart + + + 0} + show={ + + } + /> + + 0} + show={ + + } + /> + + + + + {health}% + + + {active + stale} flags + + + ); +};