From abff9a94346828f4fb9e3ee9133e33ba8aa30890 Mon Sep 17 00:00:00 2001 From: Thomas Heartman Date: Fri, 20 Jun 2025 14:29:21 +0200 Subject: [PATCH] try out base tabs --- .../Changes/Change/DiffableChange.tsx | 444 ++++++++++++++++++ .../Changes/Change/FeatureChange.tsx | 28 +- .../StrategyTooltipLink.tsx | 15 +- .../component/events/EventDiff/EventDiff.tsx | 2 +- 4 files changed, 478 insertions(+), 11 deletions(-) create mode 100644 frontend/src/component/changeRequest/ChangeRequest/Changes/Change/DiffableChange.tsx diff --git a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/DiffableChange.tsx b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/DiffableChange.tsx new file mode 100644 index 0000000000..05ccab2118 --- /dev/null +++ b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/DiffableChange.tsx @@ -0,0 +1,444 @@ +import type React from 'react'; +import { + type PropsWithChildren, + useId, + useState, + type FC, + type ReactNode, +} from 'react'; +import { Box, styled, Tab, Tabs, Tooltip, Typography } from '@mui/material'; +import { + Tab as BaseTab, + Tabs as BaseTabs, + TabPanel as BaseTabPanel, + TabsList as BaseTabsList, +} from '@mui/base'; +import BlockIcon from '@mui/icons-material/Block'; +import TrackChangesIcon from '@mui/icons-material/TrackChanges'; +import { + StrategyDiff, + StrategyTooltipLink, +} from '../../StrategyTooltipLink/StrategyTooltipLink.tsx'; +import { StrategyExecution } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyExecution/StrategyExecution'; +import type { + ChangeRequestState, + IChangeRequestAddStrategy, + IChangeRequestDeleteStrategy, + IChangeRequestUpdateStrategy, +} from 'component/changeRequest/changeRequest.types'; +import { useCurrentStrategy } from './hooks/useCurrentStrategy.ts'; +import { Badge } from 'component/common/Badge/Badge'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; +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'; + +export const ChangeItemWrapper = styled(Box)({ + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', +}); + +const ChangeItemCreateEditDeleteWrapper = styled(Box)(({ theme }) => ({ + display: 'flex', + justifyContent: 'space-between', + gap: theme.spacing(1), + alignItems: 'center', + width: '100%', +})); + +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 StyledBox: FC<{ children?: React.ReactNode }> = styled(Box)( + ({ theme }) => ({ + marginTop: theme.spacing(2), + }), +); + +const StyledTypography: FC<{ children?: React.ReactNode }> = styled(Typography)( + ({ theme }) => ({ + margin: `${theme.spacing(1)} 0`, + }), +); + +const DisabledEnabledState: FC<{ show?: boolean; disabled: boolean }> = ({ + show = true, + disabled, +}) => { + if (!show) { + return null; + } + + if (disabled) { + return ( + + }> + Disabled + + + ); + } + + return ( + + }> + Enabled + + + ); +}; + +const EditHeader: FC<{ + wasDisabled?: boolean; + willBeDisabled?: boolean; +}> = ({ wasDisabled = false, willBeDisabled = false }) => { + if (wasDisabled && willBeDisabled) { + return ( + Editing strategy: + ); + } + + if (!wasDisabled && willBeDisabled) { + return Editing strategy:; + } + + if (wasDisabled && !willBeDisabled) { + return Editing strategy:; + } + + return Editing strategy:; +}; + +const hasDiff = (object: unknown, objectToCompare: unknown) => + JSON.stringify(object) !== JSON.stringify(objectToCompare); + +const DeleteStrategy: FC<{ + change: IChangeRequestDeleteStrategy; + changeRequestState: ChangeRequestState; + currentStrategy: IFeatureStrategy | undefined; + actions?: ReactNode; +}> = ({ change, changeRequestState, currentStrategy, actions }) => { + const name = + changeRequestState === 'Applied' + ? change.payload?.snapshot?.name + : currentStrategy?.name; + const title = + changeRequestState === 'Applied' + ? change.payload?.snapshot?.title + : currentStrategy?.title; + const referenceStrategy = + changeRequestState === 'Applied' + ? change.payload.snapshot + : currentStrategy; + + return ( + <> + + + ({ + color: theme.palette.error.main, + })} + > + - Deleting strategy: + + + + + +
{actions}
+
+ {referenceStrategy && ( + + )} + + ); +}; + +const tabA11yProps = (baseId: string) => (index: number) => ({ + id: `${baseId}-tab-${index}`, + 'aria-controls': `${baseId}-${index}`, +}); + +const TabPanel: FC< + PropsWithChildren<{ + index: number; + value: number; + id: string; + 'aria-labelledby': string; + }> +> = ({ children, index, value, id, 'aria-labelledby': ariaLabelledBy }) => { + return ( + + ); +}; + +const UpdateStrategy: FC<{ + change: IChangeRequestUpdateStrategy; + changeRequestState: ChangeRequestState; + currentStrategy: IFeatureStrategy | undefined; + actions?: ReactNode; +}> = ({ change, changeRequestState, currentStrategy, actions }) => { + const [tabIndex, setTabIndex] = useState(0); + const baseId = useId(); + const allyProps = tabA11yProps(baseId); + + const previousTitle = + changeRequestState === 'Applied' + ? change.payload.snapshot?.title + : currentStrategy?.title; + const referenceStrategy = + changeRequestState === 'Applied' + ? change.payload.snapshot + : currentStrategy; + const hasVariantDiff = hasDiff( + referenceStrategy?.variants || [], + change.payload.variants || [], + ); + + return ( + <> + + + + + + + + + +
+ setTabIndex(newValue)} + > + + + + + {/* import {Tab as BaseTab, Tabs as BaseTabs, TabPanel as BaseTabPanel, TabsList as BaseTabsList} from '@mui/base'; */} + + setTabIndex(newValue)} + > + Change + Diff + + {actions} +
+
+ theme.spacing(2), + marginBottom: (theme) => theme.spacing(2), + ...flexRow, + gap: (theme) => theme.spacing(1), + }} + > + This strategy will be{' '} + + + } + /> + + + {hasVariantDiff ? ( + + {change.payload.variants?.length ? ( + <> + + {currentStrategy?.variants?.length + ? 'Updating strategy variants to:' + : 'Adding strategy variants:'} + + + + ) : ( + + Removed all strategy variants. + + )} + + ) : null} + + + + + + + +
+ + ); +}; + +const AddStrategy: FC<{ + change: IChangeRequestAddStrategy; + actions?: ReactNode; +}> = ({ change, actions }) => ( + <> + + + + + Adding strategy: + + +
+ +
+
+ +
{actions}
+
+ + {change.payload.variants?.length ? ( + + Adding strategy variants: + + + ) : null} + +); + +export const DiffableChange: FC<{ + actions?: ReactNode; + change: + | IChangeRequestAddStrategy + | IChangeRequestDeleteStrategy + | IChangeRequestUpdateStrategy; + environmentName: string; + featureName: string; + projectId: string; + changeRequestState: ChangeRequestState; +}> = ({ + actions, + change, + featureName, + environmentName, + projectId, + changeRequestState, +}) => { + const currentStrategy = useCurrentStrategy( + change, + projectId, + featureName, + environmentName, + ); + + return ( + <> + {change.action === 'addStrategy' && ( + + )} + {change.action === 'deleteStrategy' && ( + + )} + {change.action === 'updateStrategy' && ( + + )} + + ); +}; diff --git a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/FeatureChange.tsx b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/FeatureChange.tsx index 125ba4c5dd..96f219ab5b 100644 --- a/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/FeatureChange.tsx +++ b/frontend/src/component/changeRequest/ChangeRequest/Changes/Change/FeatureChange.tsx @@ -15,6 +15,7 @@ import { ArchiveFeatureChange } from './ArchiveFeatureChange.tsx'; import { DependencyChange } from './DependencyChange.tsx'; import { Link } from 'react-router-dom'; import { ReleasePlanChange } from './ReleasePlanChange.tsx'; +import { DiffableChange } from './DiffableChange.tsx'; const StyledSingleChangeBox = styled(Box, { shouldForwardProp: (prop: string) => !prop.startsWith('$'), @@ -166,14 +167,25 @@ export const FeatureChange: FC<{ {change.action === 'addStrategy' || change.action === 'deleteStrategy' || change.action === 'updateStrategy' ? ( - + <> + +
+ + ) : null} {change.action === 'patchVariant' && ( ({ overflowX: 'auto', @@ -47,12 +48,22 @@ export const StrategyDiff: FC<{ | IChangeRequestDeleteStrategy; currentStrategy?: IFeatureStrategy; }> = ({ change, currentStrategy }) => { + const useNewDiff = useUiFlag('improvedJsonDiff'); const changeRequestStrategy = change.action === 'deleteStrategy' ? undefined : change.payload; const sortedCurrentStrategy = sortSegments(currentStrategy); const sortedChangeRequestStrategy = sortSegments(changeRequestStrategy); - + if (useNewDiff) { + return ( + + ); + } return ( ({ marginInlineEnd: theme.spacing(0.5), })); -const NewEventDiff: FC = ({ entry, excludeKeys }) => { +export const NewEventDiff: FC = ({ entry, excludeKeys }) => { const changeType = entry.preData && entry.data ? 'edit' : 'replacement'; const showExpandButton = changeType === 'edit'; const [full, setFull] = useState(false);