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:
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'>
|
||||
{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'>
|
||||
|
Loading…
Reference in New Issue
Block a user