1
0
mirror of https://github.com/Unleash/unleash.git synced 2024-12-22 19:07:54 +01:00

feat: schedule changes dialog (#5285)

Closes: # 

[1-1585](https://linear.app/unleash/issue/1-1585/reschedule-changes-dialog)

[1-1582](https://linear.app/unleash/issue/1-1582/change-apply-changes-apply-or-schedule-changes)

Manually tested e2e -> Approve -> Schedule -> Reschedule ->
Apply/Reject: ui tests verifying it in next pr

---------

Signed-off-by: andreas-unleash <andreas@getunleash.ai>
Co-authored-by: Thomas Heartman <thomas@getunleash.ai>
This commit is contained in:
andreas-unleash 2023-11-07 10:59:49 +02:00 committed by GitHub
parent b3054c9277
commit addda5b022
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 179 additions and 29 deletions

View File

@ -30,6 +30,7 @@ import {
ChangeRequestApplyScheduledDialogue,
ChangeRequestRejectScheduledDialogue,
} from './ChangeRequestScheduledDialogs/changeRequestScheduledDialogs';
import { ScheduleChangeRequestDialog } from './ChangeRequestScheduledDialogs/ScheduleChangeRequestDialog';
const StyledAsideBox = styled(Box)(({ theme }) => ({
width: '30%',
@ -77,6 +78,8 @@ export const ChangeRequestOverview: FC = () => {
const projectId = useRequiredPathParam('projectId');
const [showCancelDialog, setShowCancelDialog] = useState(false);
const [showRejectDialog, setShowRejectDialog] = useState(false);
const [showScheduleChangesDialog, setShowScheduleChangeDialog] =
useState(false);
const [showApplyScheduledDialog, setShowApplyScheduledDialog] =
useState(false);
const [showRejectScheduledDialog, setShowRejectScheduledDialog] =
@ -111,6 +114,7 @@ export const ChangeRequestOverview: FC = () => {
await changeState(projectId, Number(id), {
state: 'Applied',
});
setShowApplyScheduledDialog(false);
refetchChangeRequest();
refetchChangeRequestOpen();
setToastData({
@ -123,6 +127,25 @@ export const ChangeRequestOverview: FC = () => {
}
};
const onScheduleChangeRequest = async (scheduledDate: Date) => {
try {
await changeState(projectId, Number(id), {
state: 'Scheduled',
scheduledAt: scheduledDate.toISOString(),
});
setShowScheduleChangeDialog(false);
refetchChangeRequest();
refetchChangeRequestOpen();
setToastData({
type: 'success',
title: 'Success',
text: 'Changes scheduled',
});
} catch (error: unknown) {
setToastApiError(formatUnknownError(error));
}
};
const onAddComment = async () => {
try {
await addComment(projectId, id, commentText);
@ -196,6 +219,7 @@ export const ChangeRequestOverview: FC = () => {
const onCancelAbort = () => setShowCancelDialog(false);
const onCancelReject = () => setShowRejectDialog(false);
const onApplyScheduledAbort = () => setShowApplyScheduledDialog(false);
const onScheduleChangeAbort = () => setShowApplyScheduledDialog(false);
const onRejectScheduledAbort = () => setShowRejectScheduledDialog(false);
const isSelfReview =
@ -293,11 +317,11 @@ export const ChangeRequestOverview: FC = () => {
!allowChangeRequestActions ||
loading
}
onSchedule={() => {
console.log(
'I would schedule changes now',
);
}}
onSchedule={() =>
setShowScheduleChangeDialog(
true,
)
}
>
Apply or schedule changes
</ApplyButton>
@ -339,11 +363,9 @@ export const ChangeRequestOverview: FC = () => {
!allowChangeRequestActions ||
loading
}
onSchedule={() => {
console.log(
'I would schedule changes now',
);
}}
onSchedule={() =>
setShowScheduleChangeDialog(true)
}
variant={'update'}
>
Apply or schedule changes
@ -421,6 +443,28 @@ export const ChangeRequestOverview: FC = () => {
condition={scheduleChangeRequests}
show={
<>
<ScheduleChangeRequestDialog
open={showScheduleChangesDialog}
onConfirm={onScheduleChangeRequest}
onClose={onScheduleChangeAbort}
disabled={!allowChangeRequestActions || loading}
projectId={projectId}
environment={changeRequest.environment}
primaryButtonText={
changeRequest?.schedule?.scheduledAt
? 'Update scheduled time'
: 'Schedule changes'
}
title={
changeRequest?.schedule?.scheduledAt
? 'Update schedule'
: 'Schedule changes'
}
scheduledAt={
changeRequest?.schedule?.scheduledAt ||
undefined
}
/>
<ChangeRequestApplyScheduledDialogue
open={showApplyScheduledDialog}
onConfirm={onApplyChanges}
@ -434,7 +478,7 @@ export const ChangeRequestOverview: FC = () => {
/>
<ChangeRequestRejectScheduledDialogue
open={showRejectScheduledDialog}
onConfirm={onCancelChanges}
onConfirm={onReject}
onClose={onRejectScheduledAbort}
scheduledTime={
changeRequest?.schedule?.scheduledAt

View File

@ -1,8 +1,8 @@
import { FC, ReactElement } from 'react';
import { Alert, styled, Typography } from '@mui/material';
import { Dialogue } from '../../../common/Dialogue/Dialogue';
import { Dialogue } from 'component/common/Dialogue/Dialogue';
export interface ChangeRequestScheduleDialogueProps {
export interface ChangeRequestScheduledDialogProps {
title: string;
primaryButtonText: string;
open: boolean;
@ -12,8 +12,6 @@ export interface ChangeRequestScheduleDialogueProps {
message: string;
permissionButton?: ReactElement;
disabled?: boolean;
projectId?: string;
environment?: string;
}
const StyledAlert = styled(Alert)(({ theme }) => ({
@ -23,8 +21,8 @@ const StyledAlert = styled(Alert)(({ theme }) => ({
borderColor: `${theme.palette.neutral.light}!important`,
}));
export const ChangeRequestScheduledDialogue: FC<
ChangeRequestScheduleDialogueProps
export const ChangeRequestScheduledDialog: FC<
ChangeRequestScheduledDialogProps
> = ({
open,
onConfirm,
@ -33,6 +31,7 @@ export const ChangeRequestScheduledDialogue: FC<
primaryButtonText,
message,
scheduledTime,
permissionButton,
}) => {
if (!scheduledTime) return null;
@ -44,6 +43,7 @@ export const ChangeRequestScheduledDialogue: FC<
open={open}
onClose={onClose}
onClick={() => onConfirm()}
permissionButton={permissionButton}
fullWidth
>
<StyledAlert icon={false}>

View File

@ -0,0 +1,107 @@
import { FC, useState } from 'react';
import { Alert, Box, styled, Typography } from '@mui/material';
import { Dialogue } from 'component/common/Dialogue/Dialogue';
import { APPLY_CHANGE_REQUEST } from 'component/providers/AccessProvider/permissions';
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
import { DateTimePicker } from 'component/common/DateTimePicker/DateTimePicker';
import { getBrowserTimezoneInHumanReadableUTCOffset } from '../ChangeRequestReviewStatus/utils';
export interface ScheduleChangeRequestDialogProps {
title: string;
primaryButtonText: string;
open: boolean;
onConfirm: (selectedDate: Date) => void;
onClose: () => void;
projectId: string;
environment: string;
disabled?: boolean;
scheduledAt?: string;
}
const StyledContainer = styled(Box)(({ theme }) => ({
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
gap: theme.spacing(2),
}));
export const ScheduleChangeRequestDialog: FC<ScheduleChangeRequestDialogProps> =
({
open,
onConfirm,
onClose,
title,
primaryButtonText,
projectId,
environment,
disabled,
scheduledAt,
}) => {
const [selectedDate, setSelectedDate] = useState(
scheduledAt ? new Date(scheduledAt) : new Date(),
);
const [error, setError] = useState<string | undefined>(undefined);
const timezone = getBrowserTimezoneInHumanReadableUTCOffset();
return (
<Dialogue
title={title}
primaryButtonText={primaryButtonText}
secondaryButtonText='Cancel'
open={open}
onClose={onClose}
onClick={() => onConfirm(selectedDate)}
permissionButton={
<PermissionButton
variant='contained'
onClick={() => onConfirm(selectedDate)}
projectId={projectId}
permission={APPLY_CHANGE_REQUEST}
environmentId={environment}
disabled={disabled}
>
{primaryButtonText}
</PermissionButton>
}
fullWidth
>
<Alert
severity={'info'}
sx={{ mb: (theme) => theme.spacing(2) }}
>
The time shown below is based on your browser's time zone.
</Alert>
<Typography
variant={'body1'}
sx={{ mb: (theme) => theme.spacing(4) }}
>
Select the date and time when these changes should be
applied. If you change your mind later, you can reschedule
the changes or apply the immediately.
</Typography>
<StyledContainer>
<DateTimePicker
label='Date'
value={selectedDate}
onChange={(date) => {
setError(undefined);
if (date < new Date()) {
setError(
`The time you provided (${date.toLocaleString()}) is not valid because it's in the past. Please select a time in the future.`,
);
}
setSelectedDate(date);
}}
min={new Date()}
error={Boolean(error)}
errorText={error}
required
/>
<Typography variant={'body2'}>
Your browser's time zone is {timezone}
</Typography>
</StyledContainer>
</Dialogue>
);
};

View File

@ -1,19 +1,16 @@
import { FC, useState } from 'react';
import { TextField, Box, Alert, styled, Typography } from '@mui/material';
import { Dialogue } from '../../../common/Dialogue/Dialogue';
import { ConditionallyRender } from '../../../common/ConditionallyRender/ConditionallyRender';
import { FC } from 'react';
import { APPLY_CHANGE_REQUEST } from '../../../providers/AccessProvider/permissions';
import PermissionButton from '../../../common/PermissionButton/PermissionButton';
import {
ChangeRequestScheduledDialogue,
ChangeRequestScheduleDialogueProps,
} from './ChangeRequestScheduledDialogue';
ChangeRequestScheduledDialog,
ChangeRequestScheduledDialogProps,
} from './ChangeRequestScheduledDialog';
export const ChangeRequestApplyScheduledDialogue: FC<
Omit<
ChangeRequestScheduleDialogueProps,
ChangeRequestScheduledDialogProps,
'message' | 'title' | 'primaryButtonText' | 'permissionButton'
>
> & { projectId: string; environment: string }
> = ({ projectId, environment, disabled, onConfirm, ...rest }) => {
const message =
'Applying the changes now means the scheduled time will be ignored';
@ -21,7 +18,7 @@ export const ChangeRequestApplyScheduledDialogue: FC<
const primaryButtonText = 'Apply changes now';
return (
<ChangeRequestScheduledDialogue
<ChangeRequestScheduledDialog
message={message}
title={title}
primaryButtonText={primaryButtonText}
@ -45,7 +42,7 @@ export const ChangeRequestApplyScheduledDialogue: FC<
export const ChangeRequestRejectScheduledDialogue: FC<
Omit<
ChangeRequestScheduleDialogueProps,
ChangeRequestScheduledDialogProps,
'message' | 'title' | 'primaryButtonText'
>
> = ({ ...rest }) => {
@ -55,7 +52,7 @@ export const ChangeRequestRejectScheduledDialogue: FC<
const primaryButtonText = 'Reject changes';
return (
<ChangeRequestScheduledDialogue
<ChangeRequestScheduledDialog
message={message}
title={title}
primaryButtonText={primaryButtonText}

View File

@ -58,10 +58,12 @@ export const useChangeRequestApi = () => {
state:
| 'Approved'
| 'Applied'
| 'Scheduled'
| 'Cancelled'
| 'In review'
| 'Rejected';
comment?: string;
scheduledAt?: string;
},
) => {
trackEvent('change_request', {