mirror of
https://github.com/Unleash/unleash.git
synced 2025-06-04 01:18:20 +02:00
chore: add schedule option to approved change requests (#5252)
The button doesn't do anything at the moment, but it's there visually. Because this uses the same button as the dual-function button for approve/reject, I extracted that component into a reusable "multi-action" button. I could have copied the code wholesale, but it's a complex component, so I thought this would be a better solution. I'll add the dialog in a follow-up PR. This one already has a lot of changes. Visual: 
This commit is contained in:
parent
95245c4413
commit
9fbb61a1c4
@ -0,0 +1,31 @@
|
||||
import { FC } from 'react';
|
||||
|
||||
import CheckBox from '@mui/icons-material/Check';
|
||||
import Today from '@mui/icons-material/Today';
|
||||
import { MultiActionButton } from '../MultiActionButton/MultiActionButton';
|
||||
import { APPLY_CHANGE_REQUEST } from 'component/providers/AccessProvider/permissions';
|
||||
|
||||
export const ApplyButton: FC<{
|
||||
disabled: boolean;
|
||||
onSchedule: () => void;
|
||||
onApply: () => void;
|
||||
}> = ({ disabled, onSchedule, onApply, children }) => (
|
||||
<MultiActionButton
|
||||
permission={APPLY_CHANGE_REQUEST}
|
||||
disabled={disabled}
|
||||
actions={[
|
||||
{
|
||||
label: 'Apply changes',
|
||||
onSelect: onApply,
|
||||
icon: <CheckBox fontSize='small' />,
|
||||
},
|
||||
{
|
||||
label: 'Schedule changes',
|
||||
onSelect: onSchedule,
|
||||
icon: <Today fontSize='small' />,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{children}
|
||||
</MultiActionButton>
|
||||
);
|
@ -24,6 +24,9 @@ import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
||||
import { changesCount } from '../changesCount';
|
||||
import { ChangeRequestReviewers } from './ChangeRequestReviewers/ChangeRequestReviewers';
|
||||
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%',
|
||||
@ -87,6 +90,8 @@ export const ChangeRequestOverview: FC = () => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const scheduleChangeRequests = useUiFlag('scheduledConfigurationChanges');
|
||||
|
||||
const allowChangeRequestActions = isChangeRequestConfiguredForReview(
|
||||
changeRequest.environment,
|
||||
);
|
||||
@ -267,11 +272,32 @@ export const ChangeRequestOverview: FC = () => {
|
||||
<ConditionallyRender
|
||||
condition={changeRequest.state === 'Approved'}
|
||||
show={
|
||||
<ConditionallyRender
|
||||
condition={scheduleChangeRequests}
|
||||
show={
|
||||
<ApplyButton
|
||||
onApply={onApplyChanges}
|
||||
disabled={
|
||||
!allowChangeRequestActions ||
|
||||
loading
|
||||
}
|
||||
onSchedule={() => {
|
||||
console.log(
|
||||
'I would schedule changes now',
|
||||
);
|
||||
}}
|
||||
>
|
||||
Apply or schedule changes
|
||||
</ApplyButton>
|
||||
}
|
||||
elseShow={
|
||||
<PermissionButton
|
||||
variant='contained'
|
||||
onClick={onApplyChanges}
|
||||
projectId={projectId}
|
||||
permission={APPLY_CHANGE_REQUEST}
|
||||
permission={
|
||||
APPLY_CHANGE_REQUEST
|
||||
}
|
||||
environmentId={
|
||||
changeRequest.environment
|
||||
}
|
||||
@ -284,6 +310,8 @@ export const ChangeRequestOverview: FC = () => {
|
||||
</PermissionButton>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
changeRequest.state !== 'Applied' &&
|
||||
|
@ -0,0 +1,123 @@
|
||||
import React, { FC, useContext } from 'react';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import { useChangeRequest } from 'hooks/api/getters/useChangeRequest/useChangeRequest';
|
||||
|
||||
import {
|
||||
ClickAwayListener,
|
||||
Grow,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
MenuItem,
|
||||
MenuList,
|
||||
Paper,
|
||||
Popper,
|
||||
} from '@mui/material';
|
||||
|
||||
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
|
||||
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
|
||||
import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser';
|
||||
import AccessContext from 'contexts/AccessContext';
|
||||
|
||||
type Action = {
|
||||
label: string;
|
||||
onSelect: () => void;
|
||||
icon: JSX.Element;
|
||||
};
|
||||
|
||||
export const MultiActionButton: FC<{
|
||||
disabled: boolean;
|
||||
actions: Action[];
|
||||
permission: string;
|
||||
}> = ({ disabled, children, actions, permission }) => {
|
||||
const { isAdmin } = useContext(AccessContext);
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const id = useRequiredPathParam('id');
|
||||
const { user } = useAuthUser();
|
||||
const { data } = useChangeRequest(projectId, id);
|
||||
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const anchorRef = React.useRef<HTMLButtonElement>(null);
|
||||
|
||||
const onToggle = () => {
|
||||
setOpen((prevOpen) => !prevOpen);
|
||||
};
|
||||
|
||||
const onClose = (event: Event) => {
|
||||
if (anchorRef.current?.contains(event.target as HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setOpen(false);
|
||||
};
|
||||
const popperWidth = anchorRef.current
|
||||
? anchorRef.current.offsetWidth
|
||||
: null;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<PermissionButton
|
||||
variant='contained'
|
||||
disabled={
|
||||
disabled || (data?.createdBy.id === user?.id && !isAdmin)
|
||||
}
|
||||
aria-controls={open ? 'review-options-menu' : undefined}
|
||||
aria-expanded={open ? 'true' : undefined}
|
||||
aria-label='review changes'
|
||||
aria-haspopup='menu'
|
||||
onClick={onToggle}
|
||||
ref={anchorRef}
|
||||
endIcon={<ArrowDropDownIcon />}
|
||||
permission={permission}
|
||||
projectId={projectId}
|
||||
environmentId={data?.environment}
|
||||
>
|
||||
{children}
|
||||
</PermissionButton>
|
||||
<Popper
|
||||
sx={{
|
||||
zIndex: 1,
|
||||
width: popperWidth,
|
||||
}}
|
||||
open={open}
|
||||
anchorEl={anchorRef.current}
|
||||
role={undefined}
|
||||
transition
|
||||
disablePortal
|
||||
>
|
||||
{({ TransitionProps, placement }) => (
|
||||
<Grow
|
||||
{...TransitionProps}
|
||||
style={{
|
||||
transformOrigin:
|
||||
placement === 'bottom'
|
||||
? 'center top'
|
||||
: 'center bottom',
|
||||
}}
|
||||
>
|
||||
<Paper className='dropdown-outline'>
|
||||
<ClickAwayListener onClickAway={onClose}>
|
||||
<MenuList
|
||||
id='review-options-menu'
|
||||
autoFocusItem
|
||||
>
|
||||
{actions.map(
|
||||
({ label, onSelect, icon }) => (
|
||||
<MenuItem onClick={onSelect}>
|
||||
<ListItemIcon>
|
||||
{icon}
|
||||
</ListItemIcon>
|
||||
<ListItemText>
|
||||
{label}
|
||||
</ListItemText>
|
||||
</MenuItem>
|
||||
),
|
||||
)}
|
||||
</MenuList>
|
||||
</ClickAwayListener>
|
||||
</Paper>
|
||||
</Grow>
|
||||
)}
|
||||
</Popper>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
@ -1,124 +1,31 @@
|
||||
import React, { FC, useContext } from 'react';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import { useChangeRequest } from 'hooks/api/getters/useChangeRequest/useChangeRequest';
|
||||
import { FC } from 'react';
|
||||
|
||||
import {
|
||||
ClickAwayListener,
|
||||
Grow,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
MenuItem,
|
||||
MenuList,
|
||||
Paper,
|
||||
Popper,
|
||||
} from '@mui/material';
|
||||
|
||||
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
|
||||
import { APPROVE_CHANGE_REQUEST } from 'component/providers/AccessProvider/permissions';
|
||||
import PermissionButton from 'component/common/PermissionButton/PermissionButton';
|
||||
import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser';
|
||||
import AccessContext from 'contexts/AccessContext';
|
||||
import CheckBox from '@mui/icons-material/Check';
|
||||
import Clear from '@mui/icons-material/Clear';
|
||||
import { MultiActionButton } from '../MultiActionButton/MultiActionButton';
|
||||
import { APPROVE_CHANGE_REQUEST } from 'component/providers/AccessProvider/permissions';
|
||||
|
||||
export const ReviewButton: FC<{
|
||||
disabled: boolean;
|
||||
onReject: () => void;
|
||||
onApprove: () => void;
|
||||
}> = ({ disabled, onReject, onApprove, children }) => {
|
||||
const { isAdmin } = useContext(AccessContext);
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const id = useRequiredPathParam('id');
|
||||
const { user } = useAuthUser();
|
||||
const { data } = useChangeRequest(projectId, id);
|
||||
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const anchorRef = React.useRef<HTMLButtonElement>(null);
|
||||
|
||||
const onToggle = () => {
|
||||
setOpen((prevOpen) => !prevOpen);
|
||||
};
|
||||
|
||||
const onClose = (event: Event) => {
|
||||
if (anchorRef.current?.contains(event.target as HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setOpen(false);
|
||||
};
|
||||
const popperWidth = anchorRef.current
|
||||
? anchorRef.current.offsetWidth
|
||||
: null;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<PermissionButton
|
||||
variant='contained'
|
||||
disabled={
|
||||
disabled || (data?.createdBy.id === user?.id && !isAdmin)
|
||||
}
|
||||
aria-controls={open ? 'review-options-menu' : undefined}
|
||||
aria-expanded={open ? 'true' : undefined}
|
||||
aria-label='review changes'
|
||||
aria-haspopup='menu'
|
||||
onClick={onToggle}
|
||||
ref={anchorRef}
|
||||
endIcon={<ArrowDropDownIcon />}
|
||||
}> = ({ disabled, onReject, onApprove, children }) => (
|
||||
<MultiActionButton
|
||||
permission={APPROVE_CHANGE_REQUEST}
|
||||
projectId={projectId}
|
||||
environmentId={data?.environment}
|
||||
disabled={disabled}
|
||||
actions={[
|
||||
{
|
||||
label: 'Approve',
|
||||
onSelect: onApprove,
|
||||
icon: <CheckBox fontSize='small' />,
|
||||
},
|
||||
{
|
||||
label: 'Reject',
|
||||
onSelect: onReject,
|
||||
icon: <Clear fontSize='small' />,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{children}
|
||||
</PermissionButton>
|
||||
<Popper
|
||||
sx={{
|
||||
zIndex: 1,
|
||||
width: popperWidth,
|
||||
}}
|
||||
open={open}
|
||||
anchorEl={anchorRef.current}
|
||||
role={undefined}
|
||||
transition
|
||||
disablePortal
|
||||
>
|
||||
{({ TransitionProps, placement }) => (
|
||||
<Grow
|
||||
{...TransitionProps}
|
||||
style={{
|
||||
transformOrigin:
|
||||
placement === 'bottom'
|
||||
? 'center top'
|
||||
: 'center bottom',
|
||||
}}
|
||||
>
|
||||
<Paper className='dropdown-outline'>
|
||||
<ClickAwayListener onClickAway={onClose}>
|
||||
<MenuList
|
||||
id='review-options-menu'
|
||||
autoFocusItem
|
||||
>
|
||||
<MenuItem onClick={onApprove}>
|
||||
<ListItemIcon>
|
||||
<CheckBox fontSize='small' />
|
||||
</ListItemIcon>
|
||||
<ListItemText>
|
||||
Approve changes
|
||||
</ListItemText>
|
||||
</MenuItem>
|
||||
<MenuItem onClick={onReject}>
|
||||
<ListItemIcon>
|
||||
<Clear fontSize='small' />
|
||||
</ListItemIcon>
|
||||
<ListItemText>
|
||||
Reject changes
|
||||
</ListItemText>
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</ClickAwayListener>
|
||||
</Paper>
|
||||
</Grow>
|
||||
)}
|
||||
</Popper>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
</MultiActionButton>
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user