1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-08-13 13:48:59 +02:00

feat: create flags created vs archived chart

This commit is contained in:
sjaanus 2025-07-29 15:37:39 +03:00
parent df01f53aed
commit ffffe02792
No known key found for this signature in database
GPG Key ID: 20E007C0248BA7FF
3 changed files with 264 additions and 0 deletions

View File

@ -0,0 +1,87 @@
import type { FC } from 'react';
import { Box, Paper, Typography, styled } from '@mui/material';
import type { TooltipState } from 'component/insights/components/LineChart/ChartTooltip/ChartTooltip';
import { ChartTooltipContainer } from 'component/insights/components/LineChart/ChartTooltip/ChartTooltip';
const StyledTooltipItemContainer = styled(Paper)(({ theme }) => ({
padding: theme.spacing(2),
width: 200,
}));
const StyledFlagItem = styled(Box)(({ theme }) => ({
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: theme.spacing(0.5),
}));
interface CreationArchiveRatioTooltipProps {
tooltip: TooltipState | null;
}
export const CreationArchiveRatioTooltip: FC<
CreationArchiveRatioTooltipProps
> = ({ tooltip }) => {
if (!tooltip?.dataPoints) {
return null;
}
// Filter for the percentage line dataset
const ratioDataPoint = tooltip.dataPoints.find(
(point) => point.dataset.label === 'Flags archived / Flags created',
);
if (!ratioDataPoint) {
return null;
}
// Get the raw data to extract counts
const rawData = ratioDataPoint.raw as any;
if (!rawData) {
return null;
}
const archivedCount = rawData.archivedFlags || 0;
const createdCount = rawData.totalCreatedFlags || 0;
const ratio = Math.round(ratioDataPoint.parsed.y as number);
return (
<ChartTooltipContainer tooltip={tooltip}>
<StyledTooltipItemContainer elevation={3}>
<Typography
variant='body2'
component='div'
fontWeight='bold'
sx={{ marginBottom: 1 }}
>
Ratio {ratio}%
</Typography>
<StyledFlagItem>
<Typography variant='body2' component='span'>
<Typography sx={{ color: '#4caf50' }} component='span'>
{'● '}
</Typography>
Flags created
</Typography>
<Typography variant='body2' component='span'>
{createdCount}
</Typography>
</StyledFlagItem>
<StyledFlagItem>
<Typography variant='body2' component='span'>
<Typography sx={{ color: '#9e9e9e' }} component='span'>
{'● '}
</Typography>
Flags archived
</Typography>
<Typography variant='body2' component='span'>
{archivedCount}
</Typography>
</StyledFlagItem>
</StyledTooltipItemContainer>
</ChartTooltipContainer>
);
};

View File

