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:
parent
0457f5e035
commit
299ed65ef7
@ -15,6 +15,7 @@ const setupApi = () => {
|
|||||||
flags: { total: 0 },
|
flags: { total: 0 },
|
||||||
flagTrends: [],
|
flagTrends: [],
|
||||||
environmentTypeTrends: [],
|
environmentTypeTrends: [],
|
||||||
|
lifecycleTrends: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
testServerRoute(server, '/api/admin/projects', {
|
testServerRoute(server, '/api/admin/projects', {
|
||||||
|
@ -31,6 +31,7 @@ export interface IChartsProps {
|
|||||||
>;
|
>;
|
||||||
userTrends: InstanceInsightsSchema['userTrends'];
|
userTrends: InstanceInsightsSchema['userTrends'];
|
||||||
environmentTypeTrends: InstanceInsightsSchema['environmentTypeTrends'];
|
environmentTypeTrends: InstanceInsightsSchema['environmentTypeTrends'];
|
||||||
|
lifecycleTrends: InstanceInsightsSchema['lifecycleTrends'];
|
||||||
summary: {
|
summary: {
|
||||||
total: number;
|
total: number;
|
||||||
active: number;
|
active: number;
|
||||||
|
@ -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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -17,6 +17,11 @@ export const useInsightsData = (
|
|||||||
projects,
|
projects,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const lifecycleData = useFilteredTrends(
|
||||||
|
instanceInsights.lifecycleTrends,
|
||||||
|
projects,
|
||||||
|
);
|
||||||
|
|
||||||
const groupedProjectsData = useGroupedProjectTrends(projectsData);
|
const groupedProjectsData = useGroupedProjectTrends(projectsData);
|
||||||
|
|
||||||
const metricsData = useFilteredTrends(
|
const metricsData = useFilteredTrends(
|
||||||
@ -27,6 +32,8 @@ export const useInsightsData = (
|
|||||||
|
|
||||||
const summary = useFilteredFlagsSummary(projectsData);
|
const summary = useFilteredFlagsSummary(projectsData);
|
||||||
|
|
||||||
|
const groupedLifecycleData = useGroupedProjectTrends(lifecycleData);
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
...instanceInsights,
|
...instanceInsights,
|
||||||
@ -37,6 +44,8 @@ export const useInsightsData = (
|
|||||||
environmentTypeTrends: instanceInsights.environmentTypeTrends,
|
environmentTypeTrends: instanceInsights.environmentTypeTrends,
|
||||||
summary,
|
summary,
|
||||||
allMetricsDatapoints,
|
allMetricsDatapoints,
|
||||||
|
lifecycleData,
|
||||||
|
groupedLifecycleData,
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
instanceInsights,
|
instanceInsights,
|
||||||
@ -46,6 +55,8 @@ export const useInsightsData = (
|
|||||||
metricsData,
|
metricsData,
|
||||||
groupedMetricsData,
|
groupedMetricsData,
|
||||||
summary,
|
summary,
|
||||||
|
lifecycleData,
|
||||||
|
groupedLifecycleData,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -6,6 +6,7 @@ import type { GroupedDataByProject } from './useGroupedProjectTrends.js';
|
|||||||
import useProjects from 'hooks/api/getters/useProjects/useProjects';
|
import useProjects from 'hooks/api/getters/useProjects/useProjects';
|
||||||
|
|
||||||
type ProjectFlagTrends = InstanceInsightsSchema['projectFlagTrends'];
|
type ProjectFlagTrends = InstanceInsightsSchema['projectFlagTrends'];
|
||||||
|
type LifecycleTrends = InstanceInsightsSchema['lifecycleTrends'];
|
||||||
|
|
||||||
export const calculateTechDebt = (item: {
|
export const calculateTechDebt = (item: {
|
||||||
total: number;
|
total: number;
|
||||||
@ -22,7 +23,9 @@ export const calculateTechDebt = (item: {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const useProjectChartData = (
|
export const useProjectChartData = (
|
||||||
projectFlagTrends: GroupedDataByProject<ProjectFlagTrends>,
|
projectFlagTrends:
|
||||||
|
| GroupedDataByProject<ProjectFlagTrends>
|
||||||
|
| GroupedDataByProject<LifecycleTrends>,
|
||||||
) => {
|
) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const getProjectColor = useProjectColor();
|
const getProjectColor = useProjectColor();
|
||||||
|
@ -23,6 +23,8 @@ import {
|
|||||||
StyledWidgetContent,
|
StyledWidgetContent,
|
||||||
StyledWidgetStats,
|
StyledWidgetStats,
|
||||||
} from '../InsightsCharts.styles';
|
} from '../InsightsCharts.styles';
|
||||||
|
import { useUiFlag } from 'hooks/useUiFlag';
|
||||||
|
import { NewProductionFlagsChart } from '../componentsChart/NewProductionFlagsChart/NewProductionFlagsChart.tsx';
|
||||||
|
|
||||||
export const PerformanceInsights: FC = () => {
|
export const PerformanceInsights: FC = () => {
|
||||||
const statePrefix = 'performance-';
|
const statePrefix = 'performance-';
|
||||||
@ -54,6 +56,7 @@ export const PerformanceInsights: FC = () => {
|
|||||||
flagTrends,
|
flagTrends,
|
||||||
summary,
|
summary,
|
||||||
groupedProjectsData,
|
groupedProjectsData,
|
||||||
|
groupedLifecycleData,
|
||||||
userTrends,
|
userTrends,
|
||||||
groupedMetricsData,
|
groupedMetricsData,
|
||||||
allMetricsDatapoints,
|
allMetricsDatapoints,
|
||||||
@ -73,6 +76,8 @@ export const PerformanceInsights: FC = () => {
|
|||||||
: flagsPerUserCalculation.toFixed(2);
|
: flagsPerUserCalculation.toFixed(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isLifecycleGraphsEnabled = useUiFlag('lifecycleGraphs');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InsightsSection
|
<InsightsSection
|
||||||
title='Performance insights'
|
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 ? (
|
{showAllProjects ? (
|
||||||
<StyledWidget>
|
<StyledWidget>
|
||||||
<StyledWidgetStats width={275}>
|
<StyledWidgetStats width={275}>
|
||||||
|
@ -93,6 +93,7 @@ export type UiFlags = {
|
|||||||
changeRequestApproverEmails?: boolean;
|
changeRequestApproverEmails?: boolean;
|
||||||
eventGrouping?: boolean;
|
eventGrouping?: boolean;
|
||||||
reportUnknownFlags?: boolean;
|
reportUnknownFlags?: boolean;
|
||||||
|
lifecycleGraphs?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IVersionInfo {
|
export interface IVersionInfo {
|
||||||
|
@ -10,7 +10,7 @@ export type InstanceInsightsSchemaLifecycleTrendsItem = {
|
|||||||
/** Number of flags that entered production during this week */
|
/** Number of flags that entered production during this week */
|
||||||
newProductionFlags: number;
|
newProductionFlags: number;
|
||||||
/** Project id that the flags belong to */
|
/** Project id that the flags belong to */
|
||||||
project?: string;
|
project: string;
|
||||||
/** Year and week in a given year for which the stats were calculated */
|
/** Year and week in a given year for which the stats were calculated */
|
||||||
week: string;
|
week: string;
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user