diff --git a/frontend/src/component/changeRequest/ChangeRequestOverview/ApplyButton/ApplyButton.tsx b/frontend/src/component/changeRequest/ChangeRequestOverview/ApplyButton/ApplyButton.tsx index 66493b496f..3678e1335a 100644 --- a/frontend/src/component/changeRequest/ChangeRequestOverview/ApplyButton/ApplyButton.tsx +++ b/frontend/src/component/changeRequest/ChangeRequestOverview/ApplyButton/ApplyButton.tsx @@ -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 }) => ( - , - }, - { - label: - variant === 'create' - ? 'Schedule changes' - : 'Update schedule', - onSelect: onSchedule, - icon: , - }, - ]} - > - {children} - -); +}> = ({ disabled, onSchedule, onApply, variant = 'create', children }) => { + const projectId = useRequiredPathParam('projectId'); + const id = useRequiredPathParam('id'); + const { data } = useChangeRequest(projectId, id); + + return ( + , + }, + { + label: + variant === 'create' + ? 'Schedule changes' + : 'Update schedule', + onSelect: onSchedule, + icon: , + }, + ]} + environmentId={data?.environment} + projectId={projectId} + ariaLabel='apply or schedule changes' + > + {children} + + ); +}; diff --git a/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestOverview.test.tsx b/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestOverview.test.tsx index 7398f39a02..e7f0de932d 100644 --- a/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestOverview.test.tsx +++ b/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestOverview.test.tsx @@ -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> = {}, +) => { + 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(, { + 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'); diff --git a/frontend/src/component/changeRequest/ChangeRequestOverview/ReviewButton/ReviewButton.tsx b/frontend/src/component/changeRequest/ChangeRequestOverview/ReviewButton/ReviewButton.tsx index 7091edcf1d..5bff480a47 100644 --- a/frontend/src/component/changeRequest/ChangeRequestOverview/ReviewButton/ReviewButton.tsx +++ b/frontend/src/component/changeRequest/ChangeRequestOverview/ReviewButton/ReviewButton.tsx @@ -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 }) => ( - , - }, - { - label: 'Reject', - onSelect: onReject, - icon: , - }, - ]} - > - {children} - -); +}> = ({ 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 ( + , + }, + { + label: 'Reject', + onSelect: onReject, + icon: , + }, + ]} + environmentId={data?.environment} + projectId={projectId} + ariaLabel='review or reject changes' + > + {children} + + ); +}; diff --git a/frontend/src/component/changeRequest/ChangeRequestOverview/MultiActionButton/MultiActionButton.tsx b/frontend/src/component/common/MultiActionButton/MultiActionButton.tsx similarity index 85% rename from frontend/src/component/changeRequest/ChangeRequestOverview/MultiActionButton/MultiActionButton.tsx rename to frontend/src/component/common/MultiActionButton/MultiActionButton.tsx index cc0c663e6b..c212d68fef 100644 --- a/frontend/src/component/changeRequest/ChangeRequestOverview/MultiActionButton/MultiActionButton.tsx +++ b/frontend/src/component/common/MultiActionButton/MultiActionButton.tsx @@ -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(null); @@ -57,19 +60,17 @@ export const MultiActionButton: FC<{ } permission={permission} projectId={projectId} - environmentId={data?.environment} + environmentId={environmentId} > {children}