1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-09-05 17:53:12 +02:00

feat: New production flags chart (#10400)

This commit is contained in:
Mateusz Kwasniewski 2025-07-23 15:29:47 +02:00 committed by GitHub
parent 0457f5e035
commit 299ed65ef7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 155 additions and 2 deletions

View File

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

View File

@ -31,6 +31,7 @@ export interface IChartsProps {
>;
userTrends: InstanceInsightsSchema['userTrends'];
environmentTypeTrends: InstanceInsightsSchema['environmentTypeTrends'];
lifecycleTrends: InstanceInsightsSchema['lifecycleTrends'];
summary: {
total: number;
active: number;

View File

@ -0,0 +1,116 @@
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 IProjectHealthChartProps {
lifecycleTrends: GroupedDataByProject<
InstanceInsightsSchema['lifecycleTrends']
>;
isAggregate?: boolean;
isLoading?: boolean;
}
type WeekData = {
newProductionFlags: number;
week: string;
date?: string;
};
export const NewProductionFlagsChart: FC<IProjectHealthChartProps> = ({
lifecycleTrends,
isAggregate,
isLoading,
}) => {
const lifecycleData = useProjectChartData(lifecycleTrends);
const theme = useTheme();
const placeholderData = usePlaceholderData();
const aggregateHealthData = useMemo(() => {
const labels: string[] = Array.from(
new Set(
lifecycleData.datasets.flatMap((d) =>
d.data.map((item) => item.week),
),
),
);
const weeks: WeekData[] = labels
.map((label) => {
return lifecycleData.datasets
.map((d) => d.data.find((item) => item.week === label))
.reduce(
(acc: WeekData, item: WeekData) => {
if (item) {
acc.newProductionFlags +=
item.newProductionFlags;
}
if (!acc.date) {
acc.date = item?.date;
}
return acc;
},
{
newProductionFlags: 0,
week: label,
} as WeekData,
);
})
.sort((a, b) => (a.week > b.week ? 1 : -1));
return {
datasets: [
{
label: 'New production flags',
data: weeks,
borderColor: theme.palette.primary.light,
backgroundColor: fillGradientPrimary,
fill: true,
order: 3,
},
],
};
}, [lifecycleData, theme]);
const aggregateOrProjectData = isAggregate
? aggregateHealthData
: lifecycleData;
const notEnoughData = useMemo(
() =>
!isLoading &&
!lifecycleData.datasets.some((d) => d.data.length > 1),
[lifecycleData, isLoading],
);
const data =
notEnoughData || isLoading ? placeholderData : aggregateOrProjectData;
return (
<LineChart
key={isAggregate ? 'aggregate' : 'project'}
data={data}
overrideOptions={
notEnoughData
? {}
: {
parsing: {
yAxisKey: 'newProductionFlags',
xAxisKey: 'date',
},
plugins: {
legend: {
display: !isAggregate,
},
},
}
}
cover={notEnoughData ? <NotEnoughData /> : isLoading}
/>
);
};

View File

@ -17,6 +17,11 @@ export const useInsightsData = (
projects,
);
const lifecycleData = useFilteredTrends(
instanceInsights.lifecycleTrends,
projects,
);
const groupedProjectsData = useGroupedProjectTrends(projectsData);
const metricsData = useFilteredTrends(
@ -27,6 +32,8 @@ export const useInsightsData = (
const summary = useFilteredFlagsSummary(projectsData);
const groupedLifecycleData = useGroupedProjectTrends(lifecycleData);
return useMemo(
() => ({
...instanceInsights,
@ -37,6 +44,8 @@ export const useInsightsData = (
environmentTypeTrends: instanceInsights.environmentTypeTrends,
summary,
allMetricsDatapoints,
lifecycleData,
groupedLifecycleData,
}),
[
instanceInsights,
@ -46,6 +55,8 @@ export const useInsightsData = (
metricsData,
groupedMetricsData,
summary,
lifecycleData,
groupedLifecycleData,
],
);
};

View File

@ -6,6 +6,7 @@ import type { GroupedDataByProject } from './useGroupedProjectTrends.js';
import useProjects from 'hooks/api/getters/useProjects/useProjects';
type ProjectFlagTrends = InstanceInsightsSchema['projectFlagTrends'];
type LifecycleTrends = InstanceInsightsSchema['lifecycleTrends'];
export const calculateTechDebt = (item: {
total: number;
@ -22,7 +23,9 @@ export const calculateTechDebt = (item: {
};
export const useProjectChartData = (
projectFlagTrends: GroupedDataByProject<ProjectFlagTrends>,
projectFlagTrends:
| GroupedDataByProject<ProjectFlagTrends>
| GroupedDataByProject<LifecycleTrends>,
) => {
const theme = useTheme();
const getProjectColor = useProjectColor();

View File

@ -23,6 +23,8 @@ import {
StyledWidgetContent,
StyledWidgetStats,
} from '../InsightsCharts.styles';
import { useUiFlag } from 'hooks/useUiFlag';
import { NewProductionFlagsChart } from '../componentsChart/NewProductionFlagsChart/NewProductionFlagsChart.tsx';
export const PerformanceInsights: FC = () => {
const statePrefix = 'performance-';
@ -54,6 +56,7 @@ export const PerformanceInsights: FC = () => {
flagTrends,
summary,
groupedProjectsData,
groupedLifecycleData,
userTrends,
groupedMetricsData,
allMetricsDatapoints,
@ -73,6 +76,8 @@ export const PerformanceInsights: FC = () => {
: flagsPerUserCalculation.toFixed(2);
}
const isLifecycleGraphsEnabled = useUiFlag('lifecycleGraphs');
return (
<InsightsSection
title='Performance insights'
@ -84,6 +89,21 @@ export const PerformanceInsights: FC = () => {
/>
}
>
{isLifecycleGraphsEnabled && isEnterprise() ? (
<StyledWidget>
<StyledWidgetStats width={275}>
<WidgetTitle title='New flags in production' />
</StyledWidgetStats>
<StyledChartContainer>
<NewProductionFlagsChart
lifecycleTrends={groupedLifecycleData}
isAggregate={showAllProjects}
isLoading={loading}
/>
</StyledChartContainer>
</StyledWidget>
) : null}
{showAllProjects ? (
<StyledWidget>
<StyledWidgetStats width={275}>

View File

@ -93,6 +93,7 @@ export type UiFlags = {
changeRequestApproverEmails?: boolean;
eventGrouping?: boolean;
reportUnknownFlags?: boolean;
lifecycleGraphs?: boolean;
};
export interface IVersionInfo {

View File

@ -10,7 +10,7 @@ export type InstanceInsightsSchemaLifecycleTrendsItem = {
/** Number of flags that entered production during this week */
newProductionFlags: number;
/** Project id that the flags belong to */
project?: string;
project: string;
/** Year and week in a given year for which the stats were calculated */
week: string;
};