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:
parent
b3054c9277
commit
addda5b022
@ -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
|
||||
|
@ -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}>
|
@ -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>
|
||||
);
|
||||
};
|
@ -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}
|
||||
|
@ -58,10 +58,12 @@ export const useChangeRequestApi = () => {
|
||||
state:
|
||||
| 'Approved'
|
||||
| 'Applied'
|
||||
| 'Scheduled'
|
||||
| 'Cancelled'
|
||||
| 'In review'
|
||||
| 'Rejected';
|
||||
comment?: string;
|
||||
scheduledAt?: string;
|
||||
},
|
||||
) => {
|
||||
trackEvent('change_request', {
|
||||
|
Loading…
Reference in New Issue
Block a user