Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
400 changes: 206 additions & 194 deletions src/course-unit/CourseUnit.test.jsx

Large diffs are not rendered by default.

1,126 changes: 0 additions & 1,126 deletions src/course-unit/__mocks__/courseUnitIndex.js

This file was deleted.

1 change: 0 additions & 1 deletion src/course-unit/__mocks__/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export { default as courseUnitIndexMock } from './courseUnitIndex';
export { default as courseSectionVerticalMock } from './courseSectionVertical';
export { default as courseUnitMock } from './courseUnit';
export { default as courseCreateXblockMock } from './courseCreateXblock';
Expand Down
12 changes: 6 additions & 6 deletions src/course-unit/breadcrumbs/Breadcrumbs.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import {
} from '../../testUtils';

import { executeThunk } from '../../utils';
import { getCourseSectionVerticalApiUrl, getCourseUnitApiUrl } from '../data/api';
import { getCourseSectionVerticalApiUrl } from '../data/api';
import { getApiWaffleFlagsUrl } from '../../data/api';
import { fetchWaffleFlags } from '../../data/thunks';
import { fetchCourseSectionVerticalData, fetchCourseUnitQuery } from '../data/thunk';
import { courseSectionVerticalMock, courseUnitIndexMock } from '../__mocks__';
import { fetchCourseSectionVerticalData } from '../data/thunk';
import { courseSectionVerticalMock } from '../__mocks__';
import Breadcrumbs from './Breadcrumbs';

let axiosMock;
Expand Down Expand Up @@ -43,9 +43,9 @@ describe('<Breadcrumbs />', () => {
reduxStore = mocks.reduxStore;

axiosMock
.onGet(getCourseUnitApiUrl(courseId))
.reply(200, courseUnitIndexMock);
await executeThunk(fetchCourseUnitQuery(courseId), reduxStore.dispatch);
.onGet(getCourseSectionVerticalApiUrl(courseId))
.reply(200, courseSectionVerticalMock);
await executeThunk(fetchCourseSectionVerticalData(courseId), reduxStore.dispatch);
axiosMock
.onGet(getCourseSectionVerticalApiUrl(courseId))
.reply(200, courseSectionVerticalMock);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const SequenceNavigation = ({

const shouldDisplayNotificationTriggerInSequence = useWindowSize().width < breakpoints.small.minWidth;
const renderUnitButtons = () => {
if (sequence?.unitIds?.length === 0 || unitId === null) {
if (sequence.unitIds.length === 0 || unitId === null) {
return (
<div style={{ flexBasis: '100%', minWidth: 0, borderBottom: 'solid 1px #EAEAEA' }} />
);
Expand Down
24 changes: 6 additions & 18 deletions src/course-unit/data/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,13 @@ import { isUnitReadOnly, normalizeCourseSectionVerticalData, updateXBlockBlockId

const getStudioBaseUrl = () => getConfig().STUDIO_BASE_URL;

export const getCourseUnitApiUrl = (itemId) => `${getStudioBaseUrl()}/xblock/container/${itemId}`;
export const getXBlockBaseApiUrl = (itemId) => `${getStudioBaseUrl()}/xblock/${itemId}`;
export const getCourseSectionVerticalApiUrl = (itemId) => `${getStudioBaseUrl()}/api/contentstore/v1/container_handler/${itemId}`;
export const getCourseVerticalChildrenApiUrl = (itemId) => `${getStudioBaseUrl()}/api/contentstore/v1/container/vertical/${itemId}/children`;
export const getCourseOutlineInfoUrl = (courseId) => `${getStudioBaseUrl()}/course/${courseId}?format=concise`;
export const postXBlockBaseApiUrl = () => `${getStudioBaseUrl()}/xblock/`;
export const libraryBlockChangesUrl = (blockId) => `${getStudioBaseUrl()}/api/contentstore/v2/downstreams/${blockId}/sync`;

/**
* Get course unit.
* @param {string} unitId
* @returns {Promise<Object>}
*/
export async function getCourseUnitData(unitId) {
const { data } = await getAuthenticatedHttpClient()
.get(getCourseUnitApiUrl(unitId));

const result = camelCaseObject(data);
result.readOnly = isUnitReadOnly(result);
return result;
}

/**
* Edit course unit display name.
* @param {string} unitId
Expand All @@ -47,15 +32,18 @@ export async function editUnitDisplayName(unitId, displayName) {
}

/**
* Get an object containing course section vertical data.
* Fetch vertical block data from the container_handler endpoint.
* @param {string} unitId
* @returns {Promise<Object>}
*/
export async function getCourseSectionVerticalData(unitId) {
export async function getVerticalData(unitId) {
const { data } = await getAuthenticatedHttpClient()
.get(getCourseSectionVerticalApiUrl(unitId));

return normalizeCourseSectionVerticalData(data);
const courseSectionVerticalData = normalizeCourseSectionVerticalData(data);
courseSectionVerticalData.xblockInfo.readOnly = isUnitReadOnly(courseSectionVerticalData.xblockInfo);

return courseSectionVerticalData;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/course-unit/data/selectors.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createSelector } from '@reduxjs/toolkit';
import { RequestStatus } from 'CourseAuthoring/data/constants';

export const getCourseUnitData = (state) => state.courseUnit.unit;
export const getCourseUnitData = (state) => state.courseUnit.courseSectionVertical.xblockInfo ?? {};
export const getCanEdit = (state) => state.courseUnit.canEdit;
export const getStaticFileNotices = (state) => state.courseUnit.staticFileNotices;
export const getCourseUnit = (state) => state.courseUnit;
Expand Down
19 changes: 0 additions & 19 deletions src/course-unit/data/slice.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,9 @@ const slice = createSlice({
isTitleEditFormOpen: false,
canEdit: true,
loadingStatus: {
fetchUnitLoadingStatus: RequestStatus.IN_PROGRESS,
courseSectionVerticalLoadingStatus: RequestStatus.IN_PROGRESS,
courseVerticalChildrenLoadingStatus: RequestStatus.IN_PROGRESS,
},
unit: {},
courseSectionVertical: {},
courseVerticalChildren: { children: [], isPublished: true },
staticFileNotices: {},
Expand All @@ -31,15 +29,6 @@ const slice = createSlice({
},
},
reducers: {
fetchCourseItemSuccess: (state, { payload }) => {
state.unit = payload;
},
updateLoadingCourseUnitStatus: (state, { payload }) => {
state.loadingStatus = {
...state.loadingStatus,
fetchUnitLoadingStatus: payload.status,
};
},
updateQueryPendingStatus: (state, { payload }) => {
state.isQueryPending = payload;
},
Expand Down Expand Up @@ -81,12 +70,6 @@ const slice = createSlice({
createUnitXblockLoadingStatus: payload.status,
};
},
addNewUnitStatus: (state, { payload }) => {
state.loadingStatus = {
...state.loadingStatus,
fetchUnitLoadingStatus: payload.status,
};
},
updateCourseVerticalChildren: (state, { payload }) => {
state.courseVerticalChildren = payload;
},
Expand All @@ -109,8 +92,6 @@ const slice = createSlice({
});

export const {
fetchCourseItemSuccess,
updateLoadingCourseUnitStatus,
updateSavingStatus,
updateModel,
fetchSequenceRequest,
Expand Down
54 changes: 16 additions & 38 deletions src/course-unit/data/thunk.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ import { NOTIFICATION_MESSAGES } from '../../constants';
import { updateModel, updateModels } from '../../generic/model-store';
import { messageTypes } from '../constants';
import {
getCourseUnitData,
editUnitDisplayName,
getCourseSectionVerticalData,
getVerticalData,
createCourseXblock,
getCourseVerticalChildren,
handleCourseUnitVisibilityAndData,
Expand All @@ -22,8 +21,6 @@ import {
patchUnitItem,
} from './api';
import {
updateLoadingCourseUnitStatus,
fetchCourseItemSuccess,
updateSavingStatus,
fetchSequenceRequest,
fetchSequenceFailure,
Expand All @@ -40,30 +37,13 @@ import {
} from './slice';
import { getNotificationMessage } from './utils';

export function fetchCourseUnitQuery(courseId) {
return async (dispatch) => {
dispatch(updateLoadingCourseUnitStatus({ status: RequestStatus.IN_PROGRESS }));

try {
const courseUnit = await getCourseUnitData(courseId);

dispatch(fetchCourseItemSuccess(courseUnit));
dispatch(updateLoadingCourseUnitStatus({ status: RequestStatus.SUCCESSFUL }));
return true;
} catch (error) {
dispatch(updateLoadingCourseUnitStatus({ status: RequestStatus.FAILED }));
return false;
}
};
}

export function fetchCourseSectionVerticalData(courseId, sequenceId) {
return async (dispatch) => {
dispatch(updateLoadingCourseSectionVerticalDataStatus({ status: RequestStatus.IN_PROGRESS }));
dispatch(fetchSequenceRequest({ sequenceId }));

try {
const courseSectionVerticalData = await getCourseSectionVerticalData(courseId);
const courseSectionVerticalData = await getVerticalData(courseId);
dispatch(fetchCourseSectionVerticalDataSuccess(courseSectionVerticalData));
dispatch(updateLoadingCourseSectionVerticalDataStatus({ status: RequestStatus.SUCCESSFUL }));
dispatch(updateModel({
Expand Down Expand Up @@ -94,8 +74,7 @@ export function editCourseItemQuery(itemId, displayName, sequenceId) {
try {
await editUnitDisplayName(itemId, displayName).then(async (result) => {
if (result) {
const courseUnit = await getCourseUnitData(itemId);
const courseSectionVerticalData = await getCourseSectionVerticalData(itemId);
const courseSectionVerticalData = await getVerticalData(itemId);
dispatch(fetchCourseSectionVerticalDataSuccess(courseSectionVerticalData));
dispatch(updateLoadingCourseSectionVerticalDataStatus({ status: RequestStatus.SUCCESSFUL }));
dispatch(updateModel({
Expand All @@ -107,7 +86,6 @@ export function editCourseItemQuery(itemId, displayName, sequenceId) {
models: courseSectionVerticalData.units || [],
}));
dispatch(fetchSequenceSuccess({ sequenceId }));
dispatch(fetchCourseItemSuccess(courseUnit));
dispatch(hideProcessingNotification());
dispatch(updateSavingStatus({ status: RequestStatus.SUCCESSFUL }));
}
Expand Down Expand Up @@ -146,8 +124,8 @@ export function editCourseUnitVisibilityAndData(
if (callback) {
callback();
}
const courseUnit = await getCourseUnitData(blockId);
dispatch(fetchCourseItemSuccess(courseUnit));
const courseSectionVerticalData = await getVerticalData(blockId);
dispatch(fetchCourseSectionVerticalDataSuccess(courseSectionVerticalData));
const courseVerticalChildrenData = await getCourseVerticalChildren(blockId);
dispatch(updateCourseVerticalChildren(courseVerticalChildrenData));
dispatch(hideProcessingNotification());
Expand All @@ -174,7 +152,7 @@ export function createNewCourseXBlock(body, callback, blockId, sendMessageToIfra
if (result) {
const formattedResult = camelCaseObject(result);
if (body.category === 'vertical') {
const courseSectionVerticalData = await getCourseSectionVerticalData(formattedResult.locator);
const courseSectionVerticalData = await getVerticalData(formattedResult.locator);
dispatch(fetchCourseSectionVerticalDataSuccess(courseSectionVerticalData));
}
if (body.stagedContent) {
Expand All @@ -194,8 +172,8 @@ export function createNewCourseXBlock(body, callback, blockId, sendMessageToIfra
sendMessageToIframe(messageTypes.addXBlock, { data: result });
}
const currentBlockId = body.category === 'vertical' ? formattedResult.locator : blockId;
const courseUnit = await getCourseUnitData(currentBlockId);
dispatch(fetchCourseItemSuccess(courseUnit));
const courseSectionVerticalData = await getVerticalData(currentBlockId);
dispatch(fetchCourseSectionVerticalDataSuccess(courseSectionVerticalData));
}
});
} catch (error) {
Expand Down Expand Up @@ -240,8 +218,8 @@ export function deleteUnitItemQuery(itemId, xblockId, sendMessageToIframe) {
try {
await deleteUnitItem(xblockId);
sendMessageToIframe(messageTypes.completeXBlockDeleting, { locator: xblockId });
const courseUnit = await getCourseUnitData(itemId);
dispatch(fetchCourseItemSuccess(courseUnit));
const courseSectionVerticalData = await getVerticalData(itemId);
dispatch(fetchCourseSectionVerticalDataSuccess(courseSectionVerticalData));
dispatch(hideProcessingNotification());
dispatch(updateSavingStatus({ status: RequestStatus.SUCCESSFUL }));
} catch (error) {
Expand All @@ -259,8 +237,8 @@ export function duplicateUnitItemQuery(itemId, xblockId, callback) {
try {
const { courseKey, locator } = await duplicateUnitItem(itemId, xblockId);
callback(courseKey, locator);
const courseUnit = await getCourseUnitData(itemId);
dispatch(fetchCourseItemSuccess(courseUnit));
const courseSectionVerticalData = await getVerticalData(itemId);
dispatch(fetchCourseSectionVerticalDataSuccess(courseSectionVerticalData));
const courseVerticalChildrenData = await getCourseVerticalChildren(itemId);
dispatch(updateCourseVerticalChildren(courseVerticalChildrenData));
dispatch(hideProcessingNotification());
Expand Down Expand Up @@ -316,8 +294,8 @@ export function patchUnitItemQuery({
dispatch(updateCourseOutlineInfoLoadingStatus({ status: RequestStatus.IN_PROGRESS }));
callbackFn(sourceLocator);
try {
const courseUnit = await getCourseUnitData(currentParentLocator);
dispatch(fetchCourseItemSuccess(courseUnit));
const courseSectionVerticalData = await getVerticalData(currentParentLocator);
dispatch(fetchCourseSectionVerticalDataSuccess(courseSectionVerticalData));
} catch (error) {
handleResponseErrors(error, dispatch, updateSavingStatus);
}
Expand All @@ -335,8 +313,8 @@ export function updateCourseUnitSidebar(itemId) {
dispatch(showProcessingNotification(NOTIFICATION_MESSAGES.saving));

try {
const courseUnit = await getCourseUnitData(itemId);
dispatch(fetchCourseItemSuccess(courseUnit));
const courseSectionVerticalData = await getVerticalData(itemId);
dispatch(fetchCourseSectionVerticalDataSuccess(courseSectionVerticalData));
dispatch(hideProcessingNotification());
dispatch(updateSavingStatus({ status: RequestStatus.SUCCESSFUL }));
} catch (error) {
Expand Down
54 changes: 32 additions & 22 deletions src/course-unit/header-title/HeaderTitle.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import { initializeMockApp } from '@edx/frontend-platform';

import initializeStore from '../../store';
import { executeThunk } from '../../utils';
import { getCourseUnitApiUrl } from '../data/api';
import { fetchCourseUnitQuery } from '../data/thunk';
import { courseUnitIndexMock } from '../__mocks__';
import { getCourseSectionVerticalApiUrl } from '../data/api';
import { fetchCourseSectionVerticalData } from '../data/thunk';
import { courseSectionVerticalMock } from '../__mocks__';
import HeaderTitle from './HeaderTitle';
import messages from './messages';

Expand Down Expand Up @@ -52,9 +52,9 @@ describe('<HeaderTitle />', () => {
store = initializeStore();
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
axiosMock
.onGet(getCourseUnitApiUrl(blockId))
.reply(200, courseUnitIndexMock);
await executeThunk(fetchCourseUnitQuery(blockId), store.dispatch);
.onGet(getCourseSectionVerticalApiUrl(blockId))
.reply(200, courseSectionVerticalMock);
await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch);
});

it('render HeaderTitle component correctly', () => {
Expand All @@ -80,14 +80,18 @@ describe('<HeaderTitle />', () => {
// Override mock unit with one sourced from an upstream library
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
axiosMock
.onGet(getCourseUnitApiUrl(blockId))
.onGet(getCourseSectionVerticalApiUrl(blockId))
.reply(200, {
...courseUnitIndexMock,
upstreamInfo: {
upstreamRef: 'lct:org:lib:unit:unit-1',
...courseSectionVerticalMock,
xblock_info: {
...courseSectionVerticalMock.xblock_info,
upstreamInfo: {
...courseSectionVerticalMock.xblock_info.upstreamInfo,
upstreamRef: 'lct:org:lib:unit:unit-1',
},
},
});
await executeThunk(fetchCourseUnitQuery(blockId), store.dispatch);
await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch);

const { getByRole } = renderComponent();

Expand Down Expand Up @@ -122,16 +126,19 @@ describe('<HeaderTitle />', () => {

it('displays a visibility message with the selected groups for the unit', async () => {
axiosMock
.onGet(getCourseUnitApiUrl(blockId))
.onGet(getCourseSectionVerticalApiUrl(blockId))
.reply(200, {
...courseUnitIndexMock,
user_partition_info: {
...courseUnitIndexMock.user_partition_info,
selected_partition_index: 1,
selected_groups_label: 'Visibility group 1',
...courseSectionVerticalMock,
xblock_info: {
...courseSectionVerticalMock.xblock_info,
user_partition_info: {
...courseSectionVerticalMock.xblock_info.user_partition_info,
selected_partition_index: 1,
selected_groups_label: 'Visibility group 1',
},
},
});
await executeThunk(fetchCourseUnitQuery(blockId), store.dispatch);
await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch);
const { getByText } = renderComponent();
const visibilityMessage = messages.definedVisibilityMessage.defaultMessage
.replace('{selectedGroupsLabel}', 'Visibility group 1');
Expand All @@ -143,12 +150,15 @@ describe('<HeaderTitle />', () => {

it('displays a visibility message with the selected groups for some of xblock', async () => {
axiosMock
.onGet(getCourseUnitApiUrl(blockId))
.onGet(getCourseSectionVerticalApiUrl(blockId))
.reply(200, {
...courseUnitIndexMock,
has_partition_group_components: true,
...courseSectionVerticalMock,
xblock_info: {
...courseSectionVerticalMock.xblock_info,
has_partition_group_components: true,
},
});
await executeThunk(fetchCourseUnitQuery(blockId), store.dispatch);
await executeThunk(fetchCourseSectionVerticalData(blockId), store.dispatch);
const { getByText } = renderComponent();

await waitFor(() => {
Expand Down
Loading