mirror of
https://github.com/Unleash/unleash.git
synced 2025-08-04 13:48:56 +02:00
feat: create flags created vs archived chart (#10429)
This commit is contained in:
parent
2f1ca50bc4
commit
1d3aea47dc
@ -1,127 +1,288 @@
|
||||
import 'chartjs-adapter-date-fns';
|
||||
import { type FC, useMemo } from 'react';
|
||||
import { type FC, useMemo, useState } from 'react';
|
||||
import type { InstanceInsightsSchema } from 'openapi';
|
||||
import { useProjectChartData } from 'component/insights/hooks/useProjectChartData';
|
||||
import {
|
||||
fillGradientPrimary,
|
||||
LineChart,
|
||||
NotEnoughData,
|
||||
} from 'component/insights/components/LineChart/LineChart';
|
||||
import { useTheme } from '@mui/material';
|
||||
import type { GroupedDataByProject } from 'component/insights/hooks/useGroupedProjectTrends';
|
||||
import { usePlaceholderData } from 'component/insights/hooks/usePlaceholderData';
|
||||
import { Chart } from 'react-chartjs-2';
|
||||
import {
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
BarElement,
|
||||
Tooltip,
|
||||
Legend,
|
||||
TimeScale,
|
||||
Chart as ChartJS,
|
||||
Filler,
|
||||
} from 'chart.js';
|
||||
import { useLocationSettings } from 'hooks/useLocationSettings';
|
||||
import {
|
||||
ChartTooltip,
|
||||
type TooltipState,
|
||||
} from 'component/insights/components/LineChart/ChartTooltip/ChartTooltip';
|
||||
import { createTooltip } from 'component/insights/components/LineChart/createTooltip';
|
||||
import { CreationArchiveTooltip } from './CreationArchiveTooltip.tsx';
|
||||
import { CreationArchiveRatioTooltip } from './CreationArchiveRatioTooltip.tsx';
|
||||
import { getFlagTypeColors } from './flagTypeColors.ts';
|
||||
import type { WeekData, RawWeekData } from './types.ts';
|
||||
|
||||
ChartJS.register(
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
BarElement,
|
||||
TimeScale,
|
||||
Tooltip,
|
||||
Legend,
|
||||
Filler,
|
||||
);
|
||||
|
||||
interface ICreationArchiveChartProps {
|
||||
creationArchiveTrends: GroupedDataByProject<
|
||||
InstanceInsightsSchema['creationArchiveTrends']
|
||||
>;
|
||||
isAggregate?: boolean;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
type WeekData = {
|
||||
archivedFlags: number;
|
||||
totalCreatedFlags: number;
|
||||
week: string;
|
||||
date?: string;
|
||||
};
|
||||
|
||||
type RawWeekData = {
|
||||
archivedFlags: number;
|
||||
createdFlags: Record<string, number>;
|
||||
week: string;
|
||||
date: string;
|
||||
};
|
||||
|
||||
export const CreationArchiveChart: FC<ICreationArchiveChartProps> = ({
|
||||
creationArchiveTrends,
|
||||
isAggregate,
|
||||
isLoading,
|
||||
}) => {
|
||||
const creationArchiveData = useProjectChartData(creationArchiveTrends);
|
||||
const creationVsArchivedChart = useProjectChartData(creationArchiveTrends);
|
||||
const theme = useTheme();
|
||||
const placeholderData = usePlaceholderData();
|
||||
const { locationSettings } = useLocationSettings();
|
||||
const [tooltip, setTooltip] = useState<null | TooltipState>(null);
|
||||
|
||||
const aggregateHealthData = useMemo(() => {
|
||||
const aggregateOrProjectData = useMemo(() => {
|
||||
const labels: string[] = Array.from(
|
||||
new Set(
|
||||
creationArchiveData.datasets.flatMap((d) =>
|
||||
creationVsArchivedChart.datasets.flatMap((d) =>
|
||||
d.data.map((item) => item.week),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
const allFlagTypes = new Set<string>();
|
||||
creationVsArchivedChart.datasets.forEach((d) =>
|
||||
d.data.forEach((item: any) => {
|
||||
if (item.createdFlags) {
|
||||
Object.keys(item.createdFlags).forEach((type) =>
|
||||
allFlagTypes.add(type),
|
||||
);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
const aggregateWeekData = (acc: WeekData, item: RawWeekData) => {
|
||||
if (item) {
|
||||
acc.archivedFlags += item.archivedFlags || 0;
|
||||
|
||||
if (item.createdFlags) {
|
||||
Object.entries(item.createdFlags).forEach(
|
||||
([type, count]) => {
|
||||
acc.createdFlagsByType[type] =
|
||||
(acc.createdFlagsByType[type] || 0) + count;
|
||||
acc.totalCreatedFlags += count;
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
if (!acc.date) {
|
||||
acc.date = item?.date;
|
||||
}
|
||||
return acc;
|
||||
};
|
||||
|
||||
const createInitialWeekData = (label: string): WeekData => ({
|
||||
archivedFlags: 0,
|
||||
totalCreatedFlags: 0,
|
||||
createdFlagsByType: {},
|
||||
archivePercentage: 0,
|
||||
week: label,
|
||||
});
|
||||
|
||||
const weeks: WeekData[] = labels
|
||||
.map((label) => {
|
||||
return creationArchiveData.datasets
|
||||
return creationVsArchivedChart.datasets
|
||||
.map((d) => d.data.find((item) => item.week === label))
|
||||
.reduce(
|
||||
(acc: WeekData, item: RawWeekData) => {
|
||||
if (item) {
|
||||
acc.archivedFlags += item.archivedFlags || 0;
|
||||
const createdFlagsSum = item.createdFlags
|
||||
? Object.values(item.createdFlags).reduce(
|
||||
(sum: number, count: number) =>
|
||||
sum + count,
|
||||
0,
|
||||
)
|
||||
: 0;
|
||||
acc.totalCreatedFlags += createdFlagsSum;
|
||||
}
|
||||
if (!acc.date) {
|
||||
acc.date = item?.date;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{
|
||||
archivedFlags: 0,
|
||||
totalCreatedFlags: 0,
|
||||
week: label,
|
||||
} as WeekData,
|
||||
);
|
||||
.reduce(aggregateWeekData, createInitialWeekData(label));
|
||||
})
|
||||
.map((week) => ({
|
||||
...week,
|
||||
archivePercentage:
|
||||
week.totalCreatedFlags > 0
|
||||
? (week.archivedFlags / week.totalCreatedFlags) * 100
|
||||
: 0,
|
||||
}))
|
||||
.sort((a, b) => (a.week > b.week ? 1 : -1));
|
||||
|
||||
const flagTypeColors = getFlagTypeColors(theme);
|
||||
|
||||
const flagTypeDatasets = Array.from(allFlagTypes).map(
|
||||
(flagType, index) => ({
|
||||
label: flagType,
|
||||
data: weeks,
|
||||
backgroundColor: flagTypeColors[index % flagTypeColors.length],
|
||||
borderColor: flagTypeColors[index % flagTypeColors.length],
|
||||
type: 'bar' as const,
|
||||
parsing: {
|
||||
yAxisKey: `createdFlagsByType.${flagType}`,
|
||||
xAxisKey: 'date',
|
||||
},
|
||||
yAxisID: 'y',
|
||||
stack: 'created',
|
||||
order: 2,
|
||||
}),
|
||||
);
|
||||
|
||||
const flagTypeNames = Array.from(allFlagTypes);
|
||||
|
||||
return {
|
||||
datasets: [
|
||||
{
|
||||
label: 'Number of created flags',
|
||||
label: 'Archived flags',
|
||||
data: weeks,
|
||||
backgroundColor: theme.palette.background.application,
|
||||
borderColor: theme.palette.background.application,
|
||||
parsing: { yAxisKey: 'archivedFlags', xAxisKey: 'date' },
|
||||
order: 2,
|
||||
},
|
||||
...flagTypeDatasets,
|
||||
{
|
||||
label: 'Flags archived / Flags created',
|
||||
data: weeks,
|
||||
borderColor: theme.palette.primary.light,
|
||||
backgroundColor: fillGradientPrimary,
|
||||
fill: true,
|
||||
order: 3,
|
||||
backgroundColor: theme.palette.primary.light,
|
||||
type: 'line' as const,
|
||||
parsing: {
|
||||
yAxisKey: 'archivePercentage',
|
||||
xAxisKey: 'date',
|
||||
},
|
||||
yAxisID: 'y1',
|
||||
order: 1,
|
||||
},
|
||||
],
|
||||
flagTypeNames,
|
||||
};
|
||||
}, [creationArchiveData, theme]);
|
||||
}, [creationVsArchivedChart, theme]);
|
||||
|
||||
const aggregateOrProjectData = isAggregate
|
||||
? aggregateHealthData
|
||||
: creationArchiveData;
|
||||
const notEnoughData = useMemo(
|
||||
() =>
|
||||
!isLoading &&
|
||||
!creationArchiveData.datasets.some((d) => d.data.length > 1),
|
||||
[creationArchiveData, isLoading],
|
||||
!creationVsArchivedChart.datasets.some((d) => d.data.length > 1),
|
||||
[creationVsArchivedChart, isLoading],
|
||||
);
|
||||
const data =
|
||||
notEnoughData || isLoading ? placeholderData : aggregateOrProjectData;
|
||||
|
||||
const flagTypeNames = aggregateOrProjectData.flagTypeNames || [];
|
||||
|
||||
return (
|
||||
<LineChart
|
||||
key={isAggregate ? 'aggregate' : 'project'}
|
||||
data={data}
|
||||
overrideOptions={
|
||||
notEnoughData
|
||||
? {}
|
||||
: {
|
||||
parsing: {
|
||||
yAxisKey: 'totalCreatedFlags',
|
||||
xAxisKey: 'date',
|
||||
},
|
||||
}
|
||||
}
|
||||
cover={notEnoughData ? <NotEnoughData /> : isLoading}
|
||||
/>
|
||||
<>
|
||||
<Chart
|
||||
type='bar'
|
||||
data={data}
|
||||
options={{
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'bottom' as const,
|
||||
labels: {
|
||||
filter: (legendItem) => {
|
||||
return !flagTypeNames.includes(
|
||||
legendItem.text || '',
|
||||
);
|
||||
},
|
||||
generateLabels: (chart) => {
|
||||
const original =
|
||||
ChartJS.defaults.plugins.legend.labels.generateLabels(
|
||||
chart,
|
||||
);
|
||||
const filtered = original.filter(
|
||||
(item) =>
|
||||
!flagTypeNames.includes(
|
||||
item.text || '',
|
||||
),
|
||||
);
|
||||
|
||||
filtered.push({
|
||||
text: 'Created Flags',
|
||||
fillStyle: theme.palette.success.main,
|
||||
strokeStyle: theme.palette.success.main,
|
||||
lineWidth: 0,
|
||||
hidden: false,
|
||||
index: filtered.length,
|
||||
datasetIndex: -1,
|
||||
});
|
||||
|
||||
return filtered;
|
||||
},
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
position: 'nearest',
|
||||
external: createTooltip(setTooltip),
|
||||
},
|
||||
},
|
||||
locale: locationSettings.locale,
|
||||
scales: {
|
||||
x: {
|
||||
type: 'time',
|
||||
time: {
|
||||
unit: 'week',
|
||||
tooltipFormat: 'PPP',
|
||||
},
|
||||
},
|
||||
y: {
|
||||
type: 'linear',
|
||||
position: 'left',
|
||||
beginAtZero: true,
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Number of flags',
|
||||
},
|
||||
},
|
||||
y1: {
|
||||
type: 'linear',
|
||||
display: true,
|
||||
position: 'right',
|
||||
beginAtZero: true,
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Ratio',
|
||||
},
|
||||
grid: {
|
||||
drawOnChartArea: false,
|
||||
},
|
||||
ticks: {
|
||||
callback: (value) => `${value}%`,
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
height={100}
|
||||
width={250}
|
||||
/>
|
||||
{tooltip?.dataPoints?.some(
|
||||
(point) =>
|
||||
point.dataset.label !== 'Archived flags' &&
|
||||
point.dataset.label !== 'Flags archived / Flags created',
|
||||
) ? (
|
||||
<CreationArchiveTooltip tooltip={tooltip} />
|
||||
) : tooltip?.dataPoints?.some(
|
||||
(point) =>
|
||||
point.dataset.label === 'Flags archived / Flags created',
|
||||
) ? (
|
||||
<CreationArchiveRatioTooltip tooltip={tooltip} />
|
||||
) : (
|
||||
<ChartTooltip tooltip={tooltip} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,100 @@
|
||||
import type { FC } from 'react';
|
||||
import { Box, Paper, Typography, styled, useTheme } from '@mui/material';
|
||||
import type { TooltipState } from 'component/insights/components/LineChart/ChartTooltip/ChartTooltip';
|
||||
import { ChartTooltipContainer } from 'component/insights/components/LineChart/ChartTooltip/ChartTooltip';
|
||||
import type { Theme } from '@mui/material/styles/createTheme';
|
||||
import type { WeekData } from './types.ts';
|
||||
const getRatioTooltipColors = (theme: Theme) => ({
|
||||
CREATED: theme.palette.success.main,
|
||||
ARCHIVED: theme.palette.background.application,
|
||||
});
|
||||
|
||||
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 }) => {
|
||||
const theme = useTheme();
|
||||
const colors = getRatioTooltipColors(theme);
|
||||
|
||||
if (!tooltip?.dataPoints) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const ratioDataPoint = tooltip.dataPoints.find(
|
||||
(point) => point.dataset.label === 'Flags archived / Flags created',
|
||||
);
|
||||
|
||||
if (!ratioDataPoint) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const rawData = ratioDataPoint.raw as WeekData;
|
||||
|
||||
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: colors.CREATED }}
|
||||
component='span'
|
||||
>
|
||||
{'● '}
|
||||
</Typography>
|
||||
Flags created
|
||||
</Typography>
|
||||
<Typography variant='body2' component='span'>
|
||||
{createdCount}
|
||||
</Typography>
|
||||
</StyledFlagItem>
|
||||
|
||||
<StyledFlagItem>
|
||||
<Typography variant='body2' component='span'>
|
||||
<Typography
|
||||
sx={{ color: colors.ARCHIVED }}
|
||||
component='span'
|
||||
>
|
||||
{'● '}
|
||||
</Typography>
|
||||
Flags archived
|
||||
</Typography>
|
||||
<Typography variant='body2' component='span'>
|
||||
{archivedCount}
|
||||
</Typography>
|
||||
</StyledFlagItem>
|
||||
</StyledTooltipItemContainer>
|
||||
</ChartTooltipContainer>
|
||||
);
|
||||
};
|
@ -0,0 +1,96 @@
|
||||
import type { FC } from 'react';
|
||||
import { Box, Paper, Typography, styled, useTheme } from '@mui/material';
|
||||
import type { TooltipState } from 'component/insights/components/LineChart/ChartTooltip/ChartTooltip';
|
||||
import { ChartTooltipContainer } from 'component/insights/components/LineChart/ChartTooltip/ChartTooltip';
|
||||
import { getFlagTypeColors } from './flagTypeColors.ts';
|
||||
import type { WeekData } from './types.ts';
|
||||
|
||||
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,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
if (!tooltip?.dataPoints) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const createdFlagDataPoints = tooltip.dataPoints.filter(
|
||||
(point) =>
|
||||
point.dataset.label !== 'Archived flags' &&
|
||||
point.dataset.label !== 'Flags archived / Flags created',
|
||||
);
|
||||
|
||||
if (createdFlagDataPoints.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const rawData = createdFlagDataPoints[0]?.raw as WeekData;
|
||||
|
||||
if (!rawData?.createdFlagsByType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const flagTypeNames = createdFlagDataPoints.map(
|
||||
(point) => point.dataset.label || '',
|
||||
);
|
||||
|
||||
const flagTypeColors = getFlagTypeColors(theme);
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
@ -0,0 +1,9 @@
|
||||
import type { Theme } from '@mui/material';
|
||||
|
||||
export const getFlagTypeColors = (theme: Theme) => [
|
||||
theme.palette.success.border,
|
||||
theme.palette.success.main,
|
||||
theme.palette.success.dark,
|
||||
'#4D8007',
|
||||
'#7D935E',
|
||||
];
|
@ -0,0 +1,15 @@
|
||||
export type WeekData = {
|
||||
archivedFlags: number;
|
||||
totalCreatedFlags: number;
|
||||
createdFlagsByType: Record<string, number>;
|
||||
archivePercentage: number;
|
||||
week: string;
|
||||
date?: string;
|
||||
};
|
||||
|
||||
export type RawWeekData = {
|
||||
archivedFlags: number;
|
||||
createdFlags: Record<string, number>;
|
||||
week: string;
|
||||
date: string;
|
||||
};
|
@ -0,0 +1,113 @@
|
||||
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';
|
||||
import type { GroupedDataByProject } from 'component/insights/hooks/useGroupedProjectTrends';
|
||||
import type { InstanceInsightsSchema } from 'openapi';
|
||||
|
||||
function getCurrentArchiveRatio(
|
||||
groupedCreationArchiveData: GroupedDataByProject<
|
||||
InstanceInsightsSchema['creationArchiveTrends']
|
||||
>,
|
||||
) {
|
||||
if (
|
||||
!groupedCreationArchiveData ||
|
||||
Object.keys(groupedCreationArchiveData).length === 0
|
||||
) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let totalArchived = 0;
|
||||
let totalCreated = 0;
|
||||
|
||||
Object.values(groupedCreationArchiveData).forEach((projectData) => {
|
||||
const latestData = projectData[projectData.length - 1];
|
||||
if (latestData) {
|
||||
totalArchived += latestData.archivedFlags || 0;
|
||||
const createdSum = latestData.createdFlags
|
||||
? Object.values(latestData.createdFlags).reduce(
|
||||
(sum: number, count: number) => sum + count,
|
||||
0,
|
||||
)
|
||||
: 0;
|
||||
totalCreated += createdSum;
|
||||
}
|
||||
});
|
||||
|
||||
return totalCreated > 0
|
||||
? Math.round((totalArchived / totalCreated) * 100)
|
||||
: 0;
|
||||
}
|
||||
|
||||
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,
|
||||
fontSize: theme.spacing(2.5),
|
||||
fontWeight: 'bold',
|
||||
}));
|
||||
|
||||
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 {
|
||||
groupedCreationArchiveData: GroupedDataByProject<
|
||||
InstanceInsightsSchema['creationArchiveTrends']
|
||||
>;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
export const CreationArchiveStats: FC<CreationArchiveStatsProps> = ({
|
||||
groupedCreationArchiveData,
|
||||
isLoading,
|
||||
}) => {
|
||||
const currentRatio = getCurrentArchiveRatio(groupedCreationArchiveData);
|
||||
|
||||
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>
|
||||
</>
|
||||
);
|
||||
};
|
@ -28,6 +28,7 @@ import { useUiFlag } from 'hooks/useUiFlag';
|
||||
import { NewProductionFlagsChart } from '../componentsChart/NewProductionFlagsChart/NewProductionFlagsChart.tsx';
|
||||
import Lightbulb from '@mui/icons-material/LightbulbOutlined';
|
||||
import { CreationArchiveChart } from '../componentsChart/CreationArchiveChart/CreationArchiveChart.tsx';
|
||||
import { CreationArchiveStats } from '../componentsStat/CreationArchiveStats/CreationArchiveStats.tsx';
|
||||
|
||||
export const PerformanceInsights: FC = () => {
|
||||
const statePrefix = 'performance-';
|
||||
@ -116,11 +117,16 @@ export const PerformanceInsights: FC = () => {
|
||||
<StyledWidget>
|
||||
<StyledWidgetStats width={275}>
|
||||
<WidgetTitle title='Flags created vs archived' />
|
||||
<CreationArchiveStats
|
||||
groupedCreationArchiveData={
|
||||
groupedCreationArchiveData
|
||||
}
|
||||
isLoading={loading}
|
||||
/>
|
||||
</StyledWidgetStats>
|
||||
<StyledChartContainer>
|
||||
<CreationArchiveChart
|
||||
creationArchiveTrends={groupedCreationArchiveData}
|
||||
isAggregate={showAllProjects}
|
||||
isLoading={loading}
|
||||
/>
|
||||
</StyledChartContainer>
|
||||
|
Loading…
Reference in New Issue
Block a user