mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	Insights UI (#6341)
- style for headers in Insights dashboard and project selector - fixed React element key issue in gauge chart - fixed React attribute issue in Health stats - active/inactive user stats from backend
This commit is contained in:
		
							parent
							
								
									f351ad821b
								
							
						
					
					
						commit
						fd87fd4e7d
					
				| @ -24,6 +24,7 @@ import { | ||||
|     ExecutiveSummarySchemaProjectFlagTrendsItem, | ||||
| } from '../../openapi'; | ||||
| import { HealthStats } from './HealthStats/HealthStats'; | ||||
| import { Badge } from 'component/common/Badge/Badge'; | ||||
| 
 | ||||
| const StyledGrid = styled(Box)(({ theme }) => ({ | ||||
|     display: 'grid', | ||||
| @ -128,15 +129,28 @@ export const ExecutiveDashboard: VFC = () => { | ||||
|             <Box sx={(theme) => ({ paddingBottom: theme.spacing(4) })}> | ||||
|                 <PageHeader | ||||
|                     titleElement={ | ||||
|                         <Typography variant='h1' component='span'> | ||||
|                             Dashboard | ||||
|                         <Typography | ||||
|                             variant='h1' | ||||
|                             component='div' | ||||
|                             sx={(theme) => ({ | ||||
|                                 display: 'flex', | ||||
|                                 alignItems: 'center', | ||||
|                                 gap: theme.spacing(1), | ||||
|                             })} | ||||
|                         > | ||||
|                             <span>Insights</span>{' '} | ||||
|                             <Badge color='warning'>Beta</Badge> | ||||
|                         </Typography> | ||||
|                     } | ||||
|                 /> | ||||
|             </Box> | ||||
|             <StyledGrid sx={{ gridTemplateColumns }}> | ||||
|                 <Widget title='Total users' order={1}> | ||||
|                     <UserStats count={executiveDashboardData.users.total} /> | ||||
|                     <UserStats | ||||
|                         count={executiveDashboardData.users.total} | ||||
|                         active={executiveDashboardData.users.active} | ||||
|                         inactive={executiveDashboardData.users.inactive} | ||||
|                     /> | ||||
|                 </Widget> | ||||
|                 <Widget title='Users' order={userTrendsOrder} span={chartSpan}> | ||||
|                     <UsersChart | ||||
| @ -146,7 +160,7 @@ export const ExecutiveDashboard: VFC = () => { | ||||
|                 </Widget> | ||||
|                 <Widget | ||||
|                     title='Total flags' | ||||
|                     tooltip='Total flags represent the total ctive flags (not archived) that currently exist across all projects of your application.' | ||||
|                     tooltip='Total flags represent the total active flags (not archived) that currently exist across all projects of your application.' | ||||
|                     order={flagStatsOrder} | ||||
|                 > | ||||
|                     <FlagStats | ||||
| @ -175,10 +189,10 @@ export const ExecutiveDashboard: VFC = () => { | ||||
|                 <Widget title='Average health' order={6}> | ||||
|                     <HealthStats | ||||
|                         // FIXME: data from API
 | ||||
|                         value={90} | ||||
|                         healthy={50} | ||||
|                         stale={10} | ||||
|                         potenciallyStale={5} | ||||
|                         value={80} | ||||
|                         healthy={4} | ||||
|                         stale={1} | ||||
|                         potenciallyStale={0} | ||||
|                     /> | ||||
|                 </Widget> | ||||
|                 <Widget title='Health per project' order={7} span={chartSpan}> | ||||
| @ -199,7 +213,7 @@ export const ExecutiveDashboard: VFC = () => { | ||||
|                 <Widget title='Average time to production' order={9}> | ||||
|                     <TimeToProduction | ||||
|                         //FIXME: data from API
 | ||||
|                         daysToProduction={12} | ||||
|                         daysToProduction={5.2} | ||||
|                     /> | ||||
|                 </Widget> | ||||
|                 <Widget title='Time to production' order={10} span={chartSpan}> | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| import { VFC } from 'react'; | ||||
| import { Fragment, VFC } from 'react'; | ||||
| import { Box, useTheme } from '@mui/material'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| 
 | ||||
| @ -55,9 +55,8 @@ const GaugeLines = () => { | ||||
|                 const end = polarToCartesian(0, 0, endRadius, angle); | ||||
| 
 | ||||
|                 return ( | ||||
|                     <> | ||||
|                     <Fragment key={angle}> | ||||
|                         <path | ||||
|                             key={angle} | ||||
|                             d={`M ${start.x} ${start.y} L ${end.x} ${end.y}`} | ||||
|                             fill='none' | ||||
|                             stroke={theme.palette.background.paper} | ||||
| @ -65,15 +64,13 @@ const GaugeLines = () => { | ||||
|                             strokeLinecap='round' | ||||
|                         /> | ||||
|                         <path | ||||
|                             key={angle} | ||||
|                             d={`M ${start.x} ${start.y} L ${end.x} ${end.y}`} | ||||
|                             fill='none' | ||||
|                             stroke={theme.palette.charts.gauge.sectionLine} | ||||
|                             strokeWidth={lineWidth - lineBorder} | ||||
|                             strokeLinecap='round' | ||||
|                         /> | ||||
|                         ) | ||||
|                     </> | ||||
|                     </Fragment> | ||||
|                 ); | ||||
|             })} | ||||
|         </> | ||||
|  | ||||
| @ -39,7 +39,7 @@ export const HealthStats: VFC<IHealthStatsProps> = ({ | ||||
|                 cy='129' | ||||
|                 r='121' | ||||
|                 stroke={theme.palette.charts.health.orbit} | ||||
|                 stroke-width='3' | ||||
|                 strokeWidth='3' | ||||
|             /> | ||||
|             <text | ||||
|                 x={134} | ||||
| @ -149,9 +149,9 @@ export const HealthStats: VFC<IHealthStatsProps> = ({ | ||||
|                     width='238' | ||||
|                     height='238' | ||||
|                     filterUnits='userSpaceOnUse' | ||||
|                     color-interpolation-filters='sRGB' | ||||
|                     colorInterpolationFilters='sRGB' | ||||
|                 > | ||||
|                     <feFlood flood-opacity='0' result='BackgroundImageFix' /> | ||||
|                     <feFlood floodOpacity='0' result='BackgroundImageFix' /> | ||||
|                     <feColorMatrix | ||||
|                         in='SourceAlpha' | ||||
|                         type='matrix' | ||||
| @ -190,9 +190,9 @@ export const HealthStats: VFC<IHealthStatsProps> = ({ | ||||
|                     width='124' | ||||
|                     height='124' | ||||
|                     filterUnits='userSpaceOnUse' | ||||
|                     color-interpolation-filters='sRGB' | ||||
|                     colorInterpolationFilters='sRGB' | ||||
|                 > | ||||
|                     <feFlood flood-opacity='0' result='BackgroundImageFix' /> | ||||
|                     <feFlood floodOpacity='0' result='BackgroundImageFix' /> | ||||
|                     <feColorMatrix | ||||
|                         in='SourceAlpha' | ||||
|                         type='matrix' | ||||
| @ -225,9 +225,9 @@ export const HealthStats: VFC<IHealthStatsProps> = ({ | ||||
|                     width='106' | ||||
|                     height='106' | ||||
|                     filterUnits='userSpaceOnUse' | ||||
|                     color-interpolation-filters='sRGB' | ||||
|                     colorInterpolationFilters='sRGB' | ||||
|                 > | ||||
|                     <feFlood flood-opacity='0' result='BackgroundImageFix' /> | ||||
|                     <feFlood floodOpacity='0' result='BackgroundImageFix' /> | ||||
|                     <feColorMatrix | ||||
|                         in='SourceAlpha' | ||||
|                         type='matrix' | ||||
| @ -260,9 +260,9 @@ export const HealthStats: VFC<IHealthStatsProps> = ({ | ||||
|                     width='106' | ||||
|                     height='106' | ||||
|                     filterUnits='userSpaceOnUse' | ||||
|                     color-interpolation-filters='sRGB' | ||||
|                     colorInterpolationFilters='sRGB' | ||||
|                 > | ||||
|                     <feFlood flood-opacity='0' result='BackgroundImageFix' /> | ||||
|                     <feFlood floodOpacity='0' result='BackgroundImageFix' /> | ||||
|                     <feColorMatrix | ||||
|                         in='SourceAlpha' | ||||
|                         type='matrix' | ||||
| @ -297,11 +297,11 @@ export const HealthStats: VFC<IHealthStatsProps> = ({ | ||||
|                     gradientUnits='userSpaceOnUse' | ||||
|                 > | ||||
|                     <stop | ||||
|                         stop-color={theme.palette.charts.health.gradientStale} | ||||
|                         stopColor={theme.palette.charts.health.gradientStale} | ||||
|                     /> | ||||
|                     <stop | ||||
|                         offset='1' | ||||
|                         stop-color={ | ||||
|                         stopColor={ | ||||
|                             theme.palette.charts.health.gradientPotenciallyStale | ||||
|                         } | ||||
|                     /> | ||||
|  | ||||
| @ -1,17 +1,24 @@ | ||||
| import { ComponentProps, Dispatch, SetStateAction, VFC } from 'react'; | ||||
| import { Autocomplete, Box, styled, TextField } from '@mui/material'; | ||||
| import { | ||||
|     Autocomplete, | ||||
|     Box, | ||||
|     styled, | ||||
|     TextField, | ||||
|     Typography, | ||||
| } from '@mui/material'; | ||||
| import { renderOption } from '../../playground/Playground/PlaygroundForm/renderOption'; | ||||
| import useProjects from '../../../hooks/api/getters/useProjects/useProjects'; | ||||
| 
 | ||||
| const StyledBox = styled(Box)(({ theme }) => ({ | ||||
|     width: '25%', | ||||
|     marginLeft: '75%', | ||||
|     marginBottom: theme.spacing(4), | ||||
|     marginTop: theme.spacing(4), | ||||
|     [theme.breakpoints.down('lg')]: { | ||||
|         width: '100%', | ||||
|         marginLeft: 0, | ||||
|     }, | ||||
|     display: 'flex', | ||||
|     justifyContent: 'space-between', | ||||
|     alignItems: 'center', | ||||
| })); | ||||
| 
 | ||||
| interface IOption { | ||||
| @ -74,13 +81,16 @@ export const ProjectSelect: VFC<IProjectSelectProps> = ({ | ||||
| 
 | ||||
|     return ( | ||||
|         <StyledBox> | ||||
|             <Typography variant='h2' component='span'> | ||||
|                 Insights per project | ||||
|             </Typography> | ||||
|             <Autocomplete | ||||
|                 disablePortal | ||||
|                 id='projects' | ||||
|                 limitTags={3} | ||||
|                 multiple={!isAllProjects} | ||||
|                 options={projectsOptions} | ||||
|                 sx={{ flex: 1 }} | ||||
|                 sx={{ flex: 1, maxWidth: 360 }} | ||||
|                 renderInput={(params) => ( | ||||
|                     <TextField {...params} label='Projects' /> | ||||
|                 )} | ||||
|  | ||||
| @ -1,8 +1,8 @@ | ||||
| import React, { type FC } from 'react'; | ||||
| import { ChevronRight } from '@mui/icons-material'; | ||||
| import { Box, Typography, styled } from '@mui/material'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { useUiFlag } from 'hooks/useUiFlag'; | ||||
| import React from 'react'; | ||||
| import { Link } from 'react-router-dom'; | ||||
| 
 | ||||
| const StyledUserContainer = styled(Box)(({ theme }) => ({ | ||||
| @ -71,10 +71,15 @@ const StyledLink = styled(Link)({ | ||||
| 
 | ||||
| interface IUserStatsProps { | ||||
|     count: number; | ||||
|     active?: number; | ||||
|     inactive?: number; | ||||
| } | ||||
| 
 | ||||
| export const UserStats: React.FC<IUserStatsProps> = ({ count }) => { | ||||
| export const UserStats: FC<IUserStatsProps> = ({ count, active, inactive }) => { | ||||
|     const showInactiveUsers = useUiFlag('showInactiveUsers'); | ||||
|     const showDistribution = | ||||
|         showInactiveUsers && active !== undefined && inactive !== undefined; | ||||
|     const activeUsersPercentage = ((active || 0) / count) * 100; | ||||
| 
 | ||||
|     return ( | ||||
|         <> | ||||
| @ -86,23 +91,25 @@ export const UserStats: React.FC<IUserStatsProps> = ({ count }) => { | ||||
|             </StyledUserContainer> | ||||
| 
 | ||||
|             <ConditionallyRender | ||||
|                 condition={showInactiveUsers} | ||||
|                 condition={showDistribution} | ||||
|                 show={ | ||||
|                     <> | ||||
|                         <StyledUserDistributionContainer> | ||||
|                             <UserDistribution /> | ||||
|                             <UserDistribution | ||||
|                                 activeUsersPercentage={activeUsersPercentage} | ||||
|                             /> | ||||
|                         </StyledUserDistributionContainer> | ||||
| 
 | ||||
|                         <StyledDistInfoContainer> | ||||
|                             <UserDistributionInfo | ||||
|                                 type='active' | ||||
|                                 percentage='70' | ||||
|                                 count='9999' | ||||
|                                 percentage={`${activeUsersPercentage}`} | ||||
|                                 count={`${active}`} | ||||
|                             /> | ||||
|                             <UserDistributionInfo | ||||
|                                 type='inactive' | ||||
|                                 percentage='30' | ||||
|                                 count='9999' | ||||
|                                 percentage={`${100 - activeUsersPercentage}`} | ||||
|                                 count={`${inactive}`} | ||||
|                             /> | ||||
|                         </StyledDistInfoContainer> | ||||
|                     </> | ||||
| @ -135,9 +142,9 @@ const StyledUserDistributionLine = styled(Box)<StyledLinearProgressProps>( | ||||
|     }), | ||||
| ); | ||||
| 
 | ||||
| const UserDistribution = () => { | ||||
| const UserDistribution = ({ activeUsersPercentage = 100 }) => { | ||||
|     const getLineWidth = () => { | ||||
|         return [80, 20]; | ||||
|         return [activeUsersPercentage, 100 - activeUsersPercentage]; | ||||
|     }; | ||||
| 
 | ||||
|     const [activeWidth, inactiveWidth] = getLineWidth(); | ||||
| @ -175,7 +182,6 @@ const StyledUserDistIndicator = styled(Box)<StyledLinearProgressProps>( | ||||
|                 : theme.palette.warning.border, | ||||
|         borderRadius: `2px`, | ||||
|         marginRight: theme.spacing(1), | ||||
|         marginTop: theme.spacing(0.8), | ||||
|     }), | ||||
| ); | ||||
| 
 | ||||
| @ -208,12 +214,19 @@ const UserDistributionInfo: React.FC<IUserDistributionInfoProps> = ({ | ||||
| }) => { | ||||
|     return ( | ||||
|         <StyledUserDistContainer> | ||||
|             <StyledUserDistIndicator type={type} /> | ||||
|             <StyledDistInfoInnerContainer> | ||||
|                 <StyledDistInfoTextContainer> | ||||
|                     <Typography variant='body1'> | ||||
|                     <Box | ||||
|                         sx={{ | ||||
|                             display: 'flex', | ||||
|                             alignItems: 'center', | ||||
|                         }} | ||||
|                     > | ||||
|                         <StyledUserDistIndicator type={type} /> | ||||
|                         <Typography variant='body1' component='span'> | ||||
|                             {type === 'active' ? 'Active' : 'Inactive'} users | ||||
|                         </Typography> | ||||
|                     </Box> | ||||
|                     <Typography variant='body2'>{percentage}%</Typography> | ||||
|                 </StyledDistInfoTextContainer> | ||||
|                 <StyledCountTypography variant='h2'> | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user