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

feat: rever to live connected to backend (#6951)

![Screenshot from 2024-04-26
13-30-48](https://github.com/Unleash/unleash/assets/964450/03c57c62-bf51-440b-935d-46da729e3157)
![Screenshot from 2024-04-26
13-15-45](https://github.com/Unleash/unleash/assets/964450/c5ac44be-39dc-4b6c-97ec-5fc56b0cc111)
This commit is contained in:
Jaanus Sellin 2024-04-26 13:43:38 +03:00 committed by GitHub
parent 1739f8e11d
commit 7022ce8afb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 130 additions and 36 deletions

View File

@ -15,6 +15,7 @@ const renderOpenTooltip = (
stage: LifecycleStage, stage: LifecycleStage,
onArchive = () => {}, onArchive = () => {},
onComplete = () => {}, onComplete = () => {},
onUncomplete = () => {},
loading = true, loading = true,
) => { ) => {
render( render(
@ -22,6 +23,7 @@ const renderOpenTooltip = (
stage={stage} stage={stage}
onArchive={onArchive} onArchive={onArchive}
onComplete={onComplete} onComplete={onComplete}
onUncomplete={onUncomplete}
loading={loading} loading={loading}
> >
<span>child</span> <span>child</span>

View File

@ -62,7 +62,9 @@ const Line = styled(Box)(({ theme }) => ({
const StageBox = styled(Box, { const StageBox = styled(Box, {
shouldForwardProp: (prop) => prop !== 'active', shouldForwardProp: (prop) => prop !== 'active',
})<{ active?: boolean }>(({ theme, active }) => ({ })<{
active?: boolean;
}>(({ theme, active }) => ({
position: 'relative', position: 'relative',
// speech bubble triangle for active stage // speech bubble triangle for active stage
...(active && { ...(active && {
@ -108,7 +110,9 @@ const ColorFill = styled(Box)(({ theme }) => ({
padding: theme.spacing(2, 3), padding: theme.spacing(2, 3),
})); }));
const LastSeenIcon: FC<{ lastSeen: string }> = ({ lastSeen }) => { const LastSeenIcon: FC<{
lastSeen: string;
}> = ({ lastSeen }) => {
const getColor = useLastSeenColors(); const getColor = useLastSeenColors();
return ( return (
@ -147,7 +151,9 @@ const InitialStageDescription: FC = () => {
); );
}; };
const StageTimeline: FC<{ stage: LifecycleStage }> = ({ stage }) => { const StageTimeline: FC<{
stage: LifecycleStage;
}> = ({ stage }) => {
return ( return (
<IconsRow> <IconsRow>
<StageBox <StageBox
@ -212,7 +218,10 @@ const CenteredBox = styled(Box)(({ theme }) => ({
})); }));
const Environments: FC<{ const Environments: FC<{
environments: Array<{ name: string; lastSeenAt: string }>; environments: Array<{
name: string;
lastSeenAt: string;
}>;
}> = ({ environments }) => { }> = ({ environments }) => {
return ( return (
<Box> <Box>
@ -280,7 +289,7 @@ const LiveStageDescription: FC<{
onClick={onComplete} onClick={onComplete}
disabled={loading} disabled={loading}
> >
Mark Completed Mark completed
</PermissionButton> </PermissionButton>
<InfoText sx={{ mt: 3 }}> <InfoText sx={{ mt: 3 }}>
Users have been exposed to this feature in the following Users have been exposed to this feature in the following
@ -292,61 +301,124 @@ const LiveStageDescription: FC<{
); );
}; };
const SafeToArchive: FC<{ onArchive: () => void }> = ({ onArchive }) => { const SafeToArchive: FC<{
onArchive: () => void;
onUncomplete: () => void;
loading: boolean;
}> = ({ onArchive, onUncomplete, loading }) => {
return ( return (
<> <>
<BoldTitle>Safe to archive</BoldTitle> <BoldTitle>Safe to archive</BoldTitle>
<InfoText sx={{ mt: 2, mb: 1 }}> <InfoText
sx={{
mt: 2,
mb: 1,
}}
>
We havent seen this feature flag in production for at least two We havent seen this feature flag in production for at least two
days. Its likely that its safe to archive this flag. days. Its likely that its safe to archive this flag.
</InfoText> </InfoText>
<PermissionButton <Box
color='inherit' sx={{
variant='outlined' display: 'flex',
permission={DELETE_FEATURE} flexDirection: 'row',
size='small' gap: 2,
sx={{ mb: 2 }} }}
onClick={onArchive}
> >
Archive feature <PermissionButton
</PermissionButton> color='inherit'
variant='outlined'
permission={UPDATE_FEATURE}
size='small'
onClick={onUncomplete}
disabled={loading}
>
Revert to live
</PermissionButton>
<PermissionButton
color='inherit'
variant='outlined'
permission={DELETE_FEATURE}
size='small'
sx={{ mb: 2 }}
onClick={onArchive}
>
Archive feature
</PermissionButton>
</Box>
</> </>
); );
}; };
const ActivelyUsed: FC = ({ children }) => { const ActivelyUsed: FC<{
return ( onUncomplete: () => void;
<> loading: boolean;
<InfoText sx={{ mt: 1, mb: 1 }}> }> = ({ children, onUncomplete, loading }) => (
This feature has been successfully completed, but we are still <>
seeing usage in production. Clean up the feature flag from your <InfoText
code before archiving it: sx={{
</InfoText> mt: 1,
{children} mb: 1,
</> }}
); >
}; This feature has been successfully completed, but we are still
seeing usage in production. Clean up the feature flag from your code
before archiving it:
</InfoText>
<PermissionButton
color='inherit'
variant='outlined'
permission={UPDATE_FEATURE}
size='small'
sx={{ mb: 2 }}
onClick={onUncomplete}
disabled={loading}
>
Revert to live
</PermissionButton>
{children}
</>
);
const CompletedStageDescription: FC<{ const CompletedStageDescription: FC<{
onArchive: () => void; onArchive: () => void;
environments: Array<{ name: string; lastSeenAt: string }>; onUncomplete: () => void;
}> = ({ children, environments, onArchive }) => { loading: boolean;
environments: Array<{
name: string;
lastSeenAt: string;
}>;
}> = ({ children, environments, onArchive, onUncomplete, loading }) => {
return ( return (
<ConditionallyRender <ConditionallyRender
condition={isSafeToArchive(environments)} condition={isSafeToArchive(environments)}
show={<SafeToArchive onArchive={onArchive} />} show={
elseShow={<ActivelyUsed>{children}</ActivelyUsed>} <SafeToArchive
onArchive={onArchive}
onUncomplete={onUncomplete}
loading={loading}
/>
}
elseShow={
<ActivelyUsed onUncomplete={onUncomplete} loading={loading}>
{children}
</ActivelyUsed>
}
/> />
); );
}; };
const FormatTime: FC<{ time: string }> = ({ time }) => { const FormatTime: FC<{
time: string;
}> = ({ time }) => {
const { locationSettings } = useLocationSettings(); const { locationSettings } = useLocationSettings();
return <span>{formatDateYMDHMS(time, locationSettings.locale)}</span>; return <span>{formatDateYMDHMS(time, locationSettings.locale)}</span>;
}; };
const FormatElapsedTime: FC<{ time: string }> = ({ time }) => { const FormatElapsedTime: FC<{
time: string;
}> = ({ time }) => {
const pastTime = parseISO(time); const pastTime = parseISO(time);
const elapsedTime = formatDistanceToNow(pastTime, { addSuffix: false }); const elapsedTime = formatDistanceToNow(pastTime, { addSuffix: false });
return <span>{elapsedTime}</span>; return <span>{elapsedTime}</span>;
@ -357,8 +429,9 @@ export const FeatureLifecycleTooltip: FC<{
stage: LifecycleStage; stage: LifecycleStage;
onArchive: () => void; onArchive: () => void;
onComplete: () => void; onComplete: () => void;
onUncomplete: () => void;
loading: boolean; loading: boolean;
}> = ({ children, stage, onArchive, onComplete, loading }) => ( }> = ({ children, stage, onArchive, onComplete, onUncomplete, loading }) => (
<HtmlTooltip <HtmlTooltip
maxHeight={800} maxHeight={800}
maxWidth={350} maxWidth={350}
@ -411,6 +484,8 @@ export const FeatureLifecycleTooltip: FC<{
<CompletedStageDescription <CompletedStageDescription
environments={stage.environments} environments={stage.environments}
onArchive={onArchive} onArchive={onArchive}
onUncomplete={onUncomplete}
loading={loading}
> >
<Environments environments={stage.environments} /> <Environments environments={stage.environments} />
</CompletedStageDescription> </CompletedStageDescription>

View File

@ -83,7 +83,8 @@ const FeatureOverviewMetaData = () => {
const { feature, refetchFeature } = useFeature(projectId, featureId); const { feature, refetchFeature } = useFeature(projectId, featureId);
const { project, description, type } = feature; const { project, description, type } = feature;
const featureLifecycleEnabled = useUiFlag('featureLifecycle'); const featureLifecycleEnabled = useUiFlag('featureLifecycle');
const { markFeatureCompleted, loading } = useFeatureLifecycleApi(); const { markFeatureCompleted, markFeatureUncompleted, loading } =
useFeatureLifecycleApi();
const navigate = useNavigate(); const navigate = useNavigate();
const [showDelDialog, setShowDelDialog] = useState(false); const [showDelDialog, setShowDelDialog] = useState(false);
@ -96,6 +97,11 @@ const FeatureOverviewMetaData = () => {
refetchFeature(); refetchFeature();
}; };
const onUncomplete = async () => {
await markFeatureUncompleted(featureId, projectId);
refetchFeature();
};
return ( return (
<StyledContainer> <StyledContainer>
<StyledPaddingContainerTop> <StyledPaddingContainerTop>
@ -130,6 +136,7 @@ const FeatureOverviewMetaData = () => {
stage={currentStage!} stage={currentStage!}
onArchive={() => setShowDelDialog(true)} onArchive={() => setShowDelDialog(true)}
onComplete={onComplete} onComplete={onComplete}
onUncomplete={onUncomplete}
loading={loading} loading={loading}
> >
<FeatureLifecycleStageIcon <FeatureLifecycleStageIcon

View File

@ -15,8 +15,18 @@ const useFeatureLifecycleApi = () => {
return makeRequest(req.caller, req.id); return makeRequest(req.caller, req.id);
}; };
const markFeatureUncompleted = async (name: string, project: string) => {
const path = `api/admin/projects/${project}/features/${name}/lifecycle/uncomplete`;
const req = createRequest(path, {
method: 'POST',
});
return makeRequest(req.caller, req.id);
};
return { return {
markFeatureCompleted, markFeatureCompleted,
markFeatureUncompleted,
errors, errors,
loading, loading,
}; };