Skip to content

Commit

Permalink
feat: scheduled change request state (#5261)
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
andreas-unleash authored Nov 3, 2023
1 parent 43298e1 commit 6b637d5
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -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' />,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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%',
Expand Down Expand Up @@ -85,7 +84,6 @@ export const ChangeRequestOverview: FC = () => {
const { setToastData, setToastApiError } = useToast();
const { isChangeRequestConfiguredForReview } =
useChangeRequestsEnabled(projectId);

const scheduleChangeRequests = useUiFlag('scheduledConfigurationChanges');

if (!changeRequest) {
Expand Down Expand Up @@ -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,
);

Expand Down Expand Up @@ -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' &&
Expand All @@ -329,7 +356,10 @@ export const ChangeRequestOverview: FC = () => {
variant='outlined'
onClick={onCancel}
>
Cancel changes
{changeRequest.schedule
? 'Reject'
: 'Cancel'}{' '}
changes
</Button>
}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -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) =>
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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%',
});
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -11,6 +11,9 @@ import {
StyledWarningIcon,
StyledReviewTitle,
StyledDivider,
StyledScheduledIcon,
StyledEditIcon,
StyledScheduledBox,
} from './ChangeRequestReviewStatus.styles';
import {
ChangeRequestState,
Expand All @@ -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}`;
}

Expand Down Expand Up @@ -109,6 +112,12 @@ const ResolveComponent = ({ changeRequest }: IResolveComponentProps) => {
return <Rejected />;
}

if (state === 'Scheduled') {
return (
<Scheduled scheduledDate={changeRequest.schedule?.scheduledAt} />
);
}

return <ReviewRequired minApprovals={changeRequest.minApprovals} />;
};

Expand Down Expand Up @@ -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();

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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 />;
}
Expand Down
7 changes: 7 additions & 0 deletions frontend/src/component/changeRequest/changeRequest.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -67,6 +73,7 @@ export type ChangeRequestState =
| 'Approved'
| 'In review'
| 'Applied'
| 'Scheduled'
| 'Cancelled'
| 'Rejected';

Expand Down

0 comments on commit 6b637d5

Please sign in to comment.