diff --git a/frontend/src/assets/icons/lifecycle/stage-5.svg b/frontend/src/assets/icons/lifecycle/stage-archived.svg similarity index 100% rename from frontend/src/assets/icons/lifecycle/stage-5.svg rename to frontend/src/assets/icons/lifecycle/stage-archived.svg diff --git a/frontend/src/assets/icons/lifecycle/stage-4.svg b/frontend/src/assets/icons/lifecycle/stage-completed.svg similarity index 100% rename from frontend/src/assets/icons/lifecycle/stage-4.svg rename to frontend/src/assets/icons/lifecycle/stage-completed.svg diff --git a/frontend/src/assets/icons/lifecycle/stage-1.svg b/frontend/src/assets/icons/lifecycle/stage-created.svg similarity index 100% rename from frontend/src/assets/icons/lifecycle/stage-1.svg rename to frontend/src/assets/icons/lifecycle/stage-created.svg diff --git a/frontend/src/assets/icons/lifecycle/stage-3.svg b/frontend/src/assets/icons/lifecycle/stage-live.svg similarity index 100% rename from frontend/src/assets/icons/lifecycle/stage-3.svg rename to frontend/src/assets/icons/lifecycle/stage-live.svg diff --git a/frontend/src/assets/icons/lifecycle/stage-2.svg b/frontend/src/assets/icons/lifecycle/stage-prelive.svg similarity index 100% rename from frontend/src/assets/icons/lifecycle/stage-2.svg rename to frontend/src/assets/icons/lifecycle/stage-prelive.svg diff --git a/frontend/src/assets/icons/stage-archived.svg b/frontend/src/assets/icons/stage-archived.svg deleted file mode 100644 index 53bf6ce79a..0000000000 --- a/frontend/src/assets/icons/stage-archived.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/frontend/src/assets/icons/stage-completed-discarded.svg b/frontend/src/assets/icons/stage-completed-discarded.svg deleted file mode 100644 index d7e6e40144..0000000000 --- a/frontend/src/assets/icons/stage-completed-discarded.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/frontend/src/assets/icons/stage-completed.svg b/frontend/src/assets/icons/stage-completed.svg deleted file mode 100644 index 63bbf00ba3..0000000000 --- a/frontend/src/assets/icons/stage-completed.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/frontend/src/assets/icons/stage-initial.svg b/frontend/src/assets/icons/stage-initial.svg deleted file mode 100644 index 59ea830cac..0000000000 --- a/frontend/src/assets/icons/stage-initial.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/frontend/src/assets/icons/stage-live.svg b/frontend/src/assets/icons/stage-live.svg deleted file mode 100644 index 86855fb9c0..0000000000 --- a/frontend/src/assets/icons/stage-live.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/frontend/src/assets/icons/stage-pre-live.svg b/frontend/src/assets/icons/stage-pre-live.svg deleted file mode 100644 index fac4bdf880..0000000000 --- a/frontend/src/assets/icons/stage-pre-live.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/frontend/src/component/common/FeatureLifecycle/FeatureLifecycleStageIcon.tsx b/frontend/src/component/common/FeatureLifecycle/FeatureLifecycleStageIcon.tsx index 856e99b618..4f029ed5f7 100644 --- a/frontend/src/component/common/FeatureLifecycle/FeatureLifecycleStageIcon.tsx +++ b/frontend/src/component/common/FeatureLifecycle/FeatureLifecycleStageIcon.tsx @@ -1,47 +1,25 @@ import type { FC } from 'react'; -import { ReactComponent as InitialStageIcon } from 'assets/icons/stage-initial.svg'; -import { ReactComponent as PreLiveStageIcon } from 'assets/icons/stage-pre-live.svg'; -import { ReactComponent as LiveStageIcon } from 'assets/icons/stage-live.svg'; -import { ReactComponent as CompletedStageIcon } from 'assets/icons/stage-completed.svg'; -import { ReactComponent as ArchivedStageIcon } from 'assets/icons/stage-archived.svg'; -import { ReactComponent as Stage1 } from 'assets/icons/lifecycle/stage-1.svg'; -import { ReactComponent as Stage2 } from 'assets/icons/lifecycle/stage-2.svg'; -import { ReactComponent as Stage3 } from 'assets/icons/lifecycle/stage-3.svg'; -import { ReactComponent as Stage4 } from 'assets/icons/lifecycle/stage-4.svg'; -import { ReactComponent as Stage5 } from 'assets/icons/lifecycle/stage-5.svg'; +import { ReactComponent as CreatedIcon } from 'assets/icons/lifecycle/stage-created.svg'; +import { ReactComponent as PreLiveIcon } from 'assets/icons/lifecycle/stage-prelive.svg'; +import { ReactComponent as LiveIcon } from 'assets/icons/lifecycle/stage-live.svg'; +import { ReactComponent as CompletedIcon } from 'assets/icons/lifecycle/stage-completed.svg'; +import { ReactComponent as ArchivedIcon } from 'assets/icons/lifecycle/stage-archived.svg'; import type { LifecycleStage } from '../../feature/FeatureView/FeatureOverview/FeatureLifecycle/LifecycleStage'; -import { useUiFlag } from 'hooks/useUiFlag'; export const FeatureLifecycleStageIcon: FC<{ stage: Pick; }> = ({ stage, ...props }) => { - const newIcons = useUiFlag('lifecycleImprovements'); - if (stage.name === 'archived') { - return newIcons ? ( - - ) : ( - - ); - } else if (stage.name === 'pre-live') { - return newIcons ? ( - - ) : ( - - ); - } else if (stage.name === 'live') { - return newIcons ? : ; - } else if (stage.name === 'completed') { - return newIcons ? ( - - ) : ( - - ); - } else { - return newIcons ? ( - - ) : ( - - ); + return ; } + if (stage.name === 'pre-live') { + return ; + } + if (stage.name === 'live') { + return ; + } + if (stage.name === 'completed') { + return ; + } + return ; }; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureLifecycle/FeatureLifecycle.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureLifecycle/FeatureLifecycle.tsx index e8a10ae42a..4cc7f082b9 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureLifecycle/FeatureLifecycle.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureLifecycle/FeatureLifecycle.tsx @@ -1,12 +1,10 @@ import { FeatureLifecycleStageIcon } from 'component/common/FeatureLifecycle/FeatureLifecycleStageIcon'; -import { FeatureLifecycleTooltip as LegacyFeatureLifecycleTooltip } from './LegacyFeatureLifecycleTooltip'; import { FeatureLifecycleTooltip } from './FeatureLifecycleTooltip'; import useFeatureLifecycleApi from 'hooks/api/actions/useFeatureLifecycleApi/useFeatureLifecycleApi'; import { populateCurrentStage } from './populateCurrentStage'; import type { FC } from 'react'; import type { Lifecycle } from 'interfaces/featureToggle'; import { usePlausibleTracker } from 'hooks/usePlausibleTracker'; -import { useUiFlag } from 'hooks/useUiFlag'; export interface LifecycleFeature { lifecycle?: Lifecycle; @@ -29,7 +27,6 @@ export const FeatureLifecycle: FC<{ const currentStage = populateCurrentStage(feature); const { markFeatureUncompleted, loading } = useFeatureLifecycleApi(); const { trackEvent } = usePlausibleTracker(); - const isLifecycleImprovementsEnabled = useUiFlag('lifecycleImprovements'); const onUncompleteHandler = async () => { await markFeatureUncompleted(feature.name, feature.project); @@ -41,23 +38,8 @@ export const FeatureLifecycle: FC<{ }); }; - if (isLifecycleImprovementsEnabled) { - return currentStage ? ( - - - - ) : null; - } - return currentStage ? ( - - + ) : null; }; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureLifecycle/LegacyFeatureLifecycleTooltip.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureLifecycle/LegacyFeatureLifecycleTooltip.tsx deleted file mode 100644 index f4cb1a6b8d..0000000000 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureLifecycle/LegacyFeatureLifecycleTooltip.tsx +++ /dev/null @@ -1,526 +0,0 @@ -import { Box, styled, Typography } from '@mui/material'; -import { Badge } from 'component/common/Badge/Badge'; -import { HtmlTooltip } from 'component/common/HtmlTooltip/HtmlTooltip'; -import type * as React from 'react'; -import type { FC } from 'react'; -import CloudCircle from '@mui/icons-material/CloudCircle'; -import { ReactComponent as UsageRate } from 'assets/icons/usage-rate.svg'; -import { FeatureLifecycleStageIcon } from 'component/common/FeatureLifecycle/FeatureLifecycleStageIcon'; -import { TimeAgo } from 'component/common/TimeAgo/TimeAgo'; -import { StyledIconWrapper } from '../../FeatureEnvironmentSeen/FeatureEnvironmentSeen'; -import { useLastSeenColors } from '../../FeatureEnvironmentSeen/useLastSeenColors'; -import type { LifecycleStage } from './LifecycleStage'; -import PermissionButton from 'component/common/PermissionButton/PermissionButton'; -import { - DELETE_FEATURE, - UPDATE_FEATURE, -} from 'component/providers/AccessProvider/permissions'; -import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import { isSafeToArchive } from './isSafeToArchive'; -import { useLocationSettings } from 'hooks/useLocationSettings'; -import { formatDateYMDHMS } from 'utils/formatDate'; -import { formatDistanceToNow, parseISO } from 'date-fns'; - -const TimeLabel = styled('span')(({ theme }) => ({ - color: theme.palette.text.secondary, -})); - -const InfoText = styled('p')(({ theme }) => ({ - paddingBottom: theme.spacing(1), -})); - -const MainLifecycleRow = styled(Box)(({ theme }) => ({ - display: 'flex', - justifyContent: 'space-between', - marginBottom: theme.spacing(2), -})); - -const TimeLifecycleRow = styled(Box)(({ theme }) => ({ - display: 'flex', - justifyContent: 'space-between', - marginBottom: theme.spacing(1.5), -})); - -const IconsRow = styled(Box)(({ theme }) => ({ - display: 'flex', - alignItems: 'center', - marginTop: theme.spacing(4), - marginBottom: theme.spacing(6), -})); - -const Line = styled(Box)(({ theme }) => ({ - height: '1px', - background: theme.palette.divider, - flex: 1, -})); - -const StageBox = styled(Box, { - shouldForwardProp: (prop) => prop !== 'active', -})<{ - active?: boolean; -}>(({ theme, active }) => ({ - position: 'relative', - // speech bubble triangle for active stage - ...(active && { - '&:before': { - content: '""', - position: 'absolute', - display: 'block', - borderStyle: 'solid', - borderColor: `${theme.palette.primary.light} transparent`, - borderWidth: '0 6px 6px', - top: theme.spacing(3.25), - left: theme.spacing(1.75), - }, - }), - // stage name text - '&:after': { - content: 'attr(data-after-content)', - display: 'block', - position: 'absolute', - top: theme.spacing(4), - left: theme.spacing(-1.25), - right: theme.spacing(-1.25), - textAlign: 'center', - whiteSpace: 'nowrap', - fontSize: theme.spacing(1.25), - padding: theme.spacing(0.25, 0), - color: theme.palette.text.secondary, - // active wrapper for stage name text - ...(active && { - backgroundColor: theme.palette.primary.light, - color: theme.palette.primary.contrastText, - fontWeight: theme.typography.fontWeightBold, - borderRadius: theme.spacing(0.5), - }), - }, -})); - -const ColorFill = styled(Box)(({ theme }) => ({ - backgroundColor: theme.palette.primary.light, - color: theme.palette.primary.contrastText, - borderRadius: `0 0 ${theme.shape.borderRadiusMedium}px ${theme.shape.borderRadiusMedium}px`, // has to match the parent tooltip container - margin: theme.spacing(-1, -1.5), // has to match the parent tooltip container - padding: theme.spacing(2, 3), -})); - -const LastSeenIcon: FC<{ - lastSeen: string; -}> = ({ lastSeen }) => { - const getColor = useLastSeenColors(); - const { text, background } = getColor(lastSeen); - - return ( - - - - ); -}; - -const InitialStageDescription: FC = () => { - return ( - <> - - This feature flag is currently in the initial phase of its - lifecycle. - - - This means that the flag has been created, but it has not yet - been seen in any environment. - - - Once we detect metrics for a non-production environment it will - move into pre-live. - - - ); -}; - -const StageTimeline: FC<{ - stage: LifecycleStage; -}> = ({ stage }) => { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); -}; - -const EnvironmentLine = styled(Box)(({ theme }) => ({ - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - marginTop: theme.spacing(1), - marginBottom: theme.spacing(2), -})); - -const CenteredBox = styled(Box)(({ theme }) => ({ - display: 'flex', - alignItems: 'center', - gap: theme.spacing(1), -})); - -const Environments: FC<{ - environments: Array<{ - name: string; - lastSeenAt: string; - }>; -}> = ({ environments }) => { - return ( - - {environments.map((environment) => { - return ( - - - - {environment.name} - - - - - - - ); - })} - - ); -}; - -const PreLiveStageDescription: FC<{ children?: React.ReactNode }> = ({ - children, -}) => { - return ( - <> - - We've seen the feature flag in the following environments: - - - {children} - - ); -}; - -const ArchivedStageDescription = () => { - return ( - - Your feature has been archived, it is now safe to delete it. - - ); -}; - -const BoldTitle = styled(Typography)(({ theme }) => ({ - marginTop: theme.spacing(1), - marginBottom: theme.spacing(1), - fontSize: theme.typography.body2.fontSize, - fontWeight: theme.typography.fontWeightBold, -})); - -const LiveStageDescription: FC<{ - onComplete: () => void; - loading: boolean; - children?: React.ReactNode; - project: string; -}> = ({ children, onComplete, loading, project }) => { - return ( - <> - Is this feature complete? - - Marking the feature flag as complete does not affect any - configuration; however, it moves the feature flag to its next - lifecycle stage and indicates that you have learned what you - needed in order to progress with the feature. It serves as a - reminder to start cleaning up the feature flag and removing it - from the code. - - - Mark completed - - - Users have been exposed to this feature in the following - production environments: - - - {children} - - ); -}; - -const SafeToArchive: FC<{ - onArchive: () => void; - onUncomplete: () => void; - loading: boolean; - project: string; -}> = ({ onArchive, onUncomplete, loading, project }) => { - return ( - <> - Safe to archive - - We haven’t seen this feature flag in any environment for at - least two days. It’s likely that it’s safe to archive this flag. - - - - Revert to live - - - Archive feature - - - - ); -}; - -const ActivelyUsed: FC<{ - onUncomplete: () => void; - loading: boolean; - children?: React.ReactNode; -}> = ({ children, onUncomplete, loading }) => ( - <> - - This feature has been successfully completed, but we are still - seeing usage. Clean up the feature flag from your code before - archiving it: - - {children} - - If you think this feature was completed too early you can revert to - the live stage: - - - Revert to live - - -); - -const CompletedStageDescription: FC<{ - onArchive: () => void; - onUncomplete: () => void; - loading: boolean; - environments: Array<{ - name: string; - lastSeenAt: string; - }>; - children?: React.ReactNode; - project: string; -}> = ({ - children, - environments, - onArchive, - onUncomplete, - loading, - project, -}) => { - return ( - - } - elseShow={ - - {children} - - } - /> - ); -}; - -const FormatTime: FC<{ - time: string; -}> = ({ time }) => { - const { locationSettings } = useLocationSettings(); - - return {formatDateYMDHMS(time, locationSettings.locale)}; -}; - -const FormatElapsedTime: FC<{ - time: string; -}> = ({ time }) => { - const pastTime = parseISO(time); - const elapsedTime = formatDistanceToNow(pastTime, { addSuffix: false }); - return {elapsedTime}; -}; - -export const FeatureLifecycleTooltip: FC<{ - children: React.ReactElement; - stage: LifecycleStage; - project: string; - onArchive: () => void; - onComplete: () => void; - onUncomplete: () => void; - loading: boolean; -}> = ({ - children, - stage, - project, - onArchive, - onComplete, - onUncomplete, - loading, -}) => ( - - ({ padding: theme.spacing(2) })}> - - Lifecycle - - - {stage.name} - - - - - - Stage entered at - - - - - Time spent in stage - - - - - - {stage.name === 'initial' && } - {stage.name === 'pre-live' && ( - - - - )} - {stage.name === 'live' && ( - - - - )} - {stage.name === 'completed' && ( - - - - )} - {stage.name === 'archived' && } - - - } - > - {children} - -); diff --git a/frontend/src/component/layout/MainLayout/NavigationSidebar/NewInUnleash/NewInUnleash.tsx b/frontend/src/component/layout/MainLayout/NavigationSidebar/NewInUnleash/NewInUnleash.tsx index f8ce67e0da..6c7dabc292 100644 --- a/frontend/src/component/layout/MainLayout/NavigationSidebar/NewInUnleash/NewInUnleash.tsx +++ b/frontend/src/component/layout/MainLayout/NavigationSidebar/NewInUnleash/NewInUnleash.tsx @@ -19,10 +19,8 @@ import { import { usePlausibleTracker } from 'hooks/usePlausibleTracker'; import { ReactComponent as SignalsPreview } from 'assets/img/signals.svg'; import LifecycleStagesImage from 'assets/img/lifecycle-stages.png'; -import LinearScaleIcon from '@mui/icons-material/LinearScale'; import MonitorHeartIcon from '@mui/icons-material/MonitorHeartOutlined'; import { useNavigate } from 'react-router-dom'; -import { useHighlightContext } from 'component/common/Highlight/HighlightContext'; import { formatAssetPath } from 'utils/formatPath'; const StyledNewInUnleash = styled('div')(({ theme }) => ({ @@ -75,10 +73,6 @@ const StyledSignalsIcon = styled(Signals)(({ theme }) => ({ color: theme.palette.primary.main, })); -const StyledLinearScaleIcon = styled(LinearScaleIcon)(({ theme }) => ({ - color: theme.palette.primary.main, -})); - const StyledImg = styled('img')(() => ({ maxWidth: '100%', })); @@ -93,7 +87,6 @@ export const NewInUnleash = ({ onMiniModeClick, }: INewInUnleashProps) => { const navigate = useNavigate(); - const { highlight } = useHighlightContext(); const { trackEvent } = usePlausibleTracker(); const [seenItems, setSeenItems] = useLocalStorageState( 'new-in-unleash-seen:v1', @@ -101,7 +94,6 @@ export const NewInUnleash = ({ ); const { isEnterprise } = useUiConfig(); const signalsEnabled = useUiFlag('signals'); - const improvedLifecycleEnabled = useUiFlag('lifecycleImprovements'); const items: NewInUnleashItemDetails[] = [ { @@ -116,7 +108,7 @@ export const NewInUnleash = ({ ), docsLink: 'https://docs.getunleash.io/reference/feature-toggles#feature-flag-lifecycle', - show: improvedLifecycleEnabled, + show: true, longDescription: (

We have updated the names, icons, and colors for the diff --git a/frontend/src/component/project/Project/ProjectStatus/ProjectLifecycleSummary.tsx b/frontend/src/component/project/Project/ProjectStatus/ProjectLifecycleSummary.tsx index da3c0b75b3..eb4cdcfb74 100644 --- a/frontend/src/component/project/Project/ProjectStatus/ProjectLifecycleSummary.tsx +++ b/frontend/src/component/project/Project/ProjectStatus/ProjectLifecycleSummary.tsx @@ -9,7 +9,6 @@ import type { ProjectStatusSchemaLifecycleSummary } from 'openapi'; import { HtmlTooltip } from 'component/common/HtmlTooltip/HtmlTooltip'; import { lifecycleMessages } from './LifecycleMessages'; import InfoIcon from '@mui/icons-material/Info'; -import { useUiFlag } from 'hooks/useUiFlag'; import { getFeatureLifecycleName } from 'component/common/FeatureLifecycle/getFeatureLifecycleName'; const LifecycleBoxContent = styled('div')(({ theme }) => ({ @@ -145,8 +144,6 @@ const BigNumber: FC<{ value?: number }> = ({ value }) => { export const ProjectLifecycleSummary = () => { const projectId = useRequiredPathParam('projectId'); const { data, loading } = useProjectStatus(projectId); - const isLifecycleImprovementsEnabled = useUiFlag('lifecycleImprovements'); - const loadingRef = useLoading( loading, '[data-loading-project-lifecycle-summary=true]', @@ -155,18 +152,10 @@ export const ProjectLifecycleSummary = () => { const flagWord = (stage: keyof ProjectStatusSchemaLifecycleSummary) => { const hasOneFlag = data?.lifecycleSummary[stage].currentFlags === 1; - if (hasOneFlag) { - return isLifecycleImprovementsEnabled ? 'Flag' : 'flag'; - } - - return isLifecycleImprovementsEnabled ? 'Flags' : 'flags'; + return hasOneFlag ? 'Flag' : 'Flags'; }; const stageName = (stage: keyof ProjectStatusSchemaLifecycleSummary) => { - if (!isLifecycleImprovementsEnabled) { - return `${flagWord('initial')} in ${stage}`; - } - const lifecycleStageName = stage === 'preLive' ? 'pre-live' : stage; return ( diff --git a/frontend/src/interfaces/uiConfig.ts b/frontend/src/interfaces/uiConfig.ts index 2f860737a7..306025aebc 100644 --- a/frontend/src/interfaces/uiConfig.ts +++ b/frontend/src/interfaces/uiConfig.ts @@ -92,7 +92,6 @@ export type UiFlags = { flagOverviewRedesign?: boolean; granularAdminPermissions?: boolean; sortProjectRoles?: boolean; - lifecycleImprovements?: boolean; frontendHeaderRedesign?: boolean; dataUsageMultiMonthView?: boolean; uiGlobalFontSize?: Variant; diff --git a/src/lib/types/experimental.ts b/src/lib/types/experimental.ts index ff4f83abf0..1e49f65f30 100644 --- a/src/lib/types/experimental.ts +++ b/src/lib/types/experimental.ts @@ -62,7 +62,6 @@ export type IFlagKey = | 'deltaApi' | 'uniqueSdkTracking' | 'sortProjectRoles' - | 'lifecycleImprovements' | 'frontendHeaderRedesign' | 'dataUsageMultiMonthView' | 'uiGlobalFontSize'; @@ -298,10 +297,6 @@ const flags: IFlags = { process.env.UNLEASH_EXPERIMENTAL_SORT_PROJECT_ROLES, false, ), - lifecycleImprovements: parseEnvVarBoolean( - process.env.UNLEASH_EXPERIMENTAL_LIFECYCLE_IMPROVEMENTS, - false, - ), frontendHeaderRedesign: parseEnvVarBoolean( process.env.UNLEASH_EXPERIMENTAL_FRONTEND_HEADER_REDESIGN, false, diff --git a/src/server-dev.ts b/src/server-dev.ts index 7fe818dc5e..f9c178c129 100644 --- a/src/server-dev.ts +++ b/src/server-dev.ts @@ -57,7 +57,6 @@ process.nextTick(async () => { sortProjectRoles: true, deltaApi: true, uniqueSdkTracking: true, - lifecycleImprovements: true, frontendHeaderRedesign: true, dataUsageMultiMonthView: true, },