mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
refactor: fix stats layout and unify components (#6671)
This commit is contained in:
parent
e6150def36
commit
f5a7cc9125
@ -57,7 +57,7 @@ export const ProjectInsights = () => {
|
|||||||
<NarrowContainer>
|
<NarrowContainer>
|
||||||
<FlagTypesUsed featureTypeCounts={data.featureTypeCounts} />
|
<FlagTypesUsed featureTypeCounts={data.featureTypeCounts} />
|
||||||
</NarrowContainer>
|
</NarrowContainer>
|
||||||
<NarrowContainer>
|
<NarrowContainer sx={{ padding: 0 }}>
|
||||||
<ProjectMembers projectId={projectId} members={data.members} />
|
<ProjectMembers projectId={projectId} members={data.members} />
|
||||||
</NarrowContainer>
|
</NarrowContainer>
|
||||||
<WideContainer>
|
<WideContainer>
|
||||||
|
@ -34,13 +34,7 @@ export const HelpPopper: FC<IHelpPopperProps> = ({ children, id }) => {
|
|||||||
const open = Boolean(anchor);
|
const open = Boolean(anchor);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box>
|
||||||
sx={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: (theme) => theme.spacing(0.5),
|
|
||||||
right: (theme) => theme.spacing(0.5),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconButton onClick={onOpen} aria-describedby={id} size='small'>
|
<IconButton onClick={onOpen} aria-describedby={id} size='small'>
|
||||||
<HelpOutline
|
<HelpOutline
|
||||||
sx={{
|
sx={{
|
||||||
|
@ -2,6 +2,9 @@ import { Box, styled, Typography } from '@mui/material';
|
|||||||
import type { ProjectStatsSchema } from 'openapi/models';
|
import type { ProjectStatsSchema } from 'openapi/models';
|
||||||
import { HelpPopper } from './HelpPopper';
|
import { HelpPopper } from './HelpPopper';
|
||||||
import { StatusBox } from './StatusBox';
|
import { StatusBox } from './StatusBox';
|
||||||
|
import KeyboardArrowRight from '@mui/icons-material/KeyboardArrowRight';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
|
|
||||||
const StyledBox = styled(Box)(({ theme }) => ({
|
const StyledBox = styled(Box)(({ theme }) => ({
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
@ -16,27 +19,20 @@ const StyledBox = styled(Box)(({ theme }) => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledWidget = styled(Box)(({ theme }) => ({
|
|
||||||
position: 'relative',
|
|
||||||
padding: theme.spacing(3),
|
|
||||||
backgroundColor: theme.palette.background.paper,
|
|
||||||
flex: 1,
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
borderRadius: `${theme.shape.borderRadiusLarge}px`,
|
|
||||||
[theme.breakpoints.down('lg')]: {
|
|
||||||
padding: theme.spacing(2),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
const StyledTimeToProductionDescription = styled(Typography)(({ theme }) => ({
|
const StyledTimeToProductionDescription = styled(Typography)(({ theme }) => ({
|
||||||
color: theme.palette.text.secondary,
|
color: theme.palette.text.secondary,
|
||||||
fontSize: theme.typography.body2.fontSize,
|
fontSize: theme.typography.body2.fontSize,
|
||||||
lineHeight: theme.typography.body2.lineHeight,
|
lineHeight: theme.typography.body2.lineHeight,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const NavigationBar = styled(Link)(({ theme }) => ({
|
||||||
|
marginLeft: 'auto',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
textDecoration: 'none',
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
}));
|
||||||
|
|
||||||
interface IProjectStatsProps {
|
interface IProjectStatsProps {
|
||||||
stats: ProjectStatsSchema;
|
stats: ProjectStatsSchema;
|
||||||
}
|
}
|
||||||
@ -45,6 +41,7 @@ export const ProjectInsightsStats = ({ stats }: IProjectStatsProps) => {
|
|||||||
if (Object.keys(stats).length === 0) {
|
if (Object.keys(stats).length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
const projectId = useRequiredPathParam('projectId');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
avgTimeToProdCurrentWindow,
|
avgTimeToProdCurrentWindow,
|
||||||
@ -58,7 +55,6 @@ export const ProjectInsightsStats = ({ stats }: IProjectStatsProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledBox>
|
<StyledBox>
|
||||||
<StyledWidget>
|
|
||||||
<StatusBox
|
<StatusBox
|
||||||
title='Total changes'
|
title='Total changes'
|
||||||
boxText={String(projectActivityCurrentWindow)}
|
boxText={String(projectActivityCurrentWindow)}
|
||||||
@ -71,8 +67,6 @@ export const ProjectInsightsStats = ({ stats }: IProjectStatsProps) => {
|
|||||||
project.
|
project.
|
||||||
</HelpPopper>
|
</HelpPopper>
|
||||||
</StatusBox>
|
</StatusBox>
|
||||||
</StyledWidget>
|
|
||||||
<StyledWidget>
|
|
||||||
<StatusBox
|
<StatusBox
|
||||||
title='Avg. time to production'
|
title='Avg. time to production'
|
||||||
boxText={
|
boxText={
|
||||||
@ -95,42 +89,43 @@ export const ProjectInsightsStats = ({ stats }: IProjectStatsProps) => {
|
|||||||
percentage
|
percentage
|
||||||
>
|
>
|
||||||
<HelpPopper id='avg-time-to-prod'>
|
<HelpPopper id='avg-time-to-prod'>
|
||||||
How long did it take on average from a feature toggle
|
How long did it take on average from a feature toggle was
|
||||||
was created until it was enabled in an environment of
|
created until it was enabled in an environment of type
|
||||||
type production. This is calculated only from feature
|
production. This is calculated only from feature toggles
|
||||||
toggles with the type of "release".
|
with the type of "release".
|
||||||
</HelpPopper>
|
</HelpPopper>
|
||||||
</StatusBox>
|
</StatusBox>
|
||||||
</StyledWidget>
|
|
||||||
<StyledWidget>
|
|
||||||
<StatusBox
|
<StatusBox
|
||||||
title='Features created'
|
title='Features created'
|
||||||
boxText={String(createdCurrentWindow)}
|
boxText={String(createdCurrentWindow)}
|
||||||
change={createdCurrentWindow - createdPastWindow}
|
change={createdCurrentWindow - createdPastWindow}
|
||||||
/>
|
>
|
||||||
</StyledWidget>
|
<NavigationBar to={`/projects/${projectId}`}>
|
||||||
|
<KeyboardArrowRight />
|
||||||
|
</NavigationBar>
|
||||||
|
</StatusBox>
|
||||||
|
|
||||||
<StyledWidget>
|
|
||||||
<StatusBox
|
<StatusBox
|
||||||
title='Stale toggles'
|
title='Stale flags'
|
||||||
boxText={String(projectActivityCurrentWindow)}
|
boxText={String(projectActivityCurrentWindow)}
|
||||||
change={
|
change={
|
||||||
projectActivityCurrentWindow - projectActivityPastWindow
|
projectActivityCurrentWindow - projectActivityPastWindow
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<HelpPopper id='stale-toggles'>
|
<NavigationBar to={`/projects/${projectId}/health`}>
|
||||||
Sum of all stale toggles in the project
|
<KeyboardArrowRight />
|
||||||
</HelpPopper>
|
</NavigationBar>
|
||||||
</StatusBox>
|
</StatusBox>
|
||||||
</StyledWidget>
|
|
||||||
|
|
||||||
<StyledWidget>
|
|
||||||
<StatusBox
|
<StatusBox
|
||||||
title='Features archived'
|
title='Features archived'
|
||||||
boxText={String(archivedCurrentWindow)}
|
boxText={String(archivedCurrentWindow)}
|
||||||
change={archivedCurrentWindow - archivedPastWindow}
|
change={archivedCurrentWindow - archivedPastWindow}
|
||||||
/>
|
>
|
||||||
</StyledWidget>
|
<NavigationBar to={`/projects/${projectId}/archive`}>
|
||||||
|
<KeyboardArrowRight />
|
||||||
|
</NavigationBar>
|
||||||
|
</StatusBox>
|
||||||
</StyledBox>
|
</StyledBox>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -5,10 +5,6 @@ import { Box, Typography, styled } from '@mui/material';
|
|||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { flexRow } from 'themes/themeStyles';
|
import { flexRow } from 'themes/themeStyles';
|
||||||
|
|
||||||
const StyledTypographyHeader = styled(Typography)(({ theme }) => ({
|
|
||||||
marginBottom: theme.spacing(2.5),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const StyledTypographyCount = styled(Box)(({ theme }) => ({
|
const StyledTypographyCount = styled(Box)(({ theme }) => ({
|
||||||
fontSize: theme.fontSizes.largeHeader,
|
fontSize: theme.fontSizes.largeHeader,
|
||||||
}));
|
}));
|
||||||
@ -31,8 +27,25 @@ const StyledTypographyChange = styled(Typography)(({ theme }) => ({
|
|||||||
fontWeight: theme.typography.fontWeightBold,
|
fontWeight: theme.typography.fontWeightBold,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const RowContainer = styled(Box)(({ theme }) => ({
|
||||||
|
...flexRow,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledWidget = styled(Box)(({ theme }) => ({
|
||||||
|
padding: theme.spacing(3),
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
flex: 1,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: theme.spacing(2.5),
|
||||||
|
borderRadius: `${theme.shape.borderRadiusLarge}px`,
|
||||||
|
[theme.breakpoints.down('lg')]: {
|
||||||
|
padding: theme.spacing(2),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
interface IStatusBoxProps {
|
interface IStatusBoxProps {
|
||||||
title?: string;
|
title: string;
|
||||||
boxText: ReactNode;
|
boxText: ReactNode;
|
||||||
change?: number;
|
change?: number;
|
||||||
percentage?: boolean;
|
percentage?: boolean;
|
||||||
@ -42,10 +55,24 @@ interface IStatusBoxProps {
|
|||||||
const resolveIcon = (change: number) => {
|
const resolveIcon = (change: number) => {
|
||||||
if (change > 0) {
|
if (change > 0) {
|
||||||
return (
|
return (
|
||||||
<CallMade sx={{ color: 'success.dark', height: 20, width: 20 }} />
|
<CallMade
|
||||||
|
sx={{
|
||||||
|
color: 'success.dark',
|
||||||
|
height: 20,
|
||||||
|
width: 20,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return <SouthEast sx={{ color: 'warning.dark', height: 20, width: 20 }} />;
|
return (
|
||||||
|
<SouthEast
|
||||||
|
sx={{
|
||||||
|
color: 'warning.dark',
|
||||||
|
height: 20,
|
||||||
|
width: 20,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const resolveColor = (change: number) => {
|
const resolveColor = (change: number) => {
|
||||||
@ -63,23 +90,14 @@ export const StatusBox: FC<IStatusBoxProps> = ({
|
|||||||
children,
|
children,
|
||||||
customChangeElement,
|
customChangeElement,
|
||||||
}) => (
|
}) => (
|
||||||
<>
|
<StyledWidget>
|
||||||
<ConditionallyRender
|
<RowContainer>
|
||||||
condition={Boolean(title)}
|
<Typography variant='h3' data-loading>
|
||||||
show={
|
|
||||||
<StyledTypographyHeader data-loading>
|
|
||||||
{title}
|
{title}
|
||||||
</StyledTypographyHeader>
|
</Typography>
|
||||||
}
|
|
||||||
/>
|
|
||||||
{children}
|
{children}
|
||||||
<Box
|
</RowContainer>
|
||||||
sx={{
|
<RowContainer>
|
||||||
...flexRow,
|
|
||||||
justifyContent: 'center',
|
|
||||||
width: 'auto',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<StyledTypographyCount data-loading>
|
<StyledTypographyCount data-loading>
|
||||||
{boxText}
|
{boxText}
|
||||||
</StyledTypographyCount>
|
</StyledTypographyCount>
|
||||||
@ -124,6 +142,6 @@ export const StatusBox: FC<IStatusBoxProps> = ({
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</RowContainer>
|
||||||
</>
|
</StyledWidget>
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import { Box, styled, Typography } from '@mui/material';
|
import { styled } from '@mui/material';
|
||||||
import { StatusBox } from '../ProjectInsightsStats/StatusBox';
|
import { StatusBox } from '../ProjectInsightsStats/StatusBox';
|
||||||
import KeyboardArrowRight from '@mui/icons-material/KeyboardArrowRight';
|
import KeyboardArrowRight from '@mui/icons-material/KeyboardArrowRight';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
@ -11,18 +11,13 @@ interface IProjectMembersProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const NavigationBar = styled(Link)(({ theme }) => ({
|
const NavigationBar = styled(Link)(({ theme }) => ({
|
||||||
|
marginLeft: 'auto',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
textDecoration: 'none',
|
textDecoration: 'none',
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const StyledProjectInfoWidgetContainer = styled('div')(({ theme }) => ({
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
gap: theme.spacing(2.5),
|
|
||||||
}));
|
|
||||||
|
|
||||||
export const ProjectMembers = ({
|
export const ProjectMembers = ({
|
||||||
members,
|
members,
|
||||||
projectId,
|
projectId,
|
||||||
@ -35,18 +30,14 @@ export const ProjectMembers = ({
|
|||||||
|
|
||||||
const { currentMembers, change } = members;
|
const { currentMembers, change } = members;
|
||||||
return (
|
return (
|
||||||
<StyledProjectInfoWidgetContainer>
|
<StatusBox
|
||||||
|
title={'Project members'}
|
||||||
|
boxText={`${currentMembers}`}
|
||||||
|
change={change}
|
||||||
|
>
|
||||||
<NavigationBar to={link}>
|
<NavigationBar to={link}>
|
||||||
<Typography variant='h3'>Project members</Typography>
|
|
||||||
<KeyboardArrowRight />
|
<KeyboardArrowRight />
|
||||||
</NavigationBar>
|
</NavigationBar>
|
||||||
<Box
|
</StatusBox>
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<StatusBox boxText={`${currentMembers}`} change={change} />
|
|
||||||
</Box>
|
|
||||||
</StyledProjectInfoWidgetContainer>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user