1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-31 13:47:02 +02:00

feat: basic flag creation chart (#10411)

This is partial implementation of flag creation chart. This only shows
the created flags line part, but I need to style it and also add bar
charts in next PRs.

<img width="1498" height="523" alt="image"
src="https://github.com/user-attachments/assets/6d7a3145-95ff-4d31-85dd-47d687527d47"
/>
This commit is contained in:
Jaanus Sellin 2025-07-24 14:45:16 +03:00 committed by GitHub
parent 89f843fd0d
commit 8943cc0a3d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 158 additions and 1 deletions

View File

@ -16,6 +16,7 @@ const setupApi = () => {
flagTrends: [],
environmentTypeTrends: [],
lifecycleTrends: [],
creationArchiveTrends: [],
});
testServerRoute(server, '/api/admin/projects', {

View File

@ -0,0 +1,127 @@
import 'chartjs-adapter-date-fns';
import { type FC, useMemo } 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';
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 theme = useTheme();
const placeholderData = usePlaceholderData();
const aggregateHealthData = useMemo(() => {
const labels: string[] = Array.from(
new Set(
creationArchiveData.datasets.flatMap((d) =>
d.data.map((item) => item.week),
),
),
);
const weeks: WeekData[] = labels
.map((label) => {
return creationArchiveData.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,
);
})
.sort((a, b) => (a.week > b.week ? 1 : -1));
return {
datasets: [
{
label: 'Number of created flags',
data: weeks,
borderColor: theme.palette.primary.light,
backgroundColor: fillGradientPrimary,
fill: true,
order: 3,
},
],
};
}, [creationArchiveData, theme]);
const aggregateOrProjectData = isAggregate
? aggregateHealthData
: creationArchiveData;
const notEnoughData = useMemo(
() =>
!isLoading &&
!creationArchiveData.datasets.some((d) => d.data.length > 1),
[creationArchiveData, isLoading],
);
const data =
notEnoughData || isLoading ? placeholderData : aggregateOrProjectData;
return (
<LineChart
key={isAggregate ? 'aggregate' : 'project'}
data={data}
overrideOptions={
notEnoughData
? {}
: {
parsing: {
yAxisKey: 'totalCreatedFlags',
xAxisKey: 'date',
},
}
}
cover={notEnoughData ? <NotEnoughData /> : isLoading}
/>
);
};

View File

@ -22,6 +22,11 @@ export const useInsightsData = (
projects,
);
const creationArchiveData = useFilteredTrends(
instanceInsights.creationArchiveTrends,
projects,
);
const groupedProjectsData = useGroupedProjectTrends(projectsData);
const metricsData = useFilteredTrends(
@ -34,6 +39,9 @@ export const useInsightsData = (
const groupedLifecycleData = useGroupedProjectTrends(lifecycleData);
const groupedCreationArchiveData =
useGroupedProjectTrends(creationArchiveData);
return useMemo(
() => ({
...instanceInsights,
@ -46,6 +54,7 @@ export const useInsightsData = (
allMetricsDatapoints,
lifecycleData,
groupedLifecycleData,
groupedCreationArchiveData,
}),
[
instanceInsights,
@ -57,6 +66,7 @@ export const useInsightsData = (
summary,
lifecycleData,
groupedLifecycleData,
groupedCreationArchiveData,
],
);
};

View File

@ -7,6 +7,7 @@ import useProjects from 'hooks/api/getters/useProjects/useProjects';
type ProjectFlagTrends = InstanceInsightsSchema['projectFlagTrends'];
type LifecycleTrends = InstanceInsightsSchema['lifecycleTrends'];
type CreationArchiveTrends = InstanceInsightsSchema['creationArchiveTrends'];
export const calculateTechDebt = (item: {
total: number;
@ -25,7 +26,8 @@ export const calculateTechDebt = (item: {
export const useProjectChartData = (
projectFlagTrends:
| GroupedDataByProject<ProjectFlagTrends>
| GroupedDataByProject<LifecycleTrends>,
| GroupedDataByProject<LifecycleTrends>
| GroupedDataByProject<CreationArchiveTrends>,
) => {
const theme = useTheme();
const getProjectColor = useProjectColor();

View File

@ -27,6 +27,7 @@ import {
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';
export const PerformanceInsights: FC = () => {
const statePrefix = 'performance-';
@ -63,6 +64,7 @@ export const PerformanceInsights: FC = () => {
groupedMetricsData,
allMetricsDatapoints,
environmentTypeTrends,
groupedCreationArchiveData,
} = useInsightsData(insights, projects);
const { isEnterprise } = useUiConfig();
@ -110,6 +112,21 @@ export const PerformanceInsights: FC = () => {
</StyledWidget>
) : null}
{isLifecycleGraphsEnabled && isEnterprise() ? (
<StyledWidget>
<StyledWidgetStats width={275}>
<WidgetTitle title='Flags created vs archived' />
</StyledWidgetStats>
<StyledChartContainer>
<CreationArchiveChart
creationArchiveTrends={groupedCreationArchiveData}
isAggregate={showAllProjects}
isLoading={loading}
/>
</StyledChartContainer>
</StyledWidget>
) : null}
{showAllProjects ? (
<StyledWidget>
<StyledWidgetStats width={275}>