mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-20 00:08:02 +01:00
fix: generalize multi action button (#6294)
This PR moves the CR specific logic out of the MultiActionButton and generalises so that we can re-use it across the application. The CR specific logic is moved into: * ApplyButton.tsx * ReviewButton.tsx This fixes a bug where multi action button would be disabled if you tried to apply an approved change request that you had created yourself.
This commit is contained in:
parent
ac183e76f8
commit
0ccfc29e26
@ -2,34 +2,45 @@ 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';
|
||||
import { MultiActionButton } from 'component/common/MultiActionButton/MultiActionButton';
|
||||
import { useChangeRequest } from 'hooks/api/getters/useChangeRequest/useChangeRequest';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
|
||||
export const ApplyButton: FC<{
|
||||
disabled: boolean;
|
||||
onSchedule: () => void;
|
||||
onApply: () => void;
|
||||
variant?: 'create' | 'update';
|
||||
}> = ({ disabled, onSchedule, onApply, variant = 'create', children }) => (
|
||||
<MultiActionButton
|
||||
permission={APPLY_CHANGE_REQUEST}
|
||||
disabled={disabled}
|
||||
actions={[
|
||||
{
|
||||
label: 'Apply changes',
|
||||
onSelect: onApply,
|
||||
icon: <CheckBox fontSize='small' />,
|
||||
},
|
||||
{
|
||||
label:
|
||||
variant === 'create'
|
||||
? 'Schedule changes'
|
||||
: 'Update schedule',
|
||||
onSelect: onSchedule,
|
||||
icon: <Today fontSize='small' />,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{children}
|
||||
</MultiActionButton>
|
||||
);
|
||||
}> = ({ disabled, onSchedule, onApply, variant = 'create', children }) => {
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const id = useRequiredPathParam('id');
|
||||
const { data } = useChangeRequest(projectId, id);
|
||||
|
||||
return (
|
||||
<MultiActionButton
|
||||
permission={APPLY_CHANGE_REQUEST}
|
||||
disabled={disabled}
|
||||
actions={[
|
||||
{
|
||||
label: 'Apply changes',
|
||||
onSelect: onApply,
|
||||
icon: <CheckBox fontSize='small' />,
|
||||
},
|
||||
{
|
||||
label:
|
||||
variant === 'create'
|
||||
? 'Schedule changes'
|
||||
: 'Update schedule',
|
||||
onSelect: onSchedule,
|
||||
icon: <Today fontSize='small' />,
|
||||
},
|
||||
]}
|
||||
environmentId={data?.environment}
|
||||
projectId={projectId}
|
||||
ariaLabel='apply or schedule changes'
|
||||
>
|
||||
{children}
|
||||
</MultiActionButton>
|
||||
);
|
||||
};
|
||||
|
@ -126,11 +126,17 @@ const changeRequestConfig = () =>
|
||||
'get',
|
||||
);
|
||||
|
||||
const setupChangeRequest = (featureName: string, state: ChangeRequestState) => {
|
||||
pendingChangeRequest(mockChangeRequest(featureName, state));
|
||||
changeRequest(mockChangeRequest(featureName, state));
|
||||
const setupChangeRequest = (
|
||||
featureName: string,
|
||||
state: ChangeRequestState,
|
||||
overrides: Partial<Omit<ChangeRequestType, 'state' | 'schedule'>> = {},
|
||||
) => {
|
||||
pendingChangeRequest({
|
||||
...mockChangeRequest(featureName, state),
|
||||
...overrides,
|
||||
});
|
||||
changeRequest({ ...mockChangeRequest(featureName, state), ...overrides });
|
||||
};
|
||||
|
||||
const uiConfig = () => {
|
||||
testServerRoute(server, '/api/admin/ui-config', {
|
||||
versionInfo: {
|
||||
@ -249,6 +255,44 @@ test('should show a reschedule dialog when change request is scheduled and updat
|
||||
await screen.findByRole('dialog', { name: 'Update schedule' });
|
||||
});
|
||||
|
||||
test('should be allowed to apply your own change request if it is approved', async () => {
|
||||
setupChangeRequest(featureName, 'Approved', {
|
||||
createdBy: {
|
||||
id: 17,
|
||||
imageUrl:
|
||||
'https://gravatar.com/avatar/21232f297a57a5a743894a0e4a801fc3?size=42&default=retro',
|
||||
},
|
||||
});
|
||||
|
||||
render(<Component />, {
|
||||
route: '/projects/default/change-requests/1',
|
||||
permissions: [
|
||||
{
|
||||
permission: APPLY_CHANGE_REQUEST,
|
||||
project: 'default',
|
||||
environment: 'production',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const applyOrScheduleButton = await screen.findByText(
|
||||
'Apply or schedule changes',
|
||||
);
|
||||
await waitFor(() => expect(applyOrScheduleButton).toBeEnabled(), {
|
||||
timeout: 3000,
|
||||
});
|
||||
|
||||
fireEvent.click(applyOrScheduleButton);
|
||||
|
||||
const scheduleChangesButton = await screen.findByRole('menuitem', {
|
||||
name: 'Schedule changes',
|
||||
});
|
||||
|
||||
fireEvent.click(scheduleChangesButton);
|
||||
|
||||
await screen.findByRole('dialog', { name: 'Schedule changes' });
|
||||
});
|
||||
|
||||
test('should show an apply dialog when change request is scheduled and apply is selected', async () => {
|
||||
setupChangeRequest(featureName, 'Scheduled');
|
||||
|
||||
|
@ -1,31 +1,49 @@
|
||||
import { FC } from 'react';
|
||||
import { FC, useContext } from 'react';
|
||||
|
||||
import CheckBox from '@mui/icons-material/Check';
|
||||
import Clear from '@mui/icons-material/Clear';
|
||||
import { MultiActionButton } from '../MultiActionButton/MultiActionButton';
|
||||
import { MultiActionButton } from 'component/common/MultiActionButton/MultiActionButton';
|
||||
import { APPROVE_CHANGE_REQUEST } from 'component/providers/AccessProvider/permissions';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser';
|
||||
import AccessContext from 'contexts/AccessContext';
|
||||
import { useChangeRequest } from 'hooks/api/getters/useChangeRequest/useChangeRequest';
|
||||
|
||||
export const ReviewButton: FC<{
|
||||
disabled: boolean;
|
||||
onReject: () => void;
|
||||
onApprove: () => void;
|
||||
}> = ({ disabled, onReject, onApprove, children }) => (
|
||||
<MultiActionButton
|
||||
permission={APPROVE_CHANGE_REQUEST}
|
||||
disabled={disabled}
|
||||
actions={[
|
||||
{
|
||||
label: 'Approve',
|
||||
onSelect: onApprove,
|
||||
icon: <CheckBox fontSize='small' />,
|
||||
},
|
||||
{
|
||||
label: 'Reject',
|
||||
onSelect: onReject,
|
||||
icon: <Clear fontSize='small' />,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{children}
|
||||
</MultiActionButton>
|
||||
);
|
||||
}> = ({ 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 approverIsCreator = data?.createdBy.id === user?.id;
|
||||
const disableApprove = disabled || (approverIsCreator && !isAdmin);
|
||||
|
||||
return (
|
||||
<MultiActionButton
|
||||
permission={APPROVE_CHANGE_REQUEST}
|
||||
disabled={disableApprove}
|
||||
actions={[
|
||||
{
|
||||
label: 'Approve',
|
||||
onSelect: onApprove,
|
||||
icon: <CheckBox fontSize='small' />,
|
||||
},
|
||||
{
|
||||
label: 'Reject',
|
||||
onSelect: onReject,
|
||||
icon: <Clear fontSize='small' />,
|
||||
},
|
||||
]}
|
||||
environmentId={data?.environment}
|
||||
projectId={projectId}
|
||||
ariaLabel='review or reject changes'
|
||||
>
|
||||
{children}
|
||||
</MultiActionButton>
|
||||
);
|
||||
};
|
||||
|
@ -15,8 +15,6 @@ import {
|
||||
|
||||
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;
|
||||
@ -28,13 +26,18 @@ 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);
|
||||
|
||||
projectId?: string;
|
||||
environmentId?: string;
|
||||
ariaLabel?: string;
|
||||
}> = ({
|
||||
disabled,
|
||||
children,
|
||||
actions,
|
||||
permission,
|
||||
projectId,
|
||||
ariaLabel,
|
||||
environmentId,
|
||||
}) => {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const anchorRef = React.useRef<HTMLButtonElement>(null);
|
||||
|
||||
@ -57,19 +60,17 @@ export const MultiActionButton: FC<{
|
||||
<React.Fragment>
|
||||
<PermissionButton
|
||||
variant='contained'
|
||||
disabled={
|
||||
disabled || (data?.createdBy.id === user?.id && !isAdmin)
|
||||
}
|
||||
disabled={disabled}
|
||||
aria-controls={open ? 'review-options-menu' : undefined}
|
||||
aria-expanded={open ? 'true' : undefined}
|
||||
aria-label='review changes'
|
||||
aria-label={ariaLabel}
|
||||
aria-haspopup='menu'
|
||||
onClick={onToggle}
|
||||
ref={anchorRef}
|
||||
endIcon={<ArrowDropDownIcon />}
|
||||
permission={permission}
|
||||
projectId={projectId}
|
||||
environmentId={data?.environment}
|
||||
environmentId={environmentId}
|
||||
>
|
||||
{children}
|
||||
</PermissionButton>
|
Loading…
Reference in New Issue
Block a user