mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-04 00:18:01 +01:00
feat: archive feature from lifecycle (#6938)
This commit is contained in:
parent
3fb53737c6
commit
675e1a9f8b
@ -4,17 +4,19 @@ import { render } from 'utils/testRenderer';
|
|||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { vi } from 'vitest';
|
import { vi } from 'vitest';
|
||||||
import type { LifecycleStage } from './LifecycleStage';
|
import type { LifecycleStage } from './LifecycleStage';
|
||||||
|
import { DELETE_FEATURE } from 'component/providers/AccessProvider/permissions';
|
||||||
|
|
||||||
const currentTime = '2024-04-25T08:05:00.000Z';
|
const currentTime = '2024-04-25T08:05:00.000Z';
|
||||||
const twoMinutesAgo = '2024-04-25T08:03:00.000Z';
|
const twoMinutesAgo = '2024-04-25T08:03:00.000Z';
|
||||||
const oneHourAgo = '2024-04-25T07:05:00.000Z';
|
const oneHourAgo = '2024-04-25T07:05:00.000Z';
|
||||||
const twoHoursAgo = '2024-04-25T06:05:00.000Z';
|
const twoHoursAgo = '2024-04-25T06:05:00.000Z';
|
||||||
|
|
||||||
const renderOpenTooltip = (stage: LifecycleStage) => {
|
const renderOpenTooltip = (stage: LifecycleStage, onArchive = () => {}) => {
|
||||||
render(
|
render(
|
||||||
<FeatureLifecycleTooltip stage={stage}>
|
<FeatureLifecycleTooltip stage={stage} onArchive={onArchive}>
|
||||||
<span>child</span>
|
<span>child</span>
|
||||||
</FeatureLifecycleTooltip>,
|
</FeatureLifecycleTooltip>,
|
||||||
|
{ permissions: [{ permission: DELETE_FEATURE }] },
|
||||||
);
|
);
|
||||||
|
|
||||||
const child = screen.getByText('child');
|
const child = screen.getByText('child');
|
||||||
@ -89,14 +91,25 @@ test('render completed stage with still active', async () => {
|
|||||||
test('render completed stage safe to archive', async () => {
|
test('render completed stage safe to archive', async () => {
|
||||||
vi.setSystemTime(currentTime);
|
vi.setSystemTime(currentTime);
|
||||||
const enteredStageAt = twoMinutesAgo;
|
const enteredStageAt = twoMinutesAgo;
|
||||||
|
let onArchiveInvoked = false;
|
||||||
|
const onArchive = () => {
|
||||||
|
onArchiveInvoked = true;
|
||||||
|
};
|
||||||
|
|
||||||
renderOpenTooltip({
|
renderOpenTooltip(
|
||||||
name: 'completed',
|
{
|
||||||
status: 'kept',
|
name: 'completed',
|
||||||
environments: [],
|
status: 'kept',
|
||||||
enteredStageAt,
|
environments: [],
|
||||||
});
|
enteredStageAt,
|
||||||
|
},
|
||||||
|
onArchive,
|
||||||
|
);
|
||||||
|
|
||||||
await screen.findByText('completed');
|
await screen.findByText('completed');
|
||||||
await screen.findByText('Archive feature');
|
const button = await screen.findByText('Archive feature');
|
||||||
|
|
||||||
|
button.click();
|
||||||
|
|
||||||
|
expect(onArchiveInvoked).toBe(true);
|
||||||
});
|
});
|
||||||
|
@ -287,7 +287,7 @@ const LiveStageDescription: FC = ({ children }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const SafeToArchive: FC = () => {
|
const SafeToArchive: FC<{ onArchive: () => void }> = ({ onArchive }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<BoldTitle>Safe to archive</BoldTitle>
|
<BoldTitle>Safe to archive</BoldTitle>
|
||||||
@ -301,6 +301,7 @@ const SafeToArchive: FC = () => {
|
|||||||
permission={DELETE_FEATURE}
|
permission={DELETE_FEATURE}
|
||||||
size='small'
|
size='small'
|
||||||
sx={{ mb: 2 }}
|
sx={{ mb: 2 }}
|
||||||
|
onClick={onArchive}
|
||||||
>
|
>
|
||||||
Archive feature
|
Archive feature
|
||||||
</PermissionButton>
|
</PermissionButton>
|
||||||
@ -322,12 +323,13 @@ const ActivelyUsed: FC = ({ children }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const CompletedStageDescription: FC<{
|
const CompletedStageDescription: FC<{
|
||||||
|
onArchive: () => void;
|
||||||
environments: Array<{ name: string; lastSeenAt: string }>;
|
environments: Array<{ name: string; lastSeenAt: string }>;
|
||||||
}> = ({ children, environments }) => {
|
}> = ({ children, environments, onArchive }) => {
|
||||||
return (
|
return (
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={isSafeToArchive(environments)}
|
condition={isSafeToArchive(environments)}
|
||||||
show={<SafeToArchive />}
|
show={<SafeToArchive onArchive={onArchive} />}
|
||||||
elseShow={<ActivelyUsed>{children}</ActivelyUsed>}
|
elseShow={<ActivelyUsed>{children}</ActivelyUsed>}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -348,7 +350,8 @@ const FormatElapsedTime: FC<{ time: string }> = ({ time }) => {
|
|||||||
export const FeatureLifecycleTooltip: FC<{
|
export const FeatureLifecycleTooltip: FC<{
|
||||||
children: React.ReactElement<any, any>;
|
children: React.ReactElement<any, any>;
|
||||||
stage: LifecycleStage;
|
stage: LifecycleStage;
|
||||||
}> = ({ children, stage }) => (
|
onArchive: () => void;
|
||||||
|
}> = ({ children, stage, onArchive }) => (
|
||||||
<HtmlTooltip
|
<HtmlTooltip
|
||||||
maxHeight={800}
|
maxHeight={800}
|
||||||
maxWidth={350}
|
maxWidth={350}
|
||||||
@ -397,6 +400,7 @@ export const FeatureLifecycleTooltip: FC<{
|
|||||||
{stage.name === 'completed' && (
|
{stage.name === 'completed' && (
|
||||||
<CompletedStageDescription
|
<CompletedStageDescription
|
||||||
environments={stage.environments}
|
environments={stage.environments}
|
||||||
|
onArchive={onArchive}
|
||||||
>
|
>
|
||||||
<Environments environments={stage.environments} />
|
<Environments environments={stage.environments} />
|
||||||
</CompletedStageDescription>
|
</CompletedStageDescription>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { capitalize, styled } from '@mui/material';
|
import { capitalize, styled } from '@mui/material';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
|
||||||
import { getFeatureTypeIcons } from 'utils/getFeatureTypeIcons';
|
import { getFeatureTypeIcons } from 'utils/getFeatureTypeIcons';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
@ -10,6 +10,9 @@ 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 { FeatureArchiveDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveDialog';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { FeatureArchiveNotAllowedDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveNotAllowedDialog';
|
||||||
import { populateCurrentStage } from '../FeatureLifecycle/populateCurrentStage';
|
import { populateCurrentStage } from '../FeatureLifecycle/populateCurrentStage';
|
||||||
|
|
||||||
const StyledContainer = styled('div')(({ theme }) => ({
|
const StyledContainer = styled('div')(({ theme }) => ({
|
||||||
@ -79,6 +82,8 @@ const FeatureOverviewMetaData = () => {
|
|||||||
const { feature } = useFeature(projectId, featureId);
|
const { feature } = useFeature(projectId, featureId);
|
||||||
const { project, description, type } = feature;
|
const { project, description, type } = feature;
|
||||||
const featureLifecycleEnabled = useUiFlag('featureLifecycle');
|
const featureLifecycleEnabled = useUiFlag('featureLifecycle');
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [showDelDialog, setShowDelDialog] = useState(false);
|
||||||
|
|
||||||
const IconComponent = getFeatureTypeIcons(type);
|
const IconComponent = getFeatureTypeIcons(type);
|
||||||
|
|
||||||
@ -108,20 +113,20 @@ const FeatureOverviewMetaData = () => {
|
|||||||
<span>{project}</span>
|
<span>{project}</span>
|
||||||
</StyledRow>
|
</StyledRow>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={featureLifecycleEnabled}
|
condition={
|
||||||
|
featureLifecycleEnabled && Boolean(currentStage)
|
||||||
|
}
|
||||||
show={
|
show={
|
||||||
<StyledRow data-loading>
|
<StyledRow data-loading>
|
||||||
<StyledLabel>Lifecycle:</StyledLabel>
|
<StyledLabel>Lifecycle:</StyledLabel>
|
||||||
|
<FeatureLifecycleTooltip
|
||||||
{currentStage && (
|
stage={currentStage!}
|
||||||
<FeatureLifecycleTooltip
|
onArchive={() => setShowDelDialog(true)}
|
||||||
stage={currentStage}
|
>
|
||||||
>
|
<FeatureLifecycleStageIcon
|
||||||
<FeatureLifecycleStageIcon
|
stage={currentStage!}
|
||||||
stage={currentStage}
|
/>
|
||||||
/>
|
</FeatureLifecycleTooltip>
|
||||||
</FeatureLifecycleTooltip>
|
|
||||||
)}
|
|
||||||
</StyledRow>
|
</StyledRow>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -170,6 +175,28 @@ const FeatureOverviewMetaData = () => {
|
|||||||
/>
|
/>
|
||||||
</StyledBody>
|
</StyledBody>
|
||||||
</StyledPaddingContainerTop>
|
</StyledPaddingContainerTop>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={feature.children.length > 0}
|
||||||
|
show={
|
||||||
|
<FeatureArchiveNotAllowedDialog
|
||||||
|
features={feature.children}
|
||||||
|
project={projectId}
|
||||||
|
isOpen={showDelDialog}
|
||||||
|
onClose={() => setShowDelDialog(false)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
elseShow={
|
||||||
|
<FeatureArchiveDialog
|
||||||
|
isOpen={showDelDialog}
|
||||||
|
onConfirm={() => {
|
||||||
|
navigate(`/projects/${projectId}`);
|
||||||
|
}}
|
||||||
|
onClose={() => setShowDelDialog(false)}
|
||||||
|
projectId={projectId}
|
||||||
|
featureIds={[featureId]}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user