diff --git a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/ChangeTabComponents.tsx b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/ChangeTabComponents.tsx new file mode 100644 index 0000000000..76807f37e0 --- /dev/null +++ b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/ChangeTabComponents.tsx @@ -0,0 +1,54 @@ +import { + Tab as MuiTab, + TabPanel as MuiTabPanel, + Tabs as MuiTabs, + TabsList as MuiTabsList, +} from '@mui/base'; +import { Button, type ButtonProps, styled } from '@mui/material'; +import type { PropsWithChildren } from 'react'; + +export const TabList = styled(MuiTabsList)(({ theme }) => ({ + display: 'inline-flex', + flexDirection: 'row', + gap: theme.spacing(0.5), +})); + +const StyledButton = styled(Button)(({ theme }) => ({ + whiteSpace: 'nowrap', + color: theme.palette.text.secondary, + fontWeight: 'normal', + '&[aria-selected="true"]': { + fontWeight: 'bold', + color: theme.palette.primary.main, + background: theme.palette.background.elevation1, + }, +})); + +export const Tab = styled(({ children }: ButtonProps) => ( + {children} +))(({ theme }) => ({ + position: 'absolute', + top: theme.spacing(-0.5), + left: theme.spacing(2), + transform: 'translateY(-50%)', + padding: theme.spacing(0.75, 1), + lineHeight: 1, + fontSize: theme.fontSizes.smallerBody, + color: theme.palette.text.primary, + background: theme.palette.background.application, + borderRadius: theme.shape.borderRadiusExtraLarge, + zIndex: theme.zIndex.fab, + textTransform: 'uppercase', +})); + +export const Tabs = ({ children }: PropsWithChildren) => ( + + {children} + +); + +export const TabPanel = MuiTabPanel; diff --git a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/LegacySegmentChangeDetails.tsx b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/LegacySegmentChangeDetails.tsx new file mode 100644 index 0000000000..ca516d38a8 --- /dev/null +++ b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/LegacySegmentChangeDetails.tsx @@ -0,0 +1,129 @@ +import type React from 'react'; +import type { FC, ReactNode } from 'react'; +import { Box, styled, Typography } from '@mui/material'; +import type { + ChangeRequestState, + IChangeRequestDeleteSegment, + IChangeRequestUpdateSegment, +} from 'component/changeRequest/changeRequest.types'; +import { useSegment } from 'hooks/api/getters/useSegment/useSegment'; +import { SegmentDiff, SegmentTooltipLink } from '../../SegmentTooltipLink.tsx'; + +import { ViewableConstraintsList } from 'component/common/NewConstraintAccordion/ConstraintsList/ViewableConstraintsList'; + +import { ChangeOverwriteWarning } from './ChangeOverwriteWarning/ChangeOverwriteWarning.tsx'; + +const ChangeItemCreateEditWrapper = styled(Box)(({ theme }) => ({ + display: 'grid', + gridTemplateColumns: 'auto 40px', + gap: theme.spacing(1), + alignItems: 'center', + width: '100%', + margin: theme.spacing(0, 0, 1, 0), +})); + +export const ChangeItemWrapper = styled(Box)({ + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', +}); + +const ChangeItemInfo: FC<{ children?: React.ReactNode }> = styled(Box)( + ({ theme }) => ({ + display: 'grid', + gridTemplateColumns: '150px auto', + gridAutoFlow: 'column', + alignItems: 'center', + flexGrow: 1, + gap: theme.spacing(1), + }), +); + +const SegmentContainer = styled(Box, { + shouldForwardProp: (prop) => prop !== 'conflict', +})<{ conflict: string | undefined }>(({ theme, conflict }) => ({ + borderLeft: '1px solid', + borderRight: '1px solid', + borderTop: '1px solid', + borderBottom: '1px solid', + borderColor: conflict + ? theme.palette.warning.border + : theme.palette.divider, + borderTopColor: theme.palette.divider, + padding: theme.spacing(3), + borderRadius: `0 0 ${theme.shape.borderRadiusLarge}px ${theme.shape.borderRadiusLarge}px`, +})); + +/** + * Deprecated: use SegmentChangeDetails instead. Remove file with flag crDiffView + * @deprecated + */ +export const LegacySegmentChangeDetails: FC<{ + actions?: ReactNode; + change: IChangeRequestUpdateSegment | IChangeRequestDeleteSegment; + changeRequestState: ChangeRequestState; +}> = ({ actions, change, changeRequestState }) => { + const { segment: currentSegment } = useSegment(change.payload.id); + const snapshotSegment = change.payload.snapshot; + const previousName = + changeRequestState === 'Applied' + ? change.payload?.snapshot?.name + : currentSegment?.name; + const referenceSegment = + changeRequestState === 'Applied' ? snapshotSegment : currentSegment; + + return ( + + {change.action === 'deleteSegment' && ( + + + ({ + color: theme.palette.error.main, + })} + > + - Deleting segment: + + + + + +
{actions}
+
+ )} + {change.action === 'updateSegment' && ( + <> + + + + Editing segment: + + + + +
{actions}
+
+ + + )} +
+ ); +}; diff --git a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/SegmentChange.tsx b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/SegmentChange.tsx index ad3bb088b0..3a9e5517c4 100644 --- a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/SegmentChange.tsx +++ b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/SegmentChange.tsx @@ -5,9 +5,11 @@ import type { ChangeRequestState, ISegmentChange, } from '../../../changeRequest.types'; +import { LegacySegmentChangeDetails } from './LegacySegmentChangeDetails.tsx'; import { SegmentChangeDetails } from './SegmentChangeDetails.tsx'; import { ConflictWarning } from './ConflictWarning.tsx'; import { useSegment } from 'hooks/api/getters/useSegment/useSegment.ts'; +import { useUiFlag } from 'hooks/useUiFlag.ts'; interface ISegmentChangeProps { segmentChange: ISegmentChange; @@ -24,6 +26,10 @@ export const SegmentChange: FC = ({ }) => { const { segment } = useSegment(segmentChange.payload.id); + const ChangeDetails = useUiFlag('crDiffView') + ? SegmentChangeDetails + : LegacySegmentChangeDetails; + return ( = ({ - ({ - display: 'grid', - gridTemplateColumns: 'auto 40px', + display: 'flex', gap: theme.spacing(1), alignItems: 'center', width: '100%', @@ -68,58 +67,89 @@ export const SegmentChangeDetails: FC<{ const referenceSegment = changeRequestState === 'Applied' ? snapshotSegment : currentSegment; + const actionsWithTabs = ( + <> + + Change + View diff + + {actions} + + ); + return ( - - {change.action === 'deleteSegment' && ( - - - ({ - color: theme.palette.error.main, - })} - > - - Deleting segment: - - + + + {change.action === 'deleteSegment' && ( + <> + + + ({ + color: theme.palette.error.main, + })} + > + - Deleting segment: + + + + + + {actionsWithTabs} + + + + - - -
{actions}
-
- )} - {change.action === 'updateSegment' && ( - <> - - - - Editing segment: - - - - -
{actions}
-
- - - )} -
+ + + )} + {change.action === 'updateSegment' && ( + <> + + + + Editing segment: + + + + + {actionsWithTabs} + + + + + + + + + + )} + + ); }; diff --git a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/StrategyChange.tsx b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/StrategyChange.tsx index 639e06672e..988ea09943 100644 --- a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/StrategyChange.tsx +++ b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/StrategyChange.tsx @@ -1,14 +1,6 @@ import type React from 'react'; import type { FC, ReactNode } from 'react'; -import { - Box, - Button, - type ButtonProps, - styled, - Tooltip, - Typography, -} from '@mui/material'; -import { Tab, Tabs, TabsList, TabPanel } from '@mui/base'; +import { Box, styled, Tooltip, Typography } from '@mui/material'; import BlockIcon from '@mui/icons-material/Block'; import TrackChangesIcon from '@mui/icons-material/TrackChanges'; import { @@ -29,6 +21,7 @@ import { flexRow } from 'themes/themeStyles'; import { EnvironmentVariantsTable } from 'component/feature/FeatureView/FeatureVariants/FeatureEnvironmentVariants/EnvironmentVariantsCard/EnvironmentVariantsTable/EnvironmentVariantsTable'; import { ChangeOverwriteWarning } from './ChangeOverwriteWarning/ChangeOverwriteWarning.tsx'; import type { IFeatureStrategy } from 'interfaces/strategy'; +import { Tab, TabList, TabPanel, Tabs } from './ChangeTabComponents.tsx'; export const ChangeItemWrapper = styled(Box)({ display: 'flex', @@ -41,6 +34,7 @@ const ChangeItemCreateEditDeleteWrapper = styled(Box)(({ theme }) => ({ justifyContent: 'space-between', gap: theme.spacing(1), alignItems: 'center', + marginBottom: theme.spacing(1), width: '100%', })); @@ -180,46 +174,6 @@ const DeleteStrategy: FC<{ ); }; -const ActionsContainer = styled('div')(({ theme }) => ({ - display: 'flex', - gap: theme.spacing(1), - alignItems: 'center', -})); - -const StyledTabList = styled(TabsList)(({ theme }) => ({ - display: 'inline-flex', - flexDirection: 'row', - gap: theme.spacing(0.5), -})); - -const StyledButton = styled(Button)(({ theme }) => ({ - whiteSpace: 'nowrap', - color: theme.palette.text.secondary, - fontWeight: 'normal', - '&[aria-selected="true"]': { - fontWeight: 'bold', - color: theme.palette.primary.main, - background: theme.palette.background.elevation1, - }, -})); - -export const StyledTab = styled(({ children }: ButtonProps) => ( - {children} -))(({ theme }) => ({ - position: 'absolute', - top: theme.spacing(-0.5), - left: theme.spacing(2), - transform: 'translateY(-50%)', - padding: theme.spacing(0.75, 1), - lineHeight: 1, - fontSize: theme.fontSizes.smallerBody, - color: theme.palette.text.primary, - background: theme.palette.background.application, - borderRadius: theme.shape.borderRadiusExtraLarge, - zIndex: theme.zIndex.fab, - textTransform: 'uppercase', -})); - const UpdateStrategy: FC<{ change: IChangeRequestUpdateStrategy; changeRequestState: ChangeRequestState; @@ -397,31 +351,27 @@ export const StrategyChange: FC<{ environmentName, ); - const Actions = ( - - - Change - View diff - + const actionsWithTabs = ( + <> + + Change + View diff + {actions} - + ); return ( - + {change.action === 'addStrategy' && ( - + )} {change.action === 'deleteStrategy' && ( )} {change.action === 'updateStrategy' && ( @@ -429,7 +379,7 @@ export const StrategyChange: FC<{ change={change} changeRequestState={changeRequestState} currentStrategy={currentStrategy} - actions={Actions} + actions={actionsWithTabs} /> )} diff --git a/frontend/src/component/changeRequest/ChangeRequest/SegmentTooltipLink.tsx b/frontend/src/component/changeRequest/ChangeRequest/SegmentTooltipLink.tsx index 30ee1926b1..50670c1333 100644 --- a/frontend/src/component/changeRequest/ChangeRequest/SegmentTooltipLink.tsx +++ b/frontend/src/component/changeRequest/ChangeRequest/SegmentTooltipLink.tsx @@ -3,14 +3,15 @@ import type { IChangeRequestUpdateSegment, } from 'component/changeRequest/changeRequest.types'; import type React from 'react'; -import type { FC } from 'react'; -import EventDiff from 'component/events/EventDiff/EventDiff'; +import { Fragment, type FC } from 'react'; import omit from 'lodash.omit'; import { TooltipLink } from 'component/common/TooltipLink/TooltipLink'; import { styled } from '@mui/material'; import { textTruncated } from 'themes/themeStyles'; import type { ISegment } from 'interfaces/segment'; import { NameWithChangeInfo } from './NameWithChangeInfo/NameWithChangeInfo.tsx'; +import { EventDiff } from 'component/events/EventDiff/EventDiff.tsx'; +import { useUiFlag } from 'hooks/useUiFlag.ts'; const StyledCodeSection = styled('div')(({ theme }) => ({ overflowX: 'auto', @@ -23,22 +24,32 @@ const StyledCodeSection = styled('div')(({ theme }) => ({ }, })); +const omitIfDefined = (obj: any, keys: string[]) => + obj ? omit(obj, keys) : obj; + export const SegmentDiff: FC<{ change: IChangeRequestUpdateSegment | IChangeRequestDeleteSegment; currentSegment?: ISegment; }> = ({ change, currentSegment }) => { + const useNewDiff = useUiFlag('improvedJsonDiff'); + const Wrapper = useNewDiff ? Fragment : StyledCodeSection; + const omissionFunction = useNewDiff ? omitIfDefined : omit; + const changeRequestSegment = change.action === 'deleteSegment' ? undefined : change.payload; return ( - + - + ); }; interface IStrategyTooltipLinkProps { diff --git a/frontend/src/component/changeRequest/ChangeRequest/StrategyTooltipLink/StrategyTooltipLink.tsx b/frontend/src/component/changeRequest/ChangeRequest/StrategyTooltipLink/StrategyTooltipLink.tsx index d6f7dd15c1..f3d5b4248f 100644 --- a/frontend/src/component/changeRequest/ChangeRequest/StrategyTooltipLink/StrategyTooltipLink.tsx +++ b/frontend/src/component/changeRequest/ChangeRequest/StrategyTooltipLink/StrategyTooltipLink.tsx @@ -4,12 +4,12 @@ import type { IChangeRequestUpdateStrategy, } from 'component/changeRequest/changeRequest.types'; import type React from 'react'; -import type { FC } from 'react'; +import { Fragment, type FC } from 'react'; import { formatStrategyName, GetFeatureStrategyIcon, } from 'utils/strategyNames'; -import EventDiff, { NewEventDiff } from 'component/events/EventDiff/EventDiff'; +import { EventDiff } from 'component/events/EventDiff/EventDiff'; import omit from 'lodash.omit'; import { TooltipLink } from 'component/common/TooltipLink/TooltipLink'; import { Typography, styled } from '@mui/material'; @@ -41,6 +41,9 @@ const sortSegments = ( }; }; +const omitIfDefined = (obj: any, keys: string[]) => + obj ? omit(obj, keys) : obj; + export const StrategyDiff: FC<{ change: | IChangeRequestAddStrategy @@ -54,29 +57,22 @@ export const StrategyDiff: FC<{ const sortedCurrentStrategy = sortSegments(currentStrategy); const sortedChangeRequestStrategy = sortSegments(changeRequestStrategy); - if (useNewDiff) { - return ( - - ); - } + + const Wrapper = useNewDiff ? Fragment : StyledCodeSection; + const omissionFunction = useNewDiff ? omitIfDefined : omit; return ( - + - + ); }; diff --git a/frontend/src/component/events/EventDiff/EventDiff.tsx b/frontend/src/component/events/EventDiff/EventDiff.tsx index 93968bc36f..0e43014421 100644 --- a/frontend/src/component/events/EventDiff/EventDiff.tsx +++ b/frontend/src/component/events/EventDiff/EventDiff.tsx @@ -212,7 +212,7 @@ const OldEventDiff: FC = ({ ); }; -const EventDiff: FC = (props) => { +export const EventDiff: FC = (props) => { const useNewJsonDiff = useUiFlag('improvedJsonDiff'); if (useNewJsonDiff) { return ;