diff --git a/frontend/cypress/integration/demo/demo.spec.ts b/frontend/cypress/integration/demo/demo.spec.ts index 2a3b5e0fe6..f40fb4e2e3 100644 --- a/frontend/cypress/integration/demo/demo.spec.ts +++ b/frontend/cypress/integration/demo/demo.spec.ts @@ -40,7 +40,6 @@ describe('demo', () => { res.body.flags = { ...res.body.flags, demo: true, - flagOverviewRedesign: true, }; } }); diff --git a/frontend/cypress/integration/feature/feature.spec.ts b/frontend/cypress/integration/feature/feature.spec.ts index 7b12459141..dc6c818de9 100644 --- a/frontend/cypress/integration/feature/feature.spec.ts +++ b/frontend/cypress/integration/feature/feature.spec.ts @@ -44,7 +44,6 @@ describe('feature', () => { if (res.body) { res.body.flags = { ...res.body.flags, - flagOverviewRedesign: true, }; } }); diff --git a/frontend/src/component/changeRequest/ChangeRequest.test.tsx b/frontend/src/component/changeRequest/ChangeRequest.test.tsx index 5d63df8b3d..3d5134fc0b 100644 --- a/frontend/src/component/changeRequest/ChangeRequest.test.tsx +++ b/frontend/src/component/changeRequest/ChangeRequest.test.tsx @@ -1,6 +1,6 @@ import type React from 'react'; import type { FC } from 'react'; -import { render, screen, within, fireEvent } from '@testing-library/react'; +import { render, screen, fireEvent } from '@testing-library/react'; import { MemoryRouter, Routes, Route } from 'react-router-dom'; import { ThemeProvider } from 'themes/ThemeProvider'; import { MainLayout } from 'component/layout/MainLayout/MainLayout'; @@ -271,13 +271,6 @@ const verifyBannerForPendingChangeRequest = async () => { return screen.findByText('Change request mode', {}, { timeout: 5000 }); }; -const changeFlag = async (environment: string) => { - const featureFlagStatusBox = screen.getByTestId('feature-flag-status'); - await within(featureFlagStatusBox).findByText(environment); - const flag = screen.getAllByRole('checkbox')[1]; - fireEvent.click(flag); -}; - const verifyChangeRequestDialog = async (bannerMainText: string) => { await screen.findByText('Your suggestion:'); const message = screen.getByTestId('update-enabled-message').textContent; @@ -298,7 +291,8 @@ test('add flag change to pending change request', async () => { await verifyBannerForPendingChangeRequest(); - await changeFlag('production'); + const flag = screen.getByLabelText('production'); + fireEvent.click(flag); await verifyChangeRequestDialog('Enable feature flag test in production'); }, 10000); diff --git a/frontend/src/component/changeRequest/ChangeRequest/ChangeRequest.test.tsx b/frontend/src/component/changeRequest/ChangeRequest/ChangeRequest.test.tsx index 86b041a61d..0b50bb2c6a 100644 --- a/frontend/src/component/changeRequest/ChangeRequest/ChangeRequest.test.tsx +++ b/frontend/src/component/changeRequest/ChangeRequest/ChangeRequest.test.tsx @@ -1,4 +1,4 @@ -import { screen } from '@testing-library/react'; +import { screen, waitFor } from '@testing-library/react'; import { Route, Routes } from 'react-router-dom'; @@ -226,7 +226,7 @@ test('Display default disable feature', async () => { expect(screen.getByText('Feature status will change')).toBeInTheDocument(); }); -test('Displays feature strategy variants table when addStrategy action with variants', async () => { +test('Displays strategy variant table when addStrategy action with variants', async () => { render( { @@ -297,7 +297,10 @@ test('Displays feature strategy variants table when there is a change in the var route: '/projects/default/change-requests/27', }, ); - await screen.findByText('Updating strategy variants to:'); + + waitFor(async () => { + await screen.findByText('Updating strategy variants to:'); + }); }); test('Displays feature strategy variants table when existing strategy does not have variants and change does', async () => { @@ -325,5 +328,5 @@ test('Displays feature strategy variants table when existing strategy does not h route: '/projects/default/change-requests/27', }, ); - await screen.findByText('Updating strategy variants to:'); + await screen.findByText('Adding strategy variants:'); }); diff --git a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/FeatureChange.tsx b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/FeatureChange.tsx index 9f4f070652..00ac7e3b3d 100644 --- a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/FeatureChange.tsx +++ b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/FeatureChange.tsx @@ -31,6 +31,7 @@ const StyledSingleChangeBox = styled(Box, { $isAfterWarning, $isLast, }) => ({ + overflow: 'hidden', borderLeft: '1px solid', borderRight: '1px solid', borderTop: '1px solid', diff --git a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/ReleasePlanChange.tsx b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/ReleasePlanChange.tsx index 2ef8eda1e2..7511884a7d 100644 --- a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/ReleasePlanChange.tsx +++ b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/ReleasePlanChange.tsx @@ -12,7 +12,7 @@ import { useReleasePlans } from 'hooks/api/getters/useReleasePlans/useReleasePla import { TooltipLink } from 'component/common/TooltipLink/TooltipLink'; import EventDiff from 'component/events/EventDiff/EventDiff'; import { ReleasePlan } from 'component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlan'; -import { ReleasePlanMilestone } from 'component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/LegacyReleasePlanMilestone'; +import { ReleasePlanMilestone } from 'component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/ReleasePlanMilestone'; import type { IReleasePlan } from 'interfaces/releasePlans'; export const ChangeItemWrapper = styled(Box)({ diff --git a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/StrategyChange.test.tsx b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/StrategyChange.test.tsx index a0116e4dc1..8618340525 100644 --- a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/StrategyChange.test.tsx +++ b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/StrategyChange.test.tsx @@ -119,7 +119,8 @@ test('Editing strategy before change request is applied diffs against current st await screen.findByText('+ variants.0.name: "change_variant"'); await screen.findByText('Updating strategy variants to:'); - await screen.findByText('change_variant'); + const variants = await screen.findAllByText('change_variant'); + expect(variants).toHaveLength(2); }); test('Editing strategy after change request is applied diffs against the snapshot', async () => { @@ -189,7 +190,8 @@ test('Editing strategy after change request is applied diffs against the snapsho await screen.findByText('+ variants.0.name: "change_variant"'); await screen.findByText('Updating strategy variants to:'); - await screen.findByText('change_variant'); + const variants = await screen.findAllByText('change_variant'); + expect(variants).toHaveLength(2); }); test('Deleting strategy before change request is applied diffs against current strategy', async () => { @@ -226,7 +228,6 @@ test('Deleting strategy before change request is applied diffs against current s await userEvent.hover(viewDiff); await screen.findByText('- constraints (deleted)'); - await screen.findByText('Deleting strategy variants:'); await screen.findByText('current_variant'); }); @@ -281,7 +282,6 @@ test('Deleting strategy after change request is applied diffs against the snapsh await userEvent.hover(viewDiff); await screen.findByText('- constraints (deleted)'); - await screen.findByText('Deleting strategy variants:'); await screen.findByText('snapshot_variant'); }); @@ -329,9 +329,8 @@ test('Adding strategy always diffs against undefined strategy', async () => { const viewDiff = await screen.findByText('View Diff'); await userEvent.hover(viewDiff); await screen.findByText(`+ name: "flexibleRollout"`); - - await screen.findByText('Setting strategy variants to:'); - await screen.findByText('change_variant'); + const variants = await screen.findAllByText('change_variant'); + expect(variants).toHaveLength(2); }); test('Segments order does not matter for diff calculation', async () => { diff --git a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/StrategyChange.tsx b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/StrategyChange.tsx index c2e374c9ce..412f23e8c3 100644 --- a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/StrategyChange.tsx +++ b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/StrategyChange.tsx @@ -21,7 +21,6 @@ import { flexRow } from 'themes/themeStyles'; import { EnvironmentVariantsTable } from 'component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsCard/EnvironmentVariantsTable/EnvironmentVariantsTable'; import { ChangeOverwriteWarning } from './ChangeOverwriteWarning/ChangeOverwriteWarning'; import type { IFeatureStrategy } from 'interfaces/strategy'; -import { useUiFlag } from 'hooks/useUiFlag'; export const ChangeItemWrapper = styled(Box)({ display: 'flex', @@ -163,21 +162,6 @@ const DeleteStrategy: FC<{ {referenceStrategy && ( )} - - - Deleting strategy variants: - - - - ) - } - /> ); }; @@ -251,19 +235,26 @@ const UpdateStrategy: FC<{ } /> - + {hasVariantDiff ? ( + + {change.payload.variants?.length ? ( + <> + + {currentStrategy?.variants?.length + ? 'Updating strategy variants to:' + : 'Adding strategy variants:'} + + + + ) : ( - Updating strategy variants to: + Removed all strategy variants. - - - } - /> + )} + + ) : null} ); }; @@ -271,55 +262,45 @@ const UpdateStrategy: FC<{ const AddStrategy: FC<{ change: IChangeRequestAddStrategy; actions?: ReactNode; -}> = ({ change, actions }) => { - const showOldStrategyVariants = !useUiFlag('flagOverviewRedesign'); - return ( - <> - - - - + Adding strategy: - - - - -
- -
-
-
{actions}
-
- - {showOldStrategyVariants && - change.payload.variants && - change.payload.variants.length > 0 && ( - - - Setting strategy variants to: - - - - )} - - ); -}; +}> = ({ change, actions }) => ( + <> + + + + + Adding strategy: + + + + +
+ +
+
+
{actions}
+
+ + {change.payload.variants?.length ? ( + + Adding strategy variants: + + + ) : null} + +); export const StrategyChange: FC<{ actions?: ReactNode; diff --git a/frontend/src/component/changeRequest/ChangeRequestPermissions.test.tsx b/frontend/src/component/changeRequest/ChangeRequestPermissions.test.tsx index 7aa2c57288..eace10ab19 100644 --- a/frontend/src/component/changeRequest/ChangeRequestPermissions.test.tsx +++ b/frontend/src/component/changeRequest/ChangeRequestPermissions.test.tsx @@ -287,7 +287,10 @@ const copyButtonsActiveInOtherEnv = async () => { const openEnvironments = async (envNames: string[]) => { for (const env of envNames) { - (await screen.findAllByText(env))[1].click(); + const environmentHeader = await screen.findByRole('heading', { + name: env, + }); + fireEvent.click(environmentHeader); } }; @@ -313,7 +316,6 @@ test('open mode + non-project member can perform basic change request actions', , ); - await openEnvironments(['development', 'production', 'custom']); await strategiesAreDisplayed('UserIDs', 'Standard'); diff --git a/frontend/src/component/changeRequest/ChangeRequestSidebar/EnvironmentChangeRequest/EnvironmentChangeRequest.tsx b/frontend/src/component/changeRequest/ChangeRequestSidebar/EnvironmentChangeRequest/EnvironmentChangeRequest.tsx index 4da2f41178..13054488c1 100644 --- a/frontend/src/component/changeRequest/ChangeRequestSidebar/EnvironmentChangeRequest/EnvironmentChangeRequest.tsx +++ b/frontend/src/component/changeRequest/ChangeRequestSidebar/EnvironmentChangeRequest/EnvironmentChangeRequest.tsx @@ -18,14 +18,12 @@ import { StyledFlexAlignCenterBox, StyledSuccessIcon, } from '../ChangeRequestSidebar'; -import CloudCircle from '@mui/icons-material/CloudCircle'; import { AddCommentField } from '../../ChangeRequestOverview/ChangeRequestComments/AddCommentField'; import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser'; import Input from 'component/common/Input/Input'; import { ChangeRequestTitle } from './ChangeRequestTitle'; import { UpdateCount } from 'component/changeRequest/UpdateCount'; import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi'; -import { useUiFlag } from 'hooks/useUiFlag'; const SubmitChangeRequestButton: FC<{ onClick: () => void; @@ -70,7 +68,6 @@ export const EnvironmentChangeRequest: FC<{ children?: React.ReactNode; }> = ({ environmentChangeRequest, onClose, onReview, onDiscard, children }) => { const theme = useTheme(); - const showCloudIcon = !useUiFlag('flagOverviewRedesign'); const [commentText, setCommentText] = useState(''); const { user } = useAuthUser(); const [title, setTitle] = useState(environmentChangeRequest.title); @@ -98,14 +95,6 @@ export const EnvironmentChangeRequest: FC<{ alignItems: 'center', }} > - {showCloudIcon ? ( - ({ - color: theme.palette.primary.light, - mr: 0.5, - })} - /> - ) : null} {environmentChangeRequest.environment} diff --git a/frontend/src/component/common/NewConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/ConstraintAccordionViewHeader.tsx b/frontend/src/component/common/NewConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/ConstraintAccordionViewHeader.tsx index b699a0ff32..e8f1f631b1 100644 --- a/frontend/src/component/common/NewConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/ConstraintAccordionViewHeader.tsx +++ b/frontend/src/component/common/NewConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/ConstraintAccordionViewHeader.tsx @@ -1,10 +1,7 @@ -import { ConstraintIcon } from 'component/common/LegacyConstraintAccordion/ConstraintIcon'; import type { IConstraint } from 'interfaces/strategy'; import { ConstraintAccordionViewHeaderInfo } from './ConstraintAccordionViewHeaderInfo'; -import { ConstraintAccordionViewHeaderInfo as LegacyConstraintAccordionViewHeaderInfo } from './LegacyConstraintAccordionViewHeaderInfo'; import { styled } from '@mui/system'; import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashContext'; -import { useUiFlag } from 'hooks/useUiFlag'; import { ConstraintAccordionViewActions } from '../../ConstraintAccordionViewActions/ConstraintAccordionViewActions'; import { ConstraintAccordionEditActions } from '../../ConstraintAccordionEditActions/ConstraintAccordionEditActions'; @@ -13,9 +10,15 @@ interface IConstraintAccordionViewHeaderProps { onDelete?: () => void; onEdit?: () => void; onUse?: () => void; + /** + * @deprecated + */ singleValue: boolean; expanded: boolean; allowExpand: (shouldExpand: boolean) => void; + /** + * @deprecated + */ compact?: boolean; disabled?: boolean; } @@ -43,7 +46,6 @@ export const ConstraintAccordionViewHeader = ({ disabled, }: IConstraintAccordionViewHeaderProps) => { const { context } = useUnleashContext(); - const flagOverviewRedesign = useUiFlag('flagOverviewRedesign'); const { contextName } = constraint; const disableEdit = !context @@ -52,25 +54,12 @@ export const ConstraintAccordionViewHeader = ({ return ( - {!flagOverviewRedesign ? ( - - ) : null} - {flagOverviewRedesign ? ( - - ) : ( - - )} + {onUse ? ( ) : ( diff --git a/frontend/src/component/common/NewConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/LegacyConstraintAccordionViewHeaderInfo.tsx b/frontend/src/component/common/NewConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/LegacyConstraintAccordionViewHeaderInfo.tsx deleted file mode 100644 index f7a4f09912..0000000000 --- a/frontend/src/component/common/NewConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/LegacyConstraintAccordionViewHeaderInfo.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import { styled, Tooltip } from '@mui/material'; -import { ConstraintViewHeaderOperator } from './ConstraintViewHeaderOperator'; -import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import { ConstraintAccordionViewHeaderSingleValue } from './ConstraintAccordionViewHeaderSingleValue'; -import { ConstraintAccordionViewHeaderMultipleValues } from './ConstraintAccordionViewHeaderMultipleValues'; -import type { IConstraint } from 'interfaces/strategy'; - -const StyledHeaderText = styled('span')(({ theme }) => ({ - display: '-webkit-box', - WebkitLineClamp: 3, - WebkitBoxOrient: 'vertical', - overflow: 'hidden', - maxWidth: '100px', - minWidth: '100px', - marginRight: '10px', - marginTop: 'auto', - marginBottom: 'auto', - wordBreak: 'break-word', - fontSize: theme.fontSizes.smallBody, - [theme.breakpoints.down(710)]: { - textAlign: 'center', - padding: theme.spacing(1, 0), - marginRight: 'inherit', - maxWidth: 'inherit', - }, -})); - -const StyledHeaderWrapper = styled('div')(({ theme }) => ({ - display: 'flex', - width: '100%', - justifyContent: 'space-between', - borderRadius: theme.spacing(1), -})); - -const StyledHeaderMetaInfo = styled('div')(({ theme }) => ({ - display: 'flex', - alignItems: 'stretch', - marginLeft: theme.spacing(1), - [theme.breakpoints.down('sm')]: { - marginLeft: 0, - flexDirection: 'column', - alignItems: 'center', - width: '100%', - }, -})); - -interface ConstraintAccordionViewHeaderMetaInfoProps { - constraint: IConstraint; - singleValue: boolean; - expanded: boolean; - allowExpand: (shouldExpand: boolean) => void; - disabled?: boolean; - maxLength?: number; -} - -export const ConstraintAccordionViewHeaderInfo = ({ - constraint, - singleValue, - allowExpand, - expanded, - disabled = false, - maxLength = 112, //The max number of characters in the values text for NOT allowing expansion -}: ConstraintAccordionViewHeaderMetaInfoProps) => { - return ( - - - - ({ - color: disabled - ? theme.palette.text.secondary - : 'inherit', - })} - > - {constraint.contextName} - - - - - } - elseShow={ - - } - /> - - - ); -}; diff --git a/frontend/src/component/common/NewConstraintAccordion/ConstraintIcon.tsx b/frontend/src/component/common/NewConstraintAccordion/ConstraintIcon.tsx deleted file mode 100644 index 3e1e08d747..0000000000 --- a/frontend/src/component/common/NewConstraintAccordion/ConstraintIcon.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import type { VFC } from 'react'; -import { Box } from '@mui/material'; -import TrackChanges from '@mui/icons-material/TrackChanges'; - -interface IConstraintIconProps { - compact?: boolean; - disabled?: boolean; -} - -/** - * @deprecated remove with `flagOverviewRedesign` - */ -export const ConstraintIcon: VFC = ({ - compact, - disabled, -}) => ( - ({ - backgroundColor: disabled - ? theme.palette.neutral.border - : 'primary.light', - p: compact ? '1px' : '2px', - borderRadius: '50%', - width: compact ? '18px' : '24px', - height: compact ? '18px' : '24px', - marginRight: '13px', - })} - > - ({ - fill: theme.palette.common.white, - display: 'block', - width: compact ? theme.spacing(2) : theme.spacing(2.5), - height: compact ? theme.spacing(2) : theme.spacing(2.5), - })} - /> - -); diff --git a/frontend/src/component/common/NewConstraintAccordion/NewConstraintAccordionList/NewConstraintAccordionList.tsx b/frontend/src/component/common/NewConstraintAccordion/NewConstraintAccordionList/NewConstraintAccordionList.tsx index 4f9f25261a..6357ed6a33 100644 --- a/frontend/src/component/common/NewConstraintAccordion/NewConstraintAccordionList/NewConstraintAccordionList.tsx +++ b/frontend/src/component/common/NewConstraintAccordion/NewConstraintAccordionList/NewConstraintAccordionList.tsx @@ -1,5 +1,5 @@ import type React from 'react'; -import { forwardRef, Fragment, useImperativeHandle } from 'react'; +import { forwardRef, useImperativeHandle } from 'react'; import { styled } from '@mui/material'; import type { IConstraint } from 'interfaces/strategy'; import produce from 'immer'; @@ -9,8 +9,6 @@ import { constraintId, createEmptyConstraint, } from 'component/common/LegacyConstraintAccordion/ConstraintAccordionList/createEmptyConstraint'; -import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import { StrategySeparator } from 'component/common/StrategySeparator/LegacyStrategySeparator'; import { NewConstraintAccordion } from 'component/common/NewConstraintAccordion/NewConstraintAccordion'; import { ConstraintsList } from 'component/common/ConstraintsList/ConstraintsList'; import { useUiFlag } from 'hooks/useUiFlag'; @@ -87,7 +85,6 @@ export const NewConstraintAccordionList = forwardRef< IConstraintList >(({ constraints, setConstraints, state }, ref) => { const { context } = useUnleashContext(); - const flagOverviewRedesign = useUiFlag('flagOverviewRedesign'); const addEditStrategy = useUiFlag('addEditStrategy'); const onEdit = @@ -145,77 +142,43 @@ export const NewConstraintAccordionList = forwardRef< return null; } - if (flagOverviewRedesign) { - return ( - - - {constraints.map((constraint, index) => - addEditStrategy ? ( - state.get(constraint)?.editing ? ( - - ) : ( - - ) - ) : ( - + + {constraints.map((constraint, index) => + addEditStrategy ? ( + state.get(constraint)?.editing ? ( + - ), - )} - - - ); - } - - return ( - - {constraints.map((constraint, index) => { - const id = constraint[constraintId]; - - return ( - - 0} - show={} - /> - + ) : ( + + ) + ) : ( - - ); - })} + ), + )} + ); }); diff --git a/frontend/src/component/common/StrategyItemContainer/LegacyStrategyItemContainer.tsx b/frontend/src/component/common/StrategyItemContainer/LegacyStrategyItemContainer.tsx deleted file mode 100644 index 731b8b01d5..0000000000 --- a/frontend/src/component/common/StrategyItemContainer/LegacyStrategyItemContainer.tsx +++ /dev/null @@ -1,205 +0,0 @@ -// deprecated; remove with the `flagOverviewRedesign` flag -import type React from 'react'; -import type { DragEventHandler, FC, ReactNode } from 'react'; -import DragIndicator from '@mui/icons-material/DragIndicator'; -import { Box, IconButton, styled } from '@mui/material'; -import type { IFeatureStrategy } from 'interfaces/strategy'; -import { - formatStrategyName, - getFeatureStrategyIcon, -} from 'utils/strategyNames'; -import StringTruncator from 'component/common/StringTruncator/StringTruncator'; -import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import type { PlaygroundStrategySchema } from 'openapi'; -import { Badge } from '../Badge/Badge'; -import { Link } from 'react-router-dom'; - -interface IStrategyItemContainerProps { - strategy: IFeatureStrategy | PlaygroundStrategySchema; - onDragStart?: DragEventHandler; - onDragEnd?: DragEventHandler; - actions?: ReactNode; - orderNumber?: number; - className?: string; - style?: React.CSSProperties; - description?: string; - children?: React.ReactNode; -} - -const DragIcon = styled(IconButton)({ - padding: 0, - cursor: 'inherit', - transition: 'color 0.2s ease-in-out', -}); - -const StyledIndexLabel = styled('div')(({ theme }) => ({ - fontSize: theme.typography.fontSize, - color: theme.palette.text.secondary, - position: 'absolute', - display: 'none', - right: 'calc(100% + 6px)', - top: theme.spacing(2.5), - [theme.breakpoints.up('md')]: { - display: 'block', - }, -})); -const StyledDescription = styled('div')(({ theme }) => ({ - fontSize: theme.typography.fontSize, - fontWeight: 'normal', - color: theme.palette.text.secondary, - display: 'none', - top: theme.spacing(2.5), - [theme.breakpoints.up('md')]: { - display: 'block', - }, -})); -const StyledCustomTitle = styled('div')(({ theme }) => ({ - fontWeight: 'normal', - display: 'none', - [theme.breakpoints.up('md')]: { - display: 'block', - }, -})); -const StyledHeaderContainer = styled('div')({ - flexDirection: 'column', - justifyContent: 'center', - verticalAlign: 'middle', -}); - -const StyledContainer = styled(Box, { - shouldForwardProp: (prop) => prop !== 'disabled', -})<{ disabled?: boolean }>(({ theme, disabled }) => ({ - borderRadius: theme.shape.borderRadiusMedium, - border: `1px solid ${theme.palette.divider}`, - '& + &': { - marginTop: theme.spacing(2), - }, - background: disabled - ? theme.palette.envAccordion.disabled - : theme.palette.background.paper, -})); - -const StyledHeader = styled('div', { - shouldForwardProp: (prop) => prop !== 'draggable' && prop !== 'disabled', -})<{ draggable: boolean; disabled: boolean }>( - ({ theme, draggable, disabled }) => ({ - padding: theme.spacing(0.5, 2), - display: 'flex', - gap: theme.spacing(1), - alignItems: 'center', - borderBottom: `1px solid ${theme.palette.divider}`, - fontWeight: theme.typography.fontWeightMedium, - paddingLeft: draggable ? theme.spacing(1) : theme.spacing(2), - color: disabled - ? theme.palette.text.secondary - : theme.palette.text.primary, - }), -); - -export const StrategyItemContainer: FC = ({ - strategy, - onDragStart, - onDragEnd, - actions, - children, - orderNumber, - style = {}, - description, -}) => { - const Icon = getFeatureStrategyIcon(strategy.name); - - const StrategyHeaderLink: React.FC<{ children?: React.ReactNode }> = - 'links' in strategy - ? ({ children }) => {children} - : ({ children }) => <> {children} ; - - return ( - - {orderNumber}} - /> - - - ( - - - - )} - /> - theme.palette.action.disabled, - }} - /> - - - - - {formatStrategyName( - String(strategy.title), - )} - - } - /> - - - {description} - - } - /> - - - ( - <> - Disabled - - )} - /> - theme.spacing(6), - alignItems: 'center', - }} - > - {actions} - - - {children} - - - ); -}; diff --git a/frontend/src/component/common/StrategyItemContainer/StrategyItemContainer.test.tsx b/frontend/src/component/common/StrategyItemContainer/StrategyItemContainer.test.tsx index b1c0c57c47..82cdec34ad 100644 --- a/frontend/src/component/common/StrategyItemContainer/StrategyItemContainer.test.tsx +++ b/frontend/src/component/common/StrategyItemContainer/StrategyItemContainer.test.tsx @@ -1,31 +1,8 @@ import { screen } from '@testing-library/react'; import { render } from 'utils/testRenderer'; -import { StrategyItemContainer as LegacyStrategyItemContainer } from './LegacyStrategyItemContainer'; import type { IFeatureStrategy } from 'interfaces/strategy'; import { StrategyItemContainer } from './StrategyItemContainer'; -// todo: remove this test along with the flag flagOverviewRedesign -test('(deprecated) should render strategy name, custom title and description', async () => { - const strategy: IFeatureStrategy = { - id: 'irrelevant', - name: 'strategy name', - title: 'custom title', - constraints: [], - parameters: {}, - }; - - render( - , - ); - - expect(screen.getByText('strategy name')).toBeInTheDocument(); - expect(screen.getByText('description')).toBeInTheDocument(); - await screen.findByText('custom title'); // behind async flag -}); - test('should render strategy name, custom title and description', async () => { const strategy: IFeatureStrategy = { id: 'irrelevant', diff --git a/frontend/src/component/common/StrategySeparator/LegacyStrategySeparator.tsx b/frontend/src/component/common/StrategySeparator/LegacyStrategySeparator.tsx index 9a7502a3c9..5762526ff5 100644 --- a/frontend/src/component/common/StrategySeparator/LegacyStrategySeparator.tsx +++ b/frontend/src/component/common/StrategySeparator/LegacyStrategySeparator.tsx @@ -29,6 +29,9 @@ const StyledCenteredContent = styled(StyledContent)(({ theme }) => ({ padding: theme.spacing(0.75, 1.5), })); +/** + * @deprecated remove with 'flagOverviewRedesign' flag. This pollutes a lot of places in the codebase 😞 + */ export const StrategySeparator = ({ text }: IStrategySeparatorProps) => { const theme = useTheme(); diff --git a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyEmpty/CopyButton/CopyButton.tsx b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyEmpty/CopyButton/CopyButton.tsx deleted file mode 100644 index 4d849dca5f..0000000000 --- a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyEmpty/CopyButton/CopyButton.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { type MouseEvent, useContext, useState, type VFC } from 'react'; -import { - Button, - ListItemIcon, - ListItemText, - Menu, - MenuItem, - Tooltip, -} from '@mui/material'; -import Lock from '@mui/icons-material/Lock'; -import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; -import type { IFeatureEnvironment } from 'interfaces/featureToggle'; -import AccessContext from 'contexts/AccessContext'; -import { CREATE_FEATURE_STRATEGY } from 'component/providers/AccessProvider/permissions'; -import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; - -interface ICopyButtonProps { - environmentId: IFeatureEnvironment['name']; - environments: IFeatureEnvironment['name'][]; - onClick: (environmentId: string) => void; -} - -export const CopyButton: VFC = ({ - environmentId, - environments, - onClick, -}) => { - const projectId = useRequiredPathParam('projectId'); - const [anchorEl, setAnchorEl] = useState(null); - const open = Boolean(anchorEl); - const { hasAccess } = useContext(AccessContext); - const enabled = environments.some((environment) => - hasAccess(CREATE_FEATURE_STRATEGY, projectId, environment), - ); - - return ( -
- -
- -
-
- { - setAnchorEl(null); - }} - MenuListProps={{ - 'aria-labelledby': `copy-all-strategies-${environmentId}`, - }} - > - {environments.map((environment) => { - const access = hasAccess( - CREATE_FEATURE_STRATEGY, - projectId, - environment, - ); - - return ( - -
- onClick(environment)} - disabled={!access} - > - - - - } - /> - - Copy from {environment} - - -
-
- ); - })} -
-
- ); -}; diff --git a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyEmpty/FeatureStrategyEmpty.tsx b/frontend/src/component/feature/FeatureStrategy/FeatureStrategyEmpty/FeatureStrategyEmpty.tsx deleted file mode 100644 index dba386ff22..0000000000 --- a/frontend/src/component/feature/FeatureStrategy/FeatureStrategyEmpty/FeatureStrategyEmpty.tsx +++ /dev/null @@ -1,187 +0,0 @@ -// deprecated; remove with the `flagOverviewRedesign` flag -import { Link } from 'react-router-dom'; -import { Box, styled } from '@mui/material'; -import useFeatureStrategyApi from 'hooks/api/actions/useFeatureStrategyApi/useFeatureStrategyApi'; -import useToast from 'hooks/useToast'; -import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; -import { formatUnknownError } from 'utils/formatUnknownError'; -import { useFeatureImmutable } from 'hooks/api/getters/useFeature/useFeatureImmutable'; -import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import { CopyButton } from './CopyButton/CopyButton'; -import { useChangeRequestAddStrategy } from 'hooks/useChangeRequestAddStrategy'; -import { ChangeRequestDialogue } from 'component/changeRequest/ChangeRequestConfirmDialog/ChangeRequestConfirmDialog'; -import { CopyStrategiesMessage } from 'component/changeRequest/ChangeRequestConfirmDialog/ChangeRequestMessages/CopyStrategiesMessage'; -import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled'; -import { FeatureStrategyMenuWrapper } from '../FeatureStrategyMenu/FeatureStrategyMenu'; - -interface IFeatureStrategyEmptyProps { - projectId: string; - featureId: string; - environmentId: string; -} - -const StyledContainer = styled('div')(({ theme }) => ({ - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - justifyContent: 'center', - paddingTop: theme.spacing(2), -})); - -const StyledTitle = styled('div')(({ theme }) => ({ - fontSize: theme.fontSizes.bodySize, - textAlign: 'center', - color: theme.palette.text.primary, - marginBottom: theme.spacing(1), -})); - -const StyledDescription = styled('p')(({ theme }) => ({ - color: theme.palette.text.secondary, - fontSize: theme.fontSizes.smallBody, - textAlign: 'center', - marginBottom: theme.spacing(3), - - a: { - color: theme.palette.links, - }, -})); - -export const FeatureStrategyEmpty = ({ - projectId, - featureId, - environmentId, -}: IFeatureStrategyEmptyProps) => { - const { addStrategyToFeature } = useFeatureStrategyApi(); - const { setToastData, setToastApiError } = useToast(); - const { refetchFeature } = useFeature(projectId, featureId); - const { refetchFeature: refetchFeatureImmutable } = useFeatureImmutable( - projectId, - featureId, - ); - const { feature } = useFeature(projectId, featureId); - const otherAvailableEnvironments = feature?.environments.filter( - (environment) => - environment.name !== environmentId && - environment.strategies && - environment.strategies.length > 0, - ); - const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId); - - const { - changeRequestDialogDetails, - onChangeRequestAddStrategies, - onChangeRequestAddStrategiesConfirm, - onChangeRequestAddStrategyClose, - } = useChangeRequestAddStrategy(projectId, featureId, 'addStrategy'); - - const onAfterAddStrategy = (multiple = false) => { - refetchFeature(); - refetchFeatureImmutable(); - - setToastData({ - text: multiple ? 'Strategies created' : 'Strategy created', - type: 'success', - }); - }; - - const onCopyStrategies = async (fromEnvironmentName: string) => { - const strategies = - otherAvailableEnvironments?.find( - (environment) => environment.name === fromEnvironmentName, - )?.strategies || []; - - if (isChangeRequestConfigured(environmentId)) { - await onChangeRequestAddStrategies( - environmentId, - strategies, - fromEnvironmentName, - ); - return; - } - - try { - await Promise.all( - strategies.map((strategy) => { - const { id, ...strategyCopy } = { - ...strategy, - environment: environmentId, - }; - - return addStrategyToFeature( - projectId, - featureId, - environmentId, - strategyCopy, - ); - }), - ); - onAfterAddStrategy(true); - } catch (error) { - setToastApiError(formatUnknownError(error)); - } - }; - - const canCopyFromOtherEnvironment = - otherAvailableEnvironments && otherAvailableEnvironments.length > 0; - - return ( - <> - - } - /> - - - - You have not defined any strategies yet. - - - Strategies added in this environment will only be executed - if the SDK is using an{' '} - API key configured for this - environment. - - - - environment.name, - )} - onClick={onCopyStrategies} - /> - } - /> - - - - ); -}; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverview.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverview.tsx index c754055a4f..72b4261054 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverview.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverview.tsx @@ -13,7 +13,6 @@ import { useEffect, useState } from 'react'; import { useLastViewedFlags } from 'hooks/useLastViewedFlags'; import { useUiFlag } from 'hooks/useUiFlag'; import { FeatureOverviewEnvironments } from './FeatureOverviewEnvironments/FeatureOverviewEnvironments'; -import { default as LegacyFleatureOverview } from './LegacyFeatureOverview'; import { useEnvironmentVisibility } from './FeatureOverviewMetaData/EnvironmentVisibilityMenu/hooks/useEnvironmentVisibility'; import useSplashApi from 'hooks/api/actions/useSplashApi/useSplashApi'; import { useAuthSplash } from 'hooks/api/getters/useAuth/useAuthSplash'; @@ -50,18 +49,12 @@ export const FeatureOverview = () => { useEffect(() => { setLastViewed({ featureId, projectId }); }, [featureId]); - const flagOverviewRedesign = useUiFlag('flagOverviewRedesign'); const { setSplashSeen } = useSplashApi(); const { splash } = useAuthSplash(); const [showTooltip, setShowTooltip] = useState(false); const [hasClosedTooltip, setHasClosedTooltip] = useState(false); const { feature, refetchFeature } = useFeature(projectId, featureId); const cleanupReminderEnabled = useUiFlag('cleanupReminder'); - - if (!flagOverviewRedesign) { - return ; - } - const dragTooltipSplashId = 'strategy-drag-tooltip'; const shouldShowStrategyDragTooltip = !splash?.[dragTooltipSplashId]; const toggleShowTooltip = (envIsOpen: boolean) => { diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/LegacyEnvironmentAccordionBody.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/LegacyEnvironmentAccordionBody.tsx deleted file mode 100644 index 98703fa5b3..0000000000 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/LegacyEnvironmentAccordionBody.tsx +++ /dev/null @@ -1,354 +0,0 @@ -// deprecated; remove with the `flagOverviewRedesign` flag -import { - type DragEventHandler, - type RefObject, - useEffect, - useState, -} from 'react'; -import { Alert, Pagination, styled } from '@mui/material'; -import useFeatureStrategyApi from 'hooks/api/actions/useFeatureStrategyApi/useFeatureStrategyApi'; -import { formatUnknownError } from 'utils/formatUnknownError'; -import useToast from 'hooks/useToast'; -import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import { StrategyDraggableItem } from './StrategyDraggableItem/LegacyStrategyDraggableItem'; -import type { IFeatureEnvironment } from 'interfaces/featureToggle'; -import { FeatureStrategyEmpty } from 'component/feature/FeatureStrategy/FeatureStrategyEmpty/FeatureStrategyEmpty'; -import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; -import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; -import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi'; -import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled'; -import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests'; -import usePagination from 'hooks/usePagination'; -import type { IFeatureStrategy } from 'interfaces/strategy'; -import { usePlausibleTracker } from 'hooks/usePlausibleTracker'; -import { useUiFlag } from 'hooks/useUiFlag'; -import { useReleasePlans } from 'hooks/api/getters/useReleasePlans/useReleasePlans'; -import { ReleasePlan } from '../../../ReleasePlan/LegacyReleasePlan'; -import { Badge } from 'component/common/Badge/Badge'; -import { SectionSeparator } from '../SectionSeparator/SectionSeparator'; - -interface IEnvironmentAccordionBodyProps { - isDisabled: boolean; - featureEnvironment?: IFeatureEnvironment; - otherEnvironments?: IFeatureEnvironment['name'][]; -} - -const StyledAccordionBody = styled('div')(({ theme }) => ({ - width: '100%', - position: 'relative', - paddingBottom: theme.spacing(2), -})); - -const StyledAccordionBodyInnerContainer = styled('div')(({ theme }) => ({ - [theme.breakpoints.down(400)]: { - padding: theme.spacing(1), - }, -})); - -const StyledBadge = styled(Badge)(({ theme }) => ({ - backgroundColor: theme.palette.primary.light, - border: 'none', - padding: theme.spacing(0.75, 1.5), - borderRadius: theme.shape.borderRadiusLarge, - color: theme.palette.common.white, -})); - -const AdditionalStrategiesDiv = styled('div')(({ theme }) => ({ - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - marginBottom: theme.spacing(2), -})); - -const EnvironmentAccordionBody = ({ - featureEnvironment, - isDisabled, - otherEnvironments, -}: IEnvironmentAccordionBodyProps) => { - const projectId = useRequiredPathParam('projectId'); - const featureId = useRequiredPathParam('featureId'); - const { setStrategiesSortOrder } = useFeatureStrategyApi(); - const { addChange } = useChangeRequestApi(); - const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId); - const { refetch: refetchChangeRequests } = - usePendingChangeRequests(projectId); - const { setToastData, setToastApiError } = useToast(); - const { refetchFeature } = useFeature(projectId, featureId); - const manyStrategiesPagination = useUiFlag('manyStrategiesPagination'); - const [strategies, setStrategies] = useState( - featureEnvironment?.strategies || [], - ); - const { releasePlans } = useReleasePlans( - projectId, - featureId, - featureEnvironment?.name, - ); - const { trackEvent } = usePlausibleTracker(); - - const [dragItem, setDragItem] = useState<{ - id: string; - index: number; - height: number; - } | null>(null); - useEffect(() => { - // Use state to enable drag and drop, but switch to API output when it arrives - setStrategies(featureEnvironment?.strategies || []); - }, [featureEnvironment?.strategies]); - - useEffect(() => { - if (strategies.length > 50) { - trackEvent('many-strategies'); - } - }, []); - - const pageSize = 20; - const { page, pages, setPageIndex, pageIndex } = - usePagination(strategies, pageSize); - - if (!featureEnvironment) { - return null; - } - - const onReorder = async (payload: { id: string; sortOrder: number }[]) => { - try { - await setStrategiesSortOrder( - projectId, - featureId, - featureEnvironment.name, - payload, - ); - refetchFeature(); - setToastData({ - text: 'Order of strategies updated', - type: 'success', - }); - } catch (error: unknown) { - setToastApiError(formatUnknownError(error)); - } - }; - - const onChangeRequestReorder = async ( - payload: { id: string; sortOrder: number }[], - ) => { - await addChange(projectId, featureEnvironment.name, { - action: 'reorderStrategy', - feature: featureId, - payload, - }); - - setToastData({ - text: 'Strategy execution order added to draft', - type: 'success', - }); - refetchChangeRequests(); - }; - - const onStrategyReorder = async ( - payload: { id: string; sortOrder: number }[], - ) => { - try { - if (isChangeRequestConfigured(featureEnvironment.name)) { - await onChangeRequestReorder(payload); - } else { - await onReorder(payload); - } - } catch (error: unknown) { - setToastApiError(formatUnknownError(error)); - } - }; - - const onDragStartRef = - ( - ref: RefObject, - index: number, - ): DragEventHandler => - (event) => { - setDragItem({ - id: strategies[index].id, - index, - height: ref.current?.offsetHeight || 0, - }); - - if (ref?.current) { - event.dataTransfer.effectAllowed = 'move'; - event.dataTransfer.setData('text/html', ref.current.outerHTML); - event.dataTransfer.setDragImage(ref.current, 20, 20); - } - }; - - const onDragOver = - (targetId: string) => - ( - ref: RefObject, - targetIndex: number, - ): DragEventHandler => - (event) => { - if (dragItem === null || ref.current === null) return; - if (dragItem.index === targetIndex || targetId === dragItem.id) - return; - - const { top, bottom } = ref.current.getBoundingClientRect(); - const overTargetTop = event.clientY - top < dragItem.height; - const overTargetBottom = bottom - event.clientY < dragItem.height; - const draggingUp = dragItem.index > targetIndex; - - // prevent oscillating by only reordering if there is sufficient space - if ( - (overTargetTop && draggingUp) || - (overTargetBottom && !draggingUp) - ) { - const newStrategies = [...strategies]; - const movedStrategy = newStrategies.splice( - dragItem.index, - 1, - )[0]; - newStrategies.splice(targetIndex, 0, movedStrategy); - setStrategies(newStrategies); - setDragItem({ - ...dragItem, - index: targetIndex, - }); - } - }; - - const onDragEnd = () => { - setDragItem(null); - onStrategyReorder( - strategies.map((strategy, sortOrder) => ({ - id: strategy.id, - sortOrder, - })), - ); - }; - - return ( - - - 0 || strategies.length > 0) && - isDisabled - } - show={() => ( - - This environment is disabled, which means that none - of your strategies are executing. - - )} - /> - 0 || strategies.length > 0} - show={ - <> - {releasePlans.map((plan) => ( - - ))} - 0 && - strategies.length > 0 - } - show={ - <> - - OR - - - Additional strategies - - - } - /> - - {strategies.map((strategy, index) => ( - - ))} - - } - elseShow={ - <> - - We noticed you're using a high - number of activation strategies. To - ensure a more targeted approach, - consider leveraging constraints or - segments. - -
- {page.map((strategy, index) => ( - {}) as any - } - onDragOver={(() => {}) as any} - onDragEnd={(() => {}) as any} - /> - ))} -
- - setPageIndex(page - 1) - } - /> - - } - /> - - } - elseShow={ - - } - /> -
-
- ); -}; - -export default EnvironmentAccordionBody; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/LegacyStrategyDraggableItem.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/LegacyStrategyDraggableItem.tsx deleted file mode 100644 index db47857047..0000000000 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/LegacyStrategyDraggableItem.tsx +++ /dev/null @@ -1,150 +0,0 @@ -// deprecated; remove with the `flagOverviewRedesign` flag -import { type DragEventHandler, type RefObject, useRef } from 'react'; -import { Box, useMediaQuery, useTheme } from '@mui/material'; -import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import { StrategySeparator } from 'component/common/StrategySeparator/LegacyStrategySeparator'; -import type { IFeatureEnvironment } from 'interfaces/featureToggle'; -import type { IFeatureStrategy } from 'interfaces/strategy'; -import { StrategyItem } from './StrategyItem/LegacyStrategyItem'; -import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; -import { - useStrategyChangesFromRequest, - type UseStrategyChangeFromRequestResult, -} from './StrategyItem/useStrategyChangesFromRequest'; -import { ChangesScheduledBadge } from 'component/changeRequest/ModifiedInChangeRequestStatusBadge/ChangesScheduledBadge'; -import type { IFeatureChange } from 'component/changeRequest/changeRequest.types'; -import { Badge } from 'component/common/Badge/Badge'; -import { - type ScheduledChangeRequestViewModel, - useScheduledChangeRequestsWithStrategy, -} from 'hooks/api/getters/useScheduledChangeRequestsWithStrategy/useScheduledChangeRequestsWithStrategy'; - -interface IStrategyDraggableItemProps { - strategy: IFeatureStrategy; - environmentName: string; - index: number; - otherEnvironments?: IFeatureEnvironment['name'][]; - isDragging?: boolean; - onDragStartRef: ( - ref: RefObject, - index: number, - ) => DragEventHandler; - onDragOver: ( - ref: RefObject, - index: number, - ) => DragEventHandler; - onDragEnd: () => void; -} - -export const StrategyDraggableItem = ({ - strategy, - index, - environmentName, - otherEnvironments, - isDragging, - onDragStartRef, - onDragOver, - onDragEnd, -}: IStrategyDraggableItemProps) => { - const projectId = useRequiredPathParam('projectId'); - const featureId = useRequiredPathParam('featureId'); - const ref = useRef(null); - const strategyChangesFromRequest = useStrategyChangesFromRequest( - projectId, - featureId, - environmentName, - strategy.id, - ); - - const { changeRequests: scheduledChangesUsingStrategy } = - useScheduledChangeRequestsWithStrategy(projectId, strategy.id); - - return ( - - 0} - show={} - /> - - - - ); -}; - -const ChangeRequestStatusBadge = ({ - change, -}: { - change: IFeatureChange | undefined; -}) => { - const theme = useTheme(); - const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm')); - - if (isSmallScreen) { - return null; - } - - return ( - - Modified in draft} - /> - Deleted in draft} - /> - - ); -}; - -const renderHeaderChildren = ( - changes?: UseStrategyChangeFromRequestResult, - scheduledChanges?: ScheduledChangeRequestViewModel[], -): JSX.Element[] => { - const badges: JSX.Element[] = []; - if (changes?.length === 0 && scheduledChanges?.length === 0) { - return []; - } - - const draftChange = changes?.find( - ({ isScheduledChange }) => !isScheduledChange, - ); - - if (draftChange) { - badges.push( - , - ); - } - - if (scheduledChanges && scheduledChanges.length > 0) { - badges.push( - scheduledChange.id, - )} - />, - ); - } - - return badges; -}; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/LegacyStrategyItem.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/LegacyStrategyItem.tsx deleted file mode 100644 index c2b8786467..0000000000 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/LegacyStrategyItem.tsx +++ /dev/null @@ -1,103 +0,0 @@ -// deprecated; remove with the `flagOverviewRedesign` flag -import type { DragEventHandler, FC } from 'react'; -import Edit from '@mui/icons-material/Edit'; -import { Link } from 'react-router-dom'; -import type { IFeatureEnvironment } from 'interfaces/featureToggle'; -import type { IFeatureStrategy } from 'interfaces/strategy'; -import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton'; -import { UPDATE_FEATURE_STRATEGY } from 'component/providers/AccessProvider/permissions'; -import { formatEditStrategyPath } from 'component/feature/FeatureStrategy/FeatureStrategyEdit/FeatureStrategyEdit'; -import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; -import { StrategyExecution } from './StrategyExecution/StrategyExecution'; -import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import { CopyStrategyIconMenu } from './CopyStrategyIconMenu/CopyStrategyIconMenu'; -import { StrategyItemContainer } from 'component/common/StrategyItemContainer/LegacyStrategyItemContainer'; -import MenuStrategyRemove from './MenuStrategyRemove/MenuStrategyRemove'; -import { VariantsSplitPreview } from 'component/common/VariantsSplitPreview/VariantsSplitPreview'; -import { Box } from '@mui/material'; -interface IStrategyItemProps { - environmentId: string; - strategy: IFeatureStrategy; - onDragStart?: DragEventHandler; - onDragEnd?: DragEventHandler; - otherEnvironments?: IFeatureEnvironment['name'][]; - orderNumber?: number; - headerChildren?: JSX.Element[] | JSX.Element; -} - -export const StrategyItem: FC = ({ - environmentId, - strategy, - onDragStart, - onDragEnd, - otherEnvironments, - orderNumber, - headerChildren, -}) => { - const projectId = useRequiredPathParam('projectId'); - const featureId = useRequiredPathParam('featureId'); - - const editStrategyPath = formatEditStrategyPath( - projectId, - featureId, - environmentId, - strategy.id, - ); - - return ( - - {headerChildren} - 0, - )} - show={() => ( - - )} - /> - - - - - - } - > - - - {strategy.variants && - strategy.variants.length > 0 && - (strategy.disabled ? ( - - - - ) : ( - - ))} - - ); -}; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyExecution/LegacyStrategyExecution.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyExecution/LegacyStrategyExecution.tsx deleted file mode 100644 index b4177c3498..0000000000 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyExecution/LegacyStrategyExecution.tsx +++ /dev/null @@ -1,372 +0,0 @@ -import { type FC, Fragment, useMemo } from 'react'; -import { Alert, Box, Chip, Link, styled } from '@mui/material'; -import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import PercentageCircle from 'component/common/PercentageCircle/PercentageCircle'; -import { StrategySeparator } from 'component/common/StrategySeparator/LegacyStrategySeparator'; -import { ConstraintItem } from './ConstraintItem/LegacyConstraintItem'; -import { useStrategies } from 'hooks/api/getters/useStrategies/useStrategies'; -import { useSegments } from 'hooks/api/getters/useSegments/useSegments'; -import { FeatureOverviewSegment } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewSegment/FeatureOverviewSegment'; -import { ConstraintAccordionList } from 'component/common/LegacyConstraintAccordion/ConstraintAccordionList/ConstraintAccordionList'; -import { - parseParameterNumber, - parseParameterString, - parseParameterStrings, -} from 'utils/parseParameter'; -import StringTruncator from 'component/common/StringTruncator/StringTruncator'; -import { Badge } from 'component/common/Badge/Badge'; -import type { CreateFeatureStrategySchema } from 'openapi'; -import type { IFeatureStrategyPayload } from 'interfaces/strategy'; -import { BuiltInStrategies } from 'utils/strategyNames'; - -interface IStrategyExecutionProps { - strategy: IFeatureStrategyPayload | CreateFeatureStrategySchema; - displayGroupId?: boolean; -} - -const StyledContainer = styled(Box, { - shouldForwardProp: (prop) => prop !== 'disabled', -})<{ disabled?: boolean | null }>(({ theme, disabled }) => ({ - '& p, & span, & h1, & h2, & h3, & h4, & h5, & h6': { - color: disabled ? theme.palette.neutral.main : 'inherit', - }, - '.constraint-icon-container': { - backgroundColor: disabled - ? theme.palette.neutral.border - : theme.palette.primary.light, - borderRadius: '50%', - }, - '.constraint-icon': { - fill: disabled - ? theme.palette.neutral.light - : theme.palette.common.white, - }, -})); - -const CustomStrategyDeprecationWarning = () => ( - - Custom strategies are deprecated and may be removed in a future major - version. Consider rewriting this strategy as a predefined strategy with{' '} - - constraints. - - -); - -const NoItems: FC = () => ( - - This strategy does not have constraints or parameters. - -); - -const StyledValueContainer = styled(Box)(({ theme }) => ({ - padding: theme.spacing(2, 3), - border: `1px solid ${theme.palette.divider}`, - borderRadius: theme.shape.borderRadiusMedium, - background: theme.palette.background.default, -})); - -const StyledValueSeparator = styled('span')(({ theme }) => ({ - color: theme.palette.neutral.main, -})); - -export const StrategyExecution: FC = ({ - strategy, - displayGroupId = false, -}) => { - const { parameters, constraints = [] } = strategy; - const stickiness = parameters?.stickiness; - const explainStickiness = - typeof stickiness === 'string' && stickiness !== 'default'; - const { strategies } = useStrategies(); - const { segments } = useSegments(); - const strategySegments = segments?.filter((segment) => { - return strategy.segments?.includes(segment.id); - }); - - const definition = strategies.find((strategyDefinition) => { - return strategyDefinition.name === strategy.name; - }); - - const parametersList = useMemo(() => { - if (!parameters || definition?.editable) return null; - - return Object.keys(parameters).map((key) => { - switch (key) { - case 'rollout': - case 'Rollout': { - const percentage = parseParameterNumber(parameters[key]); - - const badgeType = strategy.disabled ? 'neutral' : 'success'; - - return ( - - - - -
- {percentage}%{' '} - of your base{' '} - - {explainStickiness ? ( - <> - with {stickiness} - - ) : ( - '' - )}{' '} - - - {constraints.length > 0 - ? 'who match constraints' - : ''}{' '} - is included. - -
- {displayGroupId && parameters.groupId && ( - ({ - ml: 1, - color: theme.palette.info.contrastText, - })} - > - - GroupId: {parameters.groupId} - - - )} -
- ); - } - case 'userIds': - case 'UserIds': { - const users = parseParameterStrings(parameters[key]); - return ( - - ); - } - case 'hostNames': - case 'HostNames': { - const hosts = parseParameterStrings(parameters[key]); - return ( - - ); - } - case 'IPs': { - const IPs = parseParameterStrings(parameters[key]); - return ; - } - case 'stickiness': - case 'groupId': - return null; - default: - return null; - } - }); - }, [parameters, definition, constraints, strategy.disabled]); - - const customStrategyList = useMemo(() => { - if (!parameters || !definition?.editable) return null; - const isSetTo = ( - {' is set to '} - ); - - return definition?.parameters.map((param) => { - const { type, name } = { ...param }; - if (!type || !name || parameters[name] === undefined) { - return null; - } - const nameItem = ( - - ); - - switch (param?.type) { - case 'list': { - const values = parseParameterStrings(parameters[name]); - - return values.length > 0 ? ( - - {nameItem}{' '} - - has {values.length}{' '} - {values.length > 1 ? `items` : 'item'}:{' '} - {values.map((item: string) => ( - - } - sx={{ mr: 0.5 }} - /> - ))} - - - ) : null; - } - - case 'percentage': { - const percentage = parseParameterNumber(parameters[name]); - return parameters[name] !== '' ? ( - - - - -
- {nameItem} - {isSetTo} - {percentage}% -
-
- ) : null; - } - - case 'boolean': - return parameters[name] === 'true' || - parameters[name] === 'false' ? ( - - - {isSetTo} - - {parameters[name]} - - - ) : null; - - case 'string': { - const value = parseParameterString(parameters[name]); - return typeof parameters[name] !== 'undefined' ? ( - - {nameItem} - - {' is an empty string'} - - } - elseShow={ - <> - {isSetTo} - - - } - /> - - ) : null; - } - - case 'number': { - const number = parseParameterNumber(parameters[name]); - return parameters[name] !== '' && number !== undefined ? ( - - {nameItem} - {isSetTo} - - - ) : null; - } - case 'default': - return null; - } - - return null; - }); - }, [parameters, definition]); - - if (!parameters) { - return ; - } - - const listItems = [ - strategySegments && strategySegments.length > 0 && ( - - ), - constraints.length > 0 && ( - - ), - strategy.name === 'default' && ( - <> - - The standard strategy is ON{' '} - for all users. - - - ), - ...(parametersList ?? []), - ...(customStrategyList ?? []), - ].filter(Boolean); - - return ( - <> - } - /> - - 0} - show={ - - {listItems.map((item, index) => ( - - 0} - show={} - /> - {item} - - ))} - - } - elseShow={} - /> - - ); -}; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyExecution/StrategyExecution.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyExecution/StrategyExecution.tsx index 6a75941a71..5ca3064782 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyExecution/StrategyExecution.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyExecution/StrategyExecution.tsx @@ -1,8 +1,6 @@ import type { FC } from 'react'; import type { FeatureStrategySchema } from 'openapi'; import type { IFeatureStrategyPayload } from 'interfaces/strategy'; -import { useUiFlag } from 'hooks/useUiFlag'; -import { StrategyExecution as LegacyStrategyExecution } from './LegacyStrategyExecution'; import { ConstraintAccordionView } from 'component/common/NewConstraintAccordion/ConstraintAccordionView/ConstraintAccordionView'; import { useStrategies } from 'hooks/api/getters/useStrategies/useStrategies'; import { objectId } from 'utils/objectId'; @@ -37,16 +35,6 @@ export const StrategyExecution: FC = ({ const strategySegments = segments?.filter((segment) => strategy.segments?.includes(segment.id), ); - const flagOverviewRedesign = useUiFlag('flagOverviewRedesign'); - - if (!flagOverviewRedesign) { - return ( - - ); - } return ( <> diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentHeader/FeatureOverviewEnvironmentMetrics/LegacyFeatureOverviewEnvironmentMetrics.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentHeader/FeatureOverviewEnvironmentMetrics/LegacyFeatureOverviewEnvironmentMetrics.tsx deleted file mode 100644 index 9581355e0c..0000000000 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentHeader/FeatureOverviewEnvironmentMetrics/LegacyFeatureOverviewEnvironmentMetrics.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import FiberManualRecord from '@mui/icons-material/FiberManualRecord'; -import { useTheme } from '@mui/system'; -import type { IFeatureEnvironmentMetrics } from 'interfaces/featureToggle'; -import { calculatePercentage } from 'utils/calculatePercentage'; -import PercentageCircle from 'component/common/PercentageCircle/PercentageCircle'; -import { PrettifyLargeNumber } from 'component/common/PrettifyLargeNumber/PrettifyLargeNumber'; -import { styled } from '@mui/material'; - -interface IFeatureOverviewEnvironmentMetrics { - environmentMetric?: IFeatureEnvironmentMetrics; - disabled?: boolean; -} - -const StyledContainer = styled('div')({ - marginLeft: 'auto', - display: 'flex', - alignItems: 'center', -}); - -const StyledInfo = styled('div')(({ theme }) => ({ - marginRight: theme.spacing(1), - display: 'flex', - flexDirection: 'column', -})); - -const StyledPercentage = styled('p')(({ theme }) => ({ - color: theme.palette.primary.main, - textAlign: 'right', - fontSize: theme.fontSizes.bodySize, -})); - -const StyledInfoParagraph = styled('p')(({ theme }) => ({ - maxWidth: '270px', - marginTop: theme.spacing(0.5), - fontSize: theme.fontSizes.smallBody, - textAlign: 'right', - [theme.breakpoints.down(700)]: { - display: 'none', - }, -})); - -const StyledIcon = styled(FiberManualRecord)(({ theme }) => ({ - fill: theme.palette.background.elevation2, - height: '75px', - width: '75px', - [theme.breakpoints.down(500)]: { - display: 'none', - }, -})); - -const StyledPercentageCircle = styled('div')(({ theme }) => ({ - margin: theme.spacing(0, 2), - [theme.breakpoints.down(500)]: { - display: 'none', - }, -})); - -/** - * @deprecated remove with `flagOverviewRedesign` flag - */ -const FeatureOverviewEnvironmentMetrics = ({ - environmentMetric, - disabled = false, -}: IFeatureOverviewEnvironmentMetrics) => { - const theme = useTheme(); - - if (!environmentMetric) return null; - - const total = environmentMetric.yes + environmentMetric.no; - const percentage = calculatePercentage(total, environmentMetric?.yes); - - if ( - !environmentMetric || - (environmentMetric.yes === 0 && environmentMetric.no === 0) - ) { - return ( - - - - {percentage}% - - - The feature has been requested 0 times and - exposed - 0 times in the last hour - - - - - ); - } - - return ( - - - {percentage}% - - The feature has been requested{' '} - - times - {' '} - and exposed{' '} - - {' '} - times - {' '} - in the last hour - - - - - - - ); -}; - -export default FeatureOverviewEnvironmentMetrics; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/LegacyFeatureOverviewEnvironment.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/LegacyFeatureOverviewEnvironment.tsx deleted file mode 100644 index f23f904bca..0000000000 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/LegacyFeatureOverviewEnvironment.tsx +++ /dev/null @@ -1,252 +0,0 @@ -import { - Accordion, - AccordionDetails, - AccordionSummary, - Box, - styled, -} from '@mui/material'; -import ExpandMore from '@mui/icons-material/ExpandMore'; -import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; -import useFeatureMetrics from 'hooks/api/getters/useFeatureMetrics/useFeatureMetrics'; -import type { IFeatureEnvironment } from 'interfaces/featureToggle'; -import { getFeatureMetrics } from 'utils/getFeatureMetrics'; -import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import EnvironmentIcon from 'component/common/EnvironmentIcon/EnvironmentIcon'; -import StringTruncator from 'component/common/StringTruncator/StringTruncator'; -import EnvironmentAccordionBody from './EnvironmentAccordionBody/LegacyEnvironmentAccordionBody'; -import { EnvironmentFooter } from './EnvironmentFooter/EnvironmentFooter'; -import FeatureOverviewEnvironmentMetrics from './EnvironmentHeader/FeatureOverviewEnvironmentMetrics/LegacyFeatureOverviewEnvironmentMetrics'; -import { FeatureStrategyMenuWrapper } from 'component/feature/FeatureStrategy/FeatureStrategyMenu/FeatureStrategyMenu'; -import { FEATURE_ENVIRONMENT_ACCORDION } from 'utils/testIds'; -import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; -import { FeatureStrategyIcons } from 'component/feature/FeatureStrategy/FeatureStrategyIcons/FeatureStrategyIcons'; -import { useGlobalLocalStorage } from 'hooks/useGlobalLocalStorage'; -import { Badge } from 'component/common/Badge/Badge'; -import { UpgradeChangeRequests } from './UpgradeChangeRequests/UpgradeChangeRequests'; -import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; - -interface IFeatureOverviewEnvironmentProps { - env: IFeatureEnvironment; -} - -const StyledFeatureOverviewEnvironment = styled('div', { - shouldForwardProp: (prop) => prop !== 'enabled', -})<{ enabled: boolean }>(({ theme, enabled }) => ({ - borderRadius: theme.shape.borderRadiusLarge, - marginBottom: theme.spacing(2), - backgroundColor: enabled - ? theme.palette.background.paper - : theme.palette.envAccordion.disabled, -})); - -const StyledAccordion = styled(Accordion)({ - boxShadow: 'none', - background: 'none', -}); - -const StyledAccordionSummary = styled(AccordionSummary)(({ theme }) => ({ - boxShadow: 'none', - padding: theme.spacing(2, 4), - [theme.breakpoints.down(400)]: { - padding: theme.spacing(1, 2), - }, -})); - -const StyledAccordionDetails = styled(AccordionDetails, { - shouldForwardProp: (prop) => prop !== 'enabled', -})<{ enabled: boolean }>(({ theme }) => ({ - padding: theme.spacing(3), - background: theme.palette.envAccordion.expanded, - borderBottomLeftRadius: theme.shape.borderRadiusLarge, - borderBottomRightRadius: theme.shape.borderRadiusLarge, - boxShadow: theme.boxShadows.accordionFooter, - - [theme.breakpoints.down('md')]: { - padding: theme.spacing(2, 1), - }, -})); - -const StyledEnvironmentAccordionBody = styled(EnvironmentAccordionBody)( - ({ theme }) => ({ - width: '100%', - position: 'relative', - paddingBottom: theme.spacing(2), - }), -); - -const StyledHeader = styled('div', { - shouldForwardProp: (prop) => prop !== 'enabled', -})<{ enabled: boolean }>(({ theme, enabled }) => ({ - display: 'flex', - justifyContent: 'center', - flexDirection: 'column', - color: enabled ? theme.palette.text.primary : theme.palette.text.secondary, -})); - -const StyledHeaderTitle = styled('div')(({ theme }) => ({ - display: 'flex', - alignItems: 'center', - [theme.breakpoints.down(560)]: { - flexDirection: 'column', - textAlign: 'center', - }, -})); - -const StyledEnvironmentIcon = styled(EnvironmentIcon)(({ theme }) => ({ - [theme.breakpoints.down(560)]: { - marginBottom: '0.5rem', - }, -})); - -const StyledStringTruncator = styled(StringTruncator)(({ theme }) => ({ - fontSize: theme.fontSizes.bodySize, - fontWeight: theme.typography.fontWeightMedium, - [theme.breakpoints.down(560)]: { - textAlign: 'center', - }, -})); - -const StyledButtonContainer = styled('div')(({ theme }) => ({ - display: 'flex', - alignItems: 'center', - marginTop: theme.spacing(2), - gap: theme.spacing(2), - flexWrap: 'wrap', - [theme.breakpoints.down(560)]: { - flexDirection: 'column', - }, -})); - -/** - * @deprecated remove this file with `flagOverviewRedesign` - */ -const FeatureOverviewEnvironment = ({ - env, -}: IFeatureOverviewEnvironmentProps) => { - const projectId = useRequiredPathParam('projectId'); - const featureId = useRequiredPathParam('featureId'); - const { metrics } = useFeatureMetrics(projectId, featureId); - const { feature } = useFeature(projectId, featureId); - const { value: globalStore } = useGlobalLocalStorage(); - - const featureMetrics = getFeatureMetrics(feature?.environments, metrics); - const environmentMetric = featureMetrics.find( - (featureMetric) => featureMetric.environment === env.name, - ); - const featureEnvironment = feature?.environments.find( - (featureEnvironment) => featureEnvironment.name === env.name, - ); - const { isOss } = useUiConfig(); - const showChangeRequestUpgrade = env.type === 'production' && isOss(); - - return ( - - - } - > - - - -
- -
- - Disabled - - } - /> -
- - - - -
- - -
- - - name) - .filter((name) => name !== env.name)} - /> - 0 - } - show={ - <> - - - - - {showChangeRequestUpgrade ? ( - - ) : null} - - } - /> - -
- - } - /> - ); -}; - -export default FeatureOverviewEnvironment; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironments.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironments.tsx index f0d14d2113..fc878f59e8 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironments.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironments.tsx @@ -1,8 +1,6 @@ import type { ComponentProps, FC } from 'react'; -import { useUiFlag } from 'hooks/useUiFlag'; import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; import { FeatureOverviewEnvironment } from './FeatureOverviewEnvironment/FeatureOverviewEnvironment'; -import LegacyFeatureOverviewEnvironment from './FeatureOverviewEnvironment/LegacyFeatureOverviewEnvironment'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import useFeatureMetrics from 'hooks/api/getters/useFeatureMetrics/useFeatureMetrics'; import { getFeatureMetrics } from 'utils/getFeatureMetrics'; @@ -40,23 +38,9 @@ export const FeatureOverviewEnvironments: FC< const { feature } = useFeature(projectId, featureId); const { metrics } = useFeatureMetrics(projectId, featureId); const featureMetrics = getFeatureMetrics(feature?.environments, metrics); - const flagOverviewRedesign = useUiFlag('flagOverviewRedesign'); if (!feature) return null; - if (!flagOverviewRedesign) { - return ( - <> - {feature.environments?.map((env) => ( - - ))} - - ); - } - return feature.environments ?.filter((env) => !hiddenEnvironments.includes(env.name)) .map((env) => ( diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/OldDependencyActions.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/OldDependencyActions.tsx deleted file mode 100644 index 0e294a04c5..0000000000 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/OldDependencyActions.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import type React from 'react'; -import { type FC, useState } from 'react'; -import { - IconButton, - ListItemIcon, - ListItemText, - MenuItem, - MenuList, - Popover, - styled, - Tooltip, - Typography, - Box, -} from '@mui/material'; -import Delete from '@mui/icons-material/Delete'; -import Edit from '@mui/icons-material/Edit'; -import MoreVert from '@mui/icons-material/MoreVert'; - -const StyledPopover = styled(Popover)(({ theme }) => ({ - borderRadius: theme.shape.borderRadiusLarge, - padding: theme.spacing(1, 1.5), -})); - -export const OldDependencyActions: FC<{ - feature: string; - onEdit: () => void; - onDelete: () => void; -}> = ({ feature, onEdit, onDelete }) => { - const id = `dependency-${feature}-actions`; - const menuId = `${id}-menu`; - - const [anchorEl, setAnchorEl] = useState(null); - - const open = Boolean(anchorEl); - const openActions = (event: React.MouseEvent) => { - setAnchorEl(event.currentTarget); - }; - const closeActions = () => { - setAnchorEl(null); - }; - - return ( - - - - - - - - - { - onEdit(); - closeActions(); - }} - > - - - - - Edit - - - - { - onDelete(); - closeActions(); - }} - > - - - - - Delete - - - - - - ); -}; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/OldDependencyRow.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/OldDependencyRow.tsx deleted file mode 100644 index 6953e163ce..0000000000 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/OldDependencyRow.tsx +++ /dev/null @@ -1,218 +0,0 @@ -import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import { AddDependencyDialogue } from 'component/feature/Dependencies/AddDependencyDialogue'; -import type { IFeatureToggle } from 'interfaces/featureToggle'; -import { type FC, useState } from 'react'; -import { - FlexRow, - StyledDetail, - StyledLabel, - StyledLink, -} from '../FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/StyledRow'; -import { OldDependencyActions } from './OldDependencyActions'; -import { useDependentFeaturesApi } from 'hooks/api/actions/useDependentFeaturesApi/useDependentFeaturesApi'; -import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; -import { ChildrenTooltip } from './ChildrenTooltip'; -import PermissionButton from 'component/common/PermissionButton/PermissionButton'; -import { UPDATE_FEATURE_DEPENDENCY } from 'component/providers/AccessProvider/permissions'; -import { useCheckProjectAccess } from 'hooks/useHasAccess'; -import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi'; -import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests'; -import useToast from 'hooks/useToast'; -import { useHighestPermissionChangeRequestEnvironment } from 'hooks/useHighestPermissionChangeRequestEnvironment'; -import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled'; -import { formatUnknownError } from 'utils/formatUnknownError'; -import { usePlausibleTracker } from 'hooks/usePlausibleTracker'; -import { VariantsTooltip } from './VariantsTooltip'; - -const useDeleteDependency = (project: string, featureId: string) => { - const { trackEvent } = usePlausibleTracker(); - const { addChange } = useChangeRequestApi(); - const { refetch: refetchChangeRequests } = - usePendingChangeRequests(project); - const { setToastData, setToastApiError } = useToast(); - const { refetchFeature } = useFeature(project, featureId); - const environment = useHighestPermissionChangeRequestEnvironment(project)(); - const { isChangeRequestConfiguredInAnyEnv } = - useChangeRequestsEnabled(project); - const { removeDependencies } = useDependentFeaturesApi(project); - - const handleAddChange = async () => { - if (!environment) { - console.error('No change request environment'); - return; - } - await addChange(project, environment, [ - { - action: 'deleteDependency', - feature: featureId, - payload: undefined, - }, - ]); - }; - - const deleteDependency = async () => { - try { - if (isChangeRequestConfiguredInAnyEnv()) { - await handleAddChange(); - trackEvent('dependent_features', { - props: { - eventType: 'delete dependency added to change request', - }, - }); - setToastData({ - type: 'success', - text: 'Change added to draft', - }); - await refetchChangeRequests(); - } else { - await removeDependencies(featureId); - trackEvent('dependent_features', { - props: { - eventType: 'dependency removed', - }, - }); - setToastData({ text: 'Dependency removed', type: 'success' }); - await refetchFeature(); - } - } catch (error) { - setToastApiError(formatUnknownError(error)); - } - }; - - return deleteDependency; -}; - -export const OldDependencyRow: FC<{ feature: IFeatureToggle }> = ({ - feature, -}) => { - const [showDependencyDialogue, setShowDependencyDialogue] = useState(false); - const canAddParentDependency = - Boolean(feature.project) && - feature.dependencies.length === 0 && - feature.children.length === 0; - const hasParentDependency = - Boolean(feature.project) && Boolean(feature.dependencies.length > 0); - const hasChildren = Boolean(feature.project) && feature.children.length > 0; - const environment = useHighestPermissionChangeRequestEnvironment( - feature.project, - )(); - const checkAccess = useCheckProjectAccess(feature.project); - const deleteDependency = useDeleteDependency(feature.project, feature.name); - - return ( - <> - - - Dependency: - { - setShowDependencyDialogue(true); - }} - sx={(theme) => ({ - marginBottom: theme.spacing(0.4), - })} - > - Add parent flag - - - - } - /> - - - Dependency: - - {feature.dependencies[0]?.feature} - - - - setShowDependencyDialogue(true) - } - onDelete={deleteDependency} - /> - } - /> - - } - /> - - - Dependency value: - disabled - - - } - /> - - - Dependency value: - - - - } - /> - - - Children: - - - - } - /> - - setShowDependencyDialogue(false)} - showDependencyDialogue={showDependencyDialogue} - /> - } - /> - - ); -}; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/OldFeatureOverviewMetaData.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/OldFeatureOverviewMetaData.tsx deleted file mode 100644 index a6e86dedbc..0000000000 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/OldFeatureOverviewMetaData.tsx +++ /dev/null @@ -1,281 +0,0 @@ -import { Box, capitalize, styled } from '@mui/material'; -import { Link, useNavigate } from 'react-router-dom'; -import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; -import { getFeatureTypeIcons } from 'utils/getFeatureTypeIcons'; -import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import Edit from '@mui/icons-material/Edit'; -import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton'; -import { UPDATE_FEATURE } from 'component/providers/AccessProvider/permissions'; -import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; -import { FeatureArchiveDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveDialog'; -import { useState } from 'react'; -import { FeatureArchiveNotAllowedDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveNotAllowedDialog'; -import { StyledDetail } from '../FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/StyledRow'; -import { formatDateYMD } from 'utils/formatDate'; -import { parseISO } from 'date-fns'; -import { FeatureEnvironmentSeen } from '../../FeatureEnvironmentSeen/FeatureEnvironmentSeen'; -import { OldDependencyRow } from './OldDependencyRow'; -import { useLocationSettings } from 'hooks/useLocationSettings'; -import { useShowDependentFeatures } from './useShowDependentFeatures'; -import type { ILastSeenEnvironments } from 'interfaces/featureToggle'; -import { FeatureLifecycle } from '../FeatureLifecycle/FeatureLifecycle'; -import { MarkCompletedDialogue } from '../FeatureLifecycle/MarkCompletedDialogue'; -import { UserAvatar } from 'component/common/UserAvatar/UserAvatar'; - -const StyledContainer = styled('div')(({ theme }) => ({ - borderRadius: theme.shape.borderRadiusLarge, - backgroundColor: theme.palette.background.paper, - display: 'flex', - flexDirection: 'column', - maxWidth: '350px', - minWidth: '350px', - marginRight: theme.spacing(2), - [theme.breakpoints.down(1000)]: { - width: '100%', - maxWidth: 'none', - minWidth: 'auto', - }, -})); - -const StyledPaddingContainerTop = styled('div')({ - padding: '1.5rem 1.5rem 0 1.5rem', -}); - -const StyledMetaDataHeader = styled('div')({ - display: 'flex', - alignItems: 'center', -}); - -const StyledHeader = styled('h2')(({ theme }) => ({ - fontSize: theme.fontSizes.mainHeader, - fontWeight: 'normal', - margin: 0, -})); - -const StyledBody = styled('div')(({ theme }) => ({ - margin: theme.spacing(2, 0), - display: 'flex', - flexDirection: 'column', - fontSize: theme.fontSizes.smallBody, -})); - -const BodyItemWithIcon = styled('div')(({ theme }) => ({})); - -const SpacedBodyItem = styled('div')(({ theme }) => ({ - display: 'flex', - justifyContent: 'space-between', - padding: theme.spacing(1, 0), -})); - -const StyledDescriptionContainer = styled('div')(({ theme }) => ({ - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', -})); - -const StyledDetailsContainer = styled('div')(({ theme }) => ({ - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', -})); - -const StyledDescription = styled('p')({ - wordBreak: 'break-word', -}); - -const StyledUserAvatar = styled(UserAvatar)(({ theme }) => ({ - margin: theme.spacing(1), -})); - -export const StyledLabel = styled('span')(({ theme }) => ({ - color: theme.palette.text.secondary, - marginRight: theme.spacing(1), -})); - -const OldFeatureOverviewMetaData = () => { - const projectId = useRequiredPathParam('projectId'); - const featureId = useRequiredPathParam('featureId'); - const { feature, refetchFeature } = useFeature(projectId, featureId); - const { project, description, type } = feature; - const navigate = useNavigate(); - const [showDelDialog, setShowDelDialog] = useState(false); - const [showMarkCompletedDialogue, setShowMarkCompletedDialogue] = - useState(false); - - const { locationSettings } = useLocationSettings(); - const showDependentFeatures = useShowDependentFeatures(feature.project); - - const lastSeenEnvironments: ILastSeenEnvironments[] = - feature.environments?.map((env) => ({ - name: env.name, - lastSeenAt: env.lastSeenAt, - enabled: env.enabled, - yes: env.yes, - no: env.no, - })); - - const IconComponent = getFeatureTypeIcons(type); - - return ( - - - - ({ - marginRight: theme.spacing(2), - height: '40px', - width: '40px', - padding: theme.spacing(0.5), - backgroundColor: - theme.palette.background.alternative, - fill: theme.palette.primary.contrastText, - borderRadius: `${theme.shape.borderRadiusMedium}px`, - })} - />{' '} - {capitalize(type || '')} toggle - - - - Project: - {project} - - - Lifecycle: - setShowDelDialog(true)} - onComplete={() => - setShowMarkCompletedDialogue(true) - } - onUncomplete={refetchFeature} - /> - - } - /> - - - Description: - - - {description} - - - - - - - } - elseShow={ -
- - No description.{' '} - - - - -
- } - /> - - - - Created at: - - {formatDateYMD( - parseISO(feature.createdAt), - locationSettings.locale, - )} - - - - - - - ( - - - - Created by: - {feature.createdBy?.name} - - - - - )} - /> - } - /> -
-
- 0} - show={ - setShowDelDialog(false)} - /> - } - elseShow={ - { - navigate(`/projects/${projectId}`); - }} - onClose={() => setShowDelDialog(false)} - projectId={projectId} - featureIds={[featureId]} - /> - } - /> - - } - /> -
- ); -}; - -export default OldFeatureOverviewMetaData; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanel.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanel.tsx deleted file mode 100644 index 4428586d1b..0000000000 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanel.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { Box, styled } from '@mui/material'; -import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; -import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; -import { Sticky } from 'component/common/Sticky/Sticky'; -import { - type ITab, - VerticalTabs, -} from 'component/common/VerticalTabs/VerticalTabs'; -import EnvironmentIcon from 'component/common/EnvironmentIcon/EnvironmentIcon'; -import { useEffect } from 'react'; - -const StyledContainer = styled(Box)(({ theme }) => ({ - margin: theme.spacing(2), - marginLeft: 0, - padding: theme.spacing(3), - borderRadius: theme.shape.borderRadiusLarge, - backgroundColor: theme.palette.background.paper, - display: 'flex', - flexDirection: 'column', - gap: theme.spacing(2), - width: '350px', - [theme.breakpoints.down('md')]: { - width: '100%', - }, -})); - -const StyledHeader = styled('h3')(({ theme }) => ({ - display: 'flex', - fontSize: theme.fontSizes.bodySize, - margin: 0, - marginBottom: theme.spacing(1), -})); - -const StyledVerticalTabs = styled(VerticalTabs)(({ theme }) => ({ - '&&& .selected': { - backgroundColor: theme.palette.neutral.light, - }, -})); - -interface IFeatureOverviewSidePanelProps { - environmentId: string; - setEnvironmentId: React.Dispatch>; -} - -export const FeatureOverviewSidePanel = ({ - environmentId, - setEnvironmentId, -}: IFeatureOverviewSidePanelProps) => { - const projectId = useRequiredPathParam('projectId'); - const featureId = useRequiredPathParam('featureId'); - const { feature } = useFeature(projectId, featureId); - const isSticky = feature.environments?.length <= 3; - - const tabs: ITab[] = feature.environments.map( - ({ name, enabled, strategies }) => ({ - id: name, - label: name, - description: - strategies.length === 1 - ? '1 strategy' - : `${strategies.length || 'No'} strategies`, - startIcon: , - }), - ); - - useEffect(() => { - if (!environmentId) { - setEnvironmentId(tabs[0]?.id); - } - }, [tabs]); - - return ( - - - Environments ({feature.environments.length}) - - setEnvironmentId(id)} - /> - - ); -}; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitch/FeatureOverviewSidePanelEnvironmentHider.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitch/FeatureOverviewSidePanelEnvironmentHider.tsx deleted file mode 100644 index 384aa05693..0000000000 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitch/FeatureOverviewSidePanelEnvironmentHider.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import type { IFeatureEnvironment } from 'interfaces/featureToggle'; -import { IconButton, styled } from '@mui/material'; -import Visibility from '@mui/icons-material/Visibility'; -import VisibilityOff from '@mui/icons-material/VisibilityOff'; -import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; - -const StyledVisibilityToggle = styled(IconButton, { - shouldForwardProp: (prop) => prop !== 'visibilityOff', -})<{ visibilityOff: boolean }>(({ theme, visibilityOff }) => ({ - marginLeft: 'auto', - marginRight: theme.spacing(-1), - color: visibilityOff - ? theme.palette.action.active - : theme.palette.action.focus, - '&:hover': { - color: theme.palette.action.active, - }, -})); - -interface IFeatureOverviewSidePanelEnvironmentHiderProps { - environment: IFeatureEnvironment; - hiddenEnvironments: Set; - setHiddenEnvironments: (environment: string) => void; -} - -export const FeatureOverviewSidePanelEnvironmentHider = ({ - environment, - hiddenEnvironments, - setHiddenEnvironments, -}: IFeatureOverviewSidePanelEnvironmentHiderProps) => { - const toggleHiddenEnvironments = () => { - setHiddenEnvironments(environment.name); - }; - - const isHidden = hiddenEnvironments.has(environment.name); - - return ( - - } - elseShow={} - /> - - ); -}; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitch/FeatureOverviewSidePanelEnvironmentSwitch.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitch/FeatureOverviewSidePanelEnvironmentSwitch.tsx deleted file mode 100644 index 160d28037d..0000000000 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitch/FeatureOverviewSidePanelEnvironmentSwitch.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; -import type { IFeatureEnvironment } from 'interfaces/featureToggle'; -import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; -import { styled } from '@mui/material'; -import StringTruncator from 'component/common/StringTruncator/StringTruncator'; -import { FeatureOverviewSidePanelEnvironmentHider } from './FeatureOverviewSidePanelEnvironmentHider'; -import { FeatureToggleSwitch } from 'component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/FeatureToggleSwitch'; -import { useFeatureToggleSwitch } from 'component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/useFeatureToggleSwitch'; -import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled'; - -const StyledContainer = styled('div')(({ theme }) => ({ - marginLeft: theme.spacing(-1.5), - '&:not(:last-of-type)': { - marginBottom: theme.spacing(2), - }, - display: 'flex', - alignItems: 'center', -})); - -const StyledLabel = styled('label')(() => ({ - display: 'inline-flex', - alignItems: 'center', - cursor: 'pointer', -})); - -interface IFeatureOverviewSidePanelEnvironmentSwitchProps { - environment: IFeatureEnvironment; - callback?: () => void; - children?: React.ReactNode; - hiddenEnvironments: Set; - setHiddenEnvironments: (environment: string) => void; -} - -export const FeatureOverviewSidePanelEnvironmentSwitch = ({ - environment, - callback, - children, - hiddenEnvironments, - setHiddenEnvironments, -}: IFeatureOverviewSidePanelEnvironmentSwitchProps) => { - const projectId = useRequiredPathParam('projectId'); - const featureId = useRequiredPathParam('featureId'); - const { feature, refetchFeature } = useFeature(projectId, featureId); - const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId); - - const defaultContent = ( - <> - {' '} - - {environment.enabled ? 'enabled' : 'disabled'} in - -   - - - ); - const { onToggle: onFeatureToggle, modals: featureToggleModals } = - useFeatureToggleSwitch(projectId); - - const handleToggle = (newState: boolean, onRollback: () => void) => - onFeatureToggle(newState, { - projectId, - featureId, - environmentName: environment.name, - environmentType: environment.type, - hasStrategies: environment.strategies.length > 0, - hasEnabledStrategies: environment.strategies.some( - (strategy) => !strategy.disabled, - ), - isChangeRequestEnabled: isChangeRequestConfigured(environment.name), - onRollback, - onSuccess: () => { - if (callback) callback(); - refetchFeature(); - }, - }); - - return ( - - - - {children ?? defaultContent} - - - {featureToggleModals} - - ); -}; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitches.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitches.tsx deleted file mode 100644 index 8092fcdcb0..0000000000 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitches.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import type { IFeatureToggle } from 'interfaces/featureToggle'; -import { FeatureOverviewSidePanelEnvironmentSwitch } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitch/FeatureOverviewSidePanelEnvironmentSwitch'; -import { Link, styled, Tooltip } from '@mui/material'; -import { Link as RouterLink } from 'react-router-dom'; -import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import VariantsWarningTooltip from 'component/feature/FeatureView/FeatureVariants/VariantsTooltipWarning'; - -const StyledContainer = styled('div')(({ theme }) => ({ - padding: theme.spacing(3), -})); - -const StyledSwitchLabel = styled('div')(() => ({ - display: 'flex', - flexDirection: 'column', -})); - -const StyledLabel = styled('p')(({ theme }) => ({ - fontSize: theme.fontSizes.bodySize, -})); - -const StyledSubLabel = styled('p')(({ theme }) => ({ - fontSize: theme.fontSizes.smallBody, - color: theme.palette.text.secondary, - display: 'flex', - alignItems: 'center', -})); - -const StyledSeparator = styled('span')(({ theme }) => ({ - padding: theme.spacing(0, 0.5), - '::after': { - content: '"-"', - }, -})); - -const StyledLink = styled(Link)(() => ({ - '&:hover, &:focus': { - textDecoration: 'underline', - }, -})); - -interface IFeatureOverviewSidePanelEnvironmentSwitchesProps { - feature: IFeatureToggle; - header: React.ReactNode; - hiddenEnvironments: Set; - setHiddenEnvironments: (environment: string) => void; -} - -export const FeatureOverviewSidePanelEnvironmentSwitches = ({ - feature, - header, - hiddenEnvironments, - setHiddenEnvironments, -}: IFeatureOverviewSidePanelEnvironmentSwitchesProps) => { - const someEnabledEnvironmentHasVariants = feature.environments.some( - (environment) => environment.enabled && environment.variants?.length, - ); - return ( - - {header} - {feature.environments.map((environment) => { - const strategiesLabel = - environment.strategies.length === 1 - ? '1 strategy' - : `${environment.strategies.length} strategies`; - - const variants = environment.variants ?? []; - - const variantsLink = variants.length > 0 && ( - <> - - - - {variants.length === 1 - ? '1 variant' - : `${variants.length} variants`} - - - - ); - - const hasWarning = - environment.enabled && - variants.length === 0 && - someEnabledEnvironmentHasVariants; - return ( - - - {environment.name} - - {strategiesLabel} - {variantsLink} - - - - - } - /> - - - - ); - })} - - ); -}; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/OldFeatureOverviewSidePanel.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/OldFeatureOverviewSidePanel.tsx deleted file mode 100644 index f26d7811a6..0000000000 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/OldFeatureOverviewSidePanel.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { Box, Divider, styled } from '@mui/material'; -import { HelpIcon } from 'component/common/HelpIcon/HelpIcon'; -import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; -import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; -import { FeatureOverviewSidePanelEnvironmentSwitches } from './FeatureOverviewSidePanelEnvironmentSwitches/FeatureOverviewSidePanelEnvironmentSwitches'; -import { FeatureOverviewSidePanelTags } from './FeatureOverviewSidePanelTags/FeatureOverviewSidePanelTags'; -import { Sticky } from 'component/common/Sticky/Sticky'; - -const StyledContainer = styled(Box)(({ theme }) => ({ - top: theme.spacing(2), - borderRadius: theme.shape.borderRadiusLarge, - backgroundColor: theme.palette.background.paper, - display: 'flex', - flexDirection: 'column', - maxWidth: '350px', - minWidth: '350px', - marginRight: '1rem', - marginTop: '1rem', - [theme.breakpoints.down(1000)]: { - marginBottom: '1rem', - width: '100%', - maxWidth: 'none', - minWidth: 'auto', - }, -})); - -const StyledHeader = styled('h3')(({ theme }) => ({ - display: 'flex', - gap: theme.spacing(1), - alignItems: 'center', - fontSize: theme.fontSizes.bodySize, - margin: 0, - marginBottom: theme.spacing(3), - - // Make the help icon align with the text. - '& > :last-child': { - position: 'relative', - top: 1, - }, -})); - -interface IFeatureOverviewSidePanelProps { - hiddenEnvironments: Set; - setHiddenEnvironments: (environment: string) => void; -} - -export const OldFeatureOverviewSidePanel = ({ - hiddenEnvironments, - setHiddenEnvironments, -}: IFeatureOverviewSidePanelProps) => { - const projectId = useRequiredPathParam('projectId'); - const featureId = useRequiredPathParam('featureId'); - const { feature } = useFeature(projectId, featureId); - const isSticky = feature.environments?.length <= 3; - - return ( - - - Enabled in environments ( - { - feature.environments.filter( - ({ enabled }) => enabled, - ).length - } - ) - - - } - feature={feature} - hiddenEnvironments={hiddenEnvironments} - setHiddenEnvironments={setHiddenEnvironments} - /> - - - Tags for this feature flag - - } - feature={feature} - /> - - ); -}; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/LegacyFeatureOverview.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/LegacyFeatureOverview.tsx deleted file mode 100644 index c44fdda5fd..0000000000 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/LegacyFeatureOverview.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { FeatureOverviewEnvironments } from './FeatureOverviewEnvironments/FeatureOverviewEnvironments'; -import { Route, Routes, useNavigate } from 'react-router-dom'; -import { SidebarModal } from 'component/common/SidebarModal/SidebarModal'; -import { - FeatureStrategyEdit, - formatFeaturePath, -} from 'component/feature/FeatureStrategy/FeatureStrategyEdit/FeatureStrategyEdit'; -import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; -import { usePageTitle } from 'hooks/usePageTitle'; -import { useHiddenEnvironments } from 'hooks/useHiddenEnvironments'; -import { styled } from '@mui/material'; -import { FeatureStrategyCreate } from 'component/feature/FeatureStrategy/FeatureStrategyCreate/FeatureStrategyCreate'; -import { useEffect } from 'react'; -import { useLastViewedFlags } from 'hooks/useLastViewedFlags'; -import OldFeatureOverviewMetaData from './FeatureOverviewMetaData/OldFeatureOverviewMetaData'; -import { OldFeatureOverviewSidePanel } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/OldFeatureOverviewSidePanel'; - -const StyledContainer = styled('div')(({ theme }) => ({ - display: 'flex', - width: '100%', - [theme.breakpoints.down(1000)]: { - flexDirection: 'column', - }, -})); - -const StyledMainContent = styled('div')(({ theme }) => ({ - display: 'flex', - flexDirection: 'column', - width: `calc(100% - (350px + 1rem))`, - [theme.breakpoints.down(1000)]: { - width: '100%', - }, -})); - -const FeatureOverview = () => { - const navigate = useNavigate(); - const projectId = useRequiredPathParam('projectId'); - const featureId = useRequiredPathParam('featureId'); - const featurePath = formatFeaturePath(projectId, featureId); - const { hiddenEnvironments, setHiddenEnvironments } = - useHiddenEnvironments(); - const onSidebarClose = () => navigate(featurePath); - usePageTitle(featureId); - const { setLastViewed } = useLastViewedFlags(); - useEffect(() => { - setLastViewed({ featureId, projectId }); - }, [featureId]); - - return ( - -
- - -
- - - - - - - - } - /> - - - - } - /> - -
- ); -}; - -export default FeatureOverview; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/LegacyReleasePlan.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/LegacyReleasePlan.tsx deleted file mode 100644 index 8188aa06d9..0000000000 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/LegacyReleasePlan.tsx +++ /dev/null @@ -1,316 +0,0 @@ -import Delete from '@mui/icons-material/Delete'; -import { styled } from '@mui/material'; -import { DELETE_FEATURE_STRATEGY } from '@server/types/permissions'; -import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton'; -import { useReleasePlansApi } from 'hooks/api/actions/useReleasePlansApi/useReleasePlansApi'; -import { useReleasePlans } from 'hooks/api/getters/useReleasePlans/useReleasePlans'; -import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; -import useToast from 'hooks/useToast'; -import type { - IReleasePlan, - IReleasePlanMilestone, -} from 'interfaces/releasePlans'; -import { useState } from 'react'; -import { formatUnknownError } from 'utils/formatUnknownError'; -import { ReleasePlanRemoveDialog } from './ReleasePlanRemoveDialog'; -import { ReleasePlanMilestone } from './ReleasePlanMilestone/LegacyReleasePlanMilestone'; -import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled'; -import { useUiFlag } from 'hooks/useUiFlag'; -import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi'; -import { usePendingChangeRequests } from 'hooks/api/getters/usePendingChangeRequests/usePendingChangeRequests'; -import { RemoveReleasePlanChangeRequestDialog } from './ChangeRequest/RemoveReleasePlanChangeRequestDialog'; -import { StartMilestoneChangeRequestDialog } from './ChangeRequest/StartMilestoneChangeRequestDialog'; -import { usePlausibleTracker } from 'hooks/usePlausibleTracker'; -import { Truncator } from 'component/common/Truncator/Truncator'; - -const StyledContainer = styled('div', { - shouldForwardProp: (prop) => prop !== 'readonly', -})<{ readonly?: boolean }>(({ theme, readonly }) => ({ - padding: theme.spacing(2), - borderRadius: theme.shape.borderRadiusMedium, - '& + &': { - marginTop: theme.spacing(2), - }, - background: readonly - ? theme.palette.background.elevation1 - : theme.palette.background.paper, -})); - -const StyledHeader = styled('div')(({ theme }) => ({ - display: 'flex', - justifyContent: 'space-between', - color: theme.palette.text.primary, -})); - -const StyledHeaderTitleContainer = styled('div')(({ theme }) => ({ - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - gap: theme.spacing(1), -})); - -const StyledHeaderTitleLabel = styled('span')(({ theme }) => ({ - fontSize: theme.fontSizes.smallerBody, - lineHeight: 0.5, - color: theme.palette.text.secondary, - marginBottom: theme.spacing(0.5), -})); - -const StyledHeaderDescription = styled('span')(({ theme }) => ({ - fontSize: theme.fontSizes.smallBody, - color: theme.palette.text.secondary, -})); - -const StyledBody = styled('div')(({ theme }) => ({ - display: 'flex', - flexDirection: 'column', - marginTop: theme.spacing(3), -})); - -const StyledConnection = styled('div')(({ theme }) => ({ - width: 4, - height: theme.spacing(2), - backgroundColor: theme.palette.divider, - marginLeft: theme.spacing(3.25), -})); - -interface IReleasePlanProps { - plan: IReleasePlan; - environmentIsDisabled?: boolean; - readonly?: boolean; -} - -export const ReleasePlan = ({ - plan, - environmentIsDisabled, - readonly, -}: IReleasePlanProps) => { - const { - id, - name, - description, - activeMilestoneId, - featureName, - environment, - milestones, - } = plan; - - const projectId = useRequiredPathParam('projectId'); - const { refetch } = useReleasePlans(projectId, featureName, environment); - const { removeReleasePlanFromFeature, startReleasePlanMilestone } = - useReleasePlansApi(); - const { setToastData, setToastApiError } = useToast(); - const { trackEvent } = usePlausibleTracker(); - - const [removeOpen, setRemoveOpen] = useState(false); - const [changeRequestDialogRemoveOpen, setChangeRequestDialogRemoveOpen] = - useState(false); - const [ - changeRequestDialogStartMilestoneOpen, - setChangeRequestDialogStartMilestoneOpen, - ] = useState(false); - const [ - milestoneForChangeRequestDialog, - setMilestoneForChangeRequestDialog, - ] = useState(); - const { isChangeRequestConfigured } = useChangeRequestsEnabled(projectId); - const { addChange } = useChangeRequestApi(); - const { refetch: refetchChangeRequests } = - usePendingChangeRequests(projectId); - - const releasePlansEnabled = useUiFlag('releasePlans'); - - const onAddRemovePlanChangesConfirm = async () => { - await addChange(projectId, environment, { - feature: featureName, - action: 'deleteReleasePlan', - payload: { - planId: plan.id, - }, - }); - - await refetchChangeRequests(); - - setToastData({ - type: 'success', - text: 'Added to draft', - }); - - setChangeRequestDialogRemoveOpen(false); - }; - - const onAddStartMilestoneChangesConfirm = async () => { - await addChange(projectId, environment, { - feature: featureName, - action: 'startMilestone', - payload: { - planId: plan.id, - milestoneId: milestoneForChangeRequestDialog?.id, - }, - }); - - await refetchChangeRequests(); - - setToastData({ - type: 'success', - text: 'Added to draft', - }); - - setChangeRequestDialogStartMilestoneOpen(false); - }; - - const confirmRemoveReleasePlan = () => { - if (releasePlansEnabled && isChangeRequestConfigured(environment)) { - setChangeRequestDialogRemoveOpen(true); - } else { - setRemoveOpen(true); - } - - trackEvent('release-management', { - props: { - eventType: 'remove-plan', - plan: name, - }, - }); - }; - - const onRemoveConfirm = async () => { - try { - await removeReleasePlanFromFeature( - projectId, - featureName, - environment, - id, - ); - setToastData({ - text: `Release plan "${name}" has been removed from ${featureName} in ${environment}`, - type: 'success', - }); - - refetch(); - setRemoveOpen(false); - } catch (error: unknown) { - setToastApiError(formatUnknownError(error)); - } - }; - - const onStartMilestone = async (milestone: IReleasePlanMilestone) => { - if (releasePlansEnabled && isChangeRequestConfigured(environment)) { - setMilestoneForChangeRequestDialog(milestone); - setChangeRequestDialogStartMilestoneOpen(true); - } else { - try { - await startReleasePlanMilestone( - projectId, - featureName, - environment, - id, - milestone.id, - ); - setToastData({ - text: `Milestone "${milestone.name}" has started`, - type: 'success', - }); - refetch(); - } catch (error: unknown) { - setToastApiError(formatUnknownError(error)); - } - } - - trackEvent('release-management', { - props: { - eventType: 'start-milestone', - plan: name, - milestone: milestone.name, - }, - }); - }; - - const activeIndex = milestones.findIndex( - (milestone) => milestone.id === activeMilestoneId, - ); - - return ( - - - - - Release plan - - {name} - - - {description} - - - - {!readonly && ( - - - - )} - - - {milestones.map((milestone, index) => ( -
- - } - /> -
- ))} -
- - setChangeRequestDialogRemoveOpen(false)} - releasePlan={plan} - environmentActive={!environmentIsDisabled} - /> - { - setMilestoneForChangeRequestDialog(undefined); - setChangeRequestDialogStartMilestoneOpen(false); - }} - releasePlan={plan} - milestone={milestoneForChangeRequestDialog} - /> -
- ); -}; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/LegacyReleasePlanMilestone.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/LegacyReleasePlanMilestone.tsx deleted file mode 100644 index b286fab7cb..0000000000 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/ReleasePlan/ReleasePlanMilestone/LegacyReleasePlanMilestone.tsx +++ /dev/null @@ -1,130 +0,0 @@ -// deprecated; remove with `flagOverviewRedesign` flag -import ExpandMore from '@mui/icons-material/ExpandMore'; -import { - Accordion, - AccordionDetails, - AccordionSummary, - styled, -} from '@mui/material'; -import type { IReleasePlanMilestone } from 'interfaces/releasePlans'; -import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import { ReleasePlanMilestoneStrategy } from './ReleasePlanMilestoneStrategy'; -import { StrategySeparator } from 'component/common/StrategySeparator/LegacyStrategySeparator'; -import { - ReleasePlanMilestoneStatus, - type MilestoneStatus, -} from './ReleasePlanMilestoneStatus'; -import { useState } from 'react'; - -const StyledAccordion = styled(Accordion, { - shouldForwardProp: (prop) => prop !== 'status', -})<{ status: MilestoneStatus }>(({ theme, status }) => ({ - border: `1px solid ${status === 'active' ? theme.palette.success.border : theme.palette.divider}`, - boxShadow: 'none', - margin: 0, - backgroundColor: theme.palette.background.paper, - '&:before': { - display: 'none', - }, -})); - -const StyledAccordionSummary = styled(AccordionSummary)({ - '& .MuiAccordionSummary-content': { - justifyContent: 'space-between', - alignItems: 'center', - minHeight: '30px', - }, -}); - -const StyledTitleContainer = styled('div')(({ theme }) => ({ - display: 'flex', - alignItems: 'start', - flexDirection: 'column', - gap: theme.spacing(0.5), -})); - -const StyledTitle = styled('span')(({ theme }) => ({ - fontWeight: theme.fontWeight.bold, -})); - -const StyledSecondaryLabel = styled('span')(({ theme }) => ({ - color: theme.palette.text.secondary, - fontSize: theme.fontSizes.smallBody, -})); - -const StyledAccordionDetails = styled(AccordionDetails)(({ theme }) => ({ - borderBottomLeftRadius: theme.shape.borderRadiusLarge, - borderBottomRightRadius: theme.shape.borderRadiusLarge, -})); - -interface IReleasePlanMilestoneProps { - milestone: IReleasePlanMilestone; - status?: MilestoneStatus; - onStartMilestone?: (milestone: IReleasePlanMilestone) => void; - readonly?: boolean; -} - -export const ReleasePlanMilestone = ({ - milestone, - status = 'not-started', - onStartMilestone, - readonly, -}: IReleasePlanMilestoneProps) => { - const [expanded, setExpanded] = useState(false); - - if (!milestone.strategies.length) { - return ( - - - - {milestone.name} - {!readonly && onStartMilestone && ( - - onStartMilestone(milestone) - } - /> - )} - - No strategies - - - ); - } - - return ( - setExpanded(expanded)} - > - }> - - {milestone.name} - {!readonly && onStartMilestone && ( - onStartMilestone(milestone)} - /> - )} - - - {milestone.strategies.length === 1 - ? `${expanded ? 'Hide' : 'View'} strategy` - : `${expanded ? 'Hide' : 'View'} ${milestone.strategies.length} strategies`} - - - - {milestone.strategies.map((strategy, index) => ( -
- 0} - show={} - /> - -
- ))} -
-
- ); -}; diff --git a/frontend/src/component/feature/FeatureView/FeatureViewHeader.tsx b/frontend/src/component/feature/FeatureView/FeatureViewHeader.tsx index 8b74ce1f33..15416c1bc4 100644 --- a/frontend/src/component/feature/FeatureView/FeatureViewHeader.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureViewHeader.tsx @@ -5,19 +5,11 @@ import { Tab, Tabs, type Theme, - Tooltip, Typography, - useMediaQuery, } from '@mui/material'; -import Archive from '@mui/icons-material/Archive'; import ArchiveOutlined from '@mui/icons-material/ArchiveOutlined'; -import FileCopy from '@mui/icons-material/FileCopy'; -import Label from '@mui/icons-material/Label'; -import WatchLater from '@mui/icons-material/WatchLater'; import WatchLaterOutlined from '@mui/icons-material/WatchLaterOutlined'; -import LibraryAdd from '@mui/icons-material/LibraryAdd'; import LibraryAddOutlined from '@mui/icons-material/LibraryAddOutlined'; -import Check from '@mui/icons-material/Check'; import Star from '@mui/icons-material/Star'; import { Link, useLocation, useNavigate } from 'react-router-dom'; import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; @@ -27,17 +19,12 @@ import { UPDATE_FEATURE, } from 'component/providers/AccessProvider/permissions'; import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton'; -import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { FeatureStatusChip } from 'component/common/FeatureStatusChip/FeatureStatusChip'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import { useFavoriteFeaturesApi } from 'hooks/api/actions/useFavoriteFeaturesApi/useFavoriteFeaturesApi'; -import { FavoriteIconButton } from 'component/common/FavoriteIconButton/FavoriteIconButton'; -import { ChildrenTooltip } from './FeatureOverview/FeatureOverviewMetaData/ChildrenTooltip'; -import copy from 'copy-to-clipboard'; import useToast from 'hooks/useToast'; import { useUiFlag } from 'hooks/useUiFlag'; import type { IFeatureToggle } from 'interfaces/featureToggle'; -import { Collaborators } from './Collaborators'; import StarBorder from '@mui/icons-material/StarBorder'; import { TooltipResolver } from 'component/common/TooltipResolver/TooltipResolver'; import { ManageTagsDialog } from './FeatureOverview/ManageTagsDialog/ManageTagsDialog'; @@ -46,7 +33,7 @@ import { FeatureArchiveDialog } from 'component/common/FeatureArchiveDialog/Feat import { FeatureArchiveNotAllowedDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveNotAllowedDialog'; import { FeatureCopyName } from './FeatureCopyName/FeatureCopyName'; -const NewStyledHeader = styled('div')(({ theme }) => ({ +const StyledHeader = styled('div')(({ theme }) => ({ backgroundColor: 'none', marginBottom: theme.spacing(2), borderBottom: `1px solid ${theme.palette.divider}`, @@ -115,67 +102,6 @@ const IconButtonWithTooltip: FC< ); }; -const StyledHeader = styled('div')(({ theme }) => ({ - backgroundColor: theme.palette.background.paper, - borderRadius: theme.shape.borderRadiusLarge, - marginBottom: theme.spacing(2), -})); - -const StyledInnerContainer = styled('div')(({ theme }) => ({ - padding: theme.spacing(2, 4, 2, 2), - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - [theme.breakpoints.down(500)]: { - flexDirection: 'column', - }, -})); - -const StyledFlagInfoContainer = styled('div')(({ theme }) => ({ - display: 'flex', - alignItems: 'center', - columnGap: theme.spacing(1), -})); - -const StyledDependency = styled('div')(({ theme }) => ({ - display: 'flex', - alignItems: 'center', - gap: theme.spacing(1), - marginTop: theme.spacing(1), - fontSize: theme.fontSizes.smallBody, - padding: theme.spacing(0.75, 1.5), - backgroundColor: theme.palette.background.elevation2, - borderRadius: `${theme.shape.borderRadiusMedium}px`, - width: 'max-content', -})); - -const StyledFeatureViewHeader = styled('h1')(({ theme }) => ({ - fontSize: theme.fontSizes.mainHeader, - fontWeight: 'normal', - display: 'flex', - alignItems: 'center', - wordBreak: 'break-all', -})); - -const StyledToolbarContainer = styled('div')({ - flexShrink: 0, - display: 'flex', -}); - -const StyledSeparator = styled('div')(({ theme }) => ({ - width: '100%', - backgroundColor: theme.palette.divider, - height: '1px', -})); - -const StyledTabRow = styled('div')(({ theme }) => ({ - display: 'flex', - flexFlow: 'row nowrap', - gap: theme.spacing(4), - paddingInline: theme.spacing(4), - justifyContent: 'space-between', -})); - const StyledTabs = styled(Tabs)({ minWidth: 0, maxWidth: '100%', @@ -280,7 +206,6 @@ type Props = { export const FeatureViewHeader: FC = ({ feature }) => { const projectId = useRequiredPathParam('projectId'); const featureId = useRequiredPathParam('featureId'); - const flagOverviewRedesign = useUiFlag('flagOverviewRedesign'); const { favorite, unfavorite } = useFavoriteFeaturesApi(); const { refetchFeature } = useFeature(projectId, featureId); const { setToastData, setToastApiError } = useToast(); @@ -289,9 +214,6 @@ export const FeatureViewHeader: FC = ({ feature }) => { const [showDelDialog, setShowDelDialog] = useState(false); const [openStaleDialog, setOpenStaleDialog] = useState(false); - const [isFeatureNameCopied, setIsFeatureNameCopied] = useState(false); - const smallScreen = useMediaQuery(`(max-width:${500}px)`); - const navigate = useNavigate(); const { pathname } = useLocation(); @@ -344,21 +266,6 @@ export const FeatureViewHeader: FC = ({ feature }) => { } }; - const handleCopyToClipboard = () => { - try { - copy(feature.name); - setIsFeatureNameCopied(true); - setTimeout(() => { - setIsFeatureNameCopied(false); - }, 3000); - } catch (error: unknown) { - setToastData({ - type: 'error', - text: 'Could not copy feature name', - }); - } - }; - const HeaderActionsInner: FC<{ showOnNarrowScreens?: boolean }> = ({ showOnNarrowScreens, }) => { @@ -375,184 +282,36 @@ export const FeatureViewHeader: FC = ({ feature }) => { return ( <> - {flagOverviewRedesign ? ( - - - - {feature.name} - - - {feature.stale ? ( - - ) : null} - - - - - {tabData.map((tab) => ( - navigate(tab.path)} - data-testid={`TAB-${tab.title}`} - /> - ))} - - - - - ) : ( - - - - + + + {feature.name} + + + {feature.stale ? : null} + + + + + {tabData.map((tab) => ( + navigate(tab.path)} + data-testid={`TAB-${tab.title}`} /> -
- - - {feature.name} - - - - {isFeatureNameCopied ? ( - - ) : ( - - )} - - - - } - /> - - 0} - show={ - - Has parent: - - { - feature.dependencies[0] - ?.feature - } - - - } - /> - 0} - show={ - - Has children: - - - } - /> -
-
- - - - - - setShowDelDialog(true)} - > - - - setOpenStaleDialog(true)} - permission={UPDATE_FEATURE} - projectId={projectId} - tooltipProps={{ - title: 'Toggle stale state', - }} - data-loading - > - - - setOpenTagDialog(true)} - permission={UPDATE_FEATURE} - projectId={projectId} - tooltipProps={{ title: 'Add tag' }} - data-loading - > - - -
- - - - {tabData.map((tab) => ( - navigate(tab.path)} - data-testid={`TAB-${tab.title}`} - /> - ))} - - - -
- )} - + ))} + + + + {feature.children.length > 0 ? ( = ({ feature }) => { featureIds={[featureId]} /> )} - { test(`${name} (legacy)`, async () => { render( - ({ color: theme.palette.divider, })); -const LegacyFeatureResultInfoPopoverCell = ({ - feature, - input, -}: FeatureResultInfoPopoverCellProps) => { - const [open, setOpen] = useState(false); - const ref = useRef(null); - - const togglePopover = () => { - setOpen(!open); - }; - - return ( - - - - - setOpen(false)} - anchorEl={ref.current} - PaperProps={{ - sx: (theme) => ({ - display: 'flex', - flexDirection: 'column', - padding: theme.spacing(6), - width: 728, - maxWidth: '100%', - height: 'auto', - overflowY: 'auto', - backgroundColor: theme.palette.background.elevation2, - borderRadius: theme.shape.borderRadius, - }), - }} - anchorOrigin={{ - vertical: 'top', - horizontal: 'right', - }} - transformOrigin={{ - vertical: 'center', - horizontal: 'left', - }} - > - setOpen(false)} - /> - - - - ); -}; - export const NewFeatureResultInfoPopoverCell = ({ feature, input, @@ -136,15 +77,9 @@ export const NewFeatureResultInfoPopoverCell = ({ export const FeatureResultInfoPopoverCell = ( props: FeatureResultInfoPopoverCellProps, ) => { - const useNewStrategyDesign = useUiFlag('flagOverviewRedesign'); - if (!props.feature) { return null; } - return useNewStrategyDesign ? ( - - ) : ( - - ); + return ; }; diff --git a/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureStrategyList/LegacyPlaygroundResultFeatureStrategyList.tsx b/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureStrategyList/LegacyPlaygroundResultFeatureStrategyList.tsx deleted file mode 100644 index 451856d8ef..0000000000 --- a/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureStrategyList/LegacyPlaygroundResultFeatureStrategyList.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { - PlaygroundResultStrategyLists, - WrappedPlaygroundResultStrategyList, -} from './StrategyList/LegacyPlaygroundResultStrategyLists'; -import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import type { PlaygroundFeatureSchema, PlaygroundRequestSchema } from 'openapi'; -import { Alert } from '@mui/material'; - -interface PlaygroundResultFeatureStrategyListProps { - feature: PlaygroundFeatureSchema; - input?: PlaygroundRequestSchema; -} - -export const PlaygroundResultFeatureStrategyList = ({ - feature, - input, -}: PlaygroundResultFeatureStrategyListProps) => { - const enabledStrategies = feature.strategies?.data?.filter( - (strategy) => !strategy.disabled, - ); - const disabledStrategies = feature.strategies?.data?.filter( - (strategy) => strategy.disabled, - ); - - const showDisabledStrategies = disabledStrategies?.length > 0; - - return ( - <> - - There are no strategies added to this feature flag in - selected environment. - - } - /> - - } - elseShow={ - <> - - - } - /> - - } - /> - - ); -}; diff --git a/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureStrategyList/PlaygroundResultFeatureStrategyList.test.tsx b/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureStrategyList/PlaygroundResultFeatureStrategyList.test.tsx index 0d4b27944d..45e1d9a7cb 100644 --- a/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureStrategyList/PlaygroundResultFeatureStrategyList.test.tsx +++ b/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureStrategyList/PlaygroundResultFeatureStrategyList.test.tsx @@ -1,8 +1,7 @@ +import { vi } from 'vitest'; import { screen } from '@testing-library/react'; import { render } from 'utils/testRenderer'; import type { PlaygroundFeatureSchema, PlaygroundRequestSchema } from 'openapi'; -import { PlaygroundResultFeatureStrategyList as LegacyPlaygroundResultFeatureStrategyList } from './LegacyPlaygroundResultFeatureStrategyList'; -import { vi } from 'vitest'; import { PlaygroundResultFeatureStrategyList } from './PlaygroundResultsFeatureStrategyList'; const testCases = [ @@ -139,7 +138,7 @@ afterAll(() => { testCases.forEach(({ name, feature, expectedText }) => { test(`${name} (legacy)`, async () => { render( - ({ - display: 'flex', - padding: `0, 4px`, - flexDirection: 'column', - borderRadius: theme.shape.borderRadiusMedium, - border: `1px solid ${theme.palette.warning.border}`, -})); - -const StyledListWrapper = styled('div')(({ theme }) => ({ - padding: theme.spacing(1, 0.5), -})); - -const StyledAlert = styled(Alert)(({ theme }) => ({ - border: '0!important', - borderBottomLeftRadius: 0, - borderBottomRightRadius: 0, - borderBottom: `1px solid ${theme.palette.warning.border}!important`, -})); - -interface PlaygroundResultStrategyListProps { - strategies: PlaygroundStrategySchema[]; - input?: PlaygroundRequestSchema; - titlePrefix?: string; - infoText?: string; -} - -const StyledSubtitle = styled(Typography)(({ theme }) => ({ - margin: theme.spacing(2, 1, 2, 0), - color: 'text.secondary', -})); - -export const PlaygroundResultStrategyLists = ({ - strategies, - input, - titlePrefix, - infoText, -}: PlaygroundResultStrategyListProps) => ( - 0} - show={ - <> - {`${ - titlePrefix - ? titlePrefix.concat(' strategies') - : 'Strategies' - } (${strategies?.length})`} - - {infoText} - - } - /> - - {strategies?.map((strategy, index) => ( - - 0} - show={} - /> - - - ))} - - - } - /> -); - -interface IWrappedPlaygroundResultStrategyListProps { - feature: PlaygroundFeatureSchema; - input?: PlaygroundRequestSchema; -} - -const resolveHintText = (feature: PlaygroundFeatureSchema) => { - if ( - feature.hasUnsatisfiedDependency && - !feature.isEnabledInCurrentEnvironment - ) { - return 'If the environment was enabled and parent dependencies were satisfied'; - } - if (feature.hasUnsatisfiedDependency) { - return 'If parent dependencies were satisfied'; - } - if (!feature.isEnabledInCurrentEnvironment) { - return 'If the environment was enabled'; - } - return ''; -}; - -export const WrappedPlaygroundResultStrategyList = ({ - feature, - input, -}: IWrappedPlaygroundResultStrategyListProps) => { - const enabledStrategies = feature.strategies?.data?.filter( - (strategy) => !strategy.disabled, - ); - const disabledStrategies = feature.strategies?.data?.filter( - (strategy) => strategy.disabled, - ); - - const showDisabledStrategies = disabledStrategies?.length > 0; - - return ( - - - {resolveHintText(feature)}, then this feature flag would be{' '} - {feature.strategies?.result ? 'TRUE' : 'FALSE'} with strategies - evaluated like this:{' '} - - - - - - - - } - /> - - ); -}; diff --git a/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureStrategyList/StrategyList/StrategyItem/LegacyFeatureStrategyItem.tsx b/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureStrategyList/StrategyList/StrategyItem/LegacyFeatureStrategyItem.tsx deleted file mode 100644 index ca0e77a358..0000000000 --- a/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureStrategyList/StrategyList/StrategyItem/LegacyFeatureStrategyItem.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { useTheme } from '@mui/material'; -import { PlaygroundResultChip } from '../../../../PlaygroundResultChip/LegacyPlaygroundResultChip'; -import type { - PlaygroundStrategySchema, - PlaygroundRequestSchema, -} from 'openapi'; -import { StrategyExecution } from './PlaygroundStrategyExecution/LegacyStrategyExecution'; -import { objectId } from 'utils/objectId'; -import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import { DisabledStrategyExecution } from './PlaygroundStrategyExecution/DisabledStrategyExecution'; -import { StrategyItemContainer } from 'component/common/StrategyItemContainer/LegacyStrategyItemContainer'; - -interface IFeatureStrategyItemProps { - strategy: PlaygroundStrategySchema; - index: number; - input?: PlaygroundRequestSchema; -} - -export const FeatureStrategyItem = ({ - strategy, - input, - index, -}: IFeatureStrategyItemProps) => { - const { result } = strategy; - const theme = useTheme(); - const label = - result.evaluationStatus === 'incomplete' || - result.evaluationStatus === 'unevaluated' - ? 'Unevaluated' - : result.enabled - ? 'True' - : 'False'; - - return ( - - } - > - - } - elseShow={ - - } - /> - - ); -}; diff --git a/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureStrategyList/StrategyList/StrategyItem/PlaygroundStrategyExecution/PlaygroundStrategyExecution.tsx b/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureStrategyList/StrategyList/StrategyItem/PlaygroundStrategyExecution/PlaygroundStrategyExecution.tsx index f9466c6bda..b1e41cd573 100644 --- a/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureStrategyList/StrategyList/StrategyItem/PlaygroundStrategyExecution/PlaygroundStrategyExecution.tsx +++ b/frontend/src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureStrategyList/StrategyList/StrategyItem/PlaygroundStrategyExecution/PlaygroundStrategyExecution.tsx @@ -75,7 +75,7 @@ export const PlaygroundStrategyExecution: FC = ({ {param} )), name === 'default' && ( - + The standard strategy is ON for all users. diff --git a/frontend/src/component/playground/Playground/PlaygroundResultsTable/PlaygroundResultChip/LegacyPlaygroundResultChip.tsx b/frontend/src/component/playground/Playground/PlaygroundResultsTable/PlaygroundResultChip/LegacyPlaygroundResultChip.tsx index f5cb4a2df0..9507cf68db 100644 --- a/frontend/src/component/playground/Playground/PlaygroundResultsTable/PlaygroundResultChip/LegacyPlaygroundResultChip.tsx +++ b/frontend/src/component/playground/Playground/PlaygroundResultsTable/PlaygroundResultChip/LegacyPlaygroundResultChip.tsx @@ -1,12 +1,4 @@ -// deprecated; remove with 'flagOverviewRedesign' flag import type { VFC } from 'react'; -import { useTheme } from '@mui/material'; -import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import { ReactComponent as FeatureEnabledIcon } from 'assets/icons/isenabled-true.svg'; -import { ReactComponent as FeatureDisabledIcon } from 'assets/icons/isenabled-false.svg'; -import WarningOutlined from '@mui/icons-material/WarningOutlined'; -import { Badge } from 'component/common/Badge/Badge'; -import { useUiFlag } from 'hooks/useUiFlag'; import { PlaygroundResultChip as NewPlaygroundResultChip } from './PlaygroundResultChip'; interface IResultChipProps { @@ -16,74 +8,17 @@ interface IResultChipProps { showIcon?: boolean; } +/** + * @deprecated remove with 'flagOverviewRedesign' flag. This pollutes a lot of places in the codebase 😞 + */ export const PlaygroundResultChip: VFC = ({ enabled, label, showIcon = true, -}) => { - const theme = useTheme(); - const flagOverviewRedesign = useUiFlag('flagOverviewRedesign'); - if (flagOverviewRedesign) { - return ( - - ); - } - const icon = ( - } - elseShow={ - - } - elseShow={ - - } - /> - } - /> - ); - - return ( - - {label} - - } - elseShow={ - - {label} - - } - elseShow={ - - {label} - - } - /> - } - /> - ); -}; +}) => ( + +); diff --git a/frontend/src/component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/FeatureToggleSwitch.tsx b/frontend/src/component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/FeatureToggleSwitch.tsx index 538521e7c2..9d0ba07c9f 100644 --- a/frontend/src/component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/FeatureToggleSwitch.tsx +++ b/frontend/src/component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/FeatureToggleSwitch.tsx @@ -46,8 +46,8 @@ export const FeatureToggleSwitch: VFC = ({ ({ @@ -30,7 +28,6 @@ export const ProjectDefaultStrategySettings = () => { const { project } = useProjectOverview(projectId); const navigate = useNavigate(); usePageTitle(`Project default strategy configuration – ${projectName}`); - const flagOverviewRedesign = useUiFlag('flagOverviewRedesign'); if ( !hasAccess( @@ -64,19 +61,12 @@ export const ProjectDefaultStrategySettings = () => { specific environment. These will be used when you enable a toggle environment that has no strategies defined - {project?.environments.map((environment) => - flagOverviewRedesign ? ( - - ) : ( - - ), - )} + {project?.environments.map((environment) => ( + + ))} prop !== 'enabled', -})<{ enabled: boolean }>(({ theme, enabled }) => ({ - borderRadius: theme.shape.borderRadiusLarge, - marginBottom: theme.spacing(2), - backgroundColor: enabled - ? theme.palette.background.paper - : theme.palette.envAccordion.disabled, -})); - -const StyledAccordion = styled(Accordion)({ - boxShadow: 'none', - background: 'none', -}); - -const StyledAccordionSummary = styled(AccordionSummary)(({ theme }) => ({ - boxShadow: 'none', - padding: theme.spacing(2, 4), - pointerEvents: 'none', - [theme.breakpoints.down(400)]: { - padding: theme.spacing(1, 2), - }, -})); - -const StyledAccordionDetails = styled(AccordionDetails)(({ theme }) => ({ - padding: theme.spacing(3), - background: theme.palette.envAccordion.expanded, - borderBottomLeftRadius: theme.shape.borderRadiusLarge, - borderBottomRightRadius: theme.shape.borderRadiusLarge, - boxShadow: theme.boxShadows.accordionFooter, - - [theme.breakpoints.down('md')]: { - padding: theme.spacing(2, 1), - }, -})); - -const StyledAccordionBody = styled('div')(({ theme }) => ({ - width: '100%', - position: 'relative', - paddingBottom: theme.spacing(2), -})); - -const StyledAccordionBodyInnerContainer = styled('div')(({ theme }) => ({ - [theme.breakpoints.down(400)]: { - padding: theme.spacing(1), - }, -})); - -const StyledHeader = styled('div', { - shouldForwardProp: (prop) => prop !== 'enabled', -})<{ enabled: boolean }>(({ theme, enabled }) => ({ - display: 'flex', - justifyContent: 'center', - flexDirection: 'column', - color: enabled ? theme.palette.text.primary : theme.palette.text.secondary, -})); - -const StyledHeaderTitle = styled('div')(({ theme }) => ({ - display: 'flex', - alignItems: 'center', - fontWeight: 'bold', - [theme.breakpoints.down(560)]: { - flexDirection: 'column', - textAlign: 'center', - }, -})); - -const StyledEnvironmentIcon = styled(EnvironmentIcon)(({ theme }) => ({ - [theme.breakpoints.down(560)]: { - marginBottom: '0.5rem', - }, -})); - -const StyledStringTruncator = styled(StringTruncator)(({ theme }) => ({ - fontSize: theme.fontSizes.bodySize, - fontWeight: theme.typography.fontWeightMedium, - [theme.breakpoints.down(560)]: { - textAlign: 'center', - }, -})); - -const ProjectEnvironment = ({ environment }: IProjectEnvironmentProps) => { - const { environment: name } = environment; - const description = `Default strategy configuration in the ${name} environment`; - const theme = useTheme(); - const enabled = false; - - return ( - - e.stopPropagation()} - data-testid={`${PROJECT_ENVIRONMENT_ACCORDION}_${name}`} - className={`environment-accordion ${ - enabled ? '' : 'accordion-disabled' - }`} - style={{ - outline: `2px solid ${theme.palette.divider}`, - backgroundColor: theme.palette.background.paper, - }} - > - - - - -
- -
-
-
-
- - - - - - - - -
-
- ); -}; - -export default ProjectEnvironment; diff --git a/frontend/src/component/project/Project/ProjectSettings/ProjectDefaultStrategySettings/ProjectEnvironment/ProjectEnvironmentDefaultStrategy/LegacyProjectEnvironmentDefaultStrategy.tsx b/frontend/src/component/project/Project/ProjectSettings/ProjectDefaultStrategySettings/ProjectEnvironment/ProjectEnvironmentDefaultStrategy/LegacyProjectEnvironmentDefaultStrategy.tsx deleted file mode 100644 index 3a893bb2b5..0000000000 --- a/frontend/src/component/project/Project/ProjectSettings/ProjectDefaultStrategySettings/ProjectEnvironment/ProjectEnvironmentDefaultStrategy/LegacyProjectEnvironmentDefaultStrategy.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; -import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton'; -import { Link } from 'react-router-dom'; -import Edit from '@mui/icons-material/Edit'; -import { StrategyExecution } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyExecution/StrategyExecution'; -import type { ProjectEnvironmentType } from 'interfaces/environments'; -import { useMemo } from 'react'; -import type { CreateFeatureStrategySchema } from 'openapi'; -import { - PROJECT_DEFAULT_STRATEGY_WRITE, - UPDATE_PROJECT, -} from '@server/types/permissions'; -import { VariantsSplitPreview } from 'component/common/VariantsSplitPreview/VariantsSplitPreview'; -import { StrategyItemContainer } from 'component/common/StrategyItemContainer/LegacyStrategyItemContainer'; - -interface ProjectEnvironmentDefaultStrategyProps { - environment: ProjectEnvironmentType; - description: string; -} - -export const formatEditProjectEnvironmentStrategyPath = ( - projectId: string, - environmentId: string, -): string => { - const params = new URLSearchParams({ environmentId }); - - return `/projects/${projectId}/settings/default-strategy/edit?${params}`; -}; - -const DEFAULT_STRATEGY: CreateFeatureStrategySchema = { - name: 'flexibleRollout', - disabled: false, - constraints: [], - title: '', - parameters: { - rollout: '100', - stickiness: 'default', - groupId: '', - }, -}; - -const ProjectEnvironmentDefaultStrategy = ({ - environment, - description, -}: ProjectEnvironmentDefaultStrategyProps) => { - const projectId = useRequiredPathParam('projectId'); - const { environment: environmentId, defaultStrategy } = environment; - - const editStrategyPath = formatEditProjectEnvironmentStrategyPath( - projectId, - environmentId, - ); - - const strategy: CreateFeatureStrategySchema = useMemo(() => { - return defaultStrategy ? defaultStrategy : DEFAULT_STRATEGY; - }, [JSON.stringify(defaultStrategy)]); - - return ( - <> - - - - - - } - > - - - {strategy.variants && strategy.variants.length > 0 ? ( - - ) : null} - - - ); -}; - -export default ProjectEnvironmentDefaultStrategy; diff --git a/frontend/src/component/releases/ReleasePlanTemplate/TemplateForm/MilestoneList/MilestoneCard/LegacyMilestoneCard.tsx b/frontend/src/component/releases/ReleasePlanTemplate/TemplateForm/MilestoneList/MilestoneCard/LegacyMilestoneCard.tsx deleted file mode 100644 index 1b346a56f2..0000000000 --- a/frontend/src/component/releases/ReleasePlanTemplate/TemplateForm/MilestoneList/MilestoneCard/LegacyMilestoneCard.tsx +++ /dev/null @@ -1,528 +0,0 @@ -import { - Box, - Button, - Card, - Grid, - Popover, - styled, - Accordion, - AccordionSummary, - AccordionDetails, - IconButton, - FormHelperText, -} from '@mui/material'; -import Delete from '@mui/icons-material/DeleteOutlined'; -import type { IReleasePlanMilestoneStrategy } from 'interfaces/releasePlans'; -import { type DragEventHandler, type RefObject, useRef, useState } from 'react'; -import ExpandMore from '@mui/icons-material/ExpandMore'; -import { MilestoneCardName } from './MilestoneCardName'; -import { MilestoneStrategyMenuCards } from './MilestoneStrategyMenu/MilestoneStrategyMenuCards'; -import { MilestoneStrategyDraggableItem } from './MilestoneStrategyDraggableItem'; -import { SidebarModal } from 'component/common/SidebarModal/SidebarModal'; -import { ReleasePlanTemplateAddStrategyForm } from '../../MilestoneStrategy/ReleasePlanTemplateAddStrategyForm'; -import DragIndicator from '@mui/icons-material/DragIndicator'; -import { type OnMoveItem, useDragItem } from 'hooks/useDragItem'; -import type { IExtendedMilestonePayload } from 'component/releases/hooks/useTemplateForm'; - -const StyledMilestoneCard = styled(Card, { - shouldForwardProp: (prop) => prop !== 'hasError', -})<{ hasError: boolean }>(({ theme, hasError }) => ({ - marginTop: theme.spacing(2), - display: 'flex', - flexDirection: 'column', - justifyContent: 'space-between', - boxShadow: 'none', - border: `1px solid ${hasError ? theme.palette.error.border : theme.palette.divider}`, - borderRadius: theme.shape.borderRadiusMedium, - [theme.breakpoints.down('sm')]: { - justifyContent: 'center', - }, - transition: 'background-color 0.2s ease-in-out', - backgroundColor: theme.palette.background.default, -})); - -const StyledMilestoneCardBody = styled(Box)(({ theme }) => ({ - padding: theme.spacing(2, 2), -})); - -const StyledGridItem = styled(Grid)(({ theme }) => ({ - display: 'flex', - alignItems: 'center', -})); - -const StyledAddStrategyButton = styled(Button)(({ theme }) => ({})); - -const StyledAccordion = styled(Accordion)(({ theme }) => ({ - marginTop: theme.spacing(2), - boxShadow: 'none', - background: 'none', - display: 'flex', - flexDirection: 'column', - justifyContent: 'space-between', - border: `1px solid ${theme.palette.divider}`, - borderRadius: theme.shape.borderRadiusMedium, - [theme.breakpoints.down('sm')]: { - justifyContent: 'center', - }, - backgroundColor: theme.palette.background.default, - '&:before': { - opacity: '0 !important', - }, - '&.Mui-expanded': { marginTop: `${theme.spacing(2)} !important` }, -})); - -const StyledAccordionSummary = styled(AccordionSummary)(({ theme }) => ({ - boxShadow: 'none', - padding: theme.spacing(1.5, 2), - borderRadius: theme.shape.borderRadiusMedium, - [theme.breakpoints.down(400)]: { - padding: theme.spacing(1, 2), - }, - '&.Mui-focusVisible': { - backgroundColor: theme.palette.background.paper, - padding: theme.spacing(0.5, 2, 0.3, 2), - }, -})); - -const StyledAccordionDetails = styled(AccordionDetails)(({ theme }) => ({ - borderBottomLeftRadius: theme.shape.borderRadiusMedium, - borderBottomRightRadius: theme.shape.borderRadiusMedium, - padding: theme.spacing(0), - [theme.breakpoints.down('md')]: { - padding: theme.spacing(2, 1), - }, - backgroundColor: theme.palette.neutral.light, -})); - -const StyledAccordionFooter = styled(Grid)(({ theme }) => ({ - padding: theme.spacing(2), - paddingTop: 0, - backgroundColor: theme.palette.background.default, - borderRadius: theme.shape.borderRadiusMedium, -})); - -const StyledMilestoneActionGrid = styled(Grid)(({ theme }) => ({ - display: 'flex', - justifyContent: 'flex-end', -})); - -const StyledIconButton = styled(IconButton)(({ theme }) => ({ - marginLeft: theme.spacing(1), - color: theme.palette.primary.main, -})); - -const StyledDragIcon = styled(IconButton)(({ theme }) => ({ - padding: 0, - cursor: 'grab', - transition: 'color 0.2s ease-in-out', - marginRight: theme.spacing(1), - '& > svg': { - color: 'action.active', - }, -})); - -export interface IMilestoneCardProps { - milestone: IExtendedMilestonePayload; - milestoneChanged: (milestone: IExtendedMilestonePayload) => void; - errors: { [key: string]: string }; - clearErrors: () => void; - removable: boolean; - onDeleteMilestone: () => void; - index: number; - onMoveItem: OnMoveItem; -} - -export const MilestoneCard = ({ - milestone, - milestoneChanged, - errors, - clearErrors, - removable, - onDeleteMilestone, - index, - onMoveItem, -}: IMilestoneCardProps) => { - const [anchor, setAnchor] = useState(); - const [dragItem, setDragItem] = useState<{ - id: string; - index: number; - height: number; - } | null>(null); - const [addUpdateStrategyOpen, setAddUpdateStrategyOpen] = useState(false); - const [strategyModeEdit, setStrategyModeEdit] = useState(false); - const [expanded, setExpanded] = useState(Boolean(milestone.startExpanded)); - const isPopoverOpen = Boolean(anchor); - const popoverId = isPopoverOpen - ? 'MilestoneStrategyMenuPopover' - : undefined; - - const dragHandleRef = useRef(null); - - const dragItemRef = useDragItem( - index, - onMoveItem, - dragHandleRef, - ); - - const dragHandle = ( - - - - ); - - const onClose = () => { - setAnchor(undefined); - }; - - const [currentStrategy, setCurrentStrategy] = useState< - Omit - >({ - name: 'flexibleRollout', - parameters: { rollout: '50' }, - constraints: [], - title: '', - id: 'temp', - }); - - const milestoneStrategyChanged = ( - strategy: Omit, - ) => { - const strategies = milestone.strategies || []; - milestoneChanged({ - ...milestone, - strategies: [ - ...strategies.map((strat) => - strat.id === strategy.id ? strategy : strat, - ), - ], - }); - }; - - const milestoneStrategyAdded = ( - strategy: Omit, - ) => { - milestoneChanged({ - ...milestone, - strategies: [ - ...(milestone.strategies || []), - { - ...strategy, - strategyName: strategy.strategyName, - sortOrder: milestone.strategies?.length || 0, - }, - ], - }); - }; - - const addUpdateStrategy = ( - strategy: Omit, - ) => { - const existingStrategy = milestone.strategies?.find( - (strat) => strat.id === strategy.id, - ); - if (existingStrategy) { - milestoneStrategyChanged(strategy); - } else { - milestoneStrategyAdded(strategy); - setExpanded(true); - } - setAddUpdateStrategyOpen(false); - setStrategyModeEdit(false); - setCurrentStrategy({ - name: 'flexibleRollout', - parameters: { rollout: '50' }, - constraints: [], - title: '', - id: 'temp', - }); - clearErrors(); - }; - - const openAddUpdateStrategyForm = ( - strategy: Omit, - editing: boolean, - ) => { - setStrategyModeEdit(editing); - setCurrentStrategy(strategy); - setAddUpdateStrategyOpen(true); - }; - - const onStrategyDragOver = - (targetId: string) => - ( - ref: RefObject, - targetIndex: number, - ): DragEventHandler => - (event) => { - if (dragItem === null || ref.current === null) return; - if (dragItem.index === targetIndex || targetId === dragItem.id) - return; - - const { top, bottom } = ref.current.getBoundingClientRect(); - const overTargetTop = event.clientY - top < dragItem.height; - const overTargetBottom = bottom - event.clientY < dragItem.height; - const draggingUp = dragItem.index > targetIndex; - - // prevent oscillating by only reordering if there is sufficient space - if ( - (overTargetTop && draggingUp) || - (overTargetBottom && !draggingUp) - ) { - const oldStrategies = milestone.strategies || []; - const newStrategies = [...oldStrategies]; - const movedStrategy = newStrategies.splice( - dragItem.index, - 1, - )[0]; - newStrategies.splice(targetIndex, 0, movedStrategy); - milestoneChanged({ ...milestone, strategies: newStrategies }); - setDragItem({ - ...dragItem, - index: targetIndex, - }); - } - }; - - const onStrategyDragStartRef = - ( - ref: RefObject, - index: number, - ): DragEventHandler => - (event) => { - if (!ref.current || !milestone.strategies) { - return; - } - - setDragItem({ - id: milestone.strategies[index]?.id, - index, - height: ref.current?.offsetHeight || 0, - }); - - if (ref?.current) { - event.dataTransfer.effectAllowed = 'move'; - event.dataTransfer.setData('text/html', ref.current.outerHTML); - event.dataTransfer.setDragImage(ref.current, 20, 20); - } - }; - const onStrategyDragEnd = () => { - setDragItem(null); - onReOrderStrategies(); - }; - - const onReOrderStrategies = () => { - if (!milestone.strategies) { - return; - } - const newStrategies = [...milestone.strategies]; - newStrategies.forEach((strategy, index) => { - strategy.sortOrder = index; - }); - milestoneChanged({ ...milestone, strategies: newStrategies }); - }; - - const milestoneStrategyDeleted = (strategyId: string) => { - const strategies = milestone.strategies || []; - milestoneChanged({ - ...milestone, - strategies: [ - ...strategies.filter((strat) => strat.id !== strategyId), - ], - }); - }; - - const milestoneNameChanged = (name: string) => { - milestoneChanged({ ...milestone, name }); - }; - - if (!milestone.strategies || milestone.strategies.length === 0) { - return ( - <> - - - - - {dragHandle} - - - - - - - - - ({ - paddingBottom: theme.spacing(1), - }), - }} - > - { - openAddUpdateStrategyForm( - strategy, - false, - ); - }} - /> - - - - - - - - {errors?.[milestone.id]} - - - { - setAddUpdateStrategyOpen(false); - setStrategyModeEdit(false); - }} - open={addUpdateStrategyOpen} - > - { - setAddUpdateStrategyOpen(false); - setStrategyModeEdit(false); - }} - editMode={strategyModeEdit} - /> - - - ); - } - - return ( - <> - setExpanded(change)} - > - } - ref={dragItemRef} - > - {dragHandle} - - - - {milestone.strategies.map((strg, index) => ( -
- - milestoneStrategyDeleted(strg.id) - } - onEditClick={() => { - openAddUpdateStrategyForm(strg, true); - }} - isDragging={dragItem?.id === strg.id} - strategy={strg} - /> -
- ))} - - setAnchor(ev.currentTarget)} - > - Add strategy - - - ({ - paddingBottom: theme.spacing(1), - }), - }} - > - { - openAddUpdateStrategyForm(strategy, false); - }} - /> - - -
-
- - - {errors?.[milestone.id]} - - - { - setAddUpdateStrategyOpen(false); - setStrategyModeEdit(false); - }} - open={addUpdateStrategyOpen} - > - { - setAddUpdateStrategyOpen(false); - setStrategyModeEdit(false); - }} - editMode={strategyModeEdit} - /> - - - ); -}; diff --git a/frontend/src/component/releases/ReleasePlanTemplate/TemplateForm/MilestoneList/MilestoneList.tsx b/frontend/src/component/releases/ReleasePlanTemplate/TemplateForm/MilestoneList/MilestoneList.tsx index a4454dc527..689d265889 100644 --- a/frontend/src/component/releases/ReleasePlanTemplate/TemplateForm/MilestoneList/MilestoneList.tsx +++ b/frontend/src/component/releases/ReleasePlanTemplate/TemplateForm/MilestoneList/MilestoneList.tsx @@ -4,8 +4,6 @@ import Add from '@mui/icons-material/Add'; import { v4 as uuidv4 } from 'uuid'; import { useCallback } from 'react'; import type { OnMoveItem } from 'hooks/useDragItem'; -import { MilestoneCard as LegacyMilestoneCard } from './MilestoneCard/LegacyMilestoneCard'; -import { useUiFlag } from 'hooks/useUiFlag'; import { MilestoneCard } from './MilestoneCard/MilestoneCard'; interface IMilestoneListProps { @@ -30,10 +28,9 @@ export const MilestoneList = ({ clearErrors, milestoneChanged, }: IMilestoneListProps) => { - const useNewMilestoneCard = useUiFlag('flagOverviewRedesign'); const onMoveItem: OnMoveItem = useCallback( async ({ dragIndex, dropIndex, event, draggedElement }) => { - if (useNewMilestoneCard && event.type === 'drop') { + if (event.type === 'drop') { return; // the user has let go, we should leave the current sort order as it is currently visually displayed } @@ -83,12 +80,10 @@ export const MilestoneList = ({ ); }; - const Card = useNewMilestoneCard ? MilestoneCard : LegacyMilestoneCard; - return ( <> {milestones.map((milestone, index) => ( - { - const { trackEvent } = usePlausibleTracker(); - - const { value: globalStore, setValue: setGlobalStore } = - useGlobalLocalStorage(); - const [hiddenEnvironments, setStoredHiddenEnvironments] = useState< - Set - >(new Set(globalStore.hiddenEnvironments)); - - const setHiddenEnvironments = (environment: string) => { - setGlobalStore((params) => { - const hiddenEnvironments = new Set( - Array.from(params.hiddenEnvironments || []), - ); - if (hiddenEnvironments.has(environment)) { - hiddenEnvironments.delete(environment); - trackEvent('hidden_environment', { - props: { - eventType: `environment unhidden`, - }, - }); - } else { - hiddenEnvironments.add(environment); - } - setStoredHiddenEnvironments(hiddenEnvironments); - - return { - ...globalStore, - hiddenEnvironments: [...hiddenEnvironments], - }; - }); - }; - - return { - hiddenEnvironments, - setHiddenEnvironments, - }; -}; diff --git a/frontend/src/interfaces/uiConfig.ts b/frontend/src/interfaces/uiConfig.ts index 8219614d11..892eb10511 100644 --- a/frontend/src/interfaces/uiConfig.ts +++ b/frontend/src/interfaces/uiConfig.ts @@ -87,7 +87,6 @@ export type UiFlags = { 'enterprise-payg'?: boolean; productivityReportEmail?: boolean; showUserDeviceCount?: boolean; - flagOverviewRedesign?: boolean; consumptionModel?: boolean; edgeObservability?: boolean; adminNavUI?: boolean; diff --git a/frontend/src/themes/theme.ts b/frontend/src/themes/theme.ts index 1e31aa06cd..b98bb72240 100644 --- a/frontend/src/themes/theme.ts +++ b/frontend/src/themes/theme.ts @@ -1,6 +1,5 @@ import { createTheme } from '@mui/material/styles'; import { colors } from './colors'; -import { alpha } from '@mui/material'; import { focusable } from 'themes/themeStyles'; export const baseTheme = { @@ -517,17 +516,6 @@ export const lightTheme = createTheme({ '&:first-of-type, &:last-of-type': { borderRadius: theme.shape.borderRadiusLarge, }, - // Environment accordion -- remove with `flagOverviewRedesign` flag - '&.environment-accordion.Mui-expanded': { - outline: `2px solid ${alpha( - theme.palette.background.alternative, - 0.6, - )}`, - boxShadow: `0px 2px 8px ${alpha( - theme.palette.primary.main, - 0.2, - )}`, - }, }), }, }, diff --git a/src/lib/types/experimental.ts b/src/lib/types/experimental.ts index 0ed6d44a39..ebab8e76e3 100644 --- a/src/lib/types/experimental.ts +++ b/src/lib/types/experimental.ts @@ -51,7 +51,6 @@ export type IFlagKey = | 'productivityReportEmail' | 'productivityReportUnsubscribers' | 'enterprise-payg' - | 'flagOverviewRedesign' | 'showUserDeviceCount' | 'memorizeStats' | 'streaming' @@ -262,10 +261,6 @@ const flags: IFlags = { process.env.UNLEASH_EXPERIMENTAL_SHOW_USER_DEVICE_COUNT, false, ), - flagOverviewRedesign: parseEnvVarBoolean( - process.env.UNLEASH_EXPERIMENTAL_FLAG_OVERVIEW_REDESIGN, - false, - ), streaming: parseEnvVarBoolean( process.env.UNLEASH_EXPERIMENTAL_STREAMING, false, diff --git a/src/server-dev.ts b/src/server-dev.ts index 71b7794d74..5c57072ca8 100644 --- a/src/server-dev.ts +++ b/src/server-dev.ts @@ -50,7 +50,6 @@ process.nextTick(async () => { webhookDomainLogging: true, releasePlans: false, showUserDeviceCount: true, - flagOverviewRedesign: true, deltaApi: true, uniqueSdkTracking: true, filterExistingFlagNames: true,