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 (
+
+ {value === index ? children : null}
+
+ );
+};
+
+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);