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

feat: live and pre-live stages UI (#6913)

This commit is contained in:
Mateusz Kwasniewski 2024-04-24 08:29:52 +02:00 committed by GitHub
parent d578deab7f
commit f63bae21f5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 176 additions and 64 deletions

View File

@ -33,7 +33,7 @@ const StyledBox = styled(Box)(({ theme }) => ({
margin: '0 auto', margin: '0 auto',
})); }));
const StyledIconWrapper = styled('div')(({ theme }) => ({ export const StyledIconWrapper = styled('div')(({ theme }) => ({
width: '20px', width: '20px',
height: '20px', height: '20px',
background: theme.palette.background.paper, background: theme.palette.background.paper,

View File

@ -8,8 +8,14 @@ import { ReactComponent as ArchivedStageIcon } from 'assets/icons/stage-archived
export type LifecycleStage = export type LifecycleStage =
| { name: 'initial' } | { name: 'initial' }
| { name: 'pre-live' } | {
| { name: 'live' } name: 'pre-live';
environments: Array<{ name: string; lastSeenAt: string }>;
}
| {
name: 'live';
environments: Array<{ name: string; lastSeenAt: string }>;
}
| { | {
name: 'completed'; name: 'completed';
status: 'kept' | 'discarded'; status: 'kept' | 'discarded';

View File

@ -9,10 +9,16 @@ import { ReactComponent as LiveStageIcon } from 'assets/icons/stage-live.svg';
import { ReactComponent as CompletedStageIcon } from 'assets/icons/stage-completed.svg'; import { ReactComponent as CompletedStageIcon } from 'assets/icons/stage-completed.svg';
import { ReactComponent as CompletedDiscardedStageIcon } from 'assets/icons/stage-completed-discarded.svg'; import { ReactComponent as CompletedDiscardedStageIcon } from 'assets/icons/stage-completed-discarded.svg';
import { ReactComponent as ArchivedStageIcon } from 'assets/icons/stage-archived.svg'; import { ReactComponent as ArchivedStageIcon } from 'assets/icons/stage-archived.svg';
import CloudCircle from '@mui/icons-material/CloudCircle';
import { ReactComponent as UsageRate } from 'assets/icons/usage-rate.svg';
import { import {
FeatureLifecycleStageIcon, FeatureLifecycleStageIcon,
type LifecycleStage, type LifecycleStage,
} from './FeatureLifecycleStageIcon'; } from './FeatureLifecycleStageIcon';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import TimeAgo from 'react-timeago';
import { StyledIconWrapper } from '../../FeatureEnvironmentSeen/FeatureEnvironmentSeen';
import { useLastSeenColors } from '../../FeatureEnvironmentSeen/useLastSeenColors';
const TimeLabel = styled('span')(({ theme }) => ({ const TimeLabel = styled('span')(({ theme }) => ({
color: theme.palette.text.secondary, color: theme.palette.text.secondary,
@ -95,6 +101,159 @@ const ColorFill = styled(Box)(({ theme }) => ({
padding: theme.spacing(2, 3), padding: theme.spacing(2, 3),
})); }));
const LastSeenIcon: FC<{ lastSeen: string }> = ({ lastSeen }) => {
const getColor = useLastSeenColors();
return (
<TimeAgo
date={lastSeen}
title=''
live={false}
formatter={(value: number, unit: string) => {
const [color, textColor] = getColor(unit);
return (
<StyledIconWrapper style={{ background: color }}>
<UsageRate stroke={textColor} />
</StyledIconWrapper>
);
}}
/>
);
};
const InitialStageDescription: FC = () => {
return (
<>
<InfoText>
This feature toggle is currently in the initial phase of it's
life cycle.
</InfoText>
<InfoText>
This means that the flag has been created, but it has not yet
been seen in any environment.
</InfoText>
<InfoText>
Once we detect metrics for a non-production environment it will
move into pre-live.
</InfoText>
</>
);
};
const StageTimeline: FC<{ stage: LifecycleStage }> = ({ stage }) => {
return (
<IconsRow>
<StageBox
data-after-content='Initial'
active={stage.name === 'initial'}
>
<InitialStageIcon />
</StageBox>
<Line />
<StageBox
data-after-content='Pre-live'
active={stage.name === 'pre-live'}
>
<PreLiveStageIcon />
</StageBox>
<Line />
<StageBox data-after-content='Live' active={stage.name === 'live'}>
<LiveStageIcon />
</StageBox>
<Line />
<StageBox
data-after-content='Completed'
active={stage.name === 'completed'}
>
{stage.name === 'completed' && stage.status === 'discarded' ? (
<CompletedDiscardedStageIcon />
) : (
<CompletedStageIcon />
)}
</StageBox>
<Line />
<StageBox
data-after-content='Archived'
active={stage.name === 'archived'}
>
<ArchivedStageIcon />
</StageBox>
</IconsRow>
);
};
const EnvironmentLine = styled(Box)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
marginTop: theme.spacing(1),
marginBottom: theme.spacing(2),
}));
const CenteredBox = styled(Box)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
gap: theme.spacing(1),
}));
const LiveStageDescription: FC<{
name: 'live' | 'pre-live';
environments: Array<{ name: string; lastSeenAt: string }>;
}> = ({ name, environments }) => {
return (
<>
<ConditionallyRender
condition={name === 'pre-live'}
show={
<InfoText>
We've seen the feature flag in the following
non-production environments:
</InfoText>
}
/>
<ConditionallyRender
condition={name === 'live'}
show={
<InfoText>
Users have been exposed to this feature in the following
production environments:
</InfoText>
}
/>
<Box>
{environments.map((environment) => {
return (
<EnvironmentLine key={environment.name}>
<CenteredBox>
<CloudCircle />
<Box>{environment.name}</Box>
</CenteredBox>
<CenteredBox>
<TimeAgo
minPeriod={60}
date={environment.lastSeenAt}
/>
<LastSeenIcon
lastSeen={environment.lastSeenAt}
/>
</CenteredBox>
</EnvironmentLine>
);
})}
</Box>
</>
);
};
export const FeatureLifecycleTooltip: FC<{ export const FeatureLifecycleTooltip: FC<{
children: React.ReactElement<any, any>; children: React.ReactElement<any, any>;
stage: LifecycleStage; stage: LifecycleStage;
@ -129,69 +288,16 @@ export const FeatureLifecycleTooltip: FC<{
<TimeLabel>Time spent in stage</TimeLabel> <TimeLabel>Time spent in stage</TimeLabel>
<span>3 days</span> <span>3 days</span>
</TimeLifecycleRow> </TimeLifecycleRow>
<IconsRow> <StageTimeline stage={stage} />
<StageBox
data-after-content='Initial'
active={stage.name === 'initial'}
>
<InitialStageIcon />
</StageBox>
<Line />
<StageBox
data-after-content='Pre-live'
active={stage.name === 'pre-live'}
>
<PreLiveStageIcon />
</StageBox>
<Line />
<StageBox
data-after-content='Live'
active={stage.name === 'live'}
>
<LiveStageIcon />
</StageBox>
<Line />
<StageBox
data-after-content='Completed'
active={stage.name === 'completed'}
>
{stage.name === 'completed' &&
stage.status === 'discarded' ? (
<CompletedDiscardedStageIcon />
) : (
<CompletedStageIcon />
)}
</StageBox>
<Line />
<StageBox
data-after-content='Archived'
active={stage.name === 'archived'}
>
<ArchivedStageIcon />
</StageBox>
</IconsRow>
</Box> </Box>
<ColorFill> <ColorFill>
<InfoText> {stage.name === 'initial' && <InitialStageDescription />}
This feature toggle is currently in the initial phase of {(stage.name === 'pre-live' || stage.name === 'live') && (
it's life cycle. <LiveStageDescription
</InfoText> name={stage.name}
<InfoText> environments={stage.environments}
This means that the flag has been created, but it has />
not yet been seen in any environment. )}
</InfoText>
<InfoText>
Once we detect metrics for a non-production environment
it will move into pre-live.
</InfoText>
</ColorFill> </ColorFill>
</Box> </Box>
} }