1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-15 01:16:22 +02:00
unleash.unleash/frontend/src/component/project/Project/ProjectStatus/ProjectLifecycleSummary.tsx
Thomas Heartman 99f2b70da3
fix: show data for last 30 days where we say we do (#8850)
This change updates the stat for archived flags "this month".

Turns out we were accessing the wrong property on the data object.

Additionally, changes the label to say "last 30 days" instead of "this
month"  because that's more accurate.
2024-11-25 13:09:03 +00:00

253 lines
8.1 KiB
TypeScript

import { styled } from '@mui/material';
import { FeatureLifecycleStageIcon } from 'component/feature/FeatureView/FeatureOverview/FeatureLifecycle/FeatureLifecycleStageIcon';
import { useProjectStatus } from 'hooks/api/getters/useProjectStatus/useProjectStatus';
import useLoading from 'hooks/useLoading';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import type { FC } from 'react';
import { PrettifyLargeNumber } from 'component/common/PrettifyLargeNumber/PrettifyLargeNumber';
import type { ProjectStatusSchemaLifecycleSummary } from 'openapi';
import { HtmlTooltip } from 'component/common/HtmlTooltip/HtmlTooltip';
import { lifecycleMessages } from './LifecycleMessages';
import InfoIcon from '@mui/icons-material/Info';
const LifecycleBoxContent = styled('div')(({ theme }) => ({
padding: theme.spacing(2),
gap: theme.spacing(4),
display: 'flex',
flexFlow: 'column',
justifyContent: 'space-between',
transition: 'all 200ms',
borderRadius: theme.shape.borderRadiusExtraLarge,
border: `2px solid ${theme.palette.divider}`,
'&:focus-visible': {
outline: 'none',
borderColor: theme.palette.primary.main,
},
'&:hover': {
backgroundColor: theme.palette.table.rowHover,
},
}));
const LifecycleBoxTooltip: FC<{ text: string }> = ({ text }) => {
const Container = styled('span')(({ theme }) => ({
display: 'flex',
alignItems: 'flex-start',
gap: theme.spacing(1),
fontSize: theme.typography.body1.fontSize,
padding: theme.spacing(1),
}));
return (
<Container>
<InfoIcon fontSize='small' color='primary' />
<p>{text}</p>
</Container>
);
};
const LifecycleBox = ({
children,
tooltipText,
}: {
children: React.ReactNode;
tooltipText: string;
}) => {
return (
<li>
<HtmlTooltip
arrow
maxWidth='850px'
title={<LifecycleBoxTooltip text={tooltipText} />}
>
<LifecycleBoxContent tabIndex={0}>
{children}
</LifecycleBoxContent>
</HtmlTooltip>
</li>
);
};
const LifecycleList = styled('ul')(({ theme }) => ({
display: 'grid',
listStyle: 'none',
gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))',
gap: theme.spacing(1),
justifyContent: 'center',
padding: 0,
flex: 'auto',
margin: 0,
}));
const Counter = styled('span')({
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
});
const BigText = styled('span')(({ theme }) => ({
fontSize: `calc(2 * ${theme.typography.body1.fontSize})`,
}));
const Stats = styled('dl')(({ theme }) => ({
margin: 0,
fontSize: theme.typography.body2.fontSize,
'& dd': {
margin: 0,
fontWeight: 'bold',
},
}));
const NoData = styled('span')({
fontWeight: 'normal',
});
const AverageDaysStat: FC<{ averageDays?: number | null }> = ({
averageDays,
}) => {
const Content = () => {
if (averageDays === null || averageDays === undefined) {
return <NoData>No data</NoData>;
}
if (averageDays < 1) {
return 'less than a day';
}
return `${averageDays} days`;
};
return (
<Stats>
<dt>Avg. time in stage</dt>
<dd data-loading-project-lifecycle-summary>
<Content />
</dd>
</Stats>
);
};
const BigNumber: FC<{ value?: number }> = ({ value }) => {
return (
<BigText data-loading-project-lifecycle-summary>
<PrettifyLargeNumber
value={value ?? 0}
threshold={1000}
precision={1}
/>
</BigText>
);
};
export const ProjectLifecycleSummary = () => {
const projectId = useRequiredPathParam('projectId');
const { data, loading } = useProjectStatus(projectId);
const loadingRef = useLoading<HTMLUListElement>(
loading,
'[data-loading-project-lifecycle-summary=true]',
);
const flagWord = (stage: keyof ProjectStatusSchemaLifecycleSummary) => {
if (data?.lifecycleSummary[stage].currentFlags === 1) {
return 'flag';
} else {
return 'flags';
}
};
return (
<LifecycleList ref={loadingRef}>
<LifecycleBox tooltipText={lifecycleMessages.initial}>
<p>
<Counter>
<BigNumber
value={data?.lifecycleSummary.initial.currentFlags}
/>
<FeatureLifecycleStageIcon
aria-hidden='true'
stage={{ name: 'initial' }}
/>
</Counter>
<span>{flagWord('initial')} in initial</span>
</p>
<AverageDaysStat
averageDays={data?.lifecycleSummary.initial.averageDays}
/>
</LifecycleBox>
<LifecycleBox tooltipText={lifecycleMessages.preLive}>
<p>
<Counter>
<BigNumber
value={data?.lifecycleSummary.preLive.currentFlags}
/>
<FeatureLifecycleStageIcon
aria-hidden='true'
stage={{ name: 'pre-live' }}
/>
</Counter>
<span>{flagWord('preLive')} in pre-live</span>
</p>
<AverageDaysStat
averageDays={data?.lifecycleSummary.preLive.averageDays}
/>
</LifecycleBox>
<LifecycleBox tooltipText={lifecycleMessages.live}>
<p>
<Counter>
<BigNumber
value={data?.lifecycleSummary.live.currentFlags}
/>
<FeatureLifecycleStageIcon
aria-hidden='true'
stage={{ name: 'live' }}
/>
</Counter>
<span>{flagWord('live')} in live</span>
</p>
<AverageDaysStat
averageDays={data?.lifecycleSummary.live.averageDays}
/>
</LifecycleBox>
<LifecycleBox tooltipText={lifecycleMessages.completed}>
<p>
<Counter>
<BigNumber
value={
data?.lifecycleSummary.completed.currentFlags
}
/>
<FeatureLifecycleStageIcon
aria-hidden='true'
stage={{ name: 'completed' }}
/>
</Counter>
<span>{flagWord('completed')} in completed</span>
</p>
<AverageDaysStat
averageDays={data?.lifecycleSummary.completed.averageDays}
/>
</LifecycleBox>
<LifecycleBox tooltipText={lifecycleMessages.archived}>
<p>
<Counter>
<BigNumber
value={data?.lifecycleSummary.archived.currentFlags}
/>
<FeatureLifecycleStageIcon
aria-hidden='true'
stage={{ name: 'archived' }}
/>
</Counter>
<span>{flagWord('archived')} in archived</span>
</p>
<Stats>
<dt>Last 30 days</dt>
<dd data-loading-project-lifecycle-summary>
{data?.lifecycleSummary.archived.last30Days ?? 0} flags
archived
</dd>
</Stats>
</LifecycleBox>
</LifecycleList>
);
};