diff --git a/frontend/src/component/insights/Insights.test.tsx b/frontend/src/component/insights/Insights.test.tsx index 481922d47d..555b0fb7a3 100644 --- a/frontend/src/component/insights/Insights.test.tsx +++ b/frontend/src/component/insights/Insights.test.tsx @@ -16,6 +16,7 @@ const setupApi = () => { flagTrends: [], environmentTypeTrends: [], lifecycleTrends: [], + creationArchiveTrends: [], }); testServerRoute(server, '/api/admin/projects', { diff --git a/frontend/src/component/insights/componentsChart/CreationArchiveChart/CreationArchiveChart.tsx b/frontend/src/component/insights/componentsChart/CreationArchiveChart/CreationArchiveChart.tsx new file mode 100644 index 0000000000..965f48f43d --- /dev/null +++ b/frontend/src/component/insights/componentsChart/CreationArchiveChart/CreationArchiveChart.tsx @@ -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; + week: string; + date: string; +}; + +export const CreationArchiveChart: FC = ({ + 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 ( + : isLoading} + /> + ); +}; diff --git a/frontend/src/component/insights/hooks/useInsightsData.ts b/frontend/src/component/insights/hooks/useInsightsData.ts index 7b20ff5cdd..6276396f85 100644 --- a/frontend/src/component/insights/hooks/useInsightsData.ts +++ b/frontend/src/component/insights/hooks/useInsightsData.ts @@ -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, ], ); }; diff --git a/frontend/src/component/insights/hooks/useProjectChartData.ts b/frontend/src/component/insights/hooks/useProjectChartData.ts index a09ed2cce2..2f806562e6 100644 --- a/frontend/src/component/insights/hooks/useProjectChartData.ts +++ b/frontend/src/component/insights/hooks/useProjectChartData.ts @@ -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 - | GroupedDataByProject, + | GroupedDataByProject + | GroupedDataByProject, ) => { const theme = useTheme(); const getProjectColor = useProjectColor(); diff --git a/frontend/src/component/insights/sections/PerformanceInsights.tsx b/frontend/src/component/insights/sections/PerformanceInsights.tsx index 40f18b6110..3b5e08241e 100644 --- a/frontend/src/component/insights/sections/PerformanceInsights.tsx +++ b/frontend/src/component/insights/sections/PerformanceInsights.tsx @@ -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 = () => { ) : null} + {isLifecycleGraphsEnabled && isEnterprise() ? ( + + + + + + + + + ) : null} + {showAllProjects ? (