mirror of
https://github.com/Unleash/unleash.git
synced 2025-05-26 01:17:00 +02:00
Show conflicts in change requests (#2389)
## About the changes Show warnings about incompatible changes in changesets. Closes [1-352/display-conflicts](https://linear.app/unleash/issue/1-352/display-conflicts) Relates to [roadmap](https://github.com/orgs/Unleash/projects/10) item: #2251
This commit is contained in:
parent
89f2d81253
commit
8b057a1466
@ -1,5 +1,5 @@
|
|||||||
import { VFC } from 'react';
|
import { VFC } from 'react';
|
||||||
import { Box } from '@mui/material';
|
import { Alert, Box, styled } from '@mui/material';
|
||||||
import { ChangeRequestFeatureToggleChange } from '../ChangeRequestOverview/ChangeRequestFeatureToggleChange/ChangeRequestFeatureToggleChange';
|
import { ChangeRequestFeatureToggleChange } from '../ChangeRequestOverview/ChangeRequestFeatureToggleChange/ChangeRequestFeatureToggleChange';
|
||||||
import { objectId } from 'utils/objectId';
|
import { objectId } from 'utils/objectId';
|
||||||
import { ToggleStatusChange } from '../ChangeRequestOverview/ChangeRequestFeatureToggleChange/ToggleStatusChange';
|
import { ToggleStatusChange } from '../ChangeRequestOverview/ChangeRequestFeatureToggleChange/ToggleStatusChange';
|
||||||
@ -17,6 +17,7 @@ import {
|
|||||||
GetFeatureStrategyIcon,
|
GetFeatureStrategyIcon,
|
||||||
} from 'utils/strategyNames';
|
} from 'utils/strategyNames';
|
||||||
import { hasNameField } from '../changeRequest.types';
|
import { hasNameField } from '../changeRequest.types';
|
||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
|
||||||
interface IChangeRequestProps {
|
interface IChangeRequestProps {
|
||||||
changeRequest: IChangeRequest;
|
changeRequest: IChangeRequest;
|
||||||
@ -24,6 +25,38 @@ interface IChangeRequestProps {
|
|||||||
onNavigate?: () => void;
|
onNavigate?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const StyledSingleChangeBox = styled(Box)<{
|
||||||
|
hasConflict: boolean;
|
||||||
|
isAfterWarning: boolean;
|
||||||
|
isLast: boolean;
|
||||||
|
inInConflictFeature: boolean;
|
||||||
|
}>(({ theme, hasConflict, inInConflictFeature, isAfterWarning, isLast }) => ({
|
||||||
|
borderLeft: '1px solid',
|
||||||
|
borderRight: '1px solid',
|
||||||
|
borderTop: '1px solid',
|
||||||
|
borderBottom: isLast ? '1px solid' : 'none',
|
||||||
|
borderRadius: isLast
|
||||||
|
? `0 0
|
||||||
|
${theme.shape.borderRadiusLarge}px ${theme.shape.borderRadiusLarge}px`
|
||||||
|
: 0,
|
||||||
|
borderColor:
|
||||||
|
hasConflict || inInConflictFeature
|
||||||
|
? theme.palette.warning.border
|
||||||
|
: theme.palette.dividerAlternative,
|
||||||
|
borderTopColor:
|
||||||
|
(hasConflict || isAfterWarning) && !inInConflictFeature
|
||||||
|
? theme.palette.warning.border
|
||||||
|
: theme.palette.dividerAlternative,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledAlert = styled(Alert)(({ theme }) => ({
|
||||||
|
borderRadius: 0,
|
||||||
|
padding: theme.spacing(0, 2),
|
||||||
|
'&.MuiAlert-standardWarning': {
|
||||||
|
borderStyle: 'none none solid none',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
export const ChangeRequest: VFC<IChangeRequestProps> = ({
|
export const ChangeRequest: VFC<IChangeRequestProps> = ({
|
||||||
changeRequest,
|
changeRequest,
|
||||||
onRefetch,
|
onRefetch,
|
||||||
@ -56,17 +89,35 @@ export const ChangeRequest: VFC<IChangeRequestProps> = ({
|
|||||||
featureName={featureToggleChange.name}
|
featureName={featureToggleChange.name}
|
||||||
projectId={changeRequest.project}
|
projectId={changeRequest.project}
|
||||||
onNavigate={onNavigate}
|
onNavigate={onNavigate}
|
||||||
|
conflict={featureToggleChange.conflict}
|
||||||
>
|
>
|
||||||
{featureToggleChange.changes.map(change => (
|
{featureToggleChange.changes.map((change, index) => (
|
||||||
<Box
|
<StyledSingleChangeBox
|
||||||
key={objectId(change)}
|
key={objectId(change)}
|
||||||
sx={theme => ({
|
hasConflict={Boolean(change.conflict)}
|
||||||
padding: 2,
|
inInConflictFeature={Boolean(
|
||||||
borderTop: '1px solid',
|
featureToggleChange.conflict
|
||||||
borderColor: theme =>
|
)}
|
||||||
theme.palette.dividerAlternative,
|
isAfterWarning={Boolean(
|
||||||
})}
|
featureToggleChange.changes[index - 1]?.conflict
|
||||||
|
)}
|
||||||
|
isLast={
|
||||||
|
index + 1 === featureToggleChange.changes.length
|
||||||
|
}
|
||||||
>
|
>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={
|
||||||
|
Boolean(change.conflict) &&
|
||||||
|
!featureToggleChange.conflict
|
||||||
|
}
|
||||||
|
show={
|
||||||
|
<StyledAlert severity="warning">
|
||||||
|
<strong>Conflict!</strong> This change
|
||||||
|
can’t be applied. {change.conflict}.
|
||||||
|
</StyledAlert>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Box sx={{ p: 2 }}>
|
||||||
{change.action === 'updateEnabled' && (
|
{change.action === 'updateEnabled' && (
|
||||||
<ToggleStatusChange
|
<ToggleStatusChange
|
||||||
enabled={change.payload.enabled}
|
enabled={change.payload.enabled}
|
||||||
@ -81,7 +132,9 @@ export const ChangeRequest: VFC<IChangeRequestProps> = ({
|
|||||||
strategyName={change.payload.name}
|
strategyName={change.payload.name}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{formatStrategyName(change.payload.name)}
|
{formatStrategyName(
|
||||||
|
change.payload.name
|
||||||
|
)}
|
||||||
</StrategyAddedChange>
|
</StrategyAddedChange>
|
||||||
)}
|
)}
|
||||||
{change.action === 'deleteStrategy' && (
|
{change.action === 'deleteStrategy' && (
|
||||||
@ -109,10 +162,13 @@ export const ChangeRequest: VFC<IChangeRequestProps> = ({
|
|||||||
<GetFeatureStrategyIcon
|
<GetFeatureStrategyIcon
|
||||||
strategyName={change.payload.name}
|
strategyName={change.payload.name}
|
||||||
/>
|
/>
|
||||||
{formatStrategyName(change.payload.name)}
|
{formatStrategyName(
|
||||||
|
change.payload.name
|
||||||
|
)}
|
||||||
</StrategyEditedChange>
|
</StrategyEditedChange>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
</StyledSingleChangeBox>
|
||||||
))}
|
))}
|
||||||
</ChangeRequestFeatureToggleChange>
|
</ChangeRequestFeatureToggleChange>
|
||||||
))}
|
))}
|
||||||
|
@ -1,35 +1,67 @@
|
|||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { Box, Card, Typography } from '@mui/material';
|
import { Alert, Box, Card, Typography } from '@mui/material';
|
||||||
import ToggleOnIcon from '@mui/icons-material/ToggleOn';
|
import ToggleOnIcon from '@mui/icons-material/ToggleOn';
|
||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
|
||||||
interface IChangeRequestToggleChange {
|
interface IChangeRequestToggleChange {
|
||||||
featureName: string;
|
featureName: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
|
conflict?: string;
|
||||||
onNavigate?: () => void;
|
onNavigate?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ChangeRequestFeatureToggleChange: FC<
|
export const ChangeRequestFeatureToggleChange: FC<
|
||||||
IChangeRequestToggleChange
|
IChangeRequestToggleChange
|
||||||
> = ({ featureName, projectId, onNavigate, children }) => {
|
> = ({ featureName, projectId, conflict, onNavigate, children }) => (
|
||||||
return (
|
|
||||||
<Card
|
<Card
|
||||||
elevation={0}
|
elevation={0}
|
||||||
sx={theme => ({
|
sx={theme => ({
|
||||||
marginTop: theme.spacing(2),
|
marginTop: theme.spacing(2),
|
||||||
borderRadius: theme => `${theme.shape.borderRadiusLarge}px`,
|
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
border: '1px solid',
|
|
||||||
borderColor: theme => theme.palette.dividerAlternative,
|
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
sx={theme => ({
|
sx={theme => ({
|
||||||
backgroundColor: theme.palette.tableHeaderBackground,
|
backgroundColor: Boolean(conflict)
|
||||||
padding: theme.spacing(3),
|
? theme.palette.warning.light
|
||||||
|
: theme.palette.tableHeaderBackground,
|
||||||
|
borderRadius: theme =>
|
||||||
|
`${theme.shape.borderRadiusLarge}px ${theme.shape.borderRadiusLarge}px 0 0`,
|
||||||
|
border: '1px solid',
|
||||||
|
borderColor: theme =>
|
||||||
|
conflict
|
||||||
|
? theme.palette.warning.border
|
||||||
|
: theme.palette.dividerAlternative,
|
||||||
|
borderBottom: 'none',
|
||||||
|
overflow: 'hidden',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
<ConditionallyRender
|
||||||
|
condition={Boolean(conflict)}
|
||||||
|
show={
|
||||||
|
<Alert
|
||||||
|
severity="warning"
|
||||||
|
sx={{
|
||||||
|
mx: 1,
|
||||||
|
'&.MuiAlert-standardWarning': {
|
||||||
|
borderStyle: 'none',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<strong>Conflict!</strong> {conflict}.
|
||||||
|
</Alert>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
gap: 1,
|
||||||
|
pt: conflict ? 0 : 2,
|
||||||
|
pb: 2,
|
||||||
|
px: 3,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<ToggleOnIcon color="disabled" />
|
<ToggleOnIcon color="disabled" />
|
||||||
<Typography
|
<Typography
|
||||||
component={Link}
|
component={Link}
|
||||||
@ -44,5 +76,4 @@ export const ChangeRequestFeatureToggleChange: FC<
|
|||||||
</Box>
|
</Box>
|
||||||
<Box>{children}</Box>
|
<Box>{children}</Box>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
|
||||||
|
@ -89,10 +89,10 @@ export const ChangeRequestOverview: FC = () => {
|
|||||||
<StyledAsideBox>
|
<StyledAsideBox>
|
||||||
<ChangeRequestTimeline state={changeRequest.state} />
|
<ChangeRequestTimeline state={changeRequest.state} />
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={changeRequest.approvals.length > 0}
|
condition={changeRequest.approvals?.length > 0}
|
||||||
show={
|
show={
|
||||||
<ChangeRequestReviewers>
|
<ChangeRequestReviewers>
|
||||||
{changeRequest.approvals.map(approver => (
|
{changeRequest.approvals?.map(approver => (
|
||||||
<ChangeRequestReviewer
|
<ChangeRequestReviewer
|
||||||
name={
|
name={
|
||||||
approver.createdBy.username ||
|
approver.createdBy.username ||
|
||||||
|
@ -10,6 +10,7 @@ export interface IChangeRequest {
|
|||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
features: IChangeRequestFeature[];
|
features: IChangeRequestFeature[];
|
||||||
approvals: IChangeRequestApproval[];
|
approvals: IChangeRequestApproval[];
|
||||||
|
conflict?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IChangeRequestEnvironmentConfig {
|
export interface IChangeRequestEnvironmentConfig {
|
||||||
|
Loading…
Reference in New Issue
Block a user