@ -0,0 +1,101 @@
import type { FC } from 'react';
import { Box, Paper, Typography, styled } from '@mui/material';
import type { TooltipState } from '../../components/LineChart/ChartTooltip/ChartTooltip.tsx';
import { ChartTooltipContainer } from '../../components/LineChart/ChartTooltip/ChartTooltip.tsx';
const StyledTooltipItemContainer = styled(Paper)(({ theme }) => ({
padding: theme.spacing(2),
width: 240,
}));
const StyledFlagTypeItem = styled(Box)(({ theme }) => ({
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
}));
interface CreationArchiveTooltipProps {
tooltip: TooltipState | null;
}
export const CreationArchiveTooltip: FC<CreationArchiveTooltipProps> = ({
tooltip,
}) => {
if (!tooltip?.dataPoints) {
return null;
}
// Filter for created flag type datasets only
const createdFlagDataPoints = tooltip.dataPoints.filter((point) =>
point.dataset.label?.startsWith('Created:'),
);
if (createdFlagDataPoints.length === 0) {
return null;
}
// Get the data from the first point (they all have the same raw data)
const rawData = createdFlagDataPoints[0]?.raw as any;
if (!rawData?.createdFlagsByType) {
return null;
}
// Flag type colors matching the chart
const flagTypeColors = [
'#66bb6a', // theme.palette.success.border
'#4caf50', // theme.palette.success.main
'#388e3c', // theme.palette.success.dark
'#4D8007',
'#7D935E',
];
// Get flag type names from the chart datasets
const flagTypeNames = createdFlagDataPoints.map(
(point) => point.dataset.label?.replace('Created: ', '') || '',
);
// Create entries for each flag type with count > 0
const flagTypeEntries = Object.entries(rawData.createdFlagsByType)
.filter(([, count]) => (count as number) > 0)
.map(([flagType, count], index) => ({
type: flagType,
count: count as number,
color:
flagTypeColors[flagTypeNames.indexOf(flagType)] ||
flagTypeColors[index % flagTypeColors.length],
}));
if (flagTypeEntries.length === 0) {
return null;
}
return (
<ChartTooltipContainer tooltip={tooltip}>
<StyledTooltipItemContainer elevation={3}>
<Typography
variant='body2'
component='div'
fontWeight='bold'
sx={{ marginBottom: 1 }}
>
Flag type
</Typography>
{flagTypeEntries.map(({ type, count, color }) => (
<StyledFlagTypeItem key={type}>
<Typography variant='body2' component='span'>
<Typography sx={{ color }} component='span'>
{'● '}
</Typography>
{type.charAt(0).toUpperCase() + type.slice(1)}
</Typography>
<Typography variant='body2' component='span'>
{count}
</Typography>
</StyledFlagTypeItem>
))}
</StyledTooltipItemContainer>
</ChartTooltipContainer>
);
};

View File

@ -0,0 +1,76 @@
import type { FC } from 'react';
import { Box, Typography, Link, styled } from '@mui/material';
import { HelpIcon } from 'component/common/HelpIcon/HelpIcon';
import InfoOutlined from '@mui/icons-material/InfoOutlined';
import Lightbulb from '@mui/icons-material/LightbulbOutlined';
import { StatsExplanation } from 'component/insights/InsightsCharts.styles';
const StyledRatioContainer = styled(Box)(({ theme }) => ({
backgroundColor: theme.palette.background.elevation1,
borderRadius: theme.spacing(2),
padding: theme.spacing(2),
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(0.5),
}));
const StyledPercentageRow = styled(Box)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
}));
const StyledRatioTypography = styled(Typography)(({ theme }) => ({
color: theme.palette.primary.main,
fontFamily: 'inherit',
fontSize: '20px',
fontStyle: 'normal',
fontWeight: 700,
lineHeight: '28px',
}));
const StyledInfoIcon = styled(InfoOutlined)(({ theme }) => ({
color: theme.palette.text.secondary,
}));
const StyledLink = styled(Link)(({ theme }) => ({
color: theme.palette.primary.main,
textDecoration: 'none',
'&:hover': {
textDecoration: 'underline',
},
}));
interface CreationArchiveStatsProps {
currentRatio: number;
isLoading?: boolean;
}
export const CreationArchiveStats: FC<CreationArchiveStatsProps> = ({
currentRatio,
isLoading,
}) => {
return (
<>
<StyledRatioContainer>
<StyledPercentageRow>
<StyledRatioTypography>
{isLoading ? '...' : `${currentRatio}%`}
</StyledRatioTypography>
<HelpIcon tooltip='Ratio of archived flags to created flags'>
<StyledInfoIcon />
</HelpIcon>
</StyledPercentageRow>
<Typography variant='body2'>Current ratio</Typography>
</StyledRatioContainer>
<StatsExplanation>
<Lightbulb color='primary' />
Do you create more flags than you archive? Or do you have good
process for cleaning up?
</StatsExplanation>
<StyledLink href='/search?lifecycle=IS:completed' variant='body2'>
View flags in cleanup stage
</StyledLink>
</>
);
};