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)
 
This commit is contained in:
parent
1739f8e11d
commit
7022ce8afb
@ -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>
|
||||||
|
@ -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 haven’t seen this feature flag in production for at least two
|
We haven’t seen this feature flag in production for at least two
|
||||||
days. It’s likely that it’s safe to archive this flag.
|
days. It’s likely that it’s 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>
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user