mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: updates per environment type chart (#6449)
Creates the updates per environment type chart. (forgive the sample data) Closes # [1-2034](https://linear.app/unleash/issue/1-2034/widget-updates-per-environment-type-frontend) <img width="1385" alt="Screenshot 2024-03-06 at 16 52 18" src="https://github.com/Unleash/unleash/assets/104830839/b05479f8-de8b-4de7-98a3-a1285737db0d"> --------- Signed-off-by: andreas-unleash <andreas@getunleash.ai>
This commit is contained in:
		
							parent
							
								
									4a8faacbd8
								
							
						
					
					
						commit
						ec6c439c09
					
				| @ -25,6 +25,7 @@ import { FlagsProjectChart } from './componentsChart/FlagsProjectChart/FlagsProj | ||||
| import { ProjectHealthChart } from './componentsChart/ProjectHealthChart/ProjectHealthChart'; | ||||
| import { MetricsSummaryChart } from './componentsChart/MetricsSummaryChart/MetricsSummaryChart'; | ||||
| import { UsersPerProjectChart } from './componentsChart/UsersPerProjectChart/UsersPerProjectChart'; | ||||
| import { UpdatesPerEnvironmentTypeChart } from './componentsChart/UpdatesPerEnvironmentTypeChart/UpdatesPerEnvironmentTypeChart'; | ||||
| 
 | ||||
| const StyledGrid = styled(Box)(({ theme }) => ({ | ||||
|     display: 'grid', | ||||
| @ -65,7 +66,8 @@ export const ExecutiveDashboard: VFC = () => { | ||||
|         executiveDashboardData.metricsSummaryTrends, | ||||
|         projects, | ||||
|     ); | ||||
|     const { users } = executiveDashboardData; | ||||
| 
 | ||||
|     const { users, environmentTypeTrends } = executiveDashboardData; | ||||
| 
 | ||||
|     const summary = useFilteredFlagsSummary(projectsData); | ||||
|     const isOneProjectSelected = projects.length === 1; | ||||
| @ -194,6 +196,16 @@ export const ExecutiveDashboard: VFC = () => { | ||||
|             > | ||||
|                 <MetricsSummaryChart metricsSummaryTrends={metricsData} /> | ||||
|             </Widget> | ||||
|             <Widget | ||||
|                 title='Updates per environment type' | ||||
|                 tooltip='Summary of all configuration updates per environment type' | ||||
|                 sx={{ mt: (theme) => theme.spacing(2) }} | ||||
|             > | ||||
|                 <UpdatesPerEnvironmentTypeChart | ||||
|                     environmentTypeTrends={environmentTypeTrends} | ||||
|                     isLoading={loading} | ||||
|                 /> | ||||
|             </Widget> | ||||
|         </> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| @ -1,7 +1,8 @@ | ||||
| import { FC, ReactNode } from 'react'; | ||||
| import { Paper, Typography, styled } from '@mui/material'; | ||||
| import { Paper, Typography, styled, SxProps } from '@mui/material'; | ||||
| import { HelpIcon } from 'component/common/HelpIcon/HelpIcon'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { Theme } from '@mui/material/styles/createTheme'; | ||||
| 
 | ||||
| const StyledPaper = styled(Paper)(({ theme }) => ({ | ||||
|     padding: theme.spacing(3), | ||||
| @ -13,6 +14,7 @@ const StyledPaper = styled(Paper)(({ theme }) => ({ | ||||
| export const Widget: FC<{ | ||||
|     title: ReactNode; | ||||
|     tooltip?: ReactNode; | ||||
|     sx?: SxProps<Theme>; | ||||
| }> = ({ title, children, tooltip, ...rest }) => ( | ||||
|     <StyledPaper elevation={0} {...rest}> | ||||
|         <Typography | ||||
|  | ||||
| @ -0,0 +1,73 @@ | ||||
| import { useMemo, type VFC } from 'react'; | ||||
| import 'chartjs-adapter-date-fns'; | ||||
| import { useTheme } from '@mui/material'; | ||||
| import { | ||||
|     ExecutiveSummarySchema, | ||||
|     ExecutiveSummarySchemaEnvironmentTypeTrendsItem, | ||||
| } from 'openapi'; | ||||
| import { LineChart, NotEnoughData } from '../../components/LineChart/LineChart'; | ||||
| import { usePlaceholderData } from 'component/executiveDashboard/hooks/usePlaceholderData'; | ||||
| import { getProjectColor } from '../../executive-dashboard-utils'; | ||||
| 
 | ||||
| interface IUpdatesPerEnvironmnetTypeChart { | ||||
|     environmentTypeTrends: ExecutiveSummarySchema['environmentTypeTrends']; | ||||
|     isLoading?: boolean; | ||||
| } | ||||
| 
 | ||||
| const groupByDate = ( | ||||
|     items: ExecutiveSummarySchemaEnvironmentTypeTrendsItem[], | ||||
| ): Record<string, ExecutiveSummarySchemaEnvironmentTypeTrendsItem[]> => { | ||||
|     if (!items) { | ||||
|         return {}; | ||||
|     } | ||||
| 
 | ||||
|     const grouped = items.reduce( | ||||
|         (acc, item) => { | ||||
|             const key = item.environmentType; | ||||
| 
 | ||||
|             if (!acc[key]) { | ||||
|                 acc[key] = []; | ||||
|             } | ||||
| 
 | ||||
|             acc[key].push(item); | ||||
| 
 | ||||
|             return acc; | ||||
|         }, | ||||
|         {} as Record<string, ExecutiveSummarySchemaEnvironmentTypeTrendsItem[]>, | ||||
|     ); | ||||
| 
 | ||||
|     return grouped; | ||||
| }; | ||||
| 
 | ||||
| export const UpdatesPerEnvironmentTypeChart: VFC< | ||||
|     IUpdatesPerEnvironmnetTypeChart | ||||
| > = ({ environmentTypeTrends, isLoading }) => { | ||||
|     const theme = useTheme(); | ||||
|     const notEnoughData = environmentTypeTrends?.length < 2; | ||||
|     const placeholderData = usePlaceholderData({ fill: true, type: 'double' }); | ||||
| 
 | ||||
|     const data = useMemo(() => { | ||||
|         const grouped = groupByDate(environmentTypeTrends); | ||||
|         const labels = environmentTypeTrends?.map((item) => item.date); | ||||
|         const datasets = Object.entries(grouped).map( | ||||
|             ([environmentType, trends]) => { | ||||
|                 const color = getProjectColor(environmentType); | ||||
|                 return { | ||||
|                     label: environmentType, | ||||
|                     data: trends.map((item) => item.totalUpdates), | ||||
|                     borderColor: color, | ||||
|                     backgroundColor: color, | ||||
|                     fill: false, | ||||
|                 }; | ||||
|             }, | ||||
|         ); | ||||
|         return { labels, datasets }; | ||||
|     }, [theme, environmentTypeTrends]); | ||||
| 
 | ||||
|     return ( | ||||
|         <LineChart | ||||
|             data={notEnoughData || isLoading ? placeholderData : data} | ||||
|             cover={notEnoughData ? <NotEnoughData /> : isLoading} | ||||
|         /> | ||||
|     ); | ||||
| }; | ||||
| @ -0,0 +1,117 @@ | ||||
| import { type VFC } from 'react'; | ||||
| import { ExecutiveSummarySchemaEnvironmentTypeTrendsItem } from 'openapi'; | ||||
| import { Box, Divider, Paper, styled, Typography } from '@mui/material'; | ||||
| import { TooltipState } from '../../../components/LineChart/ChartTooltip/ChartTooltip'; | ||||
| 
 | ||||
| const StyledTooltipItemContainer = styled(Paper)(({ theme }) => ({ | ||||
|     padding: theme.spacing(2), | ||||
| })); | ||||
| 
 | ||||
| const StyledItemHeader = styled(Box)(({ theme }) => ({ | ||||
|     display: 'flex', | ||||
|     justifyContent: 'space-between', | ||||
|     gap: theme.spacing(2), | ||||
|     alignItems: 'center', | ||||
| })); | ||||
| 
 | ||||
| const InfoLine = ({ | ||||
|     iconChar, | ||||
|     title, | ||||
|     color, | ||||
| }: { | ||||
|     iconChar: string; | ||||
|     title: string; | ||||
|     color: 'info' | 'success' | 'error'; | ||||
| }) => ( | ||||
|     <Typography | ||||
|         variant='body2' | ||||
|         component='p' | ||||
|         sx={{ | ||||
|             color: (theme) => theme.palette[color].main, | ||||
|         }} | ||||
|     > | ||||
|         <Typography component='span'>{iconChar}</Typography> | ||||
|         <strong>{title}</strong> | ||||
|     </Typography> | ||||
| ); | ||||
| 
 | ||||
| const InfoSummary = ({ data }: { data: { key: string; value: number }[] }) => ( | ||||
|     <Typography variant={'body1'} component={'p'}> | ||||
|         <Box display={'flex'} flexDirection={'row'}> | ||||
|             {data.map(({ key, value }) => ( | ||||
|                 <div style={{ flex: 1, flexDirection: 'column' }}> | ||||
|                     <div | ||||
|                         style={{ | ||||
|                             flex: 1, | ||||
|                             textAlign: 'center', | ||||
|                             marginBottom: '4px', | ||||
|                         }} | ||||
|                     > | ||||
|                         {key} | ||||
|                     </div> | ||||
|                     <div style={{ flex: 1, textAlign: 'center' }}>{value}</div> | ||||
|                 </div> | ||||
|             ))} | ||||
|         </Box> | ||||
|     </Typography> | ||||
| ); | ||||
| 
 | ||||
| export const UpdatesPerEnvironmentTypeChartTooltip: VFC<{ | ||||
|     tooltip: TooltipState | null; | ||||
| }> = ({ tooltip }) => { | ||||
|     const data = tooltip?.dataPoints.map((point) => { | ||||
|         return { | ||||
|             label: point.label, | ||||
|             title: point.dataset.label, | ||||
|             color: point.dataset.borderColor, | ||||
|             value: point.raw as ExecutiveSummarySchemaEnvironmentTypeTrendsItem, | ||||
|         }; | ||||
|     }); | ||||
| 
 | ||||
|     const limitedData = data?.slice(0, 5); | ||||
| 
 | ||||
|     return ( | ||||
|         <Box | ||||
|             sx={(theme) => ({ | ||||
|                 display: 'flex', | ||||
|                 flexDirection: 'column', | ||||
|                 gap: theme.spacing(2), | ||||
|                 width: '300px', | ||||
|             })} | ||||
|         > | ||||
|             {limitedData?.map((point, index) => ( | ||||
|                 <StyledTooltipItemContainer | ||||
|                     elevation={3} | ||||
|                     key={`${point.title}-${index}`} | ||||
|                 > | ||||
|                     <StyledItemHeader> | ||||
|                         <Typography variant='body2' component='span'> | ||||
|                             <Typography | ||||
|                                 sx={{ color: point.color }} | ||||
|                                 component='span' | ||||
|                             > | ||||
|                                 {'● '} | ||||
|                             </Typography> | ||||
|                             <strong>{point.title}</strong> | ||||
|                         </Typography> | ||||
|                         <Typography | ||||
|                             variant='body2' | ||||
|                             color='textSecondary' | ||||
|                             component='span' | ||||
|                         > | ||||
|                             {point.label} | ||||
|                         </Typography> | ||||
|                     </StyledItemHeader> | ||||
|                     <Divider | ||||
|                         sx={(theme) => ({ margin: theme.spacing(1.5, 0) })} | ||||
|                     /> | ||||
|                     <InfoLine | ||||
|                         iconChar={'● '} | ||||
|                         title={`Total updates: ${point.value.totalUpdates}`} | ||||
|                         color={'info'} | ||||
|                     /> | ||||
|                 </StyledTooltipItemContainer> | ||||
|             )) || null} | ||||
|         </Box> | ||||
|     ); | ||||
| }; | ||||
| @ -34,6 +34,7 @@ export const useExecutiveDashboard = ( | ||||
|             flagTrends: [], | ||||
|             projectFlagTrends: [], | ||||
|             metricsSummaryTrends: [], | ||||
|             environmentTypeTrends: [], | ||||
|         }, | ||||
|         refetchExecutiveDashboard, | ||||
|         loading: !error && !data, | ||||
|  | ||||
| @ -3,6 +3,7 @@ | ||||
|  * Do not edit manually. | ||||
|  * See `gen:api` script in package.json | ||||
|  */ | ||||
| import type { ExecutiveSummarySchemaEnvironmentTypeTrendsItem } from './executiveSummarySchemaEnvironmentTypeTrendsItem'; | ||||
| import type { ExecutiveSummarySchemaFlags } from './executiveSummarySchemaFlags'; | ||||
| import type { ExecutiveSummarySchemaFlagTrendsItem } from './executiveSummarySchemaFlagTrendsItem'; | ||||
| import type { ExecutiveSummarySchemaMetricsSummaryTrendsItem } from './executiveSummarySchemaMetricsSummaryTrendsItem'; | ||||
| @ -14,6 +15,8 @@ import type { ExecutiveSummarySchemaUserTrendsItem } from './executiveSummarySch | ||||
|  * Executive summary of Unleash usage | ||||
|  */ | ||||
| export interface ExecutiveSummarySchema { | ||||
|     /** How updates per environment type changed over time */ | ||||
|     environmentTypeTrends: ExecutiveSummarySchemaEnvironmentTypeTrendsItem[]; | ||||
|     /** High level flag count statistics */ | ||||
|     flags: ExecutiveSummarySchemaFlags; | ||||
|     /** How number of flags changed over time */ | ||||
|  | ||||
| @ -0,0 +1,16 @@ | ||||
| /** | ||||
|  * Generated by Orval | ||||
|  * Do not edit manually. | ||||
|  * See `gen:api` script in package.json | ||||
|  */ | ||||
| 
 | ||||
| export type ExecutiveSummarySchemaEnvironmentTypeTrendsItem = { | ||||
|     /** A UTC date when the stats were captured. Time is the very end of a given day. */ | ||||
|     date: string; | ||||
|     /** Environment type the data belongs too */ | ||||
|     environmentType: string; | ||||
|     /** Total number of times configuration has been updated in the environment type  */ | ||||
|     totalUpdates: number; | ||||
|     /** Year and week in a given year for which the stats were calculated */ | ||||
|     week: string; | ||||
| }; | ||||
| @ -5,6 +5,8 @@ | ||||
|  */ | ||||
| 
 | ||||
| export type ExecutiveSummarySchemaMetricsSummaryTrendsItem = { | ||||
|     /** A UTC date when metrics summary was captured. Time is the very end of a given day. */ | ||||
|     date: string; | ||||
|     /** Project id of the project the impressions summary belong to */ | ||||
|     project: string; | ||||
|     /** Total number of applications the impression data belong to */ | ||||
|  | ||||
| @ -515,6 +515,7 @@ export * from './eventsSchema'; | ||||
| export * from './eventsSchemaVersion'; | ||||
| export * from './executiveSummarySchema'; | ||||
| export * from './executiveSummarySchemaFlagTrendsItem'; | ||||
| export * from './executiveSummarySchemaEnvironmentTypeTrendsItem'; | ||||
| export * from './executiveSummarySchemaFlags'; | ||||
| export * from './executiveSummarySchemaMetricsSummaryTrendsItem'; | ||||
| export * from './executiveSummarySchemaProjectFlagTrendsItem'; | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user