diff --git a/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestOverview.test.tsx b/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestOverview.test.tsx
new file mode 100644
index 0000000000..4205282a15
--- /dev/null
+++ b/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestOverview.test.tsx
@@ -0,0 +1,289 @@
+import { fireEvent, screen, waitFor, within } from '@testing-library/react';
+import { testServerRoute, testServerSetup } from 'utils/testServer';
+import { ChangeRequestState, IChangeRequest } from '../changeRequest.types';
+import { render } from 'utils/testRenderer';
+import { ChangeRequestOverview } from './ChangeRequestOverview';
+import {
+ ADMIN,
+ APPLY_CHANGE_REQUEST,
+} from 'component/providers/AccessProvider/permissions';
+import { Route, Routes } from 'react-router-dom';
+
+const server = testServerSetup();
+const mockChangeRequest = (
+ featureName: string,
+ state: ChangeRequestState,
+): IChangeRequest => {
+ const result: IChangeRequest = {
+ id: 1,
+ environment: 'production',
+ state: state,
+ minApprovals: 1,
+ project: 'default',
+ createdBy: {
+ id: 1,
+ username: 'admin',
+ imageUrl:
+ 'https://gravatar.com/avatar/21232f297a57a5a743894a0e4a801fc3?size=42&default=retro',
+ },
+ createdAt: new Date('2022-12-02T09:19:12.242Z'),
+ segments: [],
+ title: '',
+ features: [
+ {
+ name: featureName,
+ changes: [
+ {
+ id: 292,
+ action: 'addStrategy',
+ payload: {
+ name: 'default',
+ segments: [],
+ parameters: {},
+ constraints: [],
+ },
+ createdAt: new Date('2022-12-02T09:19:12.245Z'),
+ createdBy: {
+ id: 1,
+ username: 'admin',
+ imageUrl:
+ 'https://gravatar.com/avatar/21232f297a57a5a743894a0e4a801fc3?size=42&default=retro',
+ },
+ },
+ ],
+ },
+ ],
+ approvals: [],
+ rejections: [],
+ comments: [],
+ };
+
+ if (state === 'Scheduled') {
+ result.schedule = {
+ scheduledAt: '2022-12-02T09:19:12.242Z',
+ status: 'pending',
+ };
+ }
+
+ return result;
+};
+const pendingChangeRequest = (changeRequest: IChangeRequest) =>
+ testServerRoute(
+ server,
+ '/api/admin/projects/default/change-requests/pending',
+ [changeRequest],
+ );
+
+const changeRequest = (changeRequest: IChangeRequest) =>
+ testServerRoute(
+ server,
+ '/api/admin/projects/default/change-requests/1',
+ changeRequest,
+ 'get',
+ );
+
+const updateChangeRequestState = () =>
+ testServerRoute(
+ server,
+ '/api/admin/projects/default/change-requests/1/state',
+ {},
+ 'post',
+ );
+const changeRequestConfig = () =>
+ testServerRoute(
+ server,
+ '/api/admin/projects/default/change-requests/config',
+ [
+ {
+ environment: 'development',
+ type: 'development',
+ changeRequestEnabled: false,
+ },
+ {
+ environment: 'production',
+ type: 'production',
+ changeRequestEnabled: true,
+ },
+ ],
+ 'get',
+ );
+
+const setupChangeRequest = (featureName: string, state: ChangeRequestState) => {
+ pendingChangeRequest(mockChangeRequest(featureName, state));
+ changeRequest(mockChangeRequest(featureName, state));
+};
+
+const uiConfig = () => {
+ testServerRoute(server, '/api/admin/ui-config', {
+ versionInfo: {
+ current: { oss: 'version', enterprise: 'version' },
+ },
+ flags: {
+ scheduledConfigurationChanges: true,
+ },
+ });
+};
+
+const user = () => {
+ testServerRoute(server, '/api/admin/user', {
+ user: {
+ isAPI: false,
+ id: 17,
+ name: 'Some User',
+ email: 'user@example.com',
+ imageUrl:
+ 'https://gravatar.com/avatar/8aa1132e102345f8c79322340e15340?size=42&default=retro',
+ seenAt: '2022-11-28T14:55:18.982Z',
+ loginAttempts: 0,
+ createdAt: '2022-11-23T13:31:17.061Z',
+ },
+ permissions: [{ permission: ADMIN }],
+ feedback: [],
+ splash: {},
+ });
+};
+
+const setupHttpRoutes = () => {
+ uiConfig();
+ changeRequestConfig();
+ user();
+ updateChangeRequestState();
+};
+
+beforeEach(() => {
+ setupHttpRoutes();
+});
+
+const Component = () => {
+ return (
+ <>
+
+ }
+ />
+
+ >
+ );
+};
+
+const featureName = 'feature1';
+
+test('should allow scheduling of approved change request and show the schedule dialog', async () => {
+ setupChangeRequest(featureName, 'Approved');
+
+ 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 a reschedule dialog when change request is scheduled and update schedule is selected', async () => {
+ setupChangeRequest(featureName, 'Scheduled');
+ 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: 'Update schedule',
+ });
+
+ fireEvent.click(scheduleChangesButton);
+
+ await screen.findByRole('dialog', { name: 'Update schedule' });
+});
+
+test('should show an apply dialog when change request is scheduled and apply is selected', async () => {
+ setupChangeRequest(featureName, 'Scheduled');
+
+ 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 applyChangesButton = await screen.findByRole('menuitem', {
+ name: 'Apply changes',
+ });
+ fireEvent.click(applyChangesButton);
+
+ await screen.findByRole('dialog', { name: 'Apply changes' });
+});
+
+test('should show a reject dialog when change request is scheduled and Reject Changes button is clicked', async () => {
+ setupChangeRequest(featureName, 'Scheduled');
+
+ render(, {
+ route: '/projects/default/change-requests/1',
+ permissions: [{ permission: ADMIN }],
+ });
+
+ const applyOrScheduleButton = await screen.findByText(
+ 'Apply or schedule changes',
+ );
+ await waitFor(() => expect(applyOrScheduleButton).toBeEnabled(), {
+ timeout: 3000,
+ });
+
+ const buttons = await screen.findAllByRole('button');
+ const rejectChangesButton = buttons[buttons.length - 1];
+ expect(
+ within(rejectChangesButton).getByText('Reject changes'),
+ ).toBeInTheDocument();
+ fireEvent.click(rejectChangesButton);
+
+ await screen.findByRole('dialog', {
+ name: 'Reject changes',
+ });
+});
diff --git a/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestOverview.tsx b/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestOverview.tsx
index 43aac2d721..c814ff3264 100644
--- a/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestOverview.tsx
+++ b/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestOverview.tsx
@@ -186,13 +186,13 @@ export const ChangeRequestOverview: FC = () => {
comment,
});
setShowRejectDialog(false);
- refetchChangeRequest();
- refetchChangeRequestOpen();
setToastData({
type: 'success',
title: 'Success',
text: 'Changes rejected',
});
+ refetchChangeRequest();
+ refetchChangeRequestOpen();
} catch (error: unknown) {
setToastApiError(formatUnknownError(error));
}
diff --git a/frontend/src/component/changeRequest/ChangeRequestOverview/MultiActionButton/MultiActionButton.tsx b/frontend/src/component/changeRequest/ChangeRequestOverview/MultiActionButton/MultiActionButton.tsx
index ba86906706..a414a29321 100644
--- a/frontend/src/component/changeRequest/ChangeRequestOverview/MultiActionButton/MultiActionButton.tsx
+++ b/frontend/src/component/changeRequest/ChangeRequestOverview/MultiActionButton/MultiActionButton.tsx
@@ -102,7 +102,10 @@ export const MultiActionButton: FC<{
>
{actions.map(
({ label, onSelect, icon }) => (
-