Separate courses redux model into courseHomeMeta and coursewareMeta (#348)

This commit is contained in:
Michael Terry
2021-01-22 15:28:16 -05:00
committed by GitHub
parent 293dc9f4c3
commit 58543a34b3
39 changed files with 64 additions and 50 deletions

View File

@@ -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, {

View File

@@ -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",

View File

@@ -39,7 +39,7 @@ export function fetchTab(courseId, tab, getTabData) {
if (fetchedCourseHomeCourseMetadata) {
dispatch(addModel({
modelType: 'courses',
modelType: 'courseHomeMeta',
model: {
id: courseId,
...courseHomeCourseMetadataResult.value,

View File

@@ -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');

View File

@@ -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,

View File

@@ -9,7 +9,7 @@ const CertificateAvailableAlert = React.lazy(() => import('./CertificateAvailabl
function useCertificateAvailableAlert(courseId) {
const {
isEnrolled,
} = useModel('courses', courseId);
} = useModel('courseHomeMeta', courseId);
const {
datesWidget: {
courseDateBlocks,

View File

@@ -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,

View File

@@ -7,7 +7,7 @@ const CourseStartAlert = React.lazy(() => import('./CourseStartAlert'));
function useCourseStartAlert(courseId) {
const {
isEnrolled,
} = useModel('courses', courseId);
} = useModel('courseHomeMeta', courseId);
const {
datesWidget: {
courseDateBlocks,

View File

@@ -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,

View File

@@ -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;

View File

@@ -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);

View File

@@ -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,

View File

@@ -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),
);

View File

@@ -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);

View File

@@ -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`,
);

View File

@@ -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);

View File

@@ -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,

View File

@@ -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: {

View File

@@ -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 = (

View File

@@ -51,7 +51,7 @@ function CourseCelebration({ intl }) {
verifiedMode,
verifyIdentityUrl,
verificationStatus,
} = useModel('courses', courseId);
} = useModel('coursewareMeta', courseId);
const {
certStatus,

View File

@@ -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

View File

@@ -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

View File

@@ -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 = (

View File

@@ -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 = (

View File

@@ -33,7 +33,7 @@ function getCourseExitMode(courseId) {
hasScheduledContent,
isEnrolled,
userHasPassingGrade,
} = useModel('courses', courseId);
} = useModel('coursewareMeta', courseId);
const authenticatedUser = getAuthenticatedUser();

View File

@@ -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);

View File

@@ -81,7 +81,7 @@ function Unit({
};
const unit = useModel('units', id);
const course = useModel('courses', courseId);
const course = useModel('coursewareMeta', courseId);
const {
contentTypeGatingEnabled,
} = course;

View File

@@ -17,7 +17,7 @@ import './LockPaywall.scss';
function LockPaywall({
courseId,
}) {
const course = useModel('courses', courseId);
const course = useModel('coursewareMeta', courseId);
const {
verifiedMode,
} = course;

View File

@@ -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}` });

View File

@@ -12,7 +12,7 @@ function LockPaywall({
intl,
courseId,
}) {
const course = useModel('courses', courseId);
const course = useModel('coursewareMeta', courseId);
const {
verifiedMode,
} = course;

View File

@@ -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})` });

View File

@@ -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();
});
});

View File

@@ -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);

View File

@@ -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({

View File

@@ -33,7 +33,7 @@ function SocialIcons({
marketingUrl,
org,
title,
} = useModel('courses', courseId);
} = useModel('coursewareMeta', courseId);
if (!marketingUrl) {
return null;

View File

@@ -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,
};

View File

@@ -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 });