1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-08-27 13:49:10 +02:00

chore(AI): crDiffView flag cleanup (#10487)

This PR cleans up the crDiffView flag. These changes were automatically
generated by AI and should be reviewed carefully.

Fixes #10484



🧹 AI Flag Cleanup Summary

This PR removes the crDiffView feature flag and its associated legacy
components
for displaying changes in a Change Request. The flag has been enabled
and the
new diff view is now permanent.

This involved removing the feature flag from the configuration and code,
deleting several legacy components, and updating the components that
used them
to only use the new versions.

🚮 Removed

• Feature Flag Logic
• All checks for the crDiffView flag.
• The flag definition in uiConfig.ts, experimental.ts, and
server-dev.ts.
• Legacy Components
• LegacyStrategyChange.tsx
• StrategyTooltipLink.tsx
• LegacyReleasePlanChange.tsx
• SegmentTooltipLink.tsx
• LegacySegmentChangeDetails.tsx
• LegacyArchiveFeatureChange from ArchiveFeatureChange.tsx
• LegacyDependencyChange from DependencyChange.tsx
• LegacyToggleStatusChange from ToggleStatusChange.tsx

🛠 Kept

• New Components
• The new change request diff view components (StrategyChange,
ReleasePlanChange, etc.) are now used directly.
• The UI for displaying changes in a Change Request now consistently
uses
the improved diff view.

📝 Why

The crDiffView feature flag was deemed complete and ready for permanent
implementation. The cleanup follows standard procedure to remove the
flag and
associated dead code, simplifying the codebase and making it easier to
maintain.
This change makes the improved diff view for change requests the only
available
view.

---------

Co-authored-by: unleash-bot <194219037+unleash-bot[bot]@users.noreply.github.com>
Co-authored-by: Thomas Heartman <thomas@getunleash.io>
This commit is contained in:
unleash-bot[bot] 2025-08-21 12:33:19 +02:00 committed by GitHub
parent d6ddc95c1e
commit d2452b91f2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 48 additions and 1335 deletions

View File

@ -203,9 +203,7 @@ test('Display default add strategy', async () => {
expect(screen.getByText('feature1')).toBeInTheDocument();
expect(screen.getByText('Enabled')).toBeInTheDocument();
expect(
screen.getByText('Default strategy will be added'),
).toBeInTheDocument();
expect(screen.getByText('Adding default strategy')).toBeInTheDocument();
});
test('Display default disable feature', async () => {

View File

@ -1,33 +1,10 @@
import type { FC, ReactNode } from 'react';
import { Action, ChangeItemInfo, ChangeItemWrapper } from './Change.styles.tsx';
import { styled } from '@mui/material';
import { ChangeItemWrapper as LegacyChangeItemWrapper } from './LegacyStrategyChange.tsx';
type ArchiveFeatureChange = {
actions?: ReactNode;
};
const ArchiveBox = styled('span')(({ theme }) => ({
display: 'flex',
alignItems: 'center',
color: theme.palette.error.main,
}));
/**
* Deprecated: use ArchiveFeatureChange instead; remove with flag crDiffView
* @deprecated
*/
export const LegacyArchiveFeatureChange: FC<ArchiveFeatureChange> = ({
actions,
}) => (
<LegacyChangeItemWrapper>
<ChangeItemInfo>
<ArchiveBox>Archiving flag</ArchiveBox>
{actions}
</ChangeItemInfo>
</LegacyChangeItemWrapper>
);
export const ArchiveFeatureChange: FC<ArchiveFeatureChange> = ({ actions }) => (
<ChangeItemWrapper>
<ChangeItemInfo>

View File

@ -28,7 +28,6 @@ import Delete from '@mui/icons-material/Delete';
import Edit from '@mui/icons-material/Edit';
import MoreVert from '@mui/icons-material/MoreVert';
import { EditChange } from './EditChange.tsx';
import { useUiFlag } from 'hooks/useUiFlag.ts';
const useShowActions = (changeRequest: ChangeRequestType, change: IChange) => {
const { isChangeRequestConfigured } = useChangeRequestsEnabled(
@ -74,9 +73,6 @@ export const ChangeActions: FC<{
const { showDiscard, showEdit } = useShowActions(changeRequest, change);
const { discardChange } = useChangeRequestApi();
const { setToastData, setToastApiError } = useToast();
const useNewCrView = useUiFlag('crDiffView');
const ButtonComponent = useNewCrView ? StyledIconButton : IconButton;
const [editOpen, setEditOpen] = useState(false);
@ -121,7 +117,7 @@ export const ChangeActions: FC<{
show={
<>
<Tooltip title='Change request actions' arrow describeChild>
<ButtonComponent
<StyledIconButton
id={id}
aria-controls={open ? menuId : undefined}
aria-haspopup='true'
@ -130,7 +126,7 @@ export const ChangeActions: FC<{
type='button'
>
<MoreVert />
</ButtonComponent>
</StyledIconButton>
</Tooltip>
<StyledPopover
id={menuId}

View File

@ -1,5 +1,5 @@
import type { FC, ReactNode } from 'react';
import { Box, styled, Typography } from '@mui/material';
import { styled } from '@mui/material';
import type {
IChangeRequestAddDependency,
IChangeRequestDeleteDependency,
@ -11,7 +11,6 @@ import {
ChangeItemWrapper,
Deleted,
} from './Change.styles';
import { ChangeItemWrapper as LegacyChangeItemWrapper } from './LegacyStrategyChange.tsx';
const StyledLink = styled(Link)(({ theme }) => ({
maxWidth: '100%',
@ -59,60 +58,3 @@ export const DependencyChange: FC<{
);
}
};
const AddDependencyWrapper = styled(Box)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
gap: theme.spacing(1),
}));
/**
* @deprecated use DependencyChange instead; remove with flag crDiffView
*/
export const LegacyDependencyChange: FC<{
actions?: ReactNode;
change: IChangeRequestAddDependency | IChangeRequestDeleteDependency;
projectId: string;
onNavigate?: () => void;
}> = ({ actions, change, projectId, onNavigate }) => {
return (
<>
{change.action === 'addDependency' && (
<>
<LegacyChangeItemWrapper>
<AddDependencyWrapper>
<Typography color={'success.dark'}>
+ Adding dependency
</Typography>
<StyledLink
to={`/projects/${projectId}/features/${change.payload.feature}`}
onClick={onNavigate}
>
{change.payload.feature}
</StyledLink>
{!change.payload.enabled ? ' (disabled)' : null}
{change.payload.variants?.length
? `(${change.payload.variants?.join(', ')})`
: null}
</AddDependencyWrapper>
{actions}
</LegacyChangeItemWrapper>
</>
)}
{change.action === 'deleteDependency' && (
<ChangeItemWrapper>
<AddDependencyWrapper>
<Typography
sx={(theme) => ({
color: theme.palette.error.main,
})}
>
- Deleting dependencies
</Typography>
</AddDependencyWrapper>
{actions}
</ChangeItemWrapper>
)}
</>
);
};

View File

@ -8,7 +8,6 @@ import { StrategyExecution } from 'component/feature/FeatureView/FeatureOverview
import { formatStrategyName } from '../../../../../../utils/strategyNames.tsx';
import type { IFeatureStrategy } from 'interfaces/strategy.ts';
import { Tab, TabList, TabPanel, Tabs } from '../ChangeTabComponents.tsx';
import { useUiFlag } from 'hooks/useUiFlag.ts';
import {
ChangeItemInfo as NewChangeItemInfo,
ChangeItemWrapper,
@ -63,7 +62,6 @@ export const EnvironmentStrategyExecutionOrder = ({
actions,
}: IEnvironmentStrategyExecutionOrderProps) => {
const { feature: featureData, loading } = useFeature(project, feature);
const useDiffableComponent = useUiFlag('crDiffView');
if (loading) return null;
@ -97,76 +95,40 @@ export const EnvironmentStrategyExecutionOrder = ({
strategyIds: updatedStrategies.map((strategy) => strategy.id),
};
if (useDiffableComponent) {
return (
<Tabs>
<ChangeContent>
<ChangeItemWrapper>
<NewChangeItemInfo>
<Action>
Updating strategy execution order to
</Action>
</NewChangeItemInfo>
<div>
<TabList>
<Tab>View change</Tab>
<Tab>View diff</Tab>
</TabList>
{actions}
</div>
</ChangeItemWrapper>
<TabPanel>
<StyledStrategyExecutionWrapper>
{updatedStrategies.map((strategy, index) => (
<StyledStrategyContainer key={strategy.id}>
{`${index + 1}: `}
{formatStrategyName(strategy?.name || '')}
{strategy?.title && ` - ${strategy.title}`}
<StrategyExecution strategy={strategy!} />
</StyledStrategyContainer>
))}
</StyledStrategyExecutionWrapper>
</TabPanel>
<TabPanel variant='diff'>
<EnvironmentStrategyOrderDiff
preData={preData}
data={data}
/>
</TabPanel>
</ChangeContent>
</Tabs>
);
}
return (
<ChangeItemInfo>
<StyledChangeHeader>
<TooltipLink
tooltip={
<EnvironmentStrategyOrderDiff
preData={preData}
data={data}
/>
}
tooltipProps={{
maxWidth: 500,
maxHeight: 600,
}}
>
Updating strategy execution order to
</TooltipLink>
{actions}
</StyledChangeHeader>
<StyledStrategyExecutionWrapper>
{updatedStrategies.map((strategy, index) => (
<StyledStrategyContainer key={strategy.id}>
{`${index + 1}: `}
{formatStrategyName(strategy?.name || '')}
{strategy?.title && ` - ${strategy.title}`}
<StrategyExecution strategy={strategy!} />
</StyledStrategyContainer>
))}
</StyledStrategyExecutionWrapper>
</ChangeItemInfo>
<Tabs>
<ChangeContent>
<ChangeItemWrapper>
<NewChangeItemInfo>
<Action>Updating strategy execution order to</Action>
</NewChangeItemInfo>
<div>
<TabList>
<Tab>View change</Tab>
<Tab>View diff</Tab>
</TabList>
{actions}
</div>
</ChangeItemWrapper>
<TabPanel>
<StyledStrategyExecutionWrapper>
{updatedStrategies.map((strategy, index) => (
<StyledStrategyContainer key={strategy.id}>
{`${index + 1}: `}
{formatStrategyName(strategy?.name || '')}
{strategy?.title && ` - ${strategy.title}`}
<StrategyExecution strategy={strategy!} />
</StyledStrategyContainer>
))}
</StyledStrategyExecutionWrapper>
</TabPanel>
<TabPanel variant='diff'>
<EnvironmentStrategyOrderDiff
preData={preData}
data={data}
/>
</TabPanel>
</ChangeContent>
</Tabs>
);
};

View File

@ -7,26 +7,14 @@ import type {
import { objectId } from 'utils/objectId';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { Alert, Box, styled } from '@mui/material';
import {
LegacyToggleStatusChange,
ToggleStatusChange,
} from './ToggleStatusChange.tsx';
import { LegacyStrategyChange } from './LegacyStrategyChange.tsx';
import { ToggleStatusChange } from './ToggleStatusChange.tsx';
import { VariantPatch } from './VariantPatch/VariantPatch.tsx';
import { EnvironmentStrategyExecutionOrder } from './EnvironmentStrategyExecutionOrder/EnvironmentStrategyExecutionOrder.tsx';
import {
ArchiveFeatureChange,
LegacyArchiveFeatureChange,
} from './ArchiveFeatureChange.tsx';
import {
DependencyChange,
LegacyDependencyChange,
} from './DependencyChange.tsx';
import { ArchiveFeatureChange } from './ArchiveFeatureChange.tsx';
import { DependencyChange } from './DependencyChange.tsx';
import { Link } from 'react-router-dom';
import { LegacyReleasePlanChange } from './LegacyReleasePlanChange.tsx';
import { ReleasePlanChange } from './ReleasePlanChange.tsx';
import { StrategyChange } from './StrategyChange.tsx';
import { useUiFlag } from 'hooks/useUiFlag.ts';
const StyledSingleChangeBox = styled(Box, {
shouldForwardProp: (prop: string) => !prop.startsWith('$'),
@ -82,10 +70,6 @@ const InlineList = styled('ul')(({ theme }) => ({
const ChangeInnerBox = styled(Box)(({ theme }) => ({
padding: theme.spacing(3),
// todo: remove with flag crDiffView
'&:has(.delete-strategy-information-wrapper)': {
backgroundColor: theme.palette.error.light,
},
}));
export const FeatureChange: FC<{
@ -109,27 +93,6 @@ export const FeatureChange: FC<{
? feature.changes.length + 1
: feature.changes.length;
const useDiffableChangeComponent = useUiFlag('crDiffView');
const StrategyChangeComponent = useDiffableChangeComponent
? StrategyChange
: LegacyStrategyChange;
const ReleasePlanChangeComponent = useDiffableChangeComponent
? ReleasePlanChange
: LegacyReleasePlanChange;
const ArchiveFlagComponent = useDiffableChangeComponent
? ArchiveFeatureChange
: LegacyArchiveFeatureChange;
const DependencyChangeComponent = useDiffableChangeComponent
? DependencyChange
: LegacyDependencyChange;
const StatusChangeComponent = useDiffableChangeComponent
? ToggleStatusChange
: LegacyToggleStatusChange;
return (
<StyledSingleChangeBox
key={objectId(change)}
@ -189,7 +152,7 @@ export const FeatureChange: FC<{
<ChangeInnerBox>
{(change.action === 'addDependency' ||
change.action === 'deleteDependency') && (
<DependencyChangeComponent
<DependencyChange
actions={actions}
change={change}
projectId={changeRequest.project}
@ -197,20 +160,20 @@ export const FeatureChange: FC<{
/>
)}
{change.action === 'updateEnabled' && (
<StatusChangeComponent
<ToggleStatusChange
isDefaultChange={isDefaultChange}
enabled={change.payload.enabled}
actions={actions}
/>
)}
{change.action === 'archiveFeature' && (
<ArchiveFlagComponent actions={actions} />
<ArchiveFeatureChange actions={actions} />
)}
{change.action === 'addStrategy' ||
change.action === 'deleteStrategy' ||
change.action === 'updateStrategy' ? (
<StrategyChangeComponent
<StrategyChange
actions={actions}
isDefaultChange={isDefaultChange}
change={change}
@ -242,7 +205,7 @@ export const FeatureChange: FC<{
{(change.action === 'addReleasePlan' ||
change.action === 'deleteReleasePlan' ||
change.action === 'startMilestone') && (
<ReleasePlanChangeComponent
<ReleasePlanChange
actions={actions}
change={change}
featureName={feature.name}

View File

@ -1,316 +0,0 @@
import type React from 'react';
import { useRef, useState, type FC, type ReactNode } from 'react';
import { Box, styled, Typography } from '@mui/material';
import type {
ChangeRequestState,
IChangeRequestAddReleasePlan,
IChangeRequestDeleteReleasePlan,
IChangeRequestStartMilestone,
} from 'component/changeRequest/changeRequest.types';
import { useReleasePlanPreview } from 'hooks/useReleasePlanPreview';
import { useReleasePlans } from 'hooks/api/getters/useReleasePlans/useReleasePlans';
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/ReleasePlanMilestone';
import type { IReleasePlan } from 'interfaces/releasePlans';
export const ChangeItemWrapper = styled(Box)({
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
});
const ChangeItemCreateEditDeleteWrapper = styled(Box)(({ theme }) => ({
display: 'grid',
gridTemplateColumns: 'auto auto',
justifyContent: 'space-between',
gap: theme.spacing(1),
alignItems: 'center',
marginBottom: theme.spacing(2),
width: '100%',
}));
const ChangeItemInfo: FC<{ children?: React.ReactNode }> = styled(Box)(
({ theme }) => ({
display: 'flex',
gap: theme.spacing(1),
}),
);
const ViewDiff = styled('span')(({ theme }) => ({
color: theme.palette.primary.main,
marginLeft: theme.spacing(1),
}));
const StyledCodeSection = styled('div')(({ theme }) => ({
overflowX: 'auto',
'& code': {
wordWrap: 'break-word',
whiteSpace: 'pre-wrap',
fontFamily: 'monospace',
lineHeight: 1.5,
fontSize: theme.fontSizes.smallBody,
},
}));
const DeleteReleasePlan: FC<{
change: IChangeRequestDeleteReleasePlan;
currentReleasePlan?: IReleasePlan;
changeRequestState: ChangeRequestState;
actions?: ReactNode;
}> = ({ change, currentReleasePlan, changeRequestState, actions }) => {
const releasePlan =
changeRequestState === 'Applied' && change.payload.snapshot
? change.payload.snapshot
: currentReleasePlan;
if (!releasePlan) return;
return (
<>
<ChangeItemCreateEditDeleteWrapper>
<ChangeItemInfo>
<Typography
sx={(theme) => ({
color: theme.palette.error.main,
})}
>
- Deleting release plan
</Typography>
<Typography>{releasePlan.name}</Typography>
</ChangeItemInfo>
<div>{actions}</div>
</ChangeItemCreateEditDeleteWrapper>
<ReleasePlan plan={releasePlan} readonly />
</>
);
};
const StartMilestone: FC<{
change: IChangeRequestStartMilestone;
currentReleasePlan?: IReleasePlan;
changeRequestState: ChangeRequestState;
actions?: ReactNode;
}> = ({ change, currentReleasePlan, changeRequestState, actions }) => {
const releasePlan =
changeRequestState === 'Applied' && change.payload.snapshot
? change.payload.snapshot
: currentReleasePlan;
if (!releasePlan) return;
const previousMilestone = releasePlan.milestones.find(
(milestone) => milestone.id === releasePlan.activeMilestoneId,
);
const newMilestone = releasePlan.milestones.find(
(milestone) => milestone.id === change.payload.milestoneId,
);
if (!newMilestone) return;
return (
<>
<ChangeItemCreateEditDeleteWrapper>
<ChangeItemInfo>
<Typography color='success.dark'>
+ Start milestone:
</Typography>
<Typography>{newMilestone.name}</Typography>
<TooltipLink
tooltip={
<StyledCodeSection>
<EventDiff
entry={{
preData: previousMilestone,
data: newMilestone,
}}
/>
</StyledCodeSection>
}
tooltipProps={{
maxWidth: 500,
maxHeight: 600,
}}
>
<ViewDiff>View Diff</ViewDiff>
</TooltipLink>
</ChangeItemInfo>
<div>{actions}</div>
</ChangeItemCreateEditDeleteWrapper>
<ReleasePlanMilestone readonly milestone={newMilestone} />
</>
);
};
const AddReleasePlan: FC<{
change: IChangeRequestAddReleasePlan;
currentReleasePlan?: IReleasePlan;
environmentName: string;
featureName: string;
actions?: ReactNode;
}> = ({
change,
currentReleasePlan,
environmentName,
featureName,
actions,
}) => {
const [currentTooltipOpen, setCurrentTooltipOpen] = useState(false);
const currentTooltipCloseTimeoutRef = useRef<NodeJS.Timeout>();
const openCurrentTooltip = () => {
if (currentTooltipCloseTimeoutRef.current) {
clearTimeout(currentTooltipCloseTimeoutRef.current);
}
setCurrentTooltipOpen(true);
};
const closeCurrentTooltip = () => {
currentTooltipCloseTimeoutRef.current = setTimeout(() => {
setCurrentTooltipOpen(false);
}, 100);
};
const planPreview = useReleasePlanPreview(
change.payload.templateId,
featureName,
environmentName,
);
const planPreviewDiff = {
...planPreview,
discriminator: 'plan',
releasePlanTemplateId: change.payload.templateId,
};
return (
<>
<ChangeItemCreateEditDeleteWrapper>
<ChangeItemInfo>
{currentReleasePlan ? (
<Typography>
Replacing{' '}
<TooltipLink
tooltip={
<div
onMouseEnter={() =>
openCurrentTooltip()
}
onMouseLeave={() =>
closeCurrentTooltip()
}
>
<ReleasePlan
plan={currentReleasePlan}
readonly
/>
</div>
}
tooltipProps={{
open: currentTooltipOpen,
maxWidth: 500,
maxHeight: 600,
}}
>
<span
onMouseEnter={() => openCurrentTooltip()}
onMouseLeave={() => closeCurrentTooltip()}
>
current
</span>
</TooltipLink>{' '}
release plan with
</Typography>
) : (
<Typography color='success.dark'>
+ Adding release plan
</Typography>
)}
<Typography>{planPreview.name}</Typography>
{currentReleasePlan && (
<TooltipLink
tooltip={
<StyledCodeSection>
<EventDiff
entry={{
preData: currentReleasePlan,
data: planPreviewDiff,
}}
/>
</StyledCodeSection>
}
tooltipProps={{
maxWidth: 500,
maxHeight: 600,
}}
>
<ViewDiff>View Diff</ViewDiff>
</TooltipLink>
)}
</ChangeItemInfo>
<div>{actions}</div>
</ChangeItemCreateEditDeleteWrapper>
<ReleasePlan plan={planPreview} readonly />
</>
);
};
/**
* Deprecated: use ReleasePlanChange instead. Remove file with flag crDiffView
* @deprecated
*/
export const LegacyReleasePlanChange: FC<{
actions?: ReactNode;
change:
| IChangeRequestAddReleasePlan
| IChangeRequestDeleteReleasePlan
| IChangeRequestStartMilestone;
environmentName: string;
featureName: string;
projectId: string;
changeRequestState: ChangeRequestState;
}> = ({
actions,
change,
featureName,
environmentName,
projectId,
changeRequestState,
}) => {
const { releasePlans } = useReleasePlans(
projectId,
featureName,
environmentName,
);
const currentReleasePlan = releasePlans[0];
return (
<>
{change.action === 'addReleasePlan' && (
<AddReleasePlan
change={change}
currentReleasePlan={currentReleasePlan}
environmentName={environmentName}
featureName={featureName}
actions={actions}
/>
)}
{change.action === 'deleteReleasePlan' && (
<DeleteReleasePlan
change={change}
currentReleasePlan={currentReleasePlan}
changeRequestState={changeRequestState}
actions={actions}
/>
)}
{change.action === 'startMilestone' && (
<StartMilestone
change={change}
currentReleasePlan={currentReleasePlan}
changeRequestState={changeRequestState}
actions={actions}
/>
)}
</>
);
};

View File

@ -1,129 +0,0 @@
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 (
<SegmentContainer conflict={change.conflict}>
{change.action === 'deleteSegment' && (
<ChangeItemWrapper>
<ChangeItemInfo>
<Typography
sx={(theme) => ({
color: theme.palette.error.main,
})}
>
- Deleting segment
</Typography>
<SegmentTooltipLink
name={change.payload.name}
previousName={previousName}
>
<SegmentDiff
change={change}
currentSegment={referenceSegment}
/>
</SegmentTooltipLink>
</ChangeItemInfo>
<div>{actions}</div>
</ChangeItemWrapper>
)}
{change.action === 'updateSegment' && (
<>
<ChangeOverwriteWarning
data={{
current: currentSegment,
change,
changeType: 'segment',
}}
changeRequestState={changeRequestState}
/>
<ChangeItemCreateEditWrapper>
<ChangeItemInfo>
<Typography>Editing segment</Typography>
<SegmentTooltipLink name={change.payload.name}>
<SegmentDiff
change={change}
currentSegment={referenceSegment}
/>
</SegmentTooltipLink>
</ChangeItemInfo>
<div>{actions}</div>
</ChangeItemCreateEditWrapper>
<ViewableConstraintsList
constraints={change.payload.constraints}
/>
</>
)}
</SegmentContainer>
);
};

View File

@ -1,371 +0,0 @@
import type React from 'react';
import type { FC, ReactNode } from 'react';
import { Box, styled, Tooltip, Typography } from '@mui/material';
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: 'grid',
gridTemplateColumns: 'auto auto',
justifyContent: 'space-between',
gap: theme.spacing(1),
alignItems: 'center',
marginBottom: theme.spacing(2),
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 (
<Tooltip
title='This strategy will not be taken into account when evaluating feature flag.'
arrow
sx={{ cursor: 'pointer' }}
>
<Badge color='disabled' icon={<BlockIcon />}>
Disabled
</Badge>
</Tooltip>
);
}
return (
<Tooltip
title='This was disabled before and with this change it will be taken into account when evaluating feature flag.'
arrow
sx={{ cursor: 'pointer' }}
>
<Badge color='success' icon={<TrackChangesIcon />}>
Enabled
</Badge>
</Tooltip>
);
};
const EditHeader: FC<{
wasDisabled?: boolean;
willBeDisabled?: boolean;
}> = ({ wasDisabled = false, willBeDisabled = false }) => {
if (wasDisabled && willBeDisabled) {
return (
<Typography color='action.disabled'>Editing strategy</Typography>
);
}
if (!wasDisabled && willBeDisabled) {
return <Typography color='error.dark'>Editing strategy</Typography>;
}
if (wasDisabled && !willBeDisabled) {
return <Typography color='success.dark'>Editing strategy</Typography>;
}
return <Typography>Editing strategy</Typography>;
};
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 (
<>
<ChangeItemCreateEditDeleteWrapper className='delete-strategy-information-wrapper'>
<ChangeItemInfo>
<Typography
sx={(theme) => ({
color: theme.palette.error.main,
})}
>
- Deleting strategy
</Typography>
<StrategyTooltipLink name={name || ''} title={title}>
<StrategyDiff
change={change}
currentStrategy={referenceStrategy}
/>
</StrategyTooltipLink>
</ChangeItemInfo>
<div>{actions}</div>
</ChangeItemCreateEditDeleteWrapper>
{referenceStrategy && (
<StrategyExecution strategy={referenceStrategy} />
)}
</>
);
};
const UpdateStrategy: FC<{
change: IChangeRequestUpdateStrategy;
changeRequestState: ChangeRequestState;
currentStrategy: IFeatureStrategy | undefined;
actions?: ReactNode;
}> = ({ change, changeRequestState, currentStrategy, actions }) => {
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 (
<>
<ChangeOverwriteWarning
data={{
current: currentStrategy,
change,
changeType: 'strategy',
}}
changeRequestState={changeRequestState}
/>
<ChangeItemCreateEditDeleteWrapper>
<ChangeItemInfo>
<EditHeader
wasDisabled={currentStrategy?.disabled}
willBeDisabled={change.payload?.disabled}
/>
<StrategyTooltipLink
name={change.payload.name}
title={change.payload.title}
previousTitle={previousTitle}
>
<StrategyDiff
change={change}
currentStrategy={referenceStrategy}
/>
</StrategyTooltipLink>
</ChangeItemInfo>
<div>{actions}</div>
</ChangeItemCreateEditDeleteWrapper>
<ConditionallyRender
condition={
change.payload?.disabled !== currentStrategy?.disabled
}
show={
<Typography
sx={{
marginTop: (theme) => theme.spacing(2),
marginBottom: (theme) => theme.spacing(2),
...flexRow,
gap: (theme) => theme.spacing(1),
}}
>
This strategy will be{' '}
<DisabledEnabledState
disabled={change.payload?.disabled || false}
/>
</Typography>
}
/>
<StrategyExecution strategy={change.payload} />
{hasVariantDiff ? (
<StyledBox>
{change.payload.variants?.length ? (
<>
<StyledTypography>
{currentStrategy?.variants?.length
? 'Updating strategy variants to:'
: 'Adding strategy variants:'}
</StyledTypography>
<EnvironmentVariantsTable
variants={change.payload.variants || []}
/>
</>
) : (
<StyledTypography>
Removed all strategy variants.
</StyledTypography>
)}
</StyledBox>
) : null}
</>
);
};
const AddStrategy: FC<{
change: IChangeRequestAddStrategy;
actions?: ReactNode;
isDefaultChange?: boolean;
}> = ({ change, actions, isDefaultChange }) => (
<>
<ChangeItemCreateEditDeleteWrapper>
<ChangeItemInfo>
<Typography
color={
change.payload?.disabled
? 'action.disabled'
: 'success.dark'
}
>
+ Adding strategy
</Typography>
<StrategyTooltipLink
name={change.payload.name}
title={change.payload.title}
>
<StrategyDiff change={change} currentStrategy={undefined} />
</StrategyTooltipLink>
<div>
<DisabledEnabledState
disabled
show={change.payload?.disabled === true}
/>
</div>
</ChangeItemInfo>
<div>
{isDefaultChange ? (
<Typography variant='body2' color='text.secondary'>
Default strategy will be added
</Typography>
) : null}
{actions}
</div>
</ChangeItemCreateEditDeleteWrapper>
<StrategyExecution strategy={change.payload} />
{change.payload.variants?.length ? (
<StyledBox>
<StyledTypography>Adding strategy variants:</StyledTypography>
<EnvironmentVariantsTable
variants={change.payload.variants || []}
/>
</StyledBox>
) : null}
</>
);
/**
* Deprecated: use StrategyChange instead. Remove file with flag crDiffView
* @deprecated
*/
export const LegacyStrategyChange: FC<{
actions?: ReactNode;
change:
| IChangeRequestAddStrategy
| IChangeRequestDeleteStrategy
| IChangeRequestUpdateStrategy;
environmentName: string;
featureName: string;
projectId: string;
changeRequestState: ChangeRequestState;
isDefaultChange?: boolean;
}> = ({
actions,
change,
featureName,
environmentName,
projectId,
changeRequestState,
isDefaultChange,
}) => {
const currentStrategy = useCurrentStrategy(
change,
projectId,
featureName,
environmentName,
);
return (
<>
{change.action === 'addStrategy' && (
<AddStrategy
change={change}
actions={actions}
isDefaultChange={isDefaultChange}
/>
)}
{change.action === 'deleteStrategy' && (
<DeleteStrategy
change={change}
changeRequestState={changeRequestState}
currentStrategy={currentStrategy}
actions={actions}
/>
)}
{change.action === 'updateStrategy' && (
<UpdateStrategy
change={change}
changeRequestState={changeRequestState}
currentStrategy={currentStrategy}
actions={actions}
/>
)}
</>
);
};

View File

@ -5,11 +5,9 @@ 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;
@ -26,10 +24,6 @@ export const SegmentChange: FC<ISegmentChangeProps> = ({
}) => {
const { segment } = useSegment(segmentChange.payload.id);
const ChangeDetails = useUiFlag('crDiffView')
? SegmentChangeDetails
: LegacySegmentChangeDetails;
return (
<Card
elevation={0}
@ -81,7 +75,7 @@ export const SegmentChange: FC<ISegmentChangeProps> = ({
</Link>
</Box>
</Box>
<ChangeDetails
<SegmentChangeDetails
change={segmentChange}
actions={actions}
changeRequestState={changeRequestState}

View File

@ -1,7 +1,6 @@
import type { FC, ReactNode } from 'react';
import { Box, Typography } from '@mui/material';
import { Typography } from '@mui/material';
import { Badge } from 'component/common/Badge/Badge';
import { ChangeItemWrapper as LegacyChangeItemWrapper } from './LegacyStrategyChange.tsx';
import { Action, ChangeItemInfo, ChangeItemWrapper } from './Change.styles';
interface IToggleStatusChange {
@ -16,31 +15,6 @@ const StatusWillChange = () => (
</Typography>
);
/**
* @deprecated use ToggleStatusChange instead; remove with flag crDiffView
*/
export const LegacyToggleStatusChange: FC<IToggleStatusChange> = ({
enabled,
actions,
isDefaultChange,
}) => {
return (
<LegacyChangeItemWrapper>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
New status
<Badge
sx={(theme) => ({ marginLeft: theme.spacing(1) })}
color={enabled ? 'success' : 'error'}
>
{enabled ? ' Enabled' : 'Disabled'}
</Badge>
</Box>
{isDefaultChange ? <StatusWillChange /> : null}
{actions}
</LegacyChangeItemWrapper>
);
};
export const ToggleStatusChange: FC<IToggleStatusChange> = ({
enabled,
actions,

View File

@ -1,91 +0,0 @@
// deprecated: remove with flag crDiffView
import type {
IChangeRequestDeleteSegment,
IChangeRequestUpdateSegment,
} from 'component/changeRequest/changeRequest.types';
import type React from 'react';
import type { FC } from 'react';
import { EventDiff } from 'component/events/EventDiff/EventDiff';
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 './Changes/Change/NameWithChangeInfo/NameWithChangeInfo.tsx';
const StyledCodeSection = styled('div')(({ theme }) => ({
overflowX: 'auto',
'& code': {
wordWrap: 'break-word',
whiteSpace: 'pre-wrap',
fontFamily: 'monospace',
lineHeight: 1.5,
fontSize: theme.fontSizes.smallBody,
},
}));
export const SegmentDiff: FC<{
change: IChangeRequestUpdateSegment | IChangeRequestDeleteSegment;
currentSegment?: ISegment;
}> = ({ change, currentSegment }) => {
const changeRequestSegment =
change.action === 'deleteSegment' ? undefined : change.payload;
return (
<StyledCodeSection>
<EventDiff
entry={{
preData: omit(currentSegment, ['createdAt', 'createdBy']),
data: omit(changeRequestSegment, ['snapshot']),
}}
/>
</StyledCodeSection>
);
};
interface IStrategyTooltipLinkProps {
children?: React.ReactNode;
name?: string;
previousName?: string;
}
const StyledContainer: FC<{ children?: React.ReactNode }> = styled('div')(
({ theme }) => ({
display: 'grid',
gridAutoFlow: 'column',
gridTemplateColumns: 'auto 1fr',
gap: theme.spacing(1),
alignItems: 'center',
}),
);
const ViewDiff = styled('span')(({ theme }) => ({
color: theme.palette.primary.main,
marginLeft: theme.spacing(1),
}));
const Truncated = styled('div')(() => ({
...textTruncated,
maxWidth: 500,
display: 'flex',
}));
export const SegmentTooltipLink: FC<IStrategyTooltipLinkProps> = ({
name,
previousName,
children,
}) => (
<StyledContainer>
<Truncated>
<NameWithChangeInfo previousName={previousName} newName={name} />
<TooltipLink
tooltip={children}
tooltipProps={{
maxWidth: 500,
maxHeight: 600,
}}
>
<ViewDiff>View Diff</ViewDiff>
</TooltipLink>
</Truncated>
</StyledContainer>
);

View File

@ -1,54 +0,0 @@
import { render } from 'utils/testRenderer';
import { screen } from '@testing-library/react';
import { StrategyDiff } from './StrategyTooltipLink.tsx';
import type { IFeatureStrategy } from 'interfaces/strategy';
import type { IChangeRequestUpdateStrategy } from 'component/changeRequest/changeRequest.types';
test('Should not render the `snapshot` property', async () => {
const existingStrategy: IFeatureStrategy = {
name: 'flexibleRollout',
constraints: [],
variants: [],
parameters: {
groupId: 'aaa',
rollout: '71',
stickiness: 'default',
},
sortOrder: 0,
id: '31572930-2db7-461f-813b-3eedc200cb33',
title: '',
disabled: false,
segments: [],
};
const change: IChangeRequestUpdateStrategy = {
id: 39,
action: 'updateStrategy' as const,
payload: {
id: '31572930-2db7-461f-813b-3eedc200cb33',
name: 'flexibleRollout',
title: '',
disabled: false,
segments: [],
snapshot: existingStrategy,
variants: [],
parameters: {
groupId: 'aaa',
rollout: '38',
stickiness: 'default',
},
constraints: [],
},
createdAt: new Date('2024-01-18T07:58:36.314Z'),
createdBy: {
id: 1,
username: 'admin',
imageUrl:
'https://gravatar.com/avatar/8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918?s=42&d=retro&r=g',
},
};
render(<StrategyDiff change={change} currentStrategy={existingStrategy} />);
expect(screen.queryByText(/snapshot/)).toBeNull();
});

View File

@ -1,125 +0,0 @@
// deprecated: remove with flag crDiffView
import type {
IChangeRequestAddStrategy,
IChangeRequestDeleteStrategy,
IChangeRequestUpdateStrategy,
} from 'component/changeRequest/changeRequest.types';
import type React from 'react';
import type { FC } from 'react';
import {
formatStrategyName,
GetFeatureStrategyIcon,
} from 'utils/strategyNames';
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';
import type { IFeatureStrategy } from 'interfaces/strategy';
import { textTruncated } from 'themes/themeStyles';
import { NameWithChangeInfo } from '../Changes/Change/NameWithChangeInfo/NameWithChangeInfo.tsx';
const StyledCodeSection = styled('div')(({ theme }) => ({
overflowX: 'auto',
'& code': {
wordWrap: 'break-word',
whiteSpace: 'pre-wrap',
fontFamily: 'monospace',
lineHeight: 1.5,
fontSize: theme.fontSizes.smallBody,
},
}));
const sortSegments = <T extends { segments?: number[] }>(
item?: T,
): T | undefined => {
if (!item || !item.segments) {
return item;
}
return {
...item,
segments: [...item.segments].sort((a, b) => a - b),
};
};
export const StrategyDiff: FC<{
change:
| IChangeRequestAddStrategy
| IChangeRequestUpdateStrategy
| IChangeRequestDeleteStrategy;
currentStrategy?: IFeatureStrategy;
}> = ({ change, currentStrategy }) => {
const changeRequestStrategy =
change.action === 'deleteStrategy' ? undefined : change.payload;
const sortedCurrentStrategy = sortSegments(currentStrategy);
const sortedChangeRequestStrategy = sortSegments(changeRequestStrategy);
return (
<StyledCodeSection>
<EventDiff
entry={{
preData: omit(sortedCurrentStrategy, 'sortOrder'),
data: omit(sortedChangeRequestStrategy, 'snapshot'),
}}
/>
</StyledCodeSection>
);
};
interface IStrategyTooltipLinkProps {
name: string;
title?: string;
previousTitle?: string;
children?: React.ReactNode;
}
const StyledContainer: FC<{ children?: React.ReactNode }> = styled('div')(
({ theme }) => ({
display: 'grid',
gridAutoFlow: 'column',
gridTemplateColumns: 'auto 1fr',
gap: theme.spacing(1),
alignItems: 'center',
}),
);
const ViewDiff = styled('span')(({ theme }) => ({
color: theme.palette.primary.main,
marginLeft: theme.spacing(1),
}));
const Truncated = styled('div')(() => ({
...textTruncated,
maxWidth: 500,
}));
export const StrategyTooltipLink: FC<IStrategyTooltipLinkProps> = ({
name,
title,
previousTitle,
children,
}) => {
return (
<StyledContainer>
<GetFeatureStrategyIcon strategyName={name} />
<Truncated>
<Typography component='span'>
{formatStrategyName(name)}
</Typography>
<TooltipLink
tooltip={children}
tooltipProps={{
maxWidth: 500,
maxHeight: 600,
}}
>
<ViewDiff>View Diff</ViewDiff>
</TooltipLink>
<NameWithChangeInfo
newName={title}
previousName={previousTitle}
/>
</Truncated>
</StyledContainer>
);
};

View File

@ -89,7 +89,6 @@ export type UiFlags = {
lifecycleMetrics?: boolean;
createFlagDialogCache?: boolean;
impactMetrics?: boolean;
crDiffView?: boolean;
changeRequestApproverEmails?: boolean;
reportUnknownFlags?: boolean;
lifecycleGraphs?: boolean;

View File

@ -58,7 +58,6 @@ export type IFlagKey =
| 'customMetrics'
| 'impactMetrics'
| 'createFlagDialogCache'
| 'crDiffView'
| 'changeRequestApproverEmails'
| 'paygTrialEvents'
| 'paygInstanceStatsEvents'
@ -274,10 +273,6 @@ const flags: IFlags = {
process.env.UNLEASH_EXPERIMENTAL_CHANGE_REQUEST_APPROVER_EMAILS,
false,
),
crDiffView: parseEnvVarBoolean(
process.env.UNLEASH_EXPERIMENTAL_CR_DIFF_VIEW,
false,
),
impactMetrics: parseEnvVarBoolean(
process.env.UNLEASH_EXPERIMENTAL_IMPACT_METRICS,
false,

View File

@ -55,7 +55,6 @@ process.nextTick(async () => {
customMetrics: true,
lifecycleMetrics: true,
impactMetrics: true,
crDiffView: true,
paygTrialEvents: true,
lifecycleGraphs: true,
addConfiguration: true,