1
0
mirror of https://github.com/Unleash/unleash.git synced 2024-12-22 19:07:54 +01:00

feat: project health chart (#6594)

This commit is contained in:
Mateusz Kwasniewski 2024-03-18 15:44:57 +01:00 committed by GitHub
parent c2015c6f33
commit 72758605b0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 241 additions and 0 deletions

View File

@ -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(
<ProjectHealthChart
active={0}
stale={0}
potentiallyStale={0}
health={0}
/>,
);
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(
<ProjectHealthChart
active={1}
stale={0}
potentiallyStale={0}
health={100}
/>,
);
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(
<ProjectHealthChart
active={0}
stale={1}
potentiallyStale={0}
health={0}
/>,
);
const staleCircle = container.querySelector(
'circle[data-testid="stale-circle"]',
);
expect(staleCircle).toBeInTheDocument();
});
test('renders correctly with active, stale and potentially stale', () => {
const { container } = render(
<ProjectHealthChart
active={2}
stale={1}
potentiallyStale={1}
health={50}
/>,
);
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(
<ProjectHealthChart
active={2}
stale={1}
potentiallyStale={1}
health={50}
/>,
);
expect(screen.queryByText('3 flags')).toBeInTheDocument();
expect(screen.queryByText('50%')).toBeInTheDocument();
});
});

View File

@ -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<ProgressComponentProps> = ({
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 (
<svg width='170' height='170' viewBox='0 0 100 100'>
<title>Project Health Chart</title>
<circle
data-testid='active-circle'
cx='50'
cy='50'
r={radius}
fill='none'
stroke={theme.palette.success.border}
strokeWidth={strokeWidth}
strokeLinecap='round'
strokeDasharray={`${activeLength} ${circumference}`}
transform={`rotate(${activeRotation} 50 50)`}
/>
<ConditionallyRender
condition={potentiallyStale > 0}
show={
<circle
data-testid='potentially-stale-circle'
cx='50'
cy='50'
r={radius}
fill='none'
stroke={theme.palette.warning.border}
strokeWidth={strokeWidth}
strokeLinecap='round'
strokeDasharray={`${potentiallyStaleLength} ${circumference}`}
transform={`rotate(${potentiallyStaleRotation} 50 50)`}
/>
}
/>
<ConditionallyRender
condition={stale > 0}
show={
<circle
data-testid='stale-circle'
cx='50'
cy='50'
r={radius}
fill='none'
stroke={theme.palette.error.border}
strokeWidth={strokeWidth}
strokeLinecap='round'
strokeDasharray={`${staleLength} ${circumference}`}
transform={`rotate(${staleRotation} 50 50)`}
/>
}
/>
<circle
cx='50'
cy='50'
r={innerRadius}
fill={theme.palette.warning.light}
/>
<text
x='50%'
y='50%'
fill='black'
fontSize={theme.spacing(2.25)}
textAnchor='middle'
fontWeight='bold'
>
{health}%
</text>
<text
x='50%'
y='50%'
dy='1.5em'
fill={theme.palette.text.secondary}
fontSize={theme.spacing(1.25)}
textAnchor='middle'
fontWeight='normal'
>
{active + stale} flags
</text>
</svg>
);
};