1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-12 13:48:35 +02:00

feat: completed stage button (#6914)

This commit is contained in:
Mateusz Kwasniewski 2024-04-24 10:30:50 +02:00 committed by GitHub
parent f63bae21f5
commit e91d471d17
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 111 additions and 72 deletions

View File

@ -5,22 +5,7 @@ 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 type { LifecycleStage } from './LifecycleStage';
export type LifecycleStage =
| { name: 'initial' }
| {
name: 'pre-live';
environments: Array<{ name: string; lastSeenAt: string }>;
}
| {
name: 'live';
environments: Array<{ name: string; lastSeenAt: string }>;
}
| {
name: 'completed';
status: 'kept' | 'discarded';
}
| { name: 'archived' };
export const FeatureLifecycleStageIcon: FC<{ stage: LifecycleStage }> = ({ export const FeatureLifecycleStageIcon: FC<{ stage: LifecycleStage }> = ({
stage, stage,

View File

@ -11,14 +11,13 @@ import { ReactComponent as CompletedDiscardedStageIcon } from 'assets/icons/stag
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 CloudCircle from '@mui/icons-material/CloudCircle';
import { ReactComponent as UsageRate } from 'assets/icons/usage-rate.svg'; import { ReactComponent as UsageRate } from 'assets/icons/usage-rate.svg';
import { import { FeatureLifecycleStageIcon } from './FeatureLifecycleStageIcon';
FeatureLifecycleStageIcon,
type LifecycleStage,
} from './FeatureLifecycleStageIcon';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import TimeAgo from 'react-timeago'; import TimeAgo from 'react-timeago';
import { StyledIconWrapper } from '../../FeatureEnvironmentSeen/FeatureEnvironmentSeen'; import { StyledIconWrapper } from '../../FeatureEnvironmentSeen/FeatureEnvironmentSeen';
import { useLastSeenColors } from '../../FeatureEnvironmentSeen/useLastSeenColors'; import { useLastSeenColors } from '../../FeatureEnvironmentSeen/useLastSeenColors';
import type { LifecycleStage } from './LifecycleStage';
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
import { UPDATE_FEATURE } from 'component/providers/AccessProvider/permissions';
const TimeLabel = styled('span')(({ theme }) => ({ const TimeLabel = styled('span')(({ theme }) => ({
color: theme.palette.text.secondary, color: theme.palette.text.secondary,
@ -204,52 +203,78 @@ const CenteredBox = styled(Box)(({ theme }) => ({
gap: theme.spacing(1), gap: theme.spacing(1),
})); }));
const LiveStageDescription: FC<{ const Environments: FC<{
name: 'live' | 'pre-live';
environments: Array<{ name: string; lastSeenAt: string }>; environments: Array<{ name: string; lastSeenAt: string }>;
}> = ({ name, environments }) => { }> = ({ environments }) => {
return (
<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>
);
};
const PreLiveStageDescription: FC = ({ children }) => {
return ( return (
<> <>
<ConditionallyRender <InfoText>
condition={name === 'pre-live'} We've seen the feature flag in the following non-production
show={ environments:
<InfoText> </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> {children}
{environments.map((environment) => { </>
return ( );
<EnvironmentLine key={environment.name}> };
<CenteredBox>
<CloudCircle /> const BoldTitle = styled(Typography)(({ theme }) => ({
<Box>{environment.name}</Box> marginTop: theme.spacing(1),
</CenteredBox> marginBottom: theme.spacing(1),
<CenteredBox> fontSize: theme.fontSizes.smallBody,
<TimeAgo fontWeight: theme.fontWeight.bold,
minPeriod={60} }));
date={environment.lastSeenAt}
/> const LiveStageDescription: FC = ({ children }) => {
<LastSeenIcon return (
lastSeen={environment.lastSeenAt} <>
/> <BoldTitle>Is this feature complete?</BoldTitle>
</CenteredBox> <InfoText sx={{ mb: 1 }}>
</EnvironmentLine> Marking the feature as complete does not affect any
); configuration, but it moves the feature into its next life
})} cycle stage and is an indication that you have learned what you
</Box> needed in order to progress with the feature. It serves as a
reminder to start cleaning up the flag and removing it from the
code.
</InfoText>
<PermissionButton
color='inherit'
variant='outlined'
permission={UPDATE_FEATURE}
size='small'
>
Mark Completed
</PermissionButton>
<InfoText sx={{ mt: 3 }}>
Users have been exposed to this feature in the following
production environments:
</InfoText>
{children}
</> </>
); );
}; };
@ -292,11 +317,15 @@ export const FeatureLifecycleTooltip: FC<{
</Box> </Box>
<ColorFill> <ColorFill>
{stage.name === 'initial' && <InitialStageDescription />} {stage.name === 'initial' && <InitialStageDescription />}
{(stage.name === 'pre-live' || stage.name === 'live') && ( {stage.name === 'pre-live' && (
<LiveStageDescription <PreLiveStageDescription>
name={stage.name} <Environments environments={stage.environments} />
environments={stage.environments} </PreLiveStageDescription>
/> )}
{stage.name === 'live' && (
<LiveStageDescription>
<Environments environments={stage.environments} />
</LiveStageDescription>
)} )}
</ColorFill> </ColorFill>
</Box> </Box>

View File

@ -0,0 +1,15 @@
export type LifecycleStage =
| { name: 'initial' }
| {
name: 'pre-live';
environments: Array<{ name: string; lastSeenAt: string }>;
}
| {
name: 'live';
environments: Array<{ name: string; lastSeenAt: string }>;
}
| {
name: 'completed';
status: 'kept' | 'discarded';
}
| { name: 'archived' };

View File

@ -10,6 +10,7 @@ import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { useUiFlag } from 'hooks/useUiFlag'; import { useUiFlag } from 'hooks/useUiFlag';
import { FeatureLifecycleTooltip } from '../FeatureLifecycle/FeatureLifecycleTooltip'; import { FeatureLifecycleTooltip } from '../FeatureLifecycle/FeatureLifecycleTooltip';
import { FeatureLifecycleStageIcon } from '../FeatureLifecycle/FeatureLifecycleStageIcon'; import { FeatureLifecycleStageIcon } from '../FeatureLifecycle/FeatureLifecycleStageIcon';
import type { LifecycleStage } from '../FeatureLifecycle/LifecycleStage';
const StyledContainer = styled('div')(({ theme }) => ({ const StyledContainer = styled('div')(({ theme }) => ({
borderRadius: theme.shape.borderRadiusLarge, borderRadius: theme.shape.borderRadiusLarge,
@ -81,6 +82,17 @@ const FeatureOverviewMetaData = () => {
const IconComponent = getFeatureTypeIcons(type); const IconComponent = getFeatureTypeIcons(type);
const currentStage: LifecycleStage = {
name: 'live',
environments: [
{ name: 'production', lastSeenAt: new Date().toISOString() },
{
name: 'staging',
lastSeenAt: new Date().toISOString(),
},
],
};
return ( return (
<StyledContainer> <StyledContainer>
<StyledPaddingContainerTop> <StyledPaddingContainerTop>
@ -109,11 +121,9 @@ const FeatureOverviewMetaData = () => {
show={ show={
<StyledRow data-loading> <StyledRow data-loading>
<StyledLabel>Lifecycle:</StyledLabel> <StyledLabel>Lifecycle:</StyledLabel>
<FeatureLifecycleTooltip <FeatureLifecycleTooltip stage={currentStage}>
stage={{ name: 'initial' }}
>
<FeatureLifecycleStageIcon <FeatureLifecycleStageIcon
stage={{ name: 'initial' }} stage={currentStage}
/> />
</FeatureLifecycleTooltip> </FeatureLifecycleTooltip>
</StyledRow> </StyledRow>