Skip to content

Commit

Permalink
feat: add xpert summaries configuration by default for units (openedx…
Browse files Browse the repository at this point in the history
…#567)

* feat: add xpert summaries configuration by default for units
  • Loading branch information
germanolleunlp authored and snglth committed Jan 9, 2024
1 parent c196329 commit 64a0e05
Show file tree
Hide file tree
Showing 11 changed files with 359 additions and 21 deletions.
1 change: 1 addition & 0 deletions src/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@
@import "grading-settings/scss/GradingSettings";
@import "generic/styles";
@import "schedule-and-details/ScheduleAndDetails";
@import "pages-and-resources/PagesAndResources";
1 change: 1 addition & 0 deletions src/pages-and-resources/PagesAndResources.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import "./xpert-unit-summary/settings-modal/SettingsModal";
1 change: 1 addition & 0 deletions src/pages-and-resources/data/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export const getCourseAppsApiStatus = (state) => state.pagesAndResources.courseA
export const getCourseAppSettingValue = (setting) => (state) => (
state.pagesAndResources.courseAppSettings[setting]?.value
);
export const getResetStatus = (state) => state.pagesAndResources.resetStatus;
5 changes: 5 additions & 0 deletions src/pages-and-resources/data/slice.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const slice = createSlice({
courseAppIds: [],
loadingStatus: RequestStatus.IN_PROGRESS,
savingStatus: '',
resetStatus: '',
courseAppsApiStatus: {},
courseAppSettings: {},
},
Expand All @@ -22,6 +23,9 @@ const slice = createSlice({
updateSavingStatus: (state, { payload }) => {
state.savingStatus = payload.status;
},
updateResetStatus: (state, { payload }) => {
state.resetStatus = payload.status;
},
updateCourseAppsApiStatus: (state, { payload }) => {
state.courseAppsApiStatus = payload.status;
},
Expand All @@ -38,6 +42,7 @@ export const {
fetchCourseAppsSuccess,
updateLoadingStatus,
updateSavingStatus,
updateResetStatus,
updateCourseAppsApiStatus,
fetchCourseAppsSettingsSuccess,
updateCourseAppsSettingsSuccess,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ const XpertUnitSummarySettings = ({ intl }) => {
}
enableAppLabel={intl.formatMessage(messages.enableXpertUnitSummaryLabel)}
learnMoreText={intl.formatMessage(messages.enableXpertUnitSummaryLink)}
allUnitsEnabledText={intl.formatMessage(messages.allUnitsEnabledByDefault)}
noUnitsEnabledText={intl.formatMessage(messages.noUnitsEnabledByDefault)}
onClose={handleClose}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,12 @@ describe('XpertUnitSummarySettings', () => {
renderComponent();
});

test('Shows enabled if enabled from backend', async () => {
test('Shows switch on if enabled from backend', async () => {
expect(container.querySelector('#enable-xpert-unit-summary-toggle').checked).toBeTruthy();
expect(queryByTestId(container, 'enable-badge')).toBeTruthy();
});

test('Does not show enabled if disabled from backend', async () => {
test('Shows switch on if disabled from backend', async () => {
axiosMock.onGet(API.getXpertSettingsUrl(courseId))
.reply(200, generateCourseLevelAPIRepsonse({
success: true,
Expand All @@ -126,8 +126,25 @@ describe('XpertUnitSummarySettings', () => {

renderComponent();
await waitFor(() => expect(container.querySelector('#enable-xpert-unit-summary-toggle')).toBeTruthy());
expect(container.querySelector('#enable-xpert-unit-summary-toggle').checked).not.toBeTruthy();
expect(queryByTestId(container, 'enable-badge')).not.toBeTruthy();
expect(container.querySelector('#enable-xpert-unit-summary-toggle').checked).toBeTruthy();
expect(queryByTestId(container, 'enable-badge')).toBeTruthy();
});

test('Shows enable radio selected if enabled from backend', async () => {
await waitFor(() => expect(container.querySelector('#enable-xpert-unit-summary-toggle')).toBeTruthy());
expect(queryByTestId(container, 'enable-radio').checked).toBeTruthy();
});

test('Shows disable radio selected if enabled from backend', async () => {
axiosMock.onGet(API.getXpertSettingsUrl(courseId))
.reply(200, generateCourseLevelAPIRepsonse({
success: true,
enabled: false,
}));

renderComponent();
await waitFor(() => expect(container.querySelector('#enable-xpert-unit-summary-toggle')).toBeTruthy());
expect(queryByTestId(container, 'disable-radio').checked).toBeTruthy();
});
});

Expand All @@ -136,7 +153,7 @@ describe('XpertUnitSummarySettings', () => {
axiosMock.onGet(API.getXpertSettingsUrl(courseId))
.reply(400, generateCourseLevelAPIRepsonse({
success: false,
enabled: false,
enabled: undefined,
}));

renderComponent();
Expand All @@ -151,6 +168,12 @@ describe('XpertUnitSummarySettings', () => {

describe('saving configuration changes', () => {
beforeEach(() => {
axiosMock.onGet(API.getXpertSettingsUrl(courseId))
.reply(200, generateCourseLevelAPIRepsonse({
success: true,
enabled: false,
}));

axiosMock.onPost(API.getXpertSettingsUrl(courseId))
.reply(200, generateCourseLevelAPIRepsonse({
success: true,
Expand All @@ -164,8 +187,10 @@ describe('XpertUnitSummarySettings', () => {
jest.spyOn(API, 'postXpertSettings');

await waitFor(() => expect(container.querySelector('#enable-xpert-unit-summary-toggle')).toBeTruthy());
expect(queryByTestId(container, 'disable-radio').checked).toBeTruthy();
fireEvent.click(queryByTestId(container, 'enable-radio'));
fireEvent.click(getByText(container, 'Save'));
await waitFor(() => expect(container.querySelector('#enable-xpert-unit-summary-toggle')).not.toBeTruthy());
await waitFor(() => expect(container.querySelector('#enable-xpert-unit-summary-toggle')).toBeTruthy());
expect(API.postXpertSettings).toBeCalled();
});
});
Expand All @@ -186,4 +211,78 @@ describe('XpertUnitSummarySettings', () => {
expect(API.getXpertPluginConfigurable).toBeCalled();
});
});

describe('removing course configuration', () => {
beforeEach(() => {
axiosMock.onGet(API.getXpertSettingsUrl(courseId))
.reply(200, generateCourseLevelAPIRepsonse({
success: true,
enabled: true,
}));

axiosMock.onDelete(API.getXpertSettingsUrl(courseId))
.reply(200, generateCourseLevelAPIRepsonse({
success: true,
enabled: undefined,
}));

renderComponent();
});

test('Deleting course configuration', async () => {
jest.spyOn(API, 'deleteXpertSettings');

await waitFor(() => expect(container.querySelector('#enable-xpert-unit-summary-toggle')).toBeTruthy());
fireEvent.click(container.querySelector('#enable-xpert-unit-summary-toggle'));
fireEvent.click(getByText(container, 'Save'));
await waitFor(() => expect(container.querySelector('#enable-xpert-unit-summary-toggle')).toBeTruthy());
expect(API.deleteXpertSettings).toBeCalled();
});
});

describe('resetting course units', () => {
test('reset all units to be enabled', async () => {
axiosMock.onGet(API.getXpertSettingsUrl(courseId))
.reply(200, generateCourseLevelAPIRepsonse({
success: true,
enabled: true,
}));

axiosMock.onPost(API.getXpertSettingsUrl(courseId))
.reply(200, generateCourseLevelAPIRepsonse({
success: true,
enabled: true,
}));

renderComponent();

jest.spyOn(API, 'postXpertSettings');

await waitFor(() => expect(container.querySelector('#enable-xpert-unit-summary-toggle')).toBeTruthy());
fireEvent.click(queryByTestId(container, 'reset-units'));
expect(API.postXpertSettings).toBeCalledWith(courseId, { reset: true, enabled: true });
});

test('reset all units to be disabled', async () => {
axiosMock.onGet(API.getXpertSettingsUrl(courseId))
.reply(200, generateCourseLevelAPIRepsonse({
success: true,
enabled: false,
}));

axiosMock.onPost(API.getXpertSettingsUrl(courseId))
.reply(200, generateCourseLevelAPIRepsonse({
success: true,
enabled: false,
}));

renderComponent();

jest.spyOn(API, 'postXpertSettings');

await waitFor(() => expect(container.querySelector('#enable-xpert-unit-summary-toggle')).toBeTruthy());
fireEvent.click(queryByTestId(container, 'reset-units'));
expect(API.postXpertSettings).toBeCalledWith(courseId, { reset: true, enabled: false });
});
});
});
8 changes: 8 additions & 0 deletions src/pages-and-resources/xpert-unit-summary/data/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export async function postXpertSettings(courseId, state) {
const { data } = await getAuthenticatedHttpClient()
.post(getXpertSettingsUrl(courseId), {
enabled: state.enabled,
reset: state.reset,
});

return data;
Expand All @@ -31,3 +32,10 @@ export async function getXpertPluginConfigurable(courseId) {

return data;
}

export async function deleteXpertSettings(courseId) {
const { data } = await getAuthenticatedHttpClient()
.delete(getXpertSettingsUrl(courseId));

return data;
}
55 changes: 49 additions & 6 deletions src/pages-and-resources/xpert-unit-summary/data/thunks.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { getXpertSettings, postXpertSettings, getXpertPluginConfigurable } from './api';
import {
getXpertSettings, postXpertSettings, getXpertPluginConfigurable, deleteXpertSettings,
} from './api';

import { updateSavingStatus, updateLoadingStatus } from '../../data/slice';
import { updateSavingStatus, updateLoadingStatus, updateResetStatus } from '../../data/slice';
import { RequestStatus } from '../../../data/constants';

import { addModel, updateModel } from '../../../generic/model-store';
Expand All @@ -27,13 +29,13 @@ export function updateXpertSettings(courseId, state) {

export function fetchXpertPluginConfigurable(courseId) {
return async (dispatch) => {
let enabled = false;
let enabled;
dispatch(updateLoadingStatus({ status: RequestStatus.PENDING }));
try {
const { response } = await getXpertPluginConfigurable(courseId);
enabled = response?.enabled;
} catch (e) {
enabled = false;
enabled = undefined;
}

dispatch(addModel({
Expand All @@ -48,14 +50,14 @@ export function fetchXpertPluginConfigurable(courseId) {

export function fetchXpertSettings(courseId) {
return async (dispatch) => {
let enabled = false;
let enabled;
dispatch(updateLoadingStatus({ status: RequestStatus.PENDING }));

try {
const { response } = await getXpertSettings(courseId);
enabled = response?.enabled;
} catch (e) {
enabled = false;
enabled = undefined;
}

dispatch(addModel({
Expand All @@ -69,3 +71,44 @@ export function fetchXpertSettings(courseId) {
dispatch(updateLoadingStatus({ status: RequestStatus.SUCCESSFUL }));
};
}

export function removeXpertSettings(courseId) {
return async (dispatch) => {
dispatch(updateSavingStatus({ status: RequestStatus.PENDING }));

try {
const { response } = await deleteXpertSettings(courseId);
const { success } = response;
if (success) {
const model = { id: 'xpert-unit-summary', enabled: undefined };
dispatch(updateModel({ modelType: 'XpertSettings', model }));
dispatch(updateSavingStatus({ status: RequestStatus.SUCCESSFUL }));
return true;
}
dispatch(updateSavingStatus({ status: RequestStatus.FAILED }));
return false;
} catch (error) {
dispatch(updateSavingStatus({ status: RequestStatus.FAILED }));
return false;
}
};
}

export function resetXpertSettings(courseId, state) {
return async (dispatch) => {
dispatch(updateResetStatus({ status: RequestStatus.PENDING }));
try {
const { response } = await postXpertSettings(courseId, state);
const { success } = response;
if (success) {
dispatch(updateResetStatus({ status: RequestStatus.SUCCESSFUL }));
return true;
}
dispatch(updateResetStatus({ status: RequestStatus.FAILED }));
return false;
} catch (error) {
dispatch(updateResetStatus({ status: RequestStatus.FAILED }));
return false;
}
};
}
Loading

0 comments on commit 64a0e05

Please sign in to comment.