1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-06 00:07:44 +01:00
unleash.unleash/frontend/src/component/changeRequest/ChangeRequest/ChangeRequest.tsx

296 lines
12 KiB
TypeScript
Raw Normal View History

2022-11-29 14:59:04 +01:00
import React, { FC, VFC } from 'react';
import {
Alert,
Box,
Popover,
styled,
Tooltip,
Typography,
} from '@mui/material';
import { ChangeRequestFeatureToggleChange } from '../ChangeRequestOverview/ChangeRequestFeatureToggleChange/ChangeRequestFeatureToggleChange';
import { objectId } from 'utils/objectId';
import { ToggleStatusChange } from '../ChangeRequestOverview/ChangeRequestFeatureToggleChange/ToggleStatusChange';
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
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';
import {
2022-11-23 12:16:20 +01:00
Discard,
StrategyAddedChange,
StrategyDeletedChange,
StrategyEditedChange,
} from '../ChangeRequestOverview/ChangeRequestFeatureToggleChange/StrategyChange';
import {
formatStrategyName,
GetFeatureStrategyIcon,
} from 'utils/strategyNames';
2022-11-29 14:59:04 +01:00
import {
hasNameField,
IChangeRequestAddStrategy,
} from '../changeRequest.types';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
2022-11-29 14:59:04 +01:00
import EventDiff from '../../events/EventDiff/EventDiff';
interface IChangeRequestProps {
changeRequest: IChangeRequest;
onRefetch?: () => void;
onNavigate?: () => void;
}
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
${theme.shape.borderRadiusLarge}px ${theme.shape.borderRadiusLarge}px`
: 0,
borderColor:
$hasConflict || $isInConflictFeature
? theme.palette.warning.border
: theme.palette.dividerAlternative,
borderTopColor:
($hasConflict || $isAfterWarning) && !$isInConflictFeature
? 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',
},
}));
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>
</>
);
};
export const ChangeRequest: VFC<IChangeRequestProps> = ({
changeRequest,
onRefetch,
onNavigate,
}) => {
2022-11-30 08:55:40 +01:00
const { discardChange } = useChangeRequestApi();
const { setToastData, setToastApiError } = useToast();
const onDiscard = (id: number) => async () => {
try {
2022-11-30 08:55:40 +01:00
await discardChange(changeRequest.project, changeRequest.id, id);
setToastData({
title: 'Change discarded from change request draft.',
type: 'success',
});
onRefetch?.();
} catch (error: unknown) {
setToastApiError(formatUnknownError(error));
}
};
const { isChangeRequestConfigured } = useChangeRequestsEnabled(
changeRequest.project
);
const allowChangeRequestActions = isChangeRequestConfigured(
changeRequest.environment
);
2022-11-23 12:16:20 +01:00
const showDiscard =
allowChangeRequestActions &&
2022-11-23 12:16:20 +01:00
!['Cancelled', 'Applied'].includes(changeRequest.state) &&
changeRequest.features.flatMap(feature => feature.changes).length > 1;
return (
<Box>
{changeRequest.features?.map(featureToggleChange => (
<ChangeRequestFeatureToggleChange
key={featureToggleChange.name}
featureName={featureToggleChange.name}
projectId={changeRequest.project}
onNavigate={onNavigate}
conflict={featureToggleChange.conflict}
>
{featureToggleChange.changes.map((change, index) => (
<StyledSingleChangeBox
key={objectId(change)}
$hasConflict={Boolean(change.conflict)}
$isInConflictFeature={Boolean(
featureToggleChange.conflict
)}
$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
cant 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
)}
/>
}
/>
}
/>
)}
{change.action === 'addStrategy' && (
<StrategyAddedChange
2022-11-23 12:16:20 +01:00
discard={
<ConditionallyRender
condition={showDiscard}
show={
<Discard
onDiscard={onDiscard(
change.id
)}
/>
}
/>
}
>
2022-11-29 14:59:04 +01:00
<CodeSnippetPopover change={change} />
</StrategyAddedChange>
)}
{change.action === 'deleteStrategy' && (
<StrategyDeletedChange
2022-11-23 12:16:20 +01:00
discard={
<ConditionallyRender
condition={showDiscard}
show={
<Discard
onDiscard={onDiscard(
change.id
)}
/>
}
/>
}
>
{hasNameField(change.payload) && (
2022-11-29 14:59:04 +01:00
<CodeSnippetPopover
change={change}
/>
)}
</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-29 14:59:04 +01:00
<CodeSnippetPopover change={change} />
</StrategyEditedChange>
)}
</Box>
</StyledSingleChangeBox>
))}
</ChangeRequestFeatureToggleChange>
))}
</Box>
);
};