1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-31 00:16:47 +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:
Tymoteusz Czech 2024-02-26 15:51:14 +01:00 committed by GitHub
parent f351ad821b
commit fd87fd4e7d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 79 additions and 45 deletions

View File

@ -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}>

View File

@ -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>
);
})}
</>

View File

@ -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
}
/>

View File

@ -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' />
)}

View File

@ -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'>
{type === 'active' ? 'Active' : 'Inactive'} users
</Typography>
<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'>