2022-11-29 14:59:04 +01:00
|
|
|
|
import React, { FC, VFC } from 'react';
|
2022-12-01 14:44:33 +01:00
|
|
|
|
import { Alert, Box, styled } 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 {
|
2022-12-01 14:44:33 +01:00
|
|
|
|
IChange,
|
2022-11-29 14:59:04 +01:00
|
|
|
|
IChangeRequest,
|
2022-12-01 14:44:33 +01:00
|
|
|
|
IChangeRequestFeature,
|
2022-11-29 14:59:04 +01:00
|
|
|
|
} from '../changeRequest.types';
|
2022-12-01 14:44:33 +01:00
|
|
|
|
import { hasNameField } 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';
|
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-12-01 14:44:33 +01:00
|
|
|
|
import { StrategyExecution } from '../../feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyExecution/StrategyExecution';
|
|
|
|
|
import {
|
|
|
|
|
CodeSnippetPopover,
|
|
|
|
|
PopoverDiff,
|
|
|
|
|
} from './CodeSnippetPopover/CodeSnippetPopover';
|
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-12-01 14:44:33 +01:00
|
|
|
|
const Change: FC<{
|
|
|
|
|
onDiscard: () => Promise<void>;
|
|
|
|
|
index: number;
|
|
|
|
|
changeRequest: IChangeRequest;
|
|
|
|
|
change: IChange;
|
|
|
|
|
feature: IChangeRequestFeature;
|
|
|
|
|
}> = ({ index, change, feature, changeRequest, onDiscard }) => {
|
|
|
|
|
const { isChangeRequestConfigured } = useChangeRequestsEnabled(
|
|
|
|
|
changeRequest.project
|
|
|
|
|
);
|
|
|
|
|
const allowChangeRequestActions = isChangeRequestConfigured(
|
|
|
|
|
changeRequest.environment
|
|
|
|
|
);
|
2022-11-29 14:59:04 +01:00
|
|
|
|
|
2022-12-01 14:44:33 +01:00
|
|
|
|
const showDiscard =
|
|
|
|
|
allowChangeRequestActions &&
|
|
|
|
|
!['Cancelled', 'Applied'].includes(changeRequest.state) &&
|
|
|
|
|
changeRequest.features.flatMap(feature => feature.changes).length > 1;
|
2022-11-29 14:59:04 +01:00
|
|
|
|
|
|
|
|
|
return (
|
2022-12-01 14:44:33 +01:00
|
|
|
|
<StyledSingleChangeBox
|
|
|
|
|
key={objectId(change)}
|
|
|
|
|
$hasConflict={Boolean(change.conflict)}
|
|
|
|
|
$isInConflictFeature={Boolean(feature.conflict)}
|
|
|
|
|
$isAfterWarning={Boolean(feature.changes[index - 1]?.conflict)}
|
|
|
|
|
$isLast={index + 1 === feature.changes.length}
|
|
|
|
|
>
|
|
|
|
|
<ConditionallyRender
|
|
|
|
|
condition={Boolean(change.conflict) && !feature.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}
|
|
|
|
|
discard={
|
|
|
|
|
<ConditionallyRender
|
|
|
|
|
condition={showDiscard}
|
|
|
|
|
show={<Discard onDiscard={onDiscard} />}
|
|
|
|
|
/>
|
|
|
|
|
}
|
2022-11-29 14:59:04 +01:00
|
|
|
|
/>
|
2022-12-01 14:44:33 +01:00
|
|
|
|
)}
|
|
|
|
|
{change.action === 'addStrategy' && (
|
|
|
|
|
<>
|
|
|
|
|
<StrategyAddedChange
|
|
|
|
|
discard={
|
|
|
|
|
<ConditionallyRender
|
|
|
|
|
condition={showDiscard}
|
|
|
|
|
show={<Discard onDiscard={onDiscard} />}
|
|
|
|
|
/>
|
|
|
|
|
}
|
|
|
|
|
>
|
|
|
|
|
<CodeSnippetPopover change={change}>
|
|
|
|
|
<PopoverDiff
|
|
|
|
|
change={change}
|
|
|
|
|
feature={feature.name}
|
|
|
|
|
environmentName={changeRequest.environment}
|
|
|
|
|
project={changeRequest.project}
|
|
|
|
|
/>
|
|
|
|
|
</CodeSnippetPopover>
|
|
|
|
|
</StrategyAddedChange>
|
|
|
|
|
<StrategyExecution strategy={change.payload} />
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
{change.action === 'deleteStrategy' && (
|
|
|
|
|
<StrategyDeletedChange
|
|
|
|
|
discard={
|
|
|
|
|
<ConditionallyRender
|
|
|
|
|
condition={showDiscard}
|
|
|
|
|
show={<Discard onDiscard={onDiscard} />}
|
|
|
|
|
/>
|
|
|
|
|
}
|
|
|
|
|
>
|
|
|
|
|
{hasNameField(change.payload) && (
|
|
|
|
|
<CodeSnippetPopover change={change}>
|
|
|
|
|
<PopoverDiff
|
|
|
|
|
change={change}
|
|
|
|
|
feature={feature.name}
|
|
|
|
|
environmentName={changeRequest.environment}
|
|
|
|
|
project={changeRequest.project}
|
|
|
|
|
/>
|
|
|
|
|
</CodeSnippetPopover>
|
|
|
|
|
)}
|
|
|
|
|
</StrategyDeletedChange>
|
|
|
|
|
)}
|
|
|
|
|
{change.action === 'updateStrategy' && (
|
|
|
|
|
<>
|
|
|
|
|
<StrategyEditedChange
|
|
|
|
|
discard={
|
|
|
|
|
<ConditionallyRender
|
|
|
|
|
condition={showDiscard}
|
|
|
|
|
show={<Discard onDiscard={onDiscard} />}
|
|
|
|
|
/>
|
|
|
|
|
}
|
|
|
|
|
>
|
|
|
|
|
<CodeSnippetPopover change={change}>
|
|
|
|
|
<PopoverDiff
|
|
|
|
|
change={change}
|
|
|
|
|
feature={feature.name}
|
|
|
|
|
environmentName={changeRequest.environment}
|
|
|
|
|
project={changeRequest.project}
|
|
|
|
|
/>
|
|
|
|
|
</CodeSnippetPopover>
|
|
|
|
|
</StrategyEditedChange>
|
|
|
|
|
<StrategyExecution strategy={change.payload} />
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</Box>
|
|
|
|
|
</StyledSingleChangeBox>
|
2022-11-29 14:59:04 +01:00
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
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-30 08:55:40 +01:00
|
|
|
|
const { discardChange } = useChangeRequestApi();
|
2022-10-31 13:46:54 +01:00
|
|
|
|
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);
|
2022-10-31 13:46:54 +01:00
|
|
|
|
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-23 12:16:20 +01:00
|
|
|
|
|
2022-10-26 13:57:59 +02:00
|
|
|
|
return (
|
|
|
|
|
<Box>
|
2022-12-01 14:44:33 +01:00
|
|
|
|
{changeRequest.features?.map(feature => (
|
2022-11-02 07:34:14 +01:00
|
|
|
|
<ChangeRequestFeatureToggleChange
|
2022-12-01 14:44:33 +01:00
|
|
|
|
key={feature.name}
|
|
|
|
|
featureName={feature.name}
|
2022-11-02 07:34:14 +01:00
|
|
|
|
projectId={changeRequest.project}
|
2022-10-31 13:46:54 +01:00
|
|
|
|
onNavigate={onNavigate}
|
2022-12-01 14:44:33 +01:00
|
|
|
|
conflict={feature.conflict}
|
2022-10-26 13:57:59 +02:00
|
|
|
|
>
|
2022-12-01 14:44:33 +01:00
|
|
|
|
{feature.changes.map((change, index) => (
|
|
|
|
|
<Change
|
|
|
|
|
onDiscard={onDiscard(change.id)}
|
|
|
|
|
index={index}
|
|
|
|
|
changeRequest={changeRequest}
|
|
|
|
|
change={change}
|
|
|
|
|
feature={feature}
|
|
|
|
|
/>
|
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>
|
|
|
|
|
);
|
|
|
|
|
};
|