mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	refactor: FlagsChart and FlagsProjectChart components (#6087)
Co-authored-by: Fredrik Strand Oseberg <fredrik.no@gmail.com>
This commit is contained in:
		
							parent
							
								
									bc95ed654f
								
							
						
					
					
						commit
						d77e5391ed
					
				| @ -33,6 +33,7 @@ const useDashboardGrid = () => { | ||||
|             chartSpan: 1, | ||||
|             userTrendsOrder: 3, | ||||
|             flagStatsOrder: 2, | ||||
|             largeChartSpan: 1, | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
| @ -42,6 +43,7 @@ const useDashboardGrid = () => { | ||||
|             chartSpan: 2, | ||||
|             userTrendsOrder: 3, | ||||
|             flagStatsOrder: 2, | ||||
|             largeChartSpan: 2, | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
| @ -50,6 +52,7 @@ const useDashboardGrid = () => { | ||||
|         chartSpan: 1, | ||||
|         userTrendsOrder: 2, | ||||
|         flagStatsOrder: 3, | ||||
|         largeChartSpan: 2, | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| @ -69,8 +72,13 @@ export const ExecutiveDashboard: VFC = () => { | ||||
|         ).toFixed(1); | ||||
|     }, [executiveDashboardData]); | ||||
| 
 | ||||
|     const { gridTemplateColumns, chartSpan, userTrendsOrder, flagStatsOrder } = | ||||
|         useDashboardGrid(); | ||||
|     const { | ||||
|         gridTemplateColumns, | ||||
|         chartSpan, | ||||
|         userTrendsOrder, | ||||
|         flagStatsOrder, | ||||
|         largeChartSpan, | ||||
|     } = useDashboardGrid(); | ||||
| 
 | ||||
|     return ( | ||||
|         <> | ||||
| @ -107,11 +115,18 @@ export const ExecutiveDashboard: VFC = () => { | ||||
|                         flagTrends={executiveDashboardData.flagTrends} | ||||
|                     /> | ||||
|                 </Widget> | ||||
|                 <Widget | ||||
|                     title='Number of flags per project' | ||||
|                     order={5} | ||||
|                     span={largeChartSpan} | ||||
|                 > | ||||
|                     <FlagsProjectChart | ||||
|                         projectFlagTrends={ | ||||
|                             executiveDashboardData.projectFlagTrends | ||||
|                         } | ||||
|                     /> | ||||
|                 </Widget> | ||||
|             </StyledGrid> | ||||
| 
 | ||||
|             <FlagsProjectChart | ||||
|                 projectFlagTrends={executiveDashboardData.projectFlagTrends} | ||||
|             /> | ||||
|         </> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| @ -1,3 +1,37 @@ | ||||
| import { lazy } from 'react'; | ||||
| import { useMemo, type VFC } from 'react'; | ||||
| import 'chartjs-adapter-date-fns'; | ||||
| import { useTheme } from '@mui/material'; | ||||
| import { ExecutiveSummarySchema } from 'openapi'; | ||||
| import { LineChart } from '../LineChart/LineChart'; | ||||
| 
 | ||||
| export const FlagsChart = lazy(() => import('./FlagsChartComponent')); | ||||
| interface IFlagsChartProps { | ||||
|     flagTrends: ExecutiveSummarySchema['flagTrends']; | ||||
| } | ||||
| 
 | ||||
| export const FlagsChart: VFC<IFlagsChartProps> = ({ flagTrends }) => { | ||||
|     const theme = useTheme(); | ||||
|     const data = useMemo( | ||||
|         () => ({ | ||||
|             labels: flagTrends.map((item) => item.date), | ||||
|             datasets: [ | ||||
|                 { | ||||
|                     label: 'Total flags', | ||||
|                     data: flagTrends.map((item) => item.total), | ||||
|                     borderColor: theme.palette.primary.light, | ||||
|                     backgroundColor: theme.palette.primary.light, | ||||
|                     fill: true, | ||||
|                 }, | ||||
|                 { | ||||
|                     label: 'Stale', | ||||
|                     data: flagTrends.map((item) => item.stale), | ||||
|                     borderColor: theme.palette.warning.border, | ||||
|                     backgroundColor: theme.palette.warning.border, | ||||
|                     fill: true, | ||||
|                 }, | ||||
|             ], | ||||
|         }), | ||||
|         [theme, flagTrends], | ||||
|     ); | ||||
| 
 | ||||
|     return <LineChart data={data} />; | ||||
| }; | ||||
|  | ||||
| @ -1,137 +0,0 @@ | ||||
| import { useMemo, type VFC } from 'react'; | ||||
| import { | ||||
|     Chart as ChartJS, | ||||
|     CategoryScale, | ||||
|     LinearScale, | ||||
|     PointElement, | ||||
|     LineElement, | ||||
|     Title, | ||||
|     Tooltip, | ||||
|     Legend, | ||||
|     TimeScale, | ||||
| } from 'chart.js'; | ||||
| import { Line } from 'react-chartjs-2'; | ||||
| import 'chartjs-adapter-date-fns'; | ||||
| import { Theme, useTheme } from '@mui/material'; | ||||
| import { | ||||
|     useLocationSettings, | ||||
|     type ILocationSettings, | ||||
| } from 'hooks/useLocationSettings'; | ||||
| import { formatDateYMD } from 'utils/formatDate'; | ||||
| import { ExecutiveSummarySchema } from 'openapi'; | ||||
| 
 | ||||
| const createData = ( | ||||
|     theme: Theme, | ||||
|     flagTrends: ExecutiveSummarySchema['flagTrends'] = [], | ||||
| ) => ({ | ||||
|     labels: flagTrends.map((item) => item.date), | ||||
|     datasets: [ | ||||
|         { | ||||
|             label: 'Total flags', | ||||
|             data: flagTrends.map((item) => item.total), | ||||
|             borderColor: theme.palette.primary.main, | ||||
|             backgroundColor: theme.palette.primary.main, | ||||
|             fill: true, | ||||
|         }, | ||||
|         { | ||||
|             label: 'Stale', | ||||
|             data: flagTrends.map((item) => item.stale), | ||||
|             borderColor: theme.palette.warning.main, | ||||
|             backgroundColor: theme.palette.warning.main, | ||||
|             fill: true, | ||||
|         }, | ||||
|         { | ||||
|             label: 'Active flags', | ||||
|             data: flagTrends.map((item) => item.active), | ||||
|             borderColor: theme.palette.success.main, | ||||
|             backgroundColor: theme.palette.success.main, | ||||
|             fill: true, | ||||
|         }, | ||||
|     ], | ||||
| }); | ||||
| 
 | ||||
| const createOptions = (theme: Theme, locationSettings: ILocationSettings) => | ||||
|     ({ | ||||
|         responsive: true, | ||||
|         plugins: { | ||||
|             legend: { | ||||
|                 position: 'bottom', | ||||
|             }, | ||||
|             tooltip: { | ||||
|                 callbacks: { | ||||
|                     title: (tooltipItems: any) => { | ||||
|                         const item = tooltipItems?.[0]; | ||||
|                         const date = | ||||
|                             item?.chart?.data?.labels?.[item.dataIndex]; | ||||
|                         return date | ||||
|                             ? formatDateYMD( | ||||
|                                   date, | ||||
|                                   locationSettings.locale, | ||||
|                                   'UTC', | ||||
|                               ) | ||||
|                             : ''; | ||||
|                     }, | ||||
|                 }, | ||||
|             }, | ||||
|         }, | ||||
|         locale: locationSettings.locale, | ||||
|         interaction: { | ||||
|             intersect: false, | ||||
|             axis: 'x', | ||||
|         }, | ||||
|         color: theme.palette.text.secondary, | ||||
|         scales: { | ||||
|             y: { | ||||
|                 type: 'linear', | ||||
|                 grid: { | ||||
|                     color: theme.palette.divider, | ||||
|                     borderColor: theme.palette.divider, | ||||
|                 }, | ||||
|                 ticks: { color: theme.palette.text.secondary }, | ||||
|             }, | ||||
|             x: { | ||||
|                 type: 'time', | ||||
|                 time: { | ||||
|                     unit: 'month', | ||||
|                 }, | ||||
|                 grid: { | ||||
|                     color: theme.palette.divider, | ||||
|                     borderColor: theme.palette.divider, | ||||
|                 }, | ||||
|                 ticks: { | ||||
|                     color: theme.palette.text.secondary, | ||||
|                 }, | ||||
|             }, | ||||
|         }, | ||||
|     }) as const; | ||||
| 
 | ||||
| interface IFlagsChartComponentProps { | ||||
|     flagTrends: ExecutiveSummarySchema['flagTrends']; | ||||
| } | ||||
| 
 | ||||
| const FlagsChartComponent: VFC<IFlagsChartComponentProps> = ({ | ||||
|     flagTrends, | ||||
| }) => { | ||||
|     const theme = useTheme(); | ||||
|     const { locationSettings } = useLocationSettings(); | ||||
|     const data = useMemo( | ||||
|         () => createData(theme, flagTrends), | ||||
|         [theme, flagTrends], | ||||
|     ); | ||||
|     const options = createOptions(theme, locationSettings); | ||||
| 
 | ||||
|     return <Line options={options} data={data} />; | ||||
| }; | ||||
| 
 | ||||
| ChartJS.register( | ||||
|     CategoryScale, | ||||
|     LinearScale, | ||||
|     PointElement, | ||||
|     LineElement, | ||||
|     TimeScale, | ||||
|     Title, | ||||
|     Tooltip, | ||||
|     Legend, | ||||
| ); | ||||
| 
 | ||||
| export default FlagsChartComponent; | ||||
| @ -1,5 +1,58 @@ | ||||
| import { lazy } from 'react'; | ||||
| import { useMemo, type VFC } from 'react'; | ||||
| import 'chartjs-adapter-date-fns'; | ||||
| import { useTheme } from '@mui/material'; | ||||
| import { | ||||
|     ExecutiveSummarySchema, | ||||
|     ExecutiveSummarySchemaProjectFlagTrendsItem, | ||||
| } from 'openapi'; | ||||
| import { LineChart } from '../LineChart/LineChart'; | ||||
| 
 | ||||
| export const FlagsProjectChart = lazy( | ||||
|     () => import('./FlagsProjectChartComponent'), | ||||
| ); | ||||
| interface IFlagsProjectChartProps { | ||||
|     projectFlagTrends: ExecutiveSummarySchema['projectFlagTrends']; | ||||
| } | ||||
| 
 | ||||
| const getRandomColor = () => { | ||||
|     const letters = '0123456789ABCDEF'; | ||||
|     let color = '#'; | ||||
|     for (let i = 0; i < 6; i++) { | ||||
|         color += letters[Math.floor(Math.random() * 16)]; | ||||
|     } | ||||
|     return color; | ||||
| }; | ||||
| 
 | ||||
| export const FlagsProjectChart: VFC<IFlagsProjectChartProps> = ({ | ||||
|     projectFlagTrends, | ||||
| }) => { | ||||
|     const theme = useTheme(); | ||||
|     const data = useMemo(() => { | ||||
|         const groupedFlagTrends = projectFlagTrends.reduce< | ||||
|             Record<string, ExecutiveSummarySchemaProjectFlagTrendsItem[]> | ||||
|         >((groups, item) => { | ||||
|             if (!groups[item.project]) { | ||||
|                 groups[item.project] = []; | ||||
|             } | ||||
|             groups[item.project].push(item); | ||||
|             return groups; | ||||
|         }, {}); | ||||
| 
 | ||||
|         const datasets = Object.entries(groupedFlagTrends).map( | ||||
|             ([project, trends]) => { | ||||
|                 const color = getRandomColor(); | ||||
|                 return { | ||||
|                     label: project, | ||||
|                     data: trends.map((item) => item.total), | ||||
|                     borderColor: color, | ||||
|                     backgroundColor: color, | ||||
|                     fill: true, | ||||
|                 }; | ||||
|             }, | ||||
|         ); | ||||
| 
 | ||||
|         return { | ||||
|             labels: projectFlagTrends.map((item) => item.date), | ||||
|             datasets, | ||||
|         }; | ||||
|     }, [theme, projectFlagTrends]); | ||||
| 
 | ||||
|     return <LineChart data={data} />; | ||||
| }; | ||||
|  | ||||
| @ -1,162 +0,0 @@ | ||||
| import { useMemo, type VFC } from 'react'; | ||||
| import { | ||||
|     Chart as ChartJS, | ||||
|     CategoryScale, | ||||
|     LinearScale, | ||||
|     PointElement, | ||||
|     LineElement, | ||||
|     Title, | ||||
|     Tooltip, | ||||
|     Legend, | ||||
|     TimeScale, | ||||
| } from 'chart.js'; | ||||
| import { Line } from 'react-chartjs-2'; | ||||
| import 'chartjs-adapter-date-fns'; | ||||
| import { Paper, Theme, Typography, useTheme } from '@mui/material'; | ||||
| import { | ||||
|     useLocationSettings, | ||||
|     type ILocationSettings, | ||||
| } from 'hooks/useLocationSettings'; | ||||
| import { formatDateYMD } from 'utils/formatDate'; | ||||
| import { | ||||
|     ExecutiveSummarySchema, | ||||
|     ExecutiveSummarySchemaProjectFlagTrendsItem, | ||||
| } from 'openapi'; | ||||
| 
 | ||||
| const getRandomColor = () => { | ||||
|     const letters = '0123456789ABCDEF'; | ||||
|     let color = '#'; | ||||
|     for (let i = 0; i < 6; i++) { | ||||
|         color += letters[Math.floor(Math.random() * 16)]; | ||||
|     } | ||||
|     return color; | ||||
| }; | ||||
| 
 | ||||
| const createData = ( | ||||
|     theme: Theme, | ||||
|     flagTrends: ExecutiveSummarySchema['projectFlagTrends'] = [], | ||||
| ) => { | ||||
|     const groupedFlagTrends = flagTrends.reduce< | ||||
|         Record<string, ExecutiveSummarySchemaProjectFlagTrendsItem[]> | ||||
|     >((groups, item) => { | ||||
|         if (!groups[item.project]) { | ||||
|             groups[item.project] = []; | ||||
|         } | ||||
|         groups[item.project].push(item); | ||||
|         return groups; | ||||
|     }, {}); | ||||
| 
 | ||||
|     const datasets = Object.entries(groupedFlagTrends).map( | ||||
|         ([project, trends]) => { | ||||
|             const color = getRandomColor(); | ||||
|             return { | ||||
|                 label: project, | ||||
|                 data: trends.map((item) => item.total), | ||||
|                 borderColor: color, | ||||
|                 backgroundColor: color, | ||||
|                 fill: true, | ||||
|             }; | ||||
|         }, | ||||
|     ); | ||||
| 
 | ||||
|     return { | ||||
|         labels: flagTrends.map((item) => item.date), | ||||
|         datasets, | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| const createOptions = (theme: Theme, locationSettings: ILocationSettings) => | ||||
|     ({ | ||||
|         responsive: true, | ||||
|         plugins: { | ||||
|             legend: { | ||||
|                 position: 'bottom', | ||||
|             }, | ||||
|             tooltip: { | ||||
|                 callbacks: { | ||||
|                     title: (tooltipItems: any) => { | ||||
|                         const item = tooltipItems?.[0]; | ||||
|                         const date = | ||||
|                             item?.chart?.data?.labels?.[item.dataIndex]; | ||||
|                         return date | ||||
|                             ? formatDateYMD( | ||||
|                                   date, | ||||
|                                   locationSettings.locale, | ||||
|                                   'UTC', | ||||
|                               ) | ||||
|                             : ''; | ||||
|                     }, | ||||
|                 }, | ||||
|             }, | ||||
|         }, | ||||
|         locale: locationSettings.locale, | ||||
|         interaction: { | ||||
|             intersect: false, | ||||
|             axis: 'x', | ||||
|         }, | ||||
|         color: theme.palette.text.secondary, | ||||
|         scales: { | ||||
|             y: { | ||||
|                 type: 'linear', | ||||
|                 grid: { | ||||
|                     color: theme.palette.divider, | ||||
|                     borderColor: theme.palette.divider, | ||||
|                 }, | ||||
|                 ticks: { color: theme.palette.text.secondary }, | ||||
|             }, | ||||
|             x: { | ||||
|                 type: 'time', | ||||
|                 time: { | ||||
|                     unit: 'month', | ||||
|                 }, | ||||
|                 grid: { | ||||
|                     color: theme.palette.divider, | ||||
|                     borderColor: theme.palette.divider, | ||||
|                 }, | ||||
|                 ticks: { | ||||
|                     color: theme.palette.text.secondary, | ||||
|                 }, | ||||
|             }, | ||||
|         }, | ||||
|     }) as const; | ||||
| 
 | ||||
| interface IFlagsChartComponentProps { | ||||
|     projectFlagTrends: ExecutiveSummarySchema['projectFlagTrends']; | ||||
| } | ||||
| 
 | ||||
| const FlagsProjectChart: VFC<IFlagsChartComponentProps> = ({ | ||||
|     projectFlagTrends, | ||||
| }) => { | ||||
|     const theme = useTheme(); | ||||
|     const { locationSettings } = useLocationSettings(); | ||||
|     const data = useMemo( | ||||
|         () => createData(theme, projectFlagTrends), | ||||
|         [theme, projectFlagTrends], | ||||
|     ); | ||||
|     const options = createOptions(theme, locationSettings); | ||||
| 
 | ||||
|     return ( | ||||
|         <Paper sx={(theme) => ({ padding: theme.spacing(4) })}> | ||||
|             <Typography | ||||
|                 variant='h3' | ||||
|                 sx={(theme) => ({ marginBottom: theme.spacing(3) })} | ||||
|             > | ||||
|                 Number of flags per project | ||||
|             </Typography> | ||||
|             <Line options={options} data={data} /> | ||||
|         </Paper> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| ChartJS.register( | ||||
|     CategoryScale, | ||||
|     LinearScale, | ||||
|     PointElement, | ||||
|     LineElement, | ||||
|     TimeScale, | ||||
|     Title, | ||||
|     Tooltip, | ||||
|     Legend, | ||||
| ); | ||||
| 
 | ||||
| export default FlagsProjectChart; | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user