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:
parent
89f843fd0d
commit
8943cc0a3d
@ -16,6 +16,7 @@ const setupApi = () => {
|
||||
flagTrends: [],
|
||||
environmentTypeTrends: [],
|
||||
lifecycleTrends: [],
|
||||
creationArchiveTrends: [],
|
||||
});
|
||||
|
||||
testServerRoute(server, '/api/admin/projects', {
|
||||
|
@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
@ -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,
|
||||
],
|
||||
);
|
||||
};
|
||||
|
@ -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();
|
||||
|
@ -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}>
|
||||
|
Loading…
Reference in New Issue
Block a user