mirror of
https://github.com/Unleash/unleash.git
synced 2025-08-04 13:48:56 +02:00
Add various fixes to the CR view (#10231)
Adds a number of small corrections and fixes to (mainly) CR-related component. Discovered as part of adding the new JSON diff view. Refer to the various inline comments for explanations. --------- Co-authored-by: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com>
This commit is contained in:
parent
55b8941306
commit
f2766b6b3b
@ -16,7 +16,7 @@ export const ArchiveFeatureChange: FC<IArchiveFeatureChange> = ({
|
|||||||
actions,
|
actions,
|
||||||
}) => (
|
}) => (
|
||||||
<ChangeItemWrapper>
|
<ChangeItemWrapper>
|
||||||
<ArchiveBox>Archiving feature</ArchiveBox>
|
<ArchiveBox>Archiving flag</ArchiveBox>
|
||||||
{actions}
|
{actions}
|
||||||
</ChangeItemWrapper>
|
</ChangeItemWrapper>
|
||||||
);
|
);
|
||||||
|
@ -26,6 +26,7 @@ import { FeatureStrategyForm } from '../../../../feature/FeatureStrategy/Feature
|
|||||||
import { NewStrategyVariants } from 'component/feature/StrategyTypes/NewStrategyVariants';
|
import { NewStrategyVariants } from 'component/feature/StrategyTypes/NewStrategyVariants';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { constraintId } from 'constants/constraintId.ts';
|
import { constraintId } from 'constants/constraintId.ts';
|
||||||
|
import { apiPayloadConstraintReplacer } from 'utils/api-payload-constraint-replacer.ts';
|
||||||
|
|
||||||
interface IEditChangeProps {
|
interface IEditChangeProps {
|
||||||
change: IChangeRequestAddStrategy | IChangeRequestUpdateStrategy;
|
change: IChangeRequestAddStrategy | IChangeRequestUpdateStrategy;
|
||||||
@ -208,7 +209,7 @@ export const formatUpdateStrategyApiCode = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const url = `${unleashUrl}/api/admin/projects/${projectId}/change-requests/${changeRequestId}/changes/${changeId}`;
|
const url = `${unleashUrl}/api/admin/projects/${projectId}/change-requests/${changeRequestId}/changes/${changeId}`;
|
||||||
const payload = JSON.stringify(strategy, undefined, 2);
|
const payload = JSON.stringify(strategy, apiPayloadConstraintReplacer, 2);
|
||||||
|
|
||||||
return `curl --location --request PUT '${url}' \\
|
return `curl --location --request PUT '${url}' \\
|
||||||
--header 'Authorization: INSERT_API_KEY' \\
|
--header 'Authorization: INSERT_API_KEY' \\
|
||||||
|
@ -6,6 +6,7 @@ import { Box, styled } from '@mui/material';
|
|||||||
import { EnvironmentStrategyOrderDiff } from './EnvironmentStrategyOrderDiff.tsx';
|
import { EnvironmentStrategyOrderDiff } from './EnvironmentStrategyOrderDiff.tsx';
|
||||||
import { StrategyExecution } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyExecution/StrategyExecution';
|
import { StrategyExecution } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyExecution/StrategyExecution';
|
||||||
import { formatStrategyName } from '../../../../../../utils/strategyNames.tsx';
|
import { formatStrategyName } from '../../../../../../utils/strategyNames.tsx';
|
||||||
|
import type { IFeatureStrategy } from 'interfaces/strategy.ts';
|
||||||
|
|
||||||
const ChangeItemInfo = styled(Box)({
|
const ChangeItemInfo = styled(Box)({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@ -74,14 +75,14 @@ export const EnvironmentStrategyExecutionOrder = ({
|
|||||||
.map((strategy) => strategy.id) ?? [],
|
.map((strategy) => strategy.id) ?? [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const updatedStrategies = change.payload
|
const updatedStrategies: IFeatureStrategy[] = change.payload
|
||||||
.map(({ id }) => {
|
.map(({ id }) => {
|
||||||
return environmentStrategies.find((s) => s.id === id);
|
return environmentStrategies.find((s) => s.id === id);
|
||||||
})
|
})
|
||||||
.filter(Boolean);
|
.filter((strategy): strategy is IFeatureStrategy => Boolean(strategy));
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
strategyIds: updatedStrategies.map((strategy) => strategy!.id),
|
strategyIds: updatedStrategies.map((strategy) => strategy.id),
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -105,7 +106,7 @@ export const EnvironmentStrategyExecutionOrder = ({
|
|||||||
</StyledChangeHeader>
|
</StyledChangeHeader>
|
||||||
<StyledStrategyExecutionWrapper>
|
<StyledStrategyExecutionWrapper>
|
||||||
{updatedStrategies.map((strategy, index) => (
|
{updatedStrategies.map((strategy, index) => (
|
||||||
<StyledStrategyContainer>
|
<StyledStrategyContainer key={strategy.id}>
|
||||||
{`${index + 1}: `}
|
{`${index + 1}: `}
|
||||||
{formatStrategyName(strategy?.name || '')}
|
{formatStrategyName(strategy?.name || '')}
|
||||||
{strategy?.title && ` - ${strategy.title}`}
|
{strategy?.title && ` - ${strategy.title}`}
|
||||||
|
@ -7,6 +7,7 @@ import type {
|
|||||||
} from '../../../changeRequest.types';
|
} from '../../../changeRequest.types';
|
||||||
import { SegmentChangeDetails } from './SegmentChangeDetails.tsx';
|
import { SegmentChangeDetails } from './SegmentChangeDetails.tsx';
|
||||||
import { ConflictWarning } from './ConflictWarning.tsx';
|
import { ConflictWarning } from './ConflictWarning.tsx';
|
||||||
|
import { useSegment } from 'hooks/api/getters/useSegment/useSegment.ts';
|
||||||
|
|
||||||
interface ISegmentChangeProps {
|
interface ISegmentChangeProps {
|
||||||
segmentChange: ISegmentChange;
|
segmentChange: ISegmentChange;
|
||||||
@ -20,61 +21,65 @@ export const SegmentChange: FC<ISegmentChangeProps> = ({
|
|||||||
onNavigate,
|
onNavigate,
|
||||||
actions,
|
actions,
|
||||||
changeRequestState,
|
changeRequestState,
|
||||||
}) => (
|
}) => {
|
||||||
<Card
|
const { segment } = useSegment(segmentChange.payload.id);
|
||||||
elevation={0}
|
|
||||||
sx={(theme) => ({
|
return (
|
||||||
marginTop: theme.spacing(2),
|
<Card
|
||||||
marginBottom: theme.spacing(2),
|
elevation={0}
|
||||||
overflow: 'hidden',
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={(theme) => ({
|
sx={(theme) => ({
|
||||||
backgroundColor: theme.palette.neutral.light,
|
marginTop: theme.spacing(2),
|
||||||
borderRadius: (theme) =>
|
marginBottom: theme.spacing(2),
|
||||||
`${theme.shape.borderRadiusLarge}px ${theme.shape.borderRadiusLarge}px 0 0`,
|
|
||||||
border: '1px solid',
|
|
||||||
borderColor: (theme) =>
|
|
||||||
segmentChange.conflict
|
|
||||||
? theme.palette.warning.border
|
|
||||||
: theme.palette.divider,
|
|
||||||
borderBottom: 'none',
|
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<ConflictWarning conflict={segmentChange.conflict} />
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={(theme) => ({
|
||||||
display: 'flex',
|
backgroundColor: theme.palette.neutral.light,
|
||||||
pt: 2,
|
borderRadius: `${theme.shape.borderRadiusLarge}px ${theme.shape.borderRadiusLarge}px 0 0`,
|
||||||
pb: 2,
|
border: '1px solid',
|
||||||
px: 3,
|
borderColor: segmentChange.conflict
|
||||||
}}
|
? theme.palette.warning.border
|
||||||
|
: theme.palette.divider,
|
||||||
|
borderBottom: 'none',
|
||||||
|
overflow: 'hidden',
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
<Typography>Segment name: </Typography>
|
<ConflictWarning conflict={segmentChange.conflict} />
|
||||||
|
<Box
|
||||||
<Link
|
|
||||||
component={RouterLink}
|
|
||||||
to={`/segments/edit/${segmentChange.payload.id}`}
|
|
||||||
color='primary'
|
|
||||||
underline='hover'
|
|
||||||
sx={{
|
sx={{
|
||||||
marginLeft: 1,
|
display: 'flex',
|
||||||
'& :hover': {
|
pt: 2,
|
||||||
textDecoration: 'underline',
|
pb: 2,
|
||||||
},
|
px: 3,
|
||||||
}}
|
}}
|
||||||
onClick={onNavigate}
|
|
||||||
>
|
>
|
||||||
<strong>{segmentChange.payload.name}</strong>
|
<Typography>Segment name:</Typography>
|
||||||
</Link>
|
|
||||||
|
<Link
|
||||||
|
component={RouterLink}
|
||||||
|
to={`/segments/edit/${segmentChange.payload.id}`}
|
||||||
|
color='primary'
|
||||||
|
underline='hover'
|
||||||
|
sx={{
|
||||||
|
marginLeft: 1,
|
||||||
|
'& :hover': {
|
||||||
|
textDecoration: 'underline',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
onClick={onNavigate}
|
||||||
|
>
|
||||||
|
<strong>
|
||||||
|
{segmentChange.payload.name || segment?.name}
|
||||||
|
</strong>
|
||||||
|
</Link>
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
<SegmentChangeDetails
|
||||||
<SegmentChangeDetails
|
change={segmentChange}
|
||||||
change={segmentChange}
|
actions={actions}
|
||||||
actions={actions}
|
changeRequestState={changeRequestState}
|
||||||
changeRequestState={changeRequestState}
|
/>
|
||||||
/>
|
</Card>
|
||||||
</Card>
|
);
|
||||||
);
|
};
|
||||||
|
@ -9,13 +9,14 @@ import {
|
|||||||
formatStrategyName,
|
formatStrategyName,
|
||||||
GetFeatureStrategyIcon,
|
GetFeatureStrategyIcon,
|
||||||
} from 'utils/strategyNames';
|
} from 'utils/strategyNames';
|
||||||
import EventDiff from 'component/events/EventDiff/EventDiff';
|
import EventDiff, { NewEventDiff } from 'component/events/EventDiff/EventDiff';
|
||||||
import omit from 'lodash.omit';
|
import omit from 'lodash.omit';
|
||||||
import { TooltipLink } from 'component/common/TooltipLink/TooltipLink';
|
import { TooltipLink } from 'component/common/TooltipLink/TooltipLink';
|
||||||
import { Typography, styled } from '@mui/material';
|
import { Typography, styled } from '@mui/material';
|
||||||
import type { IFeatureStrategy } from 'interfaces/strategy';
|
import type { IFeatureStrategy } from 'interfaces/strategy';
|
||||||
import { textTruncated } from 'themes/themeStyles';
|
import { textTruncated } from 'themes/themeStyles';
|
||||||
import { NameWithChangeInfo } from '../NameWithChangeInfo/NameWithChangeInfo.tsx';
|
import { NameWithChangeInfo } from '../NameWithChangeInfo/NameWithChangeInfo.tsx';
|
||||||
|
import { useUiFlag } from 'hooks/useUiFlag.ts';
|
||||||
|
|
||||||
const StyledCodeSection = styled('div')(({ theme }) => ({
|
const StyledCodeSection = styled('div')(({ theme }) => ({
|
||||||
overflowX: 'auto',
|
overflowX: 'auto',
|
||||||
@ -47,12 +48,22 @@ export const StrategyDiff: FC<{
|
|||||||
| IChangeRequestDeleteStrategy;
|
| IChangeRequestDeleteStrategy;
|
||||||
currentStrategy?: IFeatureStrategy;
|
currentStrategy?: IFeatureStrategy;
|
||||||
}> = ({ change, currentStrategy }) => {
|
}> = ({ change, currentStrategy }) => {
|
||||||
|
const useNewDiff = useUiFlag('improvedJsonDiff');
|
||||||
const changeRequestStrategy =
|
const changeRequestStrategy =
|
||||||
change.action === 'deleteStrategy' ? undefined : change.payload;
|
change.action === 'deleteStrategy' ? undefined : change.payload;
|
||||||
|
|
||||||
const sortedCurrentStrategy = sortSegments(currentStrategy);
|
const sortedCurrentStrategy = sortSegments(currentStrategy);
|
||||||
const sortedChangeRequestStrategy = sortSegments(changeRequestStrategy);
|
const sortedChangeRequestStrategy = sortSegments(changeRequestStrategy);
|
||||||
|
if (useNewDiff) {
|
||||||
|
return (
|
||||||
|
<NewEventDiff
|
||||||
|
entry={{
|
||||||
|
preData: omit(sortedCurrentStrategy, 'sortOrder'),
|
||||||
|
data: omit(sortedChangeRequestStrategy, 'snapshot'),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<StyledCodeSection>
|
<StyledCodeSection>
|
||||||
<EventDiff
|
<EventDiff
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { styled } from '@mui/material';
|
import { styled, type TypographyProps } from '@mui/material';
|
||||||
import { Box, Card, Paper, Typography } from '@mui/material';
|
import { Box, Card, Paper, Typography } from '@mui/material';
|
||||||
import { UserAvatar } from 'component/common/UserAvatar/UserAvatar';
|
import { UserAvatar } from 'component/common/UserAvatar/UserAvatar';
|
||||||
|
|
||||||
@ -18,12 +18,14 @@ export const StyledInnerContainer = styled(Box)(({ theme }) => ({
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const StyledHeader = styled(Typography)(({ theme }) => ({
|
export const StyledHeader = styled(Typography)<TypographyProps>(
|
||||||
display: 'flex',
|
({ theme }) => ({
|
||||||
alignItems: 'center',
|
display: 'flex',
|
||||||
marginRight: theme.spacing(1),
|
alignItems: 'center',
|
||||||
fontSize: theme.fontSizes.mainHeader,
|
marginRight: theme.spacing(1),
|
||||||
}));
|
fontSize: theme.fontSizes.mainHeader,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
export const StyledCard = styled(Card)(({ theme }) => ({
|
export const StyledCard = styled(Card)(({ theme }) => ({
|
||||||
padding: theme.spacing(0.75, 1.5),
|
padding: theme.spacing(0.75, 1.5),
|
||||||
|
@ -26,7 +26,7 @@ export const ChangeRequestHeader: FC<{ changeRequest: ChangeRequestType }> = ({
|
|||||||
title={title}
|
title={title}
|
||||||
setTitle={setTitle}
|
setTitle={setTitle}
|
||||||
>
|
>
|
||||||
<StyledHeader variant='h1' sx={{ mr: 1.5 }}>
|
<StyledHeader variant='h1' component='h2' sx={{ mr: 1.5 }}>
|
||||||
{title}
|
{title}
|
||||||
</StyledHeader>
|
</StyledHeader>
|
||||||
</ChangeRequestTitle>
|
</ChangeRequestTitle>
|
||||||
|
@ -70,7 +70,7 @@ test('Add single archive feature change to change request', async () => {
|
|||||||
|
|
||||||
expect(screen.getByText('Archive feature flag')).toBeInTheDocument();
|
expect(screen.getByText('Archive feature flag')).toBeInTheDocument();
|
||||||
await screen.findByText(
|
await screen.findByText(
|
||||||
'Archiving features with dependencies will also remove those dependencies.',
|
'Archiving flags with dependencies will also remove those dependencies.',
|
||||||
);
|
);
|
||||||
const button = await screen.findByText('Add change to draft');
|
const button = await screen.findByText('Add change to draft');
|
||||||
|
|
||||||
@ -100,7 +100,7 @@ test('Add multiple archive feature changes to change request', async () => {
|
|||||||
|
|
||||||
await screen.findByText('Archive feature flags');
|
await screen.findByText('Archive feature flags');
|
||||||
await screen.findByText(
|
await screen.findByText(
|
||||||
'Archiving features with dependencies will also remove those dependencies.',
|
'Archiving flags with dependencies will also remove those dependencies.',
|
||||||
);
|
);
|
||||||
const button = await screen.findByText('Add to change request');
|
const button = await screen.findByText('Add to change request');
|
||||||
|
|
||||||
@ -163,7 +163,7 @@ test('Show error message when multiple parents of orphaned children are archived
|
|||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
screen.queryByText(
|
screen.queryByText(
|
||||||
'Archiving features with dependencies will also remove those dependencies.',
|
'Archiving flags with dependencies will also remove those dependencies.',
|
||||||
),
|
),
|
||||||
).not.toBeInTheDocument();
|
).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
@ -189,7 +189,7 @@ test('Show error message when 1 parent of orphaned children is archived', async
|
|||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
screen.queryByText(
|
screen.queryByText(
|
||||||
'Archiving features with dependencies will also remove those dependencies.',
|
'Archiving flags with dependencies will also remove those dependencies.',
|
||||||
),
|
),
|
||||||
).not.toBeInTheDocument();
|
).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
@ -26,7 +26,7 @@ interface IFeatureArchiveDialogProps {
|
|||||||
const RemovedDependenciesAlert = () => {
|
const RemovedDependenciesAlert = () => {
|
||||||
return (
|
return (
|
||||||
<Alert severity='warning' sx={{ m: (theme) => theme.spacing(2, 0) }}>
|
<Alert severity='warning' sx={{ m: (theme) => theme.spacing(2, 0) }}>
|
||||||
Archiving features with dependencies will also remove those
|
Archiving flags with dependencies will also remove those
|
||||||
dependencies.
|
dependencies.
|
||||||
</Alert>
|
</Alert>
|
||||||
);
|
);
|
||||||
|
@ -4,6 +4,7 @@ import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashCon
|
|||||||
import { ConstraintsList } from 'component/common/ConstraintsList/ConstraintsList';
|
import { ConstraintsList } from 'component/common/ConstraintsList/ConstraintsList';
|
||||||
import { ConstraintAccordionView } from 'component/common/NewConstraintAccordion/ConstraintAccordionView/ConstraintAccordionView';
|
import { ConstraintAccordionView } from 'component/common/NewConstraintAccordion/ConstraintAccordionView/ConstraintAccordionView';
|
||||||
import { constraintId } from 'constants/constraintId';
|
import { constraintId } from 'constants/constraintId';
|
||||||
|
import { objectId } from 'utils/objectId';
|
||||||
|
|
||||||
export interface IViewableConstraintsListProps {
|
export interface IViewableConstraintsListProps {
|
||||||
constraints: IConstraint[];
|
constraints: IConstraint[];
|
||||||
@ -29,7 +30,7 @@ export const ViewableConstraintsList = ({
|
|||||||
<ConstraintsList>
|
<ConstraintsList>
|
||||||
{constraints.map((constraint) => (
|
{constraints.map((constraint) => (
|
||||||
<ConstraintAccordionView
|
<ConstraintAccordionView
|
||||||
key={constraint[constraintId]}
|
key={constraint[constraintId] || objectId(constraint)}
|
||||||
constraint={constraint}
|
constraint={constraint}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
@ -78,7 +78,7 @@ const ButtonIcon = styled('span')(({ theme }) => ({
|
|||||||
marginInlineEnd: theme.spacing(0.5),
|
marginInlineEnd: theme.spacing(0.5),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const NewEventDiff: FC<IEventDiffProps> = ({ entry, excludeKeys }) => {
|
export const NewEventDiff: FC<IEventDiffProps> = ({ entry, excludeKeys }) => {
|
||||||
const changeType = entry.preData && entry.data ? 'edit' : 'replacement';
|
const changeType = entry.preData && entry.data ? 'edit' : 'replacement';
|
||||||
const showExpandButton = changeType === 'edit';
|
const showExpandButton = changeType === 'edit';
|
||||||
const [full, setFull] = useState(false);
|
const [full, setFull] = useState(false);
|
||||||
@ -220,4 +220,7 @@ const EventDiff: FC<IEventDiffProps> = (props) => {
|
|||||||
return <OldEventDiff {...props} />;
|
return <OldEventDiff {...props} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated remove the default export with flag improvedJsonDiff. Switch imports in files that use this to the named import instead.
|
||||||
|
*/
|
||||||
export default EventDiff;
|
export default EventDiff;
|
||||||
|
Loading…
Reference in New Issue
Block a user