mirror of
https://github.com/Unleash/unleash.git
synced 2025-05-12 01:17:04 +02:00
feat: scheduled change request state (#5261)
Adds the scheduled state to ChangeRequestOverview.tsx <img width="1523" alt="Screenshot 2023-11-03 at 12 52 07" src="https://github.com/Unleash/unleash/assets/104830839/710b5f26-04a0-4ee9-b646-8ff3090ad86a"> --------- Signed-off-by: andreas-unleash <andreas@getunleash.ai>
This commit is contained in:
parent
43298e16e2
commit
6b637d5fa9
@ -9,7 +9,8 @@ export const ApplyButton: FC<{
|
||||
disabled: boolean;
|
||||
onSchedule: () => void;
|
||||
onApply: () => void;
|
||||
}> = ({ disabled, onSchedule, onApply, children }) => (
|
||||
variant?: 'create' | 'update';
|
||||
}> = ({ disabled, onSchedule, onApply, variant = 'create', children }) => (
|
||||
<MultiActionButton
|
||||
permission={APPLY_CHANGE_REQUEST}
|
||||
disabled={disabled}
|
||||
@ -20,7 +21,10 @@ export const ApplyButton: FC<{
|
||||
icon: <CheckBox fontSize='small' />,
|
||||
},
|
||||
{
|
||||
label: 'Schedule changes',
|
||||
label:
|
||||
variant === 'create'
|
||||
? 'Schedule changes'
|
||||
: 'Update schedule',
|
||||
onSelect: onSchedule,
|
||||
icon: <Today fontSize='small' />,
|
||||
},
|
||||
|
@ -26,7 +26,6 @@ import { ChangeRequestReviewers } from './ChangeRequestReviewers/ChangeRequestRe
|
||||
import { ChangeRequestRejectDialogue } from './ChangeRequestRejectDialog/ChangeRequestRejectDialog';
|
||||
import { ApplyButton } from './ApplyButton/ApplyButton';
|
||||
import { useUiFlag } from 'hooks/useUiFlag';
|
||||
import { scheduler } from 'timers/promises';
|
||||
|
||||
const StyledAsideBox = styled(Box)(({ theme }) => ({
|
||||
width: '30%',
|
||||
@ -85,7 +84,6 @@ export const ChangeRequestOverview: FC = () => {
|
||||
const { setToastData, setToastApiError } = useToast();
|
||||
const { isChangeRequestConfiguredForReview } =
|
||||
useChangeRequestsEnabled(projectId);
|
||||
|
||||
const scheduleChangeRequests = useUiFlag('scheduledConfigurationChanges');
|
||||
|
||||
if (!changeRequest) {
|
||||
@ -191,7 +189,7 @@ export const ChangeRequestOverview: FC = () => {
|
||||
changeRequest.state === 'In review' &&
|
||||
!isAdmin;
|
||||
|
||||
const hasApprovedAlready = changeRequest.approvals.some(
|
||||
const hasApprovedAlready = changeRequest.approvals?.some(
|
||||
(approval) => approval.createdBy.id === user?.id,
|
||||
);
|
||||
|
||||
@ -312,6 +310,35 @@ export const ChangeRequestOverview: FC = () => {
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
scheduleChangeRequests &&
|
||||
changeRequest.state === 'Scheduled' &&
|
||||
changeRequest.schedule?.status === 'pending'
|
||||
}
|
||||
show={
|
||||
<ApplyButton
|
||||
onApply={() => {
|
||||
console.log(
|
||||
'I would show the apply now dialog',
|
||||
);
|
||||
}}
|
||||
disabled={
|
||||
!allowChangeRequestActions ||
|
||||
loading
|
||||
}
|
||||
onSchedule={() => {
|
||||
console.log(
|
||||
'I would schedule changes now',
|
||||
);
|
||||
}}
|
||||
variant={'update'}
|
||||
>
|
||||
Apply or schedule changes
|
||||
</ApplyButton>
|
||||
}
|
||||
/>
|
||||
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
changeRequest.state !== 'Applied' &&
|
||||
@ -329,7 +356,10 @@ export const ChangeRequestOverview: FC = () => {
|
||||
variant='outlined'
|
||||
onClick={onCancel}
|
||||
>
|
||||
Cancel changes
|
||||
{changeRequest.schedule
|
||||
? 'Reject'
|
||||
: 'Cancel'}{' '}
|
||||
changes
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { styled } from '@mui/material';
|
||||
import { Cancel, CheckCircle } from '@mui/icons-material';
|
||||
import { Cancel, CheckCircle, Schedule, Edit } from '@mui/icons-material';
|
||||
import { Box, Typography, Divider } from '@mui/material';
|
||||
|
||||
const styledComponentPropCheck = () => (prop: string) =>
|
||||
@ -36,6 +36,18 @@ export const StyledSuccessIcon = styled(CheckCircle)(({ theme }) => ({
|
||||
marginRight: theme.spacing(1),
|
||||
}));
|
||||
|
||||
export const StyledScheduledIcon = styled(Schedule)(({ theme }) => ({
|
||||
color: theme.palette.warning.main,
|
||||
height: '35px',
|
||||
width: '35px',
|
||||
marginRight: theme.spacing(1),
|
||||
}));
|
||||
export const StyledEditIcon = styled(Edit)(({ theme }) => ({
|
||||
color: theme.palette.text.secondary,
|
||||
height: '24px',
|
||||
width: '24px',
|
||||
}));
|
||||
|
||||
export const StyledOuterContainer = styled(Box)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
marginTop: theme.spacing(2),
|
||||
@ -77,3 +89,10 @@ export const StyledReviewTitle = styled(Typography, {
|
||||
fontWeight: 'bold',
|
||||
color,
|
||||
}));
|
||||
|
||||
export const StyledScheduledBox = styled(Box)({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
width: '100%',
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { FC } from 'react';
|
||||
import { Box, Theme, Typography, useTheme } from '@mui/material';
|
||||
import { Box, IconButton, Theme, Typography, useTheme } from '@mui/material';
|
||||
import { ReactComponent as ChangesAppliedIcon } from 'assets/icons/merge.svg';
|
||||
import {
|
||||
StyledOuterContainer,
|
||||
@ -11,6 +11,9 @@ import {
|
||||
StyledWarningIcon,
|
||||
StyledReviewTitle,
|
||||
StyledDivider,
|
||||
StyledScheduledIcon,
|
||||
StyledEditIcon,
|
||||
StyledScheduledBox,
|
||||
} from './ChangeRequestReviewStatus.styles';
|
||||
import {
|
||||
ChangeRequestState,
|
||||
@ -21,7 +24,7 @@ interface ISuggestChangeReviewsStatusProps {
|
||||
changeRequest: IChangeRequest;
|
||||
}
|
||||
const resolveBorder = (state: ChangeRequestState, theme: Theme) => {
|
||||
if (state === 'Approved') {
|
||||
if (state === 'Approved' || state === 'Scheduled') {
|
||||
return `2px solid ${theme.palette.success.main}`;
|
||||
}
|
||||
|
||||
@ -109,6 +112,12 @@ const ResolveComponent = ({ changeRequest }: IResolveComponentProps) => {
|
||||
return <Rejected />;
|
||||
}
|
||||
|
||||
if (state === 'Scheduled') {
|
||||
return (
|
||||
<Scheduled scheduledDate={changeRequest.schedule?.scheduledAt} />
|
||||
);
|
||||
}
|
||||
|
||||
return <ReviewRequired minApprovals={changeRequest.minApprovals} />;
|
||||
};
|
||||
|
||||
@ -194,6 +203,69 @@ const Applied = () => {
|
||||
);
|
||||
};
|
||||
|
||||
interface IScheduledProps {
|
||||
scheduledDate?: string;
|
||||
}
|
||||
const Scheduled = ({ scheduledDate }: IScheduledProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
if (!scheduledDate) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const getBrowserTimezone = (): string => {
|
||||
const offset = -new Date().getTimezoneOffset();
|
||||
const hours = Math.floor(Math.abs(offset) / 60);
|
||||
const minutes = Math.abs(offset) % 60;
|
||||
let sign = '+';
|
||||
if (offset < 0) {
|
||||
sign = '-';
|
||||
}
|
||||
|
||||
// Ensure that hours and minutes are two digits
|
||||
const zeroPaddedHours = hours.toString().padStart(2, '0');
|
||||
const zeroPaddedMinutes = minutes.toString().padStart(2, '0');
|
||||
|
||||
return `UTC${sign}${zeroPaddedHours}:${zeroPaddedMinutes}`;
|
||||
};
|
||||
|
||||
const timezone = getBrowserTimezone();
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledFlexAlignCenterBox>
|
||||
<StyledSuccessIcon />
|
||||
<Box>
|
||||
<StyledReviewTitle color={theme.palette.success.dark}>
|
||||
Changes approved
|
||||
</StyledReviewTitle>
|
||||
<Typography>
|
||||
One approving review from requested approvers
|
||||
</Typography>
|
||||
</Box>
|
||||
</StyledFlexAlignCenterBox>
|
||||
|
||||
<StyledDivider />
|
||||
|
||||
<StyledScheduledBox>
|
||||
<StyledFlexAlignCenterBox>
|
||||
<StyledScheduledIcon />
|
||||
<Box>
|
||||
<StyledReviewTitle color={theme.palette.warning.dark}>
|
||||
Changes are scheduled to be applied on:{' '}
|
||||
{new Date(scheduledDate).toLocaleString()}
|
||||
</StyledReviewTitle>
|
||||
<Typography>Your timezone is {timezone}</Typography>
|
||||
</Box>
|
||||
</StyledFlexAlignCenterBox>
|
||||
<IconButton>
|
||||
<StyledEditIcon />
|
||||
</IconButton>
|
||||
</StyledScheduledBox>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const Cancelled = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { VFC } from 'react';
|
||||
import { ChangeRequestState } from '../changeRequest.types';
|
||||
import { Badge } from 'component/common/Badge/Badge';
|
||||
import { Check, CircleOutlined, Close } from '@mui/icons-material';
|
||||
import { AccessTime, Check, CircleOutlined, Close } from '@mui/icons-material';
|
||||
|
||||
interface IChangeRequestStatusBadgeProps {
|
||||
state: ChangeRequestState;
|
||||
@ -47,6 +47,12 @@ export const ChangeRequestStatusBadge: VFC<IChangeRequestStatusBadgeProps> = ({
|
||||
Rejected
|
||||
</Badge>
|
||||
);
|
||||
case 'Scheduled':
|
||||
return (
|
||||
<Badge color='warning' icon={<AccessTime fontSize={'small'} />}>
|
||||
Scheduled
|
||||
</Badge>
|
||||
);
|
||||
default:
|
||||
return <ReviewRequiredBadge />;
|
||||
}
|
||||
|
@ -18,6 +18,12 @@ export interface IChangeRequest {
|
||||
rejections: IChangeRequestApproval[];
|
||||
comments: IChangeRequestComment[];
|
||||
conflict?: string;
|
||||
schedule?: IChangeRequestSchedule;
|
||||
}
|
||||
|
||||
export interface IChangeRequestSchedule {
|
||||
scheduledAt: string;
|
||||
status: 'pending' | 'failed';
|
||||
}
|
||||
|
||||
export interface IChangeRequestEnvironmentConfig {
|
||||
@ -67,6 +73,7 @@ export type ChangeRequestState =
|
||||
| 'Approved'
|
||||
| 'In review'
|
||||
| 'Applied'
|
||||
| 'Scheduled'
|
||||
| 'Cancelled'
|
||||
| 'Rejected';
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user