2022-11-29 14:59:04 +01:00
|
|
|
|
import React, { FC, VFC } from 'react';
|
|
|
|
|
import {
|
|
|
|
|
Alert,
|
|
|
|
|
Box,
|
|
|
|
|
Popover,
|
|
|
|
|
styled,
|
|
|
|
|
Tooltip,
|
|
|
|
|
Typography,
|
|
|
|
|
} from '@mui/material';
|
2022-11-02 07:34:14 +01:00
|
|
|
|
import { ChangeRequestFeatureToggleChange } from '../ChangeRequestOverview/ChangeRequestFeatureToggleChange/ChangeRequestFeatureToggleChange';
|
2022-10-26 13:57:59 +02:00
|
|
|
|
import { objectId } from 'utils/objectId';
|
2022-11-02 07:34:14 +01:00
|
|
|
|
import { ToggleStatusChange } from '../ChangeRequestOverview/ChangeRequestFeatureToggleChange/ToggleStatusChange';
|
|
|
|
|
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
|
2022-10-31 13:46:54 +01:00
|
|
|
|
import { formatUnknownError } from 'utils/formatUnknownError';
|
|
|
|
|
import useToast from 'hooks/useToast';
|
2022-11-29 14:59:04 +01:00
|
|
|
|
import type {
|
|
|
|
|
IChangeRequest,
|
|
|
|
|
IChangeRequestDeleteStrategy,
|
|
|
|
|
IChangeRequestUpdateStrategy,
|
|
|
|
|
} from '../changeRequest.types';
|
2022-11-04 10:33:07 +01:00
|
|
|
|
import {
|
2022-11-23 12:16:20 +01:00
|
|
|
|
Discard,
|
2022-11-04 10:33:07 +01:00
|
|
|
|
StrategyAddedChange,
|
|
|
|
|
StrategyDeletedChange,
|
|
|
|
|
StrategyEditedChange,
|
|
|
|
|
} from '../ChangeRequestOverview/ChangeRequestFeatureToggleChange/StrategyChange';
|
|
|
|
|
import {
|
|
|
|
|
formatStrategyName,
|
|
|
|
|
GetFeatureStrategyIcon,
|
2022-11-04 11:55:45 +01:00
|
|
|
|
} from 'utils/strategyNames';
|
2022-11-29 14:59:04 +01:00
|
|
|
|
import {
|
|
|
|
|
hasNameField,
|
|
|
|
|
IChangeRequestAddStrategy,
|
|
|
|
|
} from '../changeRequest.types';
|
2022-11-15 09:53:38 +01:00
|
|
|
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
2022-11-24 16:16:14 +01:00
|
|
|
|
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
2022-11-29 14:59:04 +01:00
|
|
|
|
import EventDiff from '../../events/EventDiff/EventDiff';
|
2022-10-26 13:57:59 +02:00
|
|
|
|
|
2022-11-02 16:05:27 +01:00
|
|
|
|
interface IChangeRequestProps {
|
|
|
|
|
changeRequest: IChangeRequest;
|
2022-10-31 13:46:54 +01:00
|
|
|
|
onRefetch?: () => void;
|
|
|
|
|
onNavigate?: () => void;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-17 15:33:23 +01:00
|
|
|
|
const StyledSingleChangeBox = styled(Box, {
|
|
|
|
|
shouldForwardProp: (prop: string) => !prop.startsWith('$'),
|
|
|
|
|
})<{
|
|
|
|
|
$hasConflict: boolean;
|
|
|
|
|
$isAfterWarning: boolean;
|
|
|
|
|
$isLast: boolean;
|
|
|
|
|
$isInConflictFeature: boolean;
|
|
|
|
|
}>(
|
|
|
|
|
({
|
|
|
|
|
theme,
|
|
|
|
|
$hasConflict,
|
|
|
|
|
$isInConflictFeature,
|
|
|
|
|
$isAfterWarning,
|
|
|
|
|
$isLast,
|
|
|
|
|
}) => ({
|
|
|
|
|
borderLeft: '1px solid',
|
|
|
|
|
borderRight: '1px solid',
|
|
|
|
|
borderTop: '1px solid',
|
|
|
|
|
borderBottom: $isLast ? '1px solid' : 'none',
|
|
|
|
|
borderRadius: $isLast
|
|
|
|
|
? `0 0
|
2022-11-15 09:53:38 +01:00
|
|
|
|
${theme.shape.borderRadiusLarge}px ${theme.shape.borderRadiusLarge}px`
|
2022-11-17 15:33:23 +01:00
|
|
|
|
: 0,
|
|
|
|
|
borderColor:
|
|
|
|
|
$hasConflict || $isInConflictFeature
|
|
|
|
|
? theme.palette.warning.border
|
|
|
|
|
: theme.palette.dividerAlternative,
|
|
|
|
|
borderTopColor:
|
|
|
|
|
($hasConflict || $isAfterWarning) && !$isInConflictFeature
|
|
|
|
|
? theme.palette.warning.border
|
|
|
|
|
: theme.palette.dividerAlternative,
|
|
|
|
|
})
|
|
|
|
|
);
|
2022-11-15 09:53:38 +01:00
|
|
|
|
|
|
|
|
|
const StyledAlert = styled(Alert)(({ theme }) => ({
|
|
|
|
|
borderRadius: 0,
|
|
|
|
|
padding: theme.spacing(0, 2),
|
|
|
|
|
'&.MuiAlert-standardWarning': {
|
|
|
|
|
borderStyle: 'none none solid none',
|
|
|
|
|
},
|
|
|
|
|
}));
|
|
|
|
|
|
2022-11-29 14:59:04 +01:00
|
|
|
|
const CodeSnippetPopover: FC<{
|
|
|
|
|
change:
|
|
|
|
|
| IChangeRequestAddStrategy
|
|
|
|
|
| IChangeRequestUpdateStrategy
|
|
|
|
|
| IChangeRequestDeleteStrategy;
|
|
|
|
|
}> = ({ change }) => {
|
|
|
|
|
const [anchorEl, setAnchorEl] = React.useState<HTMLElement | null>(null);
|
|
|
|
|
|
|
|
|
|
const handlePopoverOpen = (event: React.MouseEvent<HTMLElement>) => {
|
|
|
|
|
setAnchorEl(event.currentTarget);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handlePopoverClose = () => {
|
|
|
|
|
setAnchorEl(null);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const open = Boolean(anchorEl);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<GetFeatureStrategyIcon strategyName={change.payload.name} />
|
|
|
|
|
|
|
|
|
|
<Typography
|
|
|
|
|
onMouseEnter={handlePopoverOpen}
|
|
|
|
|
onMouseLeave={handlePopoverClose}
|
|
|
|
|
>
|
|
|
|
|
{formatStrategyName(change.payload.name)}
|
|
|
|
|
</Typography>
|
|
|
|
|
<Popover
|
|
|
|
|
id={String(change.id)}
|
|
|
|
|
sx={{
|
|
|
|
|
pointerEvents: 'none',
|
|
|
|
|
}}
|
|
|
|
|
open={open}
|
|
|
|
|
anchorEl={anchorEl}
|
|
|
|
|
anchorOrigin={{
|
|
|
|
|
vertical: 'bottom',
|
|
|
|
|
horizontal: 'left',
|
|
|
|
|
}}
|
|
|
|
|
transformOrigin={{
|
|
|
|
|
vertical: 'top',
|
|
|
|
|
horizontal: 'left',
|
|
|
|
|
}}
|
|
|
|
|
onClose={handlePopoverClose}
|
|
|
|
|
disableRestoreFocus
|
|
|
|
|
>
|
|
|
|
|
<Box sx={{ paddingLeft: 3, paddingRight: 3 }}>
|
|
|
|
|
<EventDiff
|
|
|
|
|
entry={{
|
|
|
|
|
data: change.payload,
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</Box>
|
|
|
|
|
</Popover>
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2022-11-02 16:05:27 +01:00
|
|
|
|
export const ChangeRequest: VFC<IChangeRequestProps> = ({
|
2022-11-02 07:34:14 +01:00
|
|
|
|
changeRequest,
|
2022-10-31 13:46:54 +01:00
|
|
|
|
onRefetch,
|
|
|
|
|
onNavigate,
|
|
|
|
|
}) => {
|
2022-11-02 07:34:14 +01:00
|
|
|
|
const { discardChangeRequestEvent } = useChangeRequestApi();
|
2022-10-31 13:46:54 +01:00
|
|
|
|
const { setToastData, setToastApiError } = useToast();
|
|
|
|
|
const onDiscard = (id: number) => async () => {
|
|
|
|
|
try {
|
2022-11-02 07:34:14 +01:00
|
|
|
|
await discardChangeRequestEvent(
|
|
|
|
|
changeRequest.project,
|
|
|
|
|
changeRequest.id,
|
2022-10-31 13:46:54 +01:00
|
|
|
|
id
|
|
|
|
|
);
|
|
|
|
|
setToastData({
|
2022-11-02 07:34:14 +01:00
|
|
|
|
title: 'Change discarded from change request draft.',
|
2022-10-31 13:46:54 +01:00
|
|
|
|
type: 'success',
|
|
|
|
|
});
|
|
|
|
|
onRefetch?.();
|
|
|
|
|
} catch (error: unknown) {
|
|
|
|
|
setToastApiError(formatUnknownError(error));
|
|
|
|
|
}
|
|
|
|
|
};
|
2022-11-24 16:16:14 +01:00
|
|
|
|
const { isChangeRequestConfigured } = useChangeRequestsEnabled(
|
|
|
|
|
changeRequest.project
|
|
|
|
|
);
|
|
|
|
|
const allowChangeRequestActions = isChangeRequestConfigured(
|
|
|
|
|
changeRequest.environment
|
|
|
|
|
);
|
2022-10-31 13:46:54 +01:00
|
|
|
|
|
2022-11-23 12:16:20 +01:00
|
|
|
|
const showDiscard =
|
2022-11-24 16:16:14 +01:00
|
|
|
|
allowChangeRequestActions &&
|
2022-11-23 12:16:20 +01:00
|
|
|
|
!['Cancelled', 'Applied'].includes(changeRequest.state) &&
|
|
|
|
|
changeRequest.features.flatMap(feature => feature.changes).length > 1;
|
|
|
|
|
|
2022-10-26 13:57:59 +02:00
|
|
|
|
return (
|
|
|
|
|
<Box>
|
2022-11-02 07:34:14 +01:00
|
|
|
|
{changeRequest.features?.map(featureToggleChange => (
|
|
|
|
|
<ChangeRequestFeatureToggleChange
|
2022-10-28 09:43:49 +02:00
|
|
|
|
key={featureToggleChange.name}
|
2022-10-31 13:46:54 +01:00
|
|
|
|
featureName={featureToggleChange.name}
|
2022-11-02 07:34:14 +01:00
|
|
|
|
projectId={changeRequest.project}
|
2022-10-31 13:46:54 +01:00
|
|
|
|
onNavigate={onNavigate}
|
2022-11-15 09:53:38 +01:00
|
|
|
|
conflict={featureToggleChange.conflict}
|
2022-10-26 13:57:59 +02:00
|
|
|
|
>
|
2022-11-15 09:53:38 +01:00
|
|
|
|
{featureToggleChange.changes.map((change, index) => (
|
|
|
|
|
<StyledSingleChangeBox
|
2022-11-04 12:52:47 +01:00
|
|
|
|
key={objectId(change)}
|
2022-11-17 15:33:23 +01:00
|
|
|
|
$hasConflict={Boolean(change.conflict)}
|
|
|
|
|
$isInConflictFeature={Boolean(
|
2022-11-15 09:53:38 +01:00
|
|
|
|
featureToggleChange.conflict
|
2022-11-04 12:52:47 +01:00
|
|
|
|
)}
|
2022-11-17 15:33:23 +01:00
|
|
|
|
$isAfterWarning={Boolean(
|
2022-11-15 09:53:38 +01:00
|
|
|
|
featureToggleChange.changes[index - 1]?.conflict
|
|
|
|
|
)}
|
2022-11-17 15:33:23 +01:00
|
|
|
|
$isLast={
|
2022-11-15 09:53:38 +01:00
|
|
|
|
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' && (
|
|
|
|
|
<ToggleStatusChange
|
|
|
|
|
enabled={change.payload.enabled}
|
2022-11-23 12:16:20 +01:00
|
|
|
|
discard={
|
|
|
|
|
<ConditionallyRender
|
|
|
|
|
condition={showDiscard}
|
|
|
|
|
show={
|
|
|
|
|
<Discard
|
|
|
|
|
onDiscard={onDiscard(
|
|
|
|
|
change.id
|
|
|
|
|
)}
|
|
|
|
|
/>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
}
|
2022-10-26 13:57:59 +02:00
|
|
|
|
/>
|
2022-11-15 09:53:38 +01:00
|
|
|
|
)}
|
|
|
|
|
{change.action === 'addStrategy' && (
|
|
|
|
|
<StrategyAddedChange
|
2022-11-23 12:16:20 +01:00
|
|
|
|
discard={
|
|
|
|
|
<ConditionallyRender
|
|
|
|
|
condition={showDiscard}
|
|
|
|
|
show={
|
|
|
|
|
<Discard
|
|
|
|
|
onDiscard={onDiscard(
|
|
|
|
|
change.id
|
|
|
|
|
)}
|
|
|
|
|
/>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
}
|
2022-11-15 09:53:38 +01:00
|
|
|
|
>
|
2022-11-29 14:59:04 +01:00
|
|
|
|
<CodeSnippetPopover change={change} />
|
2022-11-15 09:53:38 +01:00
|
|
|
|
</StrategyAddedChange>
|
|
|
|
|
)}
|
|
|
|
|
{change.action === 'deleteStrategy' && (
|
|
|
|
|
<StrategyDeletedChange
|
2022-11-23 12:16:20 +01:00
|
|
|
|
discard={
|
|
|
|
|
<ConditionallyRender
|
|
|
|
|
condition={showDiscard}
|
|
|
|
|
show={
|
|
|
|
|
<Discard
|
|
|
|
|
onDiscard={onDiscard(
|
|
|
|
|
change.id
|
|
|
|
|
)}
|
|
|
|
|
/>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
}
|
2022-11-15 09:53:38 +01:00
|
|
|
|
>
|
|
|
|
|
{hasNameField(change.payload) && (
|
2022-11-29 14:59:04 +01:00
|
|
|
|
<CodeSnippetPopover
|
|
|
|
|
change={change}
|
|
|
|
|
/>
|
2022-11-15 09:53:38 +01:00
|
|
|
|
)}
|
|
|
|
|
</StrategyDeletedChange>
|
|
|
|
|
)}
|
|
|
|
|
{change.action === 'updateStrategy' && (
|
|
|
|
|
<StrategyEditedChange
|
2022-11-23 12:16:20 +01:00
|
|
|
|
discard={
|
|
|
|
|
<ConditionallyRender
|
|
|
|
|
condition={showDiscard}
|
|
|
|
|
show={
|
|
|
|
|
<Discard
|
|
|
|
|
onDiscard={onDiscard(
|
|
|
|
|
change.id
|
|
|
|
|
)}
|
|
|
|
|
/>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
}
|
2022-11-15 09:53:38 +01:00
|
|
|
|
>
|
2022-11-29 14:59:04 +01:00
|
|
|
|
<CodeSnippetPopover change={change} />
|
2022-11-15 09:53:38 +01:00
|
|
|
|
</StrategyEditedChange>
|
|
|
|
|
)}
|
|
|
|
|
</Box>
|
|
|
|
|
</StyledSingleChangeBox>
|
2022-10-26 13:57:59 +02:00
|
|
|
|
))}
|
2022-11-02 07:34:14 +01:00
|
|
|
|
</ChangeRequestFeatureToggleChange>
|
2022-10-26 13:57:59 +02:00
|
|
|
|
))}
|
|
|
|
|
</Box>
|
|
|
|
|
);
|
|
|
|
|
};
|