Separate courses redux model into courseHomeMeta and coursewareMeta (#348)
This commit is contained in:
@@ -13,7 +13,7 @@ const EnrollmentAlert = React.lazy(() => import('./EnrollmentAlert'));
|
||||
|
||||
export function useEnrollmentAlert(courseId) {
|
||||
const { authenticatedUser } = useContext(AppContext);
|
||||
const course = useModel('courses', courseId);
|
||||
const course = useModel('courseHomeMeta', courseId);
|
||||
const outline = useModel('outline', courseId);
|
||||
const enrolledUser = course && course.isEnrolled !== undefined && course.isEnrolled;
|
||||
const privateOutline = outline && outline.courseBlocks && !outline.courseBlocks.courses;
|
||||
@@ -28,7 +28,7 @@ export function useEnrollmentAlert(courseId) {
|
||||
canEnroll: outline && outline.enrollAlert ? outline.enrollAlert.canEnroll : false,
|
||||
courseId,
|
||||
extraText: outline && outline.enrollAlert ? outline.enrollAlert.extraText : '',
|
||||
isStaff: course.isStaff,
|
||||
isStaff: course && course.isStaff,
|
||||
};
|
||||
|
||||
useAlert(isVisible, {
|
||||
|
||||
@@ -16,7 +16,7 @@ Object {
|
||||
"sequenceStatus": "loading",
|
||||
},
|
||||
"models": Object {
|
||||
"courses": Object {
|
||||
"courseHomeMeta": Object {
|
||||
"course-v1:edX+DemoX+Demo_Course_1": Object {
|
||||
"courseId": "course-v1:edX+DemoX+Demo_Course_1",
|
||||
"id": "course-v1:edX+DemoX+Demo_Course_1",
|
||||
@@ -301,7 +301,7 @@ Object {
|
||||
"sequenceStatus": "loading",
|
||||
},
|
||||
"models": Object {
|
||||
"courses": Object {
|
||||
"courseHomeMeta": Object {
|
||||
"course-v1:edX+DemoX+Demo_Course_1": Object {
|
||||
"courseId": "course-v1:edX+DemoX+Demo_Course_1",
|
||||
"id": "course-v1:edX+DemoX+Demo_Course_1",
|
||||
|
||||
@@ -39,7 +39,7 @@ export function fetchTab(courseId, tab, getTabData) {
|
||||
|
||||
if (fetchedCourseHomeCourseMetadata) {
|
||||
dispatch(addModel({
|
||||
modelType: 'courses',
|
||||
modelType: 'courseHomeMeta',
|
||||
model: {
|
||||
id: courseId,
|
||||
...courseHomeCourseMetadataResult.value,
|
||||
|
||||
@@ -27,7 +27,7 @@ function DatesBannerContainer({
|
||||
|
||||
const {
|
||||
isSelfPaced,
|
||||
} = useModel('courses', courseId);
|
||||
} = useModel('courseHomeMeta', courseId);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const hasDeadlines = courseDateBlocks.some(x => x.dateType === 'assignment-due-date');
|
||||
|
||||
@@ -22,7 +22,7 @@ import useCertificateAvailableAlert from './alerts/certificate-available-alert';
|
||||
import useCourseEndAlert from './alerts/course-end-alert';
|
||||
import useCourseStartAlert from './alerts/course-start-alert';
|
||||
import useOfferAlert from '../../alerts/offer-alert';
|
||||
import usePrivateCourseAlert from '../../alerts/private-course-alert';
|
||||
import usePrivateCourseAlert from './alerts/private-course-alert';
|
||||
import { useModel } from '../../generic/model-store';
|
||||
import WelcomeMessage from './widgets/WelcomeMessage';
|
||||
import ProctoringInfoPanel from './widgets/ProctoringInfoPanel';
|
||||
@@ -34,7 +34,7 @@ function OutlineTab({ intl }) {
|
||||
|
||||
const {
|
||||
title,
|
||||
} = useModel('courses', courseId);
|
||||
} = useModel('courseHomeMeta', courseId);
|
||||
|
||||
const {
|
||||
accessExpiration,
|
||||
|
||||
@@ -9,7 +9,7 @@ const CertificateAvailableAlert = React.lazy(() => import('./CertificateAvailabl
|
||||
function useCertificateAvailableAlert(courseId) {
|
||||
const {
|
||||
isEnrolled,
|
||||
} = useModel('courses', courseId);
|
||||
} = useModel('courseHomeMeta', courseId);
|
||||
const {
|
||||
datesWidget: {
|
||||
courseDateBlocks,
|
||||
|
||||
@@ -11,7 +11,7 @@ const WARNING_PERIOD_MS = 14 * 24 * 60 * 60 * 1000; // 14 days
|
||||
export function useCourseEndAlert(courseId) {
|
||||
const {
|
||||
isEnrolled,
|
||||
} = useModel('courses', courseId);
|
||||
} = useModel('courseHomeMeta', courseId);
|
||||
const {
|
||||
datesWidget: {
|
||||
courseDateBlocks,
|
||||
|
||||
@@ -7,7 +7,7 @@ const CourseStartAlert = React.lazy(() => import('./CourseStartAlert'));
|
||||
function useCourseStartAlert(courseId) {
|
||||
const {
|
||||
isEnrolled,
|
||||
} = useModel('courses', courseId);
|
||||
} = useModel('courseHomeMeta', courseId);
|
||||
const {
|
||||
datesWidget: {
|
||||
courseDateBlocks,
|
||||
|
||||
@@ -7,12 +7,12 @@ import { Button, Hyperlink } from '@edx/paragon';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faSpinner } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
import { Alert } from '../../generic/user-messages';
|
||||
import enrollmentMessages from '../enrollment-alert/messages';
|
||||
import genericMessages from '../../generic/messages';
|
||||
import outlineMessages from '../../course-home/outline-tab/messages';
|
||||
import { useEnrollClickHandler } from '../enrollment-alert/hooks';
|
||||
import { useModel } from '../../generic/model-store';
|
||||
import { Alert } from '../../../../generic/user-messages';
|
||||
import enrollmentMessages from '../../../../alerts/enrollment-alert/messages';
|
||||
import genericMessages from '../../../../generic/messages';
|
||||
import outlineMessages from '../../messages';
|
||||
import { useEnrollClickHandler } from '../../../../alerts/enrollment-alert/hooks';
|
||||
import { useModel } from '../../../../generic/model-store';
|
||||
|
||||
function PrivateCourseAlert({ intl, payload }) {
|
||||
const {
|
||||
@@ -23,7 +23,7 @@ function PrivateCourseAlert({ intl, payload }) {
|
||||
|
||||
const {
|
||||
title,
|
||||
} = useModel('courses', courseId);
|
||||
} = useModel('courseHomeMeta', courseId);
|
||||
|
||||
const { enrollClickHandler, loading } = useEnrollClickHandler(
|
||||
courseId,
|
||||
@@ -1,14 +1,14 @@
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
import React, { useContext, useMemo } from 'react';
|
||||
import { AppContext } from '@edx/frontend-platform/react';
|
||||
import { ALERT_TYPES, useAlert } from '../../generic/user-messages';
|
||||
import { useModel } from '../../generic/model-store';
|
||||
import { ALERT_TYPES, useAlert } from '../../../../generic/user-messages';
|
||||
import { useModel } from '../../../../generic/model-store';
|
||||
|
||||
const PrivateCourseAlert = React.lazy(() => import('./PrivateCourseAlert'));
|
||||
|
||||
export function usePrivateCourseAlert(courseId) {
|
||||
const { authenticatedUser } = useContext(AppContext);
|
||||
const course = useModel('courses', courseId);
|
||||
const course = useModel('courseHomeMeta', courseId);
|
||||
const outline = useModel('outline', courseId);
|
||||
const enrolledUser = course && course.isEnrolled !== undefined && course.isEnrolled;
|
||||
const privateOutline = outline && outline.courseBlocks && !outline.courseBlocks.courses;
|
||||
@@ -14,7 +14,7 @@ import messages from '../messages';
|
||||
import { useModel } from '../../../generic/model-store';
|
||||
|
||||
function CourseTools({ courseId, intl }) {
|
||||
const { org } = useModel('courses', courseId);
|
||||
const { org } = useModel('courseHomeMeta', courseId);
|
||||
const {
|
||||
courseTools,
|
||||
} = useModel('outline', courseId);
|
||||
|
||||
@@ -11,7 +11,7 @@ import { UpgradeButton } from '../../../generic/upgrade-button';
|
||||
import VerifiedCert from '../../../generic/assets/edX_certificate.png';
|
||||
|
||||
function UpgradeCard({ courseId, intl, onLearnMore }) {
|
||||
const { org } = useModel('courses', courseId);
|
||||
const { org } = useModel('courseHomeMeta', courseId);
|
||||
const {
|
||||
offer,
|
||||
verifiedMode,
|
||||
|
||||
@@ -234,6 +234,7 @@ class CoursewareContainer extends Component {
|
||||
courseId={courseId}
|
||||
unitId={routeUnitId}
|
||||
courseStatus={courseStatus}
|
||||
metadataModel="coursewareMeta"
|
||||
>
|
||||
<Course
|
||||
courseId={courseId}
|
||||
@@ -296,7 +297,7 @@ CoursewareContainer.defaultProps = {
|
||||
};
|
||||
|
||||
const currentCourseSelector = createSelector(
|
||||
(state) => state.models.courses || {},
|
||||
(state) => state.models.coursewareMeta || {},
|
||||
(state) => state.courseware.courseId,
|
||||
(coursesById, courseId) => (coursesById[courseId] ? coursesById[courseId] : null),
|
||||
);
|
||||
|
||||
@@ -24,7 +24,7 @@ function Course({
|
||||
previousSequenceHandler,
|
||||
unitNavigationHandler,
|
||||
}) {
|
||||
const course = useModel('courses', courseId);
|
||||
const course = useModel('coursewareMeta', courseId);
|
||||
const sequence = useModel('sequences', sequenceId);
|
||||
const section = useModel('sections', sequence ? sequence.sectionId : null);
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ describe('Course', () => {
|
||||
const { models } = store.getState();
|
||||
const sequence = models.sequences[mockData.sequenceId];
|
||||
const section = models.sections[sequence.sectionId];
|
||||
const course = models.courses[mockData.courseId];
|
||||
const course = models.coursewareMeta[mockData.courseId];
|
||||
expect(document.title).toMatch(
|
||||
`${sequence.title} | ${section.title} | ${course.title} | edX`,
|
||||
);
|
||||
|
||||
@@ -40,7 +40,7 @@ export default function CourseBreadcrumbs({
|
||||
REV1512FlyoverEnabled, /* This line should be reverted after the REV1512 experiment */
|
||||
isREV1512FlyoverVisible, /* This line should be reverted after the REV1512 experiment */
|
||||
}) {
|
||||
const course = useModel('courses', courseId);
|
||||
const course = useModel('coursewareMeta', courseId);
|
||||
const sequence = useModel('sequences', sequenceId);
|
||||
const section = useModel('sections', sectionId);
|
||||
const courseStatus = useSelector(state => state.courseware.courseStatus);
|
||||
|
||||
@@ -14,7 +14,7 @@ import { useModel } from '../../../generic/model-store';
|
||||
function CelebrationModal({
|
||||
courseId, intl, open, ...rest
|
||||
}) {
|
||||
const { org } = useModel('courses', courseId);
|
||||
const { org } = useModel('coursewareMeta', courseId);
|
||||
|
||||
const layout = layoutGenerator({
|
||||
mobile: 0,
|
||||
|
||||
@@ -52,7 +52,7 @@ function shouldCelebrateOnSectionLoad(courseId, sequenceId, unitId, celebrateFir
|
||||
|
||||
// Update our local copy of course data from LMS
|
||||
dispatch(updateModel({
|
||||
modelType: 'courses',
|
||||
modelType: 'coursewareMeta',
|
||||
model: {
|
||||
id: courseId,
|
||||
celebrations: {
|
||||
|
||||
@@ -18,7 +18,7 @@ import { logClick } from './utils';
|
||||
|
||||
function CatalogSuggestion({ intl, variant }) {
|
||||
const { courseId } = useSelector(state => state.courseware);
|
||||
const { org } = useModel('courses', courseId);
|
||||
const { org } = useModel('coursewareMeta', courseId);
|
||||
const { administrator } = getAuthenticatedUser();
|
||||
|
||||
const searchOurCatalogLink = (
|
||||
|
||||
@@ -51,7 +51,7 @@ function CourseCelebration({ intl }) {
|
||||
verifiedMode,
|
||||
verifyIdentityUrl,
|
||||
verificationStatus,
|
||||
} = useModel('courses', courseId);
|
||||
} = useModel('coursewareMeta', courseId);
|
||||
|
||||
const {
|
||||
certStatus,
|
||||
|
||||
@@ -16,7 +16,7 @@ import { logClick, logVisit } from './utils';
|
||||
|
||||
function CourseInProgress({ intl }) {
|
||||
const { courseId } = useSelector(state => state.courseware);
|
||||
const { org, tabs, title } = useModel('courses', courseId);
|
||||
const { org, tabs, title } = useModel('coursewareMeta', courseId);
|
||||
const { administrator } = getAuthenticatedUser();
|
||||
|
||||
// Get dates tab link for 'view course schedule' button
|
||||
|
||||
@@ -16,7 +16,7 @@ import { logClick, logVisit } from './utils';
|
||||
|
||||
function CourseNonPassing({ intl }) {
|
||||
const { courseId } = useSelector(state => state.courseware);
|
||||
const { org, tabs, title } = useModel('courses', courseId);
|
||||
const { org, tabs, title } = useModel('coursewareMeta', courseId);
|
||||
const { administrator } = getAuthenticatedUser();
|
||||
|
||||
// Get progress tab link for 'view grades' button
|
||||
|
||||
@@ -18,7 +18,7 @@ import { logClick } from './utils';
|
||||
|
||||
function DashboardFootnote({ intl, variant }) {
|
||||
const { courseId } = useSelector(state => state.courseware);
|
||||
const { org } = useModel('courses', courseId);
|
||||
const { org } = useModel('coursewareMeta', courseId);
|
||||
const { administrator } = getAuthenticatedUser();
|
||||
|
||||
const dashboardLink = (
|
||||
|
||||
@@ -16,7 +16,7 @@ import { useModel } from '../../../generic/model-store';
|
||||
|
||||
function UpgradeFootnote({ deadline, href, intl }) {
|
||||
const { courseId } = useSelector(state => state.courseware);
|
||||
const { org } = useModel('courses', courseId);
|
||||
const { org } = useModel('coursewareMeta', courseId);
|
||||
const { administrator } = getAuthenticatedUser();
|
||||
|
||||
const upgradeLink = (
|
||||
|
||||
@@ -33,7 +33,7 @@ function getCourseExitMode(courseId) {
|
||||
hasScheduledContent,
|
||||
isEnrolled,
|
||||
userHasPassingGrade,
|
||||
} = useModel('courses', courseId);
|
||||
} = useModel('coursewareMeta', courseId);
|
||||
|
||||
const authenticatedUser = getAuthenticatedUser();
|
||||
|
||||
|
||||
@@ -193,7 +193,7 @@ function Sequence({
|
||||
REV1512FlyoverEnabled, /* This line should be reverted after the REV1512 experiment */
|
||||
toggleREV1512Flyover, /* This line should be reverted after the REV1512 experiment */
|
||||
}) {
|
||||
const course = useModel('courses', courseId);
|
||||
const course = useModel('coursewareMeta', courseId);
|
||||
const sequence = useModel('sequences', sequenceId);
|
||||
const unit = useModel('units', unitId);
|
||||
const sequenceStatus = useSelector(state => state.courseware.sequenceStatus);
|
||||
|
||||
@@ -81,7 +81,7 @@ function Unit({
|
||||
};
|
||||
|
||||
const unit = useModel('units', id);
|
||||
const course = useModel('courses', courseId);
|
||||
const course = useModel('coursewareMeta', courseId);
|
||||
const {
|
||||
contentTypeGatingEnabled,
|
||||
} = course;
|
||||
|
||||
@@ -17,7 +17,7 @@ import './LockPaywall.scss';
|
||||
function LockPaywall({
|
||||
courseId,
|
||||
}) {
|
||||
const course = useModel('courses', courseId);
|
||||
const course = useModel('coursewareMeta', courseId);
|
||||
const {
|
||||
verifiedMode,
|
||||
} = course;
|
||||
|
||||
@@ -23,7 +23,11 @@ describe('Lock Paywall', () => {
|
||||
});
|
||||
|
||||
it('displays unlock link with price', () => {
|
||||
const { currencySymbol, price, upgradeUrl } = store.getState().models.courses[mockData.courseId].verifiedMode;
|
||||
const {
|
||||
currencySymbol,
|
||||
price,
|
||||
upgradeUrl,
|
||||
} = store.getState().models.coursewareMeta[mockData.courseId].verifiedMode;
|
||||
render(<LockPaywall {...mockData} />);
|
||||
|
||||
const upgradeLink = screen.getByRole('link', { name: `Upgrade for ${currencySymbol}${price}` });
|
||||
|
||||
@@ -12,7 +12,7 @@ function LockPaywall({
|
||||
intl,
|
||||
courseId,
|
||||
}) {
|
||||
const course = useModel('courses', courseId);
|
||||
const course = useModel('coursewareMeta', courseId);
|
||||
const {
|
||||
verifiedMode,
|
||||
} = course;
|
||||
|
||||
@@ -22,7 +22,11 @@ describe('Lock Paywall', () => {
|
||||
});
|
||||
|
||||
it('displays unlock link with price', () => {
|
||||
const { currencySymbol, price, upgradeUrl } = store.getState().models.courses[mockData.courseId].verifiedMode;
|
||||
const {
|
||||
currencySymbol,
|
||||
price,
|
||||
upgradeUrl,
|
||||
} = store.getState().models.coursewareMeta[mockData.courseId].verifiedMode;
|
||||
render(<LockPaywall {...mockData} />);
|
||||
|
||||
const upgradeLink = screen.getByRole('link', { name: `Upgrade to unlock (${currencySymbol}${price})` });
|
||||
|
||||
@@ -81,7 +81,7 @@ describe('Data layer integration tests', () => {
|
||||
expect(state.courseware.courseStatus).toEqual('denied');
|
||||
|
||||
// check that at least one key camel cased, thus course data normalized
|
||||
expect(state.models.courses[forbiddenCourseMetadata.id].canLoadCourseware).not.toBeUndefined();
|
||||
expect(state.models.coursewareMeta[forbiddenCourseMetadata.id].canLoadCourseware).not.toBeUndefined();
|
||||
});
|
||||
|
||||
it('Should fetch, normalize, and save metadata', async () => {
|
||||
@@ -98,7 +98,7 @@ describe('Data layer integration tests', () => {
|
||||
expect(state.courseware.sequenceId).toEqual(null);
|
||||
|
||||
// check that at least one key camel cased, thus course data normalized
|
||||
expect(state.models.courses[courseId].canLoadCourseware).not.toBeUndefined();
|
||||
expect(state.models.coursewareMeta[courseId].canLoadCourseware).not.toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ export function sequenceIdsSelector(state) {
|
||||
if (state.courseware.courseStatus !== 'loaded') {
|
||||
return [];
|
||||
}
|
||||
const { sectionIds = [] } = state.models.courses[state.courseware.courseId];
|
||||
const { sectionIds = [] } = state.models.coursewareMeta[state.courseware.courseId];
|
||||
|
||||
const sequenceIds = sectionIds
|
||||
.flatMap(sectionId => state.models.sections[sectionId].sequenceIds);
|
||||
|
||||
@@ -28,7 +28,7 @@ export function fetchCourse(courseId) {
|
||||
]).then(([courseMetadataResult, courseBlocksResult]) => {
|
||||
if (courseMetadataResult.status === 'fulfilled') {
|
||||
dispatch(addModel({
|
||||
modelType: 'courses',
|
||||
modelType: 'coursewareMeta',
|
||||
model: courseMetadataResult.value,
|
||||
}));
|
||||
}
|
||||
@@ -40,7 +40,7 @@ export function fetchCourse(courseId) {
|
||||
|
||||
// This updates the course with a sectionIds array from the blocks data.
|
||||
dispatch(updateModelsMap({
|
||||
modelType: 'courses',
|
||||
modelType: 'coursewareMeta',
|
||||
modelsMap: courses,
|
||||
}));
|
||||
dispatch(addModelsMap({
|
||||
|
||||
@@ -33,7 +33,7 @@ function SocialIcons({
|
||||
marketingUrl,
|
||||
org,
|
||||
title,
|
||||
} = useModel('courses', courseId);
|
||||
} = useModel('coursewareMeta', courseId);
|
||||
|
||||
if (!marketingUrl) {
|
||||
return null;
|
||||
|
||||
@@ -15,6 +15,7 @@ function LoadedTabPage({
|
||||
activeTabSlug,
|
||||
children,
|
||||
courseId,
|
||||
metadataModel,
|
||||
unitId,
|
||||
}) {
|
||||
const {
|
||||
@@ -23,8 +24,10 @@ function LoadedTabPage({
|
||||
org,
|
||||
tabs,
|
||||
title,
|
||||
} = useModel('courses', courseId);
|
||||
} = useModel(metadataModel, courseId);
|
||||
|
||||
// Logistration and enrollment alerts are only really used for the outline tab, but loaded here to put them above
|
||||
// breadcrumbs when they are visible.
|
||||
const logistrationAlert = useLogistrationAlert(courseId);
|
||||
const enrollmentAlert = useEnrollmentAlert(courseId);
|
||||
|
||||
@@ -68,11 +71,13 @@ LoadedTabPage.propTypes = {
|
||||
activeTabSlug: PropTypes.string.isRequired,
|
||||
children: PropTypes.node,
|
||||
courseId: PropTypes.string.isRequired,
|
||||
metadataModel: PropTypes.string,
|
||||
unitId: PropTypes.string,
|
||||
};
|
||||
|
||||
LoadedTabPage.defaultProps = {
|
||||
children: null,
|
||||
metadataModel: 'courseHomeMeta',
|
||||
unitId: null,
|
||||
};
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ jest.mock('../course-header/CourseTabsNavigation', () => () => <div data-testid=
|
||||
jest.mock('../instructor-toolbar/InstructorToolbar', () => () => <div data-testid="InstructorToolbar" />);
|
||||
|
||||
describe('Loaded Tab Page', () => {
|
||||
const mockData = { activeTabSlug: 'courseware' };
|
||||
const mockData = { activeTabSlug: 'courseware', metadataModel: 'coursewareMeta' };
|
||||
|
||||
beforeAll(async () => {
|
||||
const store = await initializeTestStore({ excludeFetchSequence: true });
|
||||
|
||||
Reference in New Issue
Block a user