diff --git a/frontend/src/component/executiveDashboard/Charts.tsx b/frontend/src/component/executiveDashboard/Charts.tsx new file mode 100644 index 0000000000..2369b0d724 --- /dev/null +++ b/frontend/src/component/executiveDashboard/Charts.tsx @@ -0,0 +1,218 @@ +import { ConditionallyRender } from '../common/ConditionallyRender/ConditionallyRender'; +import { Widget } from './components/Widget/Widget'; +import { UserStats } from './componentsStat/UserStats/UserStats'; +import { UsersChart } from './componentsChart/UsersChart/UsersChart'; +import { UsersPerProjectChart } from './componentsChart/UsersPerProjectChart/UsersPerProjectChart'; +import { FlagStats } from './componentsStat/FlagStats/FlagStats'; +import { FlagsChart } from './componentsChart/FlagsChart/FlagsChart'; +import { FlagsProjectChart } from './componentsChart/FlagsProjectChart/FlagsProjectChart'; +import { HealthStats } from './componentsStat/HealthStats/HealthStats'; +import { ProjectHealthChart } from './componentsChart/ProjectHealthChart/ProjectHealthChart'; +import { TimeToProduction } from './componentsStat/TimeToProduction/TimeToProduction'; +import { TimeToProductionChart } from './componentsChart/TimeToProductionChart/TimeToProductionChart'; +import { MetricsSummaryChart } from './componentsChart/MetricsSummaryChart/MetricsSummaryChart'; +import { UpdatesPerEnvironmentTypeChart } from './componentsChart/UpdatesPerEnvironmentTypeChart/UpdatesPerEnvironmentTypeChart'; +import type { ExecutiveSummarySchema } from '../../openapi'; +import type { GroupedDataByProject } from './hooks/useGroupedProjectTrends'; +import { Box, styled } from '@mui/material'; +import { allOption } from '../common/ProjectSelect/ProjectSelect'; +import type { VFC } from 'react'; + +interface IChartsProps { + flagTrends: ExecutiveSummarySchema['flagTrends']; + projectsData: ExecutiveSummarySchema['projectFlagTrends']; + groupedProjectsData: GroupedDataByProject< + ExecutiveSummarySchema['projectFlagTrends'] + >; + metricsData: ExecutiveSummarySchema['metricsSummaryTrends']; + groupedMetricsData: GroupedDataByProject< + ExecutiveSummarySchema['metricsSummaryTrends'] + >; + users: ExecutiveSummarySchema['users']; + userTrends: ExecutiveSummarySchema['userTrends']; + environmentTypeTrends: ExecutiveSummarySchema['environmentTypeTrends']; + summary: { + total: number; + active: number; + stale: number; + potentiallyStale: number; + averageUsers: number; + averageHealth?: string; + }; + avgDaysToProduction: number; + loading: boolean; + projects: string[]; +} + +const StyledGrid = styled(Box)(({ theme }) => ({ + display: 'grid', + gridTemplateColumns: `repeat(2, 1fr)`, + gridAutoRows: 'auto', + gap: theme.spacing(2), + paddingBottom: theme.spacing(2), + [theme.breakpoints.up('md')]: { + gridTemplateColumns: `300px 1fr`, + }, +})); + +const ChartWidget = styled(Widget)(({ theme }) => ({ + [theme.breakpoints.down('md')]: { + gridColumnStart: 'span 2', + order: 2, + }, +})); + +export const Charts: VFC = ({ + projects, + users, + summary, + userTrends, + groupedProjectsData, + flagTrends, + avgDaysToProduction, + groupedMetricsData, + environmentTypeTrends, + loading, +}) => { + const showAllProjects = projects[0] === allOption.id; + const isOneProjectSelected = projects.length === 1; + + return ( + <> + + + + + } + elseShow={ + + + + } + /> + + + + } + elseShow={ + + + + } + /> + + + + + + + } + elseShow={ + + + + } + /> + + + + + + + + + + + + + + + + + theme.spacing(2) }} + > + + + + ); +}; diff --git a/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx b/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx index b8f0c9e30c..f72968298f 100644 --- a/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx +++ b/frontend/src/component/executiveDashboard/ExecutiveDashboard.tsx @@ -1,64 +1,26 @@ import { useState, type VFC } from 'react'; import { Box, styled } from '@mui/material'; import { ArrayParam, withDefault } from 'use-query-params'; -import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { usePersistentTableState } from 'hooks/usePersistentTableState'; import { allOption, ProjectSelect, } from 'component/common/ProjectSelect/ProjectSelect'; import { useExecutiveDashboard } from 'hooks/api/getters/useExecutiveSummary/useExecutiveSummary'; - -import { useFilteredFlagsSummary } from './hooks/useFilteredFlagsSummary'; -import { useFilteredTrends } from './hooks/useFilteredTrends'; - -import { Widget } from './components/Widget/Widget'; import { DashboardHeader } from './components/DashboardHeader/DashboardHeader'; +import { useDashboardData } from './hooks/useDashboardData'; +import { Charts } from './Charts'; -import { UserStats } from './componentsStat/UserStats/UserStats'; -import { FlagStats } from './componentsStat/FlagStats/FlagStats'; -import { HealthStats } from './componentsStat/HealthStats/HealthStats'; - -import { UsersChart } from './componentsChart/UsersChart/UsersChart'; -import { FlagsChart } from './componentsChart/FlagsChart/FlagsChart'; -import { FlagsProjectChart } from './componentsChart/FlagsProjectChart/FlagsProjectChart'; -import { ProjectHealthChart } from './componentsChart/ProjectHealthChart/ProjectHealthChart'; -import { MetricsSummaryChart } from './componentsChart/MetricsSummaryChart/MetricsSummaryChart'; -import { UsersPerProjectChart } from './componentsChart/UsersPerProjectChart/UsersPerProjectChart'; -import { UpdatesPerEnvironmentTypeChart } from './componentsChart/UpdatesPerEnvironmentTypeChart/UpdatesPerEnvironmentTypeChart'; -import { TimeToProduction } from './componentsStat/TimeToProduction/TimeToProduction'; -import { TimeToProductionChart } from './componentsChart/TimeToProductionChart/TimeToProductionChart'; -import { useGroupedProjectTrends } from './hooks/useGroupedProjectTrends'; -import { useAvgTimeToProduction } from './hooks/useAvgTimeToProduction'; - -const StyledGrid = styled(Box)(({ theme }) => ({ - display: 'grid', - gridTemplateColumns: `repeat(2, 1fr)`, - gridAutoRows: 'auto', - gap: theme.spacing(2), - paddingBottom: theme.spacing(2), - [theme.breakpoints.up('md')]: { - gridTemplateColumns: `300px 1fr`, - }, -})); - -const ChartWidget = styled(Widget)(({ theme }) => ({ - [theme.breakpoints.down('md')]: { - gridColumnStart: 'span 2', - order: 2, - }, -})); - -const StickyWrapper = styled(Box, { - shouldForwardProp: (prop) => prop !== 'scrolled', -})<{ scrolled?: boolean }>(({ theme, scrolled }) => ({ - position: 'sticky', - top: 0, - zIndex: 1000, - padding: scrolled ? theme.spacing(2, 0) : theme.spacing(0, 0, 2), - background: theme.palette.background.application, - transition: 'padding 0.3s ease', -})); +const StickyWrapper = styled(Box)<{ scrolled?: boolean }>( + ({ theme, scrolled }) => ({ + position: 'sticky', + top: 0, + zIndex: 1000, + padding: scrolled ? theme.spacing(2, 0) : theme.spacing(0, 0, 2), + background: theme.palette.background.application, + transition: 'padding 0.3s ease', + }), +); export const ExecutiveDashboard: VFC = () => { const [scrolled, setScrolled] = useState(false); @@ -73,27 +35,8 @@ export const ExecutiveDashboard: VFC = () => { const projects = state.projects ? (state.projects.filter(Boolean) as string[]) : []; - const showAllProjects = projects[0] === allOption.id; - const projectsData = useFilteredTrends( - executiveDashboardData.projectFlagTrends, - projects, - ); - const groupedProjectsData = useGroupedProjectTrends(projectsData); - - const metricsData = useFilteredTrends( - executiveDashboardData.metricsSummaryTrends, - projects, - ); - const groupedMetricsData = useGroupedProjectTrends(metricsData); - - const { users, environmentTypeTrends } = executiveDashboardData; - - const summary = useFilteredFlagsSummary(projectsData); - - const avgDaysToProduction = useAvgTimeToProduction(groupedProjectsData); - - const isOneProjectSelected = projects.length === 1; + const dashboardData = useDashboardData(executiveDashboardData, projects); const handleScroll = () => { if (!scrolled && window.scrollY > 0) { @@ -121,133 +64,7 @@ export const ExecutiveDashboard: VFC = () => { } /> - - - - - } - elseShow={ - - - - } - /> - - - - } - elseShow={ - - - - } - /> - - - - - - - } - elseShow={ - - - - } - /> - - - - - - - - - - - - - - - - - theme.spacing(2) }} - > - - + ); }; diff --git a/frontend/src/component/executiveDashboard/componentsChart/MetricsSummaryChart/MetricsChartTooltip/MetricsChartTooltip.tsx b/frontend/src/component/executiveDashboard/componentsChart/MetricsSummaryChart/MetricsChartTooltip/MetricsChartTooltip.tsx index 316eab6c0a..4d0ace90cb 100644 --- a/frontend/src/component/executiveDashboard/componentsChart/MetricsSummaryChart/MetricsChartTooltip/MetricsChartTooltip.tsx +++ b/frontend/src/component/executiveDashboard/componentsChart/MetricsSummaryChart/MetricsChartTooltip/MetricsChartTooltip.tsx @@ -35,24 +35,28 @@ const InfoLine = ({ ); -const InfoSummary = ({ data }: { data: { key: string; value: number }[] }) => ( +const InfoSummary = ({ + data, +}: { data: { key: string; value: string | number }[] }) => ( - {data.map(({ key, value }) => ( -
-
- - {key} - + {data + .filter(({ value }) => value !== 'N/A') + .map(({ key, value }) => ( +
+
+ + {key} + +
+
{value}
-
{value}
-
- ))} + ))} ); @@ -131,15 +135,15 @@ export const MetricsSummaryTooltip: VFC<{ tooltip: TooltipState | null }> = ({ data={[ { key: 'Flags', - value: point.value.totalFlags ?? 0, + value: point.value.totalFlags ?? 'N/A', }, { key: 'Environments', - value: point.value.totalEnvironments ?? 0, + value: point.value.totalEnvironments ?? 'N/A', }, { key: 'Apps', - value: point.value.totalApps ?? 0, + value: point.value.totalApps ?? 'N/A', }, ]} /> diff --git a/frontend/src/component/executiveDashboard/componentsChart/MetricsSummaryChart/MetricsChartTooltip/aggregate-metrics-by-day.test.ts b/frontend/src/component/executiveDashboard/componentsChart/MetricsSummaryChart/MetricsChartTooltip/aggregate-metrics-by-day.test.ts new file mode 100644 index 0000000000..b7d0fbbfaa --- /dev/null +++ b/frontend/src/component/executiveDashboard/componentsChart/MetricsSummaryChart/MetricsChartTooltip/aggregate-metrics-by-day.test.ts @@ -0,0 +1,165 @@ +import { aggregateDataPerDate } from './aggregate-metrics-by-day'; + +describe('aggregateDataPerDate', () => { + it('should correctly aggregate data for a single item', () => { + const items = [ + { + date: '2024-03-19', + totalFlags: 5, + totalNo: 2, + totalRequests: 7, + totalYes: 3, + project: 'default', + totalApps: 2, + totalEnvironments: 3, + week: '2024-01', + }, + ]; + + const expected = { + '2024-03-19': { + totalFlags: 5, + totalNo: 2, + totalRequests: 7, + totalYes: 3, + }, + }; + + expect(aggregateDataPerDate(items)).toEqual(expected); + }); + + it('should aggregate multiple items for the same date correctly', () => { + const items = [ + { + date: '2024-03-19', + totalFlags: 1, + totalNo: 2, + totalRequests: 3, + totalYes: 4, + project: 'default', + totalApps: 2, + totalEnvironments: 3, + week: '2024-01', + }, + { + date: '2024-03-19', + totalFlags: 5, + totalNo: 6, + totalRequests: 7, + totalYes: 8, + project: 'default', + totalApps: 2, + totalEnvironments: 3, + week: '2024-01', + }, + ]; + + const expected = { + '2024-03-19': { + totalFlags: 6, + totalNo: 8, + totalRequests: 10, + totalYes: 12, + }, + }; + + expect(aggregateDataPerDate(items)).toEqual(expected); + }); + + it('should aggregate items across different dates correctly', () => { + const items = [ + { + date: '2024-03-18', + totalFlags: 10, + totalNo: 20, + totalRequests: 30, + totalYes: 40, + project: 'default', + totalApps: 2, + totalEnvironments: 3, + week: '2024-01', + }, + { + date: '2024-03-19', + totalFlags: 1, + totalNo: 2, + totalRequests: 3, + totalYes: 4, + project: 'default', + totalApps: 2, + totalEnvironments: 3, + week: '2024-01', + }, + ]; + + const expected = { + '2024-03-18': { + totalFlags: 10, + totalNo: 20, + totalRequests: 30, + totalYes: 40, + }, + '2024-03-19': { + totalFlags: 1, + totalNo: 2, + totalRequests: 3, + totalYes: 4, + }, + }; + + expect(aggregateDataPerDate(items)).toEqual(expected); + }); + + it('should correctly handle items with all metrics at zero', () => { + const items = [ + { + date: '2024-03-19', + totalFlags: 0, + totalNo: 0, + totalRequests: 0, + totalYes: 0, + project: 'default', + totalApps: 2, + totalEnvironments: 3, + week: '2024-01', + }, + ]; + + const expected = { + '2024-03-19': { + totalFlags: 0, + totalNo: 0, + totalRequests: 0, + totalYes: 0, + }, + }; + + expect(aggregateDataPerDate(items)).toEqual(expected); + }); + + it('should return an empty object for an empty array input', () => { + expect(aggregateDataPerDate([])).toEqual({}); + }); + + // Test for immutability of input + it('should not mutate the input array', () => { + const items = [ + { + date: '2024-03-19', + totalFlags: 1, + totalNo: 2, + totalRequests: 3, + totalYes: 4, + project: 'default', + totalApps: 2, + totalEnvironments: 3, + week: '2024-01', + }, + ]; + const itemsCopy = [...items]; + + aggregateDataPerDate(items); + + expect(items).toEqual(itemsCopy); + }); +}); diff --git a/frontend/src/component/executiveDashboard/componentsChart/MetricsSummaryChart/MetricsChartTooltip/aggregate-metrics-by-day.ts b/frontend/src/component/executiveDashboard/componentsChart/MetricsSummaryChart/MetricsChartTooltip/aggregate-metrics-by-day.ts new file mode 100644 index 0000000000..84916011da --- /dev/null +++ b/frontend/src/component/executiveDashboard/componentsChart/MetricsSummaryChart/MetricsChartTooltip/aggregate-metrics-by-day.ts @@ -0,0 +1,33 @@ +import type { ExecutiveSummarySchema } from 'openapi'; + +export function aggregateDataPerDate( + items: ExecutiveSummarySchema['metricsSummaryTrends'], +) { + return items.reduce( + (acc, item) => { + if (!acc[item.date]) { + acc[item.date] = { + totalFlags: 0, + totalNo: 0, + totalRequests: 0, + totalYes: 0, + }; + } + + acc[item.date].totalFlags += item.totalFlags; + acc[item.date].totalNo += item.totalNo; + acc[item.date].totalRequests += item.totalRequests; + acc[item.date].totalYes += item.totalYes; + + return acc; + }, + {} as { + [date: string]: { + totalFlags: number; + totalNo: number; + totalRequests: number; + totalYes: number; + }; + }, + ); +} diff --git a/frontend/src/component/executiveDashboard/componentsChart/MetricsSummaryChart/MetricsSummaryChart.tsx b/frontend/src/component/executiveDashboard/componentsChart/MetricsSummaryChart/MetricsSummaryChart.tsx index 984720c642..cc2871a494 100644 --- a/frontend/src/component/executiveDashboard/componentsChart/MetricsSummaryChart/MetricsSummaryChart.tsx +++ b/frontend/src/component/executiveDashboard/componentsChart/MetricsSummaryChart/MetricsSummaryChart.tsx @@ -1,36 +1,80 @@ import { useMemo, type VFC } from 'react'; import 'chartjs-adapter-date-fns'; + import type { ExecutiveSummarySchema } from 'openapi'; -import { LineChart, NotEnoughData } from '../../components/LineChart/LineChart'; +import { + fillGradientPrimary, + LineChart, + NotEnoughData, +} from '../../components/LineChart/LineChart'; import { MetricsSummaryTooltip } from './MetricsChartTooltip/MetricsChartTooltip'; import { useMetricsSummary } from '../../hooks/useMetricsSummary'; import { usePlaceholderData } from 'component/executiveDashboard/hooks/usePlaceholderData'; import type { GroupedDataByProject } from '../../hooks/useGroupedProjectTrends'; +import { useTheme } from '@mui/material'; +import { aggregateDataPerDate } from './MetricsChartTooltip/aggregate-metrics-by-day'; interface IMetricsSummaryChartProps { metricsSummaryTrends: GroupedDataByProject< ExecutiveSummarySchema['metricsSummaryTrends'] >; + isAggregate?: boolean; } export const MetricsSummaryChart: VFC = ({ metricsSummaryTrends, + isAggregate, }) => { - const data = useMetricsSummary(metricsSummaryTrends); + const theme = useTheme(); + const metricsSummary = useMetricsSummary(metricsSummaryTrends); const notEnoughData = useMemo( - () => !data.datasets.some((d) => d.data.length > 1), - [data], + () => !metricsSummary.datasets.some((d) => d.data.length > 1), + [metricsSummary], ); const placeholderData = usePlaceholderData(); + const aggregatedPerDay = useMemo(() => { + const result = aggregateDataPerDate( + Object.values(metricsSummary.datasets).flatMap((item) => item.data), + ); + const data = Object.entries(result) + .map(([date, trends]) => ({ date, ...trends })) + .sort( + (a, b) => + new Date(a.date).getTime() - new Date(b.date).getTime(), + ); + + return { + datasets: [ + { + label: 'Total Requests', + data: data, + borderColor: theme.palette.primary.light, + backgroundColor: fillGradientPrimary, + fill: true, + order: 3, + }, + ], + }; + }, [JSON.stringify(metricsSummaryTrends), theme]); + + const data = isAggregate ? aggregatedPerDay : metricsSummary; + return ( : false} /> ); diff --git a/frontend/src/component/executiveDashboard/componentsChart/ProjectHealthChart/ProjectHealthChart.tsx b/frontend/src/component/executiveDashboard/componentsChart/ProjectHealthChart/ProjectHealthChart.tsx index 392ebba141..b6cec83424 100644 --- a/frontend/src/component/executiveDashboard/componentsChart/ProjectHealthChart/ProjectHealthChart.tsx +++ b/frontend/src/component/executiveDashboard/componentsChart/ProjectHealthChart/ProjectHealthChart.tsx @@ -4,6 +4,7 @@ import type { ExecutiveSummarySchema } from 'openapi'; import { HealthTooltip } from './HealthChartTooltip/HealthChartTooltip'; import { useProjectChartData } from 'component/executiveDashboard/hooks/useProjectChartData'; import { + fillGradientPrimary, LineChart, NotEnoughData, } from 'component/executiveDashboard/components/LineChart/LineChart'; @@ -73,7 +74,8 @@ export const ProjectHealthChart: VFC = ({ date: item.date, })), borderColor: theme.palette.primary.light, - fill: false, + backgroundColor: fillGradientPrimary, + fill: true, order: 3, }, ], diff --git a/frontend/src/component/executiveDashboard/componentsChart/TimeToProductionChart/TimeToProductionChart.tsx b/frontend/src/component/executiveDashboard/componentsChart/TimeToProductionChart/TimeToProductionChart.tsx index 9b4a305c43..d251604d87 100644 --- a/frontend/src/component/executiveDashboard/componentsChart/TimeToProductionChart/TimeToProductionChart.tsx +++ b/frontend/src/component/executiveDashboard/componentsChart/TimeToProductionChart/TimeToProductionChart.tsx @@ -1,39 +1,108 @@ import { useMemo, type VFC } from 'react'; import 'chartjs-adapter-date-fns'; import type { ExecutiveSummarySchema } from 'openapi'; -import { LineChart } from '../../components/LineChart/LineChart'; +import { + fillGradientPrimary, + LineChart, + NotEnoughData, +} from '../../components/LineChart/LineChart'; import { useProjectChartData } from '../../hooks/useProjectChartData'; import type { GroupedDataByProject } from '../../hooks/useGroupedProjectTrends'; import { usePlaceholderData } from '../../hooks/usePlaceholderData'; import { TimeToProductionTooltip } from './TimeToProductionTooltip/TimeToProductionTooltip'; +import { useTheme } from '@mui/material'; interface ITimeToProductionChartProps { projectFlagTrends: GroupedDataByProject< ExecutiveSummarySchema['projectFlagTrends'] >; + isAggregate?: boolean; +} + +type GroupedDataByDate = Record; + +type DateResult = Record; + +function averageTimeToProduction( + projectsData: ExecutiveSummarySchema['projectFlagTrends'], +): DateResult { + // Group the data by date + const groupedData: GroupedDataByDate = {}; + projectsData.forEach((item) => { + const { date, timeToProduction } = item; + if (!groupedData[date]) { + groupedData[date] = []; + } + if (timeToProduction !== undefined) { + groupedData[date].push(timeToProduction); + } + }); + // Calculate the average time to production for each date + const averageByDate: DateResult = {}; + Object.entries(groupedData).forEach(([date, times]) => { + const sum = times.reduce((acc, curr) => acc + curr, 0); + const average = sum / times.length; + averageByDate[date] = average; + }); + return averageByDate; } export const TimeToProductionChart: VFC = ({ projectFlagTrends, + isAggregate, }) => { - const data = useProjectChartData(projectFlagTrends); + const theme = useTheme(); + const projectsDatasets = useProjectChartData(projectFlagTrends); const notEnoughData = useMemo( - () => !data.datasets.some((d) => d.data.length > 1), - [data], + () => !projectsDatasets.datasets.some((d) => d.data.length > 1), + [projectsDatasets], ); + const aggregatedPerDay = useMemo(() => { + const result = averageTimeToProduction( + Object.values(projectsDatasets.datasets).flatMap( + (item) => item.data, + ), + ); + const data = Object.entries(result) + .map(([date, timeToProduction]) => ({ date, timeToProduction })) + .sort( + (a, b) => + new Date(a.date).getTime() - new Date(b.date).getTime(), + ); + + return { + datasets: [ + { + label: 'Time to production', + data, + borderColor: theme.palette.primary.light, + backgroundColor: fillGradientPrimary, + fill: true, + order: 3, + }, + ], + }; + }, [JSON.stringify(projectsDatasets), theme]); + + const data = isAggregate ? aggregatedPerDay : projectsDatasets; const placeholderData = usePlaceholderData(); return ( : false} /> ); }; diff --git a/frontend/src/component/executiveDashboard/componentsChart/TimeToProductionChart/TimeToProductionTooltip/TimeToProductionTooltip.tsx b/frontend/src/component/executiveDashboard/componentsChart/TimeToProductionChart/TimeToProductionTooltip/TimeToProductionTooltip.tsx index 20362fb8ed..15e9c7d180 100644 --- a/frontend/src/component/executiveDashboard/componentsChart/TimeToProductionChart/TimeToProductionTooltip/TimeToProductionTooltip.tsx +++ b/frontend/src/component/executiveDashboard/componentsChart/TimeToProductionChart/TimeToProductionTooltip/TimeToProductionTooltip.tsx @@ -29,7 +29,7 @@ const getInterval = (days?: number) => { return `${weeks.toFixed(1)} weeks`; } } else { - return `${days} days`; + return `${days.toFixed(2)} days`; } }; diff --git a/frontend/src/component/executiveDashboard/componentsChart/UpdatesPerEnvironmentTypeChart/UpdatesPerEnvironmentTypeChart.tsx b/frontend/src/component/executiveDashboard/componentsChart/UpdatesPerEnvironmentTypeChart/UpdatesPerEnvironmentTypeChart.tsx index 5e2d7fa846..ccf457d5b7 100644 --- a/frontend/src/component/executiveDashboard/componentsChart/UpdatesPerEnvironmentTypeChart/UpdatesPerEnvironmentTypeChart.tsx +++ b/frontend/src/component/executiveDashboard/componentsChart/UpdatesPerEnvironmentTypeChart/UpdatesPerEnvironmentTypeChart.tsx @@ -7,6 +7,7 @@ import type { } from 'openapi'; import { LineChart, NotEnoughData } from '../../components/LineChart/LineChart'; import { usePlaceholderData } from 'component/executiveDashboard/hooks/usePlaceholderData'; +import { UpdatesPerEnvironmentTypeChartTooltip } from './UpdatesPerEnvironmentTypeChartTooltip/UpdatesPerEnvironmentTypeChartTooltip'; interface IUpdatesPerEnvironmnetTypeChart { environmentTypeTrends: ExecutiveSummarySchema['environmentTypeTrends']; @@ -67,25 +68,35 @@ export const UpdatesPerEnvironmentTypeChart: VFC< const data = useMemo(() => { const grouped = groupByDate(environmentTypeTrends); - const labels = environmentTypeTrends?.map((item) => item.date); const datasets = Object.entries(grouped).map( ([environmentType, trends]) => { const color = getEnvironmentTypeColor(environmentType); return { label: environmentType, - data: trends.map((item) => item.totalUpdates), + data: trends, borderColor: color, backgroundColor: color, fill: false, }; }, ); - return { labels, datasets }; + return { datasets }; }, [theme, environmentTypeTrends]); return ( : isLoading} /> ); diff --git a/frontend/src/component/executiveDashboard/hooks/useDashboardData.ts b/frontend/src/component/executiveDashboard/hooks/useDashboardData.ts new file mode 100644 index 0000000000..82133e0df4 --- /dev/null +++ b/frontend/src/component/executiveDashboard/hooks/useDashboardData.ts @@ -0,0 +1,43 @@ +import { useMemo } from 'react'; +import type { ExecutiveSummarySchema } from 'openapi'; +import { useFilteredTrends } from './useFilteredTrends'; +import { useGroupedProjectTrends } from './useGroupedProjectTrends'; +import { useFilteredFlagsSummary } from './useFilteredFlagsSummary'; +import { useAvgTimeToProduction } from './useAvgTimeToProduction'; + +export const useDashboardData = ( + executiveDashboardData: ExecutiveSummarySchema, + projects: string[], +) => + useMemo(() => { + const projectsData = useFilteredTrends( + executiveDashboardData.projectFlagTrends, + projects, + ); + + const groupedProjectsData = useGroupedProjectTrends(projectsData); + + const metricsData = useFilteredTrends( + executiveDashboardData.metricsSummaryTrends, + projects, + ); + const groupedMetricsData = useGroupedProjectTrends(metricsData); + + const { users, environmentTypeTrends } = executiveDashboardData; + + const summary = useFilteredFlagsSummary(projectsData); + + const avgDaysToProduction = useAvgTimeToProduction(groupedProjectsData); + + return { + ...executiveDashboardData, + projectsData, + groupedProjectsData, + metricsData, + groupedMetricsData, + users, + environmentTypeTrends, + summary, + avgDaysToProduction, + }; + }, [executiveDashboardData, projects]);