1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-23 00:22:19 +01:00

1-3083: add remaining lifecycle header + tooltip (#8722)

This PR adds a header and a tooltip to the lifecycle widget. Most of the
changes in ProjectLifecycleSummary is indentation changes due to
wrapping the component in another row container.

Additionally, this PR touches the `HelpIcon` component because we'd like
the tooltip to be wider than what we currently set as the default for
the help icon. The help icon uses the html tooltip component, which has
a maxWidth prop, but it does not expose that. So I've adjusted it to let
you do that.



Header with tooltip:

![image](https://github.com/user-attachments/assets/6ae1984b-256b-4f09-8fa2-b86ac2c17558)
This commit is contained in:
Thomas Heartman 2024-11-13 10:16:35 +01:00 committed by GitHub
parent bb0403d551
commit b87c47d7c4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 189 additions and 99 deletions

View File

@ -1,6 +1,9 @@
import { styled, Tooltip, type TooltipProps } from '@mui/material';
import HelpOutline from '@mui/icons-material/HelpOutline';
import { HtmlTooltip } from 'component/common/HtmlTooltip/HtmlTooltip';
import {
HtmlTooltip,
type IHtmlTooltipProps,
} from 'component/common/HtmlTooltip/HtmlTooltip';
const StyledContainer = styled('span')<{ size: string | undefined }>(
({ theme, size }) => ({
@ -24,13 +27,18 @@ const StyledContainer = styled('span')<{ size: string | undefined }>(
}),
);
interface IHelpIconProps {
type IHelpIconProps = {
tooltip: React.ReactNode;
htmlTooltip?: boolean;
placement?: TooltipProps['placement'];
children?: React.ReactNode;
size?: string;
}
} & (
| {
htmlTooltip: true;
htmlTooltipMaxWidth?: IHtmlTooltipProps['maxWidth'];
}
| { htmlTooltip?: false }
);
export const HelpIcon = ({
tooltip,
@ -38,10 +46,20 @@ export const HelpIcon = ({
placement,
children,
size,
...props
}: IHelpIconProps) => {
if (htmlTooltip) {
const { htmlTooltipMaxWidth } = props as {
htmlTooltipMaxWidth?: IHtmlTooltipProps['maxWidth'];
};
return (
<HtmlTooltip title={tooltip} placement={placement} arrow>
<HtmlTooltip
title={tooltip}
placement={placement}
arrow
maxWidth={htmlTooltipMaxWidth}
>
<StyledContainer size={size} tabIndex={0} aria-label='Help'>
{children ?? <HelpOutline />}
</StyledContainer>

View File

@ -6,6 +6,24 @@ import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import type { FC } from 'react';
import { PrettifyLargeNumber } from 'component/common/PrettifyLargeNumber/PrettifyLargeNumber';
import type { ProjectStatusSchemaLifecycleSummary } from 'openapi';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { HelpIcon } from 'component/common/HelpIcon/HelpIcon';
const LifecycleRow = styled('div')(({ theme }) => ({
display: 'flex',
flexFlow: 'column',
gap: theme.spacing(1),
}));
const HeaderRow = styled('div')(({ theme }) => ({
display: 'flex',
flex: 'auto',
'& > *': {
marginBlock: 0,
fontWeight: 'normal',
},
}));
const LifecycleBox = styled('li')(({ theme }) => ({
padding: theme.spacing(2),
borderRadius: theme.shape.borderRadiusExtraLarge,
@ -16,13 +34,14 @@ const LifecycleBox = styled('li')(({ theme }) => ({
justifyContent: 'space-between',
}));
const Wrapper = styled('ul')(({ theme }) => ({
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',
}));
const Counter = styled('span')({
@ -83,9 +102,47 @@ const BigNumber: FC<{ value?: number }> = ({ value }) => {
);
};
const TooltipContent = styled('div')(({ theme }) => ({
padding: theme.spacing(0.5),
}));
const TooltipText = styled('p')(({ theme }) => ({
fontSize: theme.typography.body1.fontSize,
'& + p': {
marginTop: theme.spacing(1),
},
}));
const LifecycleTooltip: FC = () => {
return (
<HelpIcon
htmlTooltip
htmlTooltipMaxWidth='550px'
tooltip={
<TooltipContent>
<TooltipText>
Based on usage metrics and interactions with Unleash,
feature flags can go through five distinct lifecycle
stages. These stages mirror the typical software
development process and allow you to identify
bottlenecks at any stage of the lifecycle.
</TooltipText>
<TooltipText>
<a href='https://docs.getunleash.io/reference/feature-toggles#feature-flag-lifecycle'>
Read more in our documentation
</a>
</TooltipText>
</TooltipContent>
}
/>
);
};
export const ProjectLifecycleSummary = () => {
const projectId = useRequiredPathParam('projectId');
const { data, loading } = useProjectStatus(projectId);
const { isEnterprise } = useUiConfig();
const loadingRef = useLoading<HTMLUListElement>(
loading,
@ -99,103 +156,118 @@ export const ProjectLifecycleSummary = () => {
}
};
return (
<Wrapper ref={loadingRef}>
<LifecycleBox>
<p>
<Counter>
<BigNumber
value={data?.lifecycleSummary.initial.currentFlags}
/>
<LifecycleRow>
<HeaderRow>
<h4>Flag lifecycle</h4>
<LifecycleTooltip />
</HeaderRow>
<LifecycleList ref={loadingRef}>
<LifecycleBox>
<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>
<p>
<Counter>
<BigNumber
value={data?.lifecycleSummary.preLive.currentFlags}
/>
<FeatureLifecycleStageIcon
aria-hidden='true'
stage={{ name: 'initial' }}
/>
</Counter>
<span>{flagWord('initial')} in initial</span>
</p>
<AverageDaysStat
averageDays={data?.lifecycleSummary.initial.averageDays}
/>
</LifecycleBox>
<LifecycleBox>
<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>
<p>
<Counter>
<BigNumber
value={data?.lifecycleSummary.live.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>
<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>
<p>
<Counter>
<BigNumber
value={
data?.lifecycleSummary.completed.currentFlags
}
/>
<FeatureLifecycleStageIcon
aria-hidden='true'
stage={{ name: 'live' }}
/>
</Counter>
<span>{flagWord('live')} in live</span>
</p>
<AverageDaysStat
averageDays={data?.lifecycleSummary.live.averageDays}
/>
</LifecycleBox>
<LifecycleBox>
<p>
<Counter>
<BigNumber
value={
data?.lifecycleSummary.completed
.currentFlags
}
/>
<FeatureLifecycleStageIcon
aria-hidden='true'
stage={{ name: 'completed' }}
/>
</Counter>
<span>{flagWord('completed')} in cleanup</span>
</p>
<AverageDaysStat
averageDays={data?.lifecycleSummary.completed.averageDays}
/>
</LifecycleBox>
<LifecycleBox>
<p>
<Counter>
<BigNumber
value={data?.lifecycleSummary.archived.currentFlags}
/>
<FeatureLifecycleStageIcon
aria-hidden='true'
stage={{ name: 'completed' }}
/>
</Counter>
<span>{flagWord('completed')} in cleanup</span>
</p>
<AverageDaysStat
averageDays={
data?.lifecycleSummary.completed.averageDays
}
/>
</LifecycleBox>
<LifecycleBox>
<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>This month</dt>
<dd data-loading-project-lifecycle-summary>
{data?.lifecycleSummary.archived.currentFlags ?? 0}{' '}
flags archived
</dd>
</Stats>
</LifecycleBox>
</Wrapper>
<FeatureLifecycleStageIcon
aria-hidden='true'
stage={{ name: 'archived' }}
/>
</Counter>
<span>{flagWord('archived')} in archived</span>
</p>
<Stats>
<dt>This month</dt>
<dd data-loading-project-lifecycle-summary>
{data?.lifecycleSummary.archived.currentFlags ?? 0}{' '}
flags archived
</dd>
</Stats>
</LifecycleBox>
</LifecycleList>
</LifecycleRow>
);
};