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,
onArchive = () => {},
onComplete = () => {},
onUncomplete = () => {},
loading = true,
) => {
render(
@ -22,6 +23,7 @@ const renderOpenTooltip = (
stage={stage}
onArchive={onArchive}
onComplete={onComplete}
onUncomplete={onUncomplete}
loading={loading}
>
<span>child</span>

View File

@ -62,7 +62,9 @@ const Line = styled(Box)(({ theme }) => ({
const StageBox = styled(Box, {
shouldForwardProp: (prop) => prop !== 'active',
})<{ active?: boolean }>(({ theme, active }) => ({
})<{
active?: boolean;
}>(({ theme, active }) => ({
position: 'relative',
// speech bubble triangle for active stage
...(active && {
@ -108,7 +110,9 @@ const ColorFill = styled(Box)(({ theme }) => ({
padding: theme.spacing(2, 3),
}));
const LastSeenIcon: FC<{ lastSeen: string }> = ({ lastSeen }) => {
const LastSeenIcon: FC<{
lastSeen: string;
}> = ({ lastSeen }) => {
const getColor = useLastSeenColors();
return (
@ -147,7 +151,9 @@ const InitialStageDescription: FC = () => {
);
};
const StageTimeline: FC<{ stage: LifecycleStage }> = ({ stage }) => {
const StageTimeline: FC<{
stage: LifecycleStage;
}> = ({ stage }) => {
return (
<IconsRow>
<StageBox
@ -212,7 +218,10 @@ const CenteredBox = styled(Box)(({ theme }) => ({
}));
const Environments: FC<{
environments: Array<{ name: string; lastSeenAt: string }>;
environments: Array<{
name: string;
lastSeenAt: string;
}>;
}> = ({ environments }) => {
return (
<Box>
@ -280,7 +289,7 @@ const LiveStageDescription: FC<{
onClick={onComplete}
disabled={loading}
>
Mark Completed
Mark completed
</PermissionButton>
<InfoText sx={{ mt: 3 }}>
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 (
<>
<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
days. Its likely that its safe to archive this flag.
</InfoText>
<PermissionButton
color='inherit'
variant='outlined'
permission={DELETE_FEATURE}
size='small'
sx={{ mb: 2 }}
onClick={onArchive}
<Box
sx={{
display: 'flex',
flexDirection: 'row',
gap: 2,
}}
>
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 }) => {
return (
<>
<InfoText sx={{ mt: 1, 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>
{children}
</>
);
};
const ActivelyUsed: FC<{
onUncomplete: () => void;
loading: boolean;
}> = ({ children, onUncomplete, loading }) => (
<>
<InfoText
sx={{
mt: 1,
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<{
onArchive: () => void;
environments: Array<{ name: string; lastSeenAt: string }>;
}> = ({ children, environments, onArchive }) => {
onUncomplete: () => void;
loading: boolean;
environments: Array<{
name: string;
lastSeenAt: string;
}>;
}> = ({ children, environments, onArchive, onUncomplete, loading }) => {
return (
<ConditionallyRender
condition={isSafeToArchive(environments)}
show={<SafeToArchive onArchive={onArchive} />}
elseShow={<ActivelyUsed>{children}</ActivelyUsed>}
show={
<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();
return <span>{formatDateYMDHMS(time, locationSettings.locale)}</span>;
};
const FormatElapsedTime: FC<{ time: string }> = ({ time }) => {
const FormatElapsedTime: FC<{
time: string;
}> = ({ time }) => {
const pastTime = parseISO(time);
const elapsedTime = formatDistanceToNow(pastTime, { addSuffix: false });
return <span>{elapsedTime}</span>;
@ -357,8 +429,9 @@ export const FeatureLifecycleTooltip: FC<{
stage: LifecycleStage;
onArchive: () => void;
onComplete: () => void;
onUncomplete: () => void;
loading: boolean;
}> = ({ children, stage, onArchive, onComplete, loading }) => (
}> = ({ children, stage, onArchive, onComplete, onUncomplete, loading }) => (
<HtmlTooltip
maxHeight={800}
maxWidth={350}
@ -411,6 +484,8 @@ export const FeatureLifecycleTooltip: FC<{
<CompletedStageDescription
environments={stage.environments}
onArchive={onArchive}
onUncomplete={onUncomplete}
loading={loading}
>
<Environments environments={stage.environments} />
</CompletedStageDescription>

View File

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

View File

@ -15,8 +15,18 @@ const useFeatureLifecycleApi = () => {
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 {
markFeatureCompleted,
markFeatureUncompleted,
errors,
loading,
};