From 3e57c4803cb09df3aa153a00c3ec91196696c71b Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Tue, 27 May 2025 14:06:48 +0100 Subject: [PATCH] Chore(1-3755)/split insights in three (#10035) Creates sections for the insights dashboard and moves charts around into the same order as the sketches and into the right sections. There's no charts for the top section (lifecycle currently) yet, and the sections also don't have their own filters. To make this re-ordering easier, I've also moved the previous insights chart into a legacy file and set up a proxy component that handles switching based on the flag. ![image](https://github.com/user-attachments/assets/f0929998-def3-4643-babd-ab53f4ea8e98) Next step is separating the filters. --- .../src/component/insights/InsightsCharts.tsx | 309 ++++++++--------- .../insights/LegacyInsightsCharts.tsx | 320 ++++++++++++++++++ .../InsightsHeader/InsightsHeader.tsx | 4 +- 3 files changed, 468 insertions(+), 165 deletions(-) create mode 100644 frontend/src/component/insights/LegacyInsightsCharts.tsx diff --git a/frontend/src/component/insights/InsightsCharts.tsx b/frontend/src/component/insights/InsightsCharts.tsx index 26ce5c245c..a491a2aec3 100644 --- a/frontend/src/component/insights/InsightsCharts.tsx +++ b/frontend/src/component/insights/InsightsCharts.tsx @@ -1,4 +1,4 @@ -import type { FC } from 'react'; +import type { FC, PropsWithChildren } from 'react'; import { Box, Paper, styled } from '@mui/material'; import { UserStats } from './componentsStat/UserStats/UserStats.tsx'; import { UsersChart } from './componentsChart/UsersChart/UsersChart.tsx'; @@ -8,8 +8,6 @@ import { FlagsChart } from './componentsChart/FlagsChart/FlagsChart.tsx'; import { FlagsProjectChart } from './componentsChart/FlagsProjectChart/FlagsProjectChart.tsx'; import { HealthStats } from './componentsStat/HealthStats/HealthStats.tsx'; import { ProjectHealthChart } from './componentsChart/ProjectHealthChart/ProjectHealthChart.tsx'; -import { TimeToProduction } from './componentsStat/TimeToProduction/TimeToProduction.tsx'; -import { TimeToProductionChart } from './componentsChart/TimeToProductionChart/TimeToProductionChart.tsx'; import { MetricsSummaryChart } from './componentsChart/MetricsSummaryChart/MetricsSummaryChart.tsx'; import { UpdatesPerEnvironmentTypeChart } from './componentsChart/UpdatesPerEnvironmentTypeChart/UpdatesPerEnvironmentTypeChart.tsx'; import type { InstanceInsightsSchema } from 'openapi'; @@ -17,8 +15,8 @@ import type { GroupedDataByProject } from './hooks/useGroupedProjectTrends.ts'; import { allOption } from 'component/common/ProjectSelect/ProjectSelect'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import { WidgetTitle } from './components/WidgetTitle/WidgetTitle.tsx'; -import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { useUiFlag } from 'hooks/useUiFlag.ts'; +import { LegacyInsightsCharts } from './LegacyInsightsCharts.tsx'; export interface IChartsProps { flagTrends: InstanceInsightsSchema['flagTrends']; @@ -50,7 +48,7 @@ export interface IChartsProps { const StyledContainer = styled(Box)(({ theme }) => ({ display: 'flex', flexDirection: 'column', - gap: theme.spacing(2), + gap: theme.spacing(4), })); const StyledWidget = styled(Paper)(({ theme }) => ({ @@ -91,7 +89,23 @@ const StyledChartContainer = styled(Box)(({ theme }) => ({ padding: theme.spacing(3), })); -export const InsightsCharts: FC = ({ +const StyledSection = styled('section')(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(2), +})); + +const Section: FC> = ({ + title, + children, +}) => ( + +

{title}

+ {children} +
+); + +const NewInsightsCharts: FC = ({ projects, summary, userTrends, @@ -105,7 +119,6 @@ export const InsightsCharts: FC = ({ const showAllProjects = projects[0] === allOption.id; const isOneProjectSelected = projects.length === 1; const { isEnterprise } = useUiConfig(); - const showMedianTimeToProduction = !useUiFlag('lifecycleMetrics'); const lastUserTrend = userTrends[userTrends.length - 1]; const lastFlagTrend = flagTrends[flagTrends.length - 1]; @@ -124,163 +137,74 @@ export const InsightsCharts: FC = ({ return ( - - - - - - - - - - - - } - elseShow={ - <> - - - - - - - - - - - } - /> - - - - - } - /> - - - - - - {showMedianTimeToProduction ? ( - - +
+
+ {showAllProjects ? ( + + + + + + + + + + ) : ( + + + + + + + + + + )} + {isEnterprise() ? ( + + + - - - - - - - ) : null} - - } - /> - - - - - - - - - - - - } - elseShow={ - <> - - - - - - - - - - - } - /> - + + + + + + ) : null} + {isEnterprise() ? ( <> @@ -317,8 +241,67 @@ export const InsightsCharts: FC = ({ - } - /> + ) : null} +
+ +
+ {showAllProjects ? ( + + + + + + + + + + ) : ( + + + + + + + + + + )} +
); }; + +export const InsightsCharts: FC = (props) => { + const useNewInsightsCharts = useUiFlag('lifecycleMetrics'); + + return useNewInsightsCharts ? ( + + ) : ( + + ); +}; diff --git a/frontend/src/component/insights/LegacyInsightsCharts.tsx b/frontend/src/component/insights/LegacyInsightsCharts.tsx new file mode 100644 index 0000000000..512b4fdd73 --- /dev/null +++ b/frontend/src/component/insights/LegacyInsightsCharts.tsx @@ -0,0 +1,320 @@ +import type { FC } from 'react'; +import { Box, Paper, styled } from '@mui/material'; +import { UserStats } from './componentsStat/UserStats/UserStats.tsx'; +import { UsersChart } from './componentsChart/UsersChart/UsersChart.tsx'; +import { UsersPerProjectChart } from './componentsChart/UsersPerProjectChart/UsersPerProjectChart.tsx'; +import { FlagStats } from './componentsStat/FlagStats/FlagStats.tsx'; +import { FlagsChart } from './componentsChart/FlagsChart/FlagsChart.tsx'; +import { FlagsProjectChart } from './componentsChart/FlagsProjectChart/FlagsProjectChart.tsx'; +import { HealthStats } from './componentsStat/HealthStats/HealthStats.tsx'; +import { ProjectHealthChart } from './componentsChart/ProjectHealthChart/ProjectHealthChart.tsx'; +import { TimeToProduction } from './componentsStat/TimeToProduction/TimeToProduction.tsx'; +import { TimeToProductionChart } from './componentsChart/TimeToProductionChart/TimeToProductionChart.tsx'; +import { MetricsSummaryChart } from './componentsChart/MetricsSummaryChart/MetricsSummaryChart.tsx'; +import { UpdatesPerEnvironmentTypeChart } from './componentsChart/UpdatesPerEnvironmentTypeChart/UpdatesPerEnvironmentTypeChart.tsx'; +import type { InstanceInsightsSchema } from 'openapi'; +import type { GroupedDataByProject } from './hooks/useGroupedProjectTrends.ts'; +import { allOption } from 'component/common/ProjectSelect/ProjectSelect'; +import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; +import { WidgetTitle } from './components/WidgetTitle/WidgetTitle.tsx'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; + +export interface IChartsProps { + flagTrends: InstanceInsightsSchema['flagTrends']; + projectsData: InstanceInsightsSchema['projectFlagTrends']; + groupedProjectsData: GroupedDataByProject< + InstanceInsightsSchema['projectFlagTrends'] + >; + metricsData: InstanceInsightsSchema['metricsSummaryTrends']; + groupedMetricsData: GroupedDataByProject< + InstanceInsightsSchema['metricsSummaryTrends'] + >; + userTrends: InstanceInsightsSchema['userTrends']; + environmentTypeTrends: InstanceInsightsSchema['environmentTypeTrends']; + summary: { + total: number; + active: number; + stale: number; + potentiallyStale: number; + averageUsers: number; + averageHealth?: string; + flagsPerUser?: string; + medianTimeToProduction?: number; + }; + loading: boolean; + projects: string[]; + allMetricsDatapoints: string[]; +} + +const StyledContainer = styled(Box)(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(2), +})); + +const StyledWidget = styled(Paper)(({ theme }) => ({ + borderRadius: `${theme.shape.borderRadiusLarge}px`, + boxShadow: 'none', + display: 'flex', + flexWrap: 'wrap', + [theme.breakpoints.up('md')]: { + flexDirection: 'row', + flexWrap: 'nowrap', + }, +})); + +const StyledWidgetContent = styled(Box)(({ theme }) => ({ + padding: theme.spacing(3), + width: '100%', +})); + +const StyledWidgetStats = styled(Box)<{ width?: number; padding?: number }>( + ({ theme, width = 300, padding = 3 }) => ({ + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(2), + padding: theme.spacing(padding), + minWidth: '100%', + [theme.breakpoints.up('md')]: { + minWidth: `${width}px`, + borderRight: `1px solid ${theme.palette.background.application}`, + }, + }), +); + +const StyledChartContainer = styled(Box)(({ theme }) => ({ + position: 'relative', + minWidth: 0, // bugfix, see: https://github.com/chartjs/Chart.js/issues/4156#issuecomment-295180128 + flexGrow: 1, + margin: 'auto 0', + padding: theme.spacing(3), +})); + +export const LegacyInsightsCharts: FC = ({ + projects, + summary, + userTrends, + groupedProjectsData, + flagTrends, + groupedMetricsData, + environmentTypeTrends, + allMetricsDatapoints, + loading, +}) => { + const showAllProjects = projects[0] === allOption.id; + const isOneProjectSelected = projects.length === 1; + const { isEnterprise } = useUiConfig(); + + const lastUserTrend = userTrends[userTrends.length - 1]; + const lastFlagTrend = flagTrends[flagTrends.length - 1]; + + const usersTotal = lastUserTrend?.total ?? 0; + const usersActive = lastUserTrend?.active ?? 0; + const usersInactive = lastUserTrend?.inactive ?? 0; + const flagsTotal = lastFlagTrend?.total ?? 0; + + function getFlagsPerUser(flagsTotal: number, usersTotal: number) { + const flagsPerUserCalculation = flagsTotal / usersTotal; + return Number.isNaN(flagsPerUserCalculation) + ? 'N/A' + : flagsPerUserCalculation.toFixed(2); + } + + return ( + + + + + + + + + + + + + } + elseShow={ + <> + + + + + + + + + + + } + /> + + + + + } + /> + + + + + + + + + + + + + + + + } + /> + + + + + + + + + + + + } + elseShow={ + <> + + + + + + + + + + + } + /> + + + + + + + + + + + + + + + + + } + /> + + ); +}; diff --git a/frontend/src/component/insights/components/InsightsHeader/InsightsHeader.tsx b/frontend/src/component/insights/components/InsightsHeader/InsightsHeader.tsx index b9e5e07ac1..cc91c4b1a4 100644 --- a/frontend/src/component/insights/components/InsightsHeader/InsightsHeader.tsx +++ b/frontend/src/component/insights/components/InsightsHeader/InsightsHeader.tsx @@ -73,14 +73,14 @@ export const InsightsHeader: VFC = ({ actions }) => { titleElement={ ({ display: 'flex', alignItems: 'center', gap: theme.spacing(1), })} > - Insights{' '} + Insights } actions